From b967dc482756ab3add8044a2bc7b1e23b9f4ed66 Mon Sep 17 00:00:00 2001 From: aajkl Date: Thu, 15 Aug 2024 21:01:07 +0800 Subject: [PATCH] hostdir volume --- go.mod | 1 + go.sum | 2 + internal/service/boar/util.go | 25 ++- internal/types/const.go | 1 + internal/virt/domain/domain.go | 35 +--- internal/virt/domain/templates/guest.xml | 11 +- internal/virt/guest/bot.go | 8 +- internal/volume/base/util.go | 232 +++++++++++++++++++++ internal/volume/base/volume.go | 32 ++- internal/volume/factory/factory.go | 22 +- internal/volume/factory/guest.go | 206 +----------------- internal/volume/hostdir/snapshot.go | 1 + internal/volume/hostdir/snapshot_api.go | 43 ++++ internal/volume/hostdir/templates/disk.xml | 5 + internal/volume/hostdir/volume.go | 183 ++++++++++++++++ internal/volume/hostdir/volume_test.go | 37 ++++ internal/volume/local/volume.go | 21 +- internal/volume/mocks/Volume.go | 115 ++++++++-- internal/volume/rbd/volume.go | 21 +- internal/volume/volume.go | 11 +- 20 files changed, 727 insertions(+), 285 deletions(-) create mode 100644 internal/volume/hostdir/snapshot.go create mode 100644 internal/volume/hostdir/snapshot_api.go create mode 100644 internal/volume/hostdir/templates/disk.xml create mode 100644 internal/volume/hostdir/volume.go create mode 100644 internal/volume/hostdir/volume_test.go diff --git a/go.mod b/go.mod index d11963c..2888265 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2.0.20230206183746-70ca0345eede github.com/yuyang0/resource-bandwidth v0.0.0-20231102113253-8e47795c92e5 github.com/yuyang0/resource-gpu v0.0.0-20231026065700-1577d804efa8 + github.com/yuyang0/resource-hostdir v0.0.0-20240815062057-56acd9dde1f8 github.com/yuyang0/resource-rbd v0.0.2-0.20230701090628-cb86da0f60b9 go.etcd.io/etcd v3.3.27+incompatible go.etcd.io/etcd/client/v3 v3.5.12 diff --git a/go.sum b/go.sum index 44ee2cb..f933672 100644 --- a/go.sum +++ b/go.sum @@ -515,6 +515,8 @@ github.com/yuyang0/resource-bandwidth v0.0.0-20231102113253-8e47795c92e5 h1:qLiO github.com/yuyang0/resource-bandwidth v0.0.0-20231102113253-8e47795c92e5/go.mod h1:qq6SbQf88tieRqLkMzUgAoANLszQWdcH8H4Ji7xo2sE= github.com/yuyang0/resource-gpu v0.0.0-20231026065700-1577d804efa8 h1:U1GBBWRCG0kmo3XG3sI5pz0i4nMwjDsM92uxfOOc/1A= github.com/yuyang0/resource-gpu v0.0.0-20231026065700-1577d804efa8/go.mod h1:oggnae33QHkm9k2Xd0J4BFjdIV1VhPdpm4VUujYUvo0= +github.com/yuyang0/resource-hostdir v0.0.0-20240815062057-56acd9dde1f8 h1:VOtdr1qCKiCfhpe1Shv3GZjW4bOgiFjauC92iv6M6is= +github.com/yuyang0/resource-hostdir v0.0.0-20240815062057-56acd9dde1f8/go.mod h1:7lboIilJ/Ck1qALkQm5hj0kzUgF6Qm063bVH21riMSE= github.com/yuyang0/resource-rbd v0.0.2-0.20230701090628-cb86da0f60b9 h1:2La8T7mqVy98jyAkwxIN9gB+Akx3qbLGmVEtleaxND4= github.com/yuyang0/resource-rbd v0.0.2-0.20230701090628-cb86da0f60b9/go.mod h1:ANjyr7r+YfKtpWiIsZPzF7+krI55Uf84R9AvbNr5WAg= go.etcd.io/etcd v3.3.27+incompatible h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0= diff --git a/internal/service/boar/util.go b/internal/service/boar/util.go index d5d3f57..90c61ff 100644 --- a/internal/service/boar/util.go +++ b/internal/service/boar/util.go @@ -13,12 +13,14 @@ import ( "github.com/projecteru2/yavirt/internal/models" intertypes "github.com/projecteru2/yavirt/internal/types" "github.com/projecteru2/yavirt/internal/volume" + "github.com/projecteru2/yavirt/internal/volume/hostdir" "github.com/projecteru2/yavirt/internal/volume/local" "github.com/projecteru2/yavirt/internal/volume/rbd" cpumemtypes "github.com/projecteru2/core/resource/plugins/cpumem/types" stotypes "github.com/projecteru2/resource-storage/storage/types" gputypes "github.com/yuyang0/resource-gpu/gpu/types" + hostdirtypes "github.com/yuyang0/resource-hostdir/hostdir/types" rbdtypes "github.com/yuyang0/resource-rbd/rbd/types" ) @@ -42,7 +44,7 @@ func extractGPU(resources map[string][]byte) (eParams *gputypes.EngineParams, er return &ans, err } -func extractVols(resources map[string][]byte) ([]volume.Volume, error) { +func extractVols(resources map[string][]byte) ([]volume.Volume, error) { //nolint var sysVol volume.Volume vols := make([]volume.Volume, 1) // first place is for sys volume appendVol := func(vol volume.Volume) error { @@ -57,8 +59,7 @@ func extractVols(resources map[string][]byte) ([]volume.Volume, error) { return nil } - stoResRaw, ok := resources[intertypes.PluginNameStorage] - if ok { + if stoResRaw, ok := resources[intertypes.PluginNameStorage]; ok { eParams := &stotypes.EngineParams{} if err := json.Unmarshal(stoResRaw, eParams); err != nil { return nil, errors.Wrap(err, "") @@ -73,8 +74,7 @@ func extractVols(resources map[string][]byte) ([]volume.Volume, error) { } } } - rbdResRaw, ok := resources[intertypes.PluginNameRBD] - if ok { + if rbdResRaw, ok := resources[intertypes.PluginNameRBD]; ok { eParams := &rbdtypes.EngineParams{} if err := json.Unmarshal(rbdResRaw, eParams); err != nil { return nil, errors.Wrap(err, "") @@ -89,6 +89,21 @@ func extractVols(resources map[string][]byte) ([]volume.Volume, error) { } } } + if hostdirResRaw, ok := resources[intertypes.PluginNameHostdir]; ok { + eParams := &hostdirtypes.EngineParams{} + if err := json.Unmarshal(hostdirResRaw, eParams); err != nil { + return nil, errors.Wrap(err, "") + } + for _, part := range eParams.Volumes { + vol, err := hostdir.NewFromStr(part) + if err != nil { + return nil, err + } + if err := appendVol(vol); err != nil { + return nil, err + } + } + } if sysVol != nil { vols[0] = sysVol } else { diff --git a/internal/types/const.go b/internal/types/const.go index 2975d56..39ec65a 100644 --- a/internal/types/const.go +++ b/internal/types/const.go @@ -4,6 +4,7 @@ const ( PluginNameCPUMem = "cpumem" PluginNameGPU = "gpu" PluginNameRBD = "rbd" + PluginNameHostdir = "hostdir" PluginNameStorage = "storage" PluginNameBandwidth = "bandwidth" ) diff --git a/internal/virt/domain/domain.go b/internal/virt/domain/domain.go index 43a9fcd..d33548f 100644 --- a/internal/virt/domain/domain.go +++ b/internal/virt/domain/domain.go @@ -402,17 +402,12 @@ func (d *VirtDomain) render() ([]byte, error) { return nil, err } } - hostDirs, err := d.hostDirs() - if err != nil { - return nil, err - } var args = map[string]any{ "name": d.guest.ID, "uuid": uuid, "memory": d.guest.MemoryInMiB(), "cpu": d.guest.CPU, "gpus": gpus, - "host_dirs": hostDirs, "sysvol": string(sysVolXML), "datavols": dataVols, "interface": d.getInterfaceType(), @@ -560,29 +555,6 @@ func (d *VirtDomain) gpus() ([]map[string]string, error) { return allocGPUs(d.guest.GPUEngineParams) } -func (d *VirtDomain) hostDirs() ([]map[string]string, error) { - ss, ok := d.guest.JSONLabels["instance/host-dirs"] - if !ok { - return nil, nil - } - parts := strings.FieldsFunc(ss, func(r rune) bool { - return r == ',' || r == ' ' || r == ';' - }) - ans := make([]map[string]string, 0, len(parts)) - for _, p := range parts { - switch parts2 := strings.Split(p, ":"); len(parts2) { - case 2: - ans = append(ans, map[string]string{ - "src": parts2[0], - "dst": parts2[1], - }) - default: - return nil, fmt.Errorf("invalid host dir: %s", p) - } - } - return ans, nil -} - type vncConfig struct { Port int `json:"port"` Password string `json:"password"` @@ -755,24 +727,23 @@ func (d *VirtDomain) AttachVolume(buf []byte) (st libvirt.DomainState, err error return dom.AttachDevice(string(buf)) } -func (d *VirtDomain) DetachVolume(devPath string) (st libvirt.DomainState, err error) { +func (d *VirtDomain) DetachVolume(xmlQStr string) (st libvirt.DomainState, err error) { x, err := d.GetXMLString() if err != nil { return } - dev := filepath.Base(devPath) doc, err := xmlquery.Parse(strings.NewReader(x)) if err != nil { return } - node := xmlquery.FindOne(doc, fmt.Sprintf("//devices/disk[target[@dev='%s']]", dev)) + node := xmlquery.FindOne(doc, xmlQStr) if node == nil { err = errors.New("can't find device") return } xml := node.OutputXML(true) - log.Infof(context.TODO(), "Detach volume, device(%s) xml: %s", devPath, xml) + log.Infof(context.TODO(), "Detach volume, device(%s) xml: %s", xmlQStr, xml) var dom libvirt.Domain if dom, err = d.Lookup(); err != nil { return diff --git a/internal/virt/domain/templates/guest.xml b/internal/virt/domain/templates/guest.xml index b62bbde..f69b436 100644 --- a/internal/virt/domain/templates/guest.xml +++ b/internal/virt/domain/templates/guest.xml @@ -43,12 +43,11 @@ - {{if .host_dirs }} + - {{end}} {{ .sysvol }} @@ -65,14 +64,6 @@
- {{range .host_dirs}} - - - - - - {{end}} -
diff --git a/internal/virt/guest/bot.go b/internal/virt/guest/bot.go index 4101648..749ff3d 100644 --- a/internal/virt/guest/bot.go +++ b/internal/virt/guest/bot.go @@ -279,20 +279,19 @@ func (v *bot) AttachVolume(vol volume.Volume) (rollback func(), err error) { // DetachVolume . func (v *bot) DetachVolume(vol volume.Volume) (err error) { logger := log.WithFunc("DetachVolume") - devPath := vol.GetDevice() if configs.Conf.Storage.InitGuestVolume { switch st, err := v.GetState(); { case err != nil: return errors.Wrap(err, "") case st == libvirt.DomainRunning: - if err := volFact.Unmount(vol, v.ga, devPath); err != nil { + if err := volFact.Umount(vol, v.ga); err != nil { return errors.Wrap(err, "") } default: logger.Warnf(context.TODO(), "the guest is not running, so ignore to umount") } } - _, err = v.dom.DetachVolume(devPath) + _, err = v.dom.DetachVolume(vol.GetXMLQStr()) return } @@ -306,12 +305,11 @@ func (v *bot) ReplaceSysVolume(vol volume.Volume) error { // AmplifyVolume . func (v *bot) AmplifyVolume(vol volume.Volume, delta int64) (err error) { - devPath := base.GetDevicePathByName(vol.GetDevice()) dom, err := v.dom.Lookup() if err != nil { return errors.Wrap(err, "") } - _, err = volFact.Amplify(vol, delta, dom, v.ga, devPath) + _, err = volFact.Amplify(vol, delta, dom, v.ga) return err } diff --git a/internal/volume/base/util.go b/internal/volume/base/util.go index da460b5..85c3383 100644 --- a/internal/volume/base/util.go +++ b/internal/volume/base/util.go @@ -1,6 +1,7 @@ package base import ( + "bytes" "context" "fmt" "path/filepath" @@ -8,10 +9,13 @@ import ( "strings" "github.com/cockroachdb/errors" + "github.com/projecteru2/core/log" + "github.com/projecteru2/yavirt/configs" "github.com/projecteru2/yavirt/internal/types" "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/internal/virt/guestfs" "github.com/projecteru2/yavirt/internal/virt/nic" + "github.com/projecteru2/yavirt/pkg/libvirt" "github.com/projecteru2/yavirt/pkg/utils" ) @@ -36,6 +40,234 @@ func ResetFstab(gfs guestfs.Guestfs) error { return gfs.Write(types.FstabFile, cont) } +// func SaveFstab( +// ctx context.Context, ga agent.Interface, +// devPath, mountDir, fs string, +// backupDump, fsckPass int, +// ) error { +// var blkid, err = ga.Blkid(ctx, devPath) +// if err != nil { +// return errors.Wrap(err, "") +// } +// searchStrs := []string{blkid, mountDir} +// for _, searchStr := range searchStrs { +// switch exists, err := ga.Grep(ctx, searchStr, types.FstabFile); { +// case err != nil: +// return errors.Wrap(err, "") +// case exists: +// return nil +// } +// } + +// var line = fmt.Sprintf("\nUUID=%s %s %s defaults %d %d", +// blkid, mountDir, fs, backupDump, fsckPass) + +// return ga.AppendLine(ctx, types.FstabFile, []byte(line)) +// } + +func SaveFstab( + ctx context.Context, ga agent.Interface, + devPath, mountDir, fs string, + backupDump, fsckPass int, +) error { + searchStrs := []string{mountDir, devPath} + for _, searchStr := range searchStrs { + switch exists, err := ga.Grep(ctx, searchStr, types.FstabFile); { + case err != nil: + return errors.Wrap(err, "") + case exists: + return nil + } + } + + var line = fmt.Sprintf("\n%s %s %s defaults %d %d", + devPath, mountDir, fs, backupDump, fsckPass) + + return ga.AppendLine(ctx, types.FstabFile, []byte(line)) +} + +func MountBlockDevice( + ctx context.Context, ga agent.Interface, + name, devPath, mountDir string, +) error { + const ( + fs = "ext4" + // Disable backing up of the device/partition + backupDump = 0 + // Enable fsck checking the device/partition for errors at boot time. + fsckPass = 2 + ) + log.Debugf(ctx, "Mount: format") + if err := Format(ctx, ga, name, devPath, fs); err != nil { + return errors.Wrap(err, "") + } + + log.Debugf(ctx, "Mount: mount") + if err := Mount(ctx, ga, devPath, mountDir, fs); err != nil { + return errors.Wrap(err, "") + } + + log.Debugf(ctx, "Mount: save fstab") + if err := SaveFstab(ctx, ga, devPath, mountDir, fs, backupDump, fsckPass); err != nil { + return errors.Wrap(err, "") + } + + log.Debugf(ctx, "Mount: amplify if necessary") + switch amplified, err := IsAmplifying(ctx, ga, devPath, mountDir); { + case err != nil: + return errors.Wrap(err, "") + + case amplified: + return AmplifyDiskInGuest(ctx, ga, devPath) + + default: + return nil + } +} + +func UmountDevice( + ctx context.Context, ga agent.Interface, devPath string, +) error { + logger := log.WithFunc("base.UmountDevice") + logger.Debugf(ctx, "Umount: umount %s", devPath) + cmds := []string{"umount", devPath} + st := <-ga.ExecOutput(ctx, cmds[0], cmds[1:]...) + if err := st.Error(); err != nil { + logger.Warnf(ctx, "failed to run `%s`: %s", strings.Join(cmds, " "), err) + } + + logger.Debugf(ctx, "Umount: save fstab") + escapeDir := strings.ReplaceAll(devPath, "/", "\\/") + regex := fmt.Sprintf("/%s/d", escapeDir) + cmds = []string{"sed", "-i", regex, "/etc/fstab"} + st = <-ga.ExecOutput(ctx, cmds[0], cmds[1:]...) + if err := st.Error(); err != nil { + return errors.Wrapf(err, "failed to run `%v`", strings.Join(cmds, " ")) + } + return nil +} + +func Mount( + ctx context.Context, ga agent.Interface, + devPath, mountDir, fs string, +) error { + var st = <-ga.Exec(ctx, "mkdir", "-p", mountDir) + if err := st.Error(); err != nil { + return errors.Wrapf(err, "mkdir %s failed", mountDir) + } + + st = <-ga.ExecOutput(ctx, "mount", "-t", fs, devPath, mountDir) + _, _, err := st.CheckStdio(func(_, se []byte) bool { + return bytes.Contains(se, []byte("already mounted")) + }) + if err != nil { + return errors.Wrapf(err, "mount %s failed", mountDir) + } + return nil +} + +func Format( + ctx context.Context, ga agent.Interface, + volName, devPath, fs string, +) error { + switch formatted, err := isFormatted(ctx, ga, volName); { + case err != nil: + return errors.Wrap(err, "") + case formatted: + return nil + } + + if err := fdisk(ctx, ga, devPath, fs); err != nil { + return errors.Wrap(err, "") + } + + return ga.Touch(ctx, formattedFlagPath(volName)) +} + +// parted -s /dev/vdN mklabel gpt +// parted -s /dev/vdN mkpart primary 1049K -- -1 +// mkfs -F -t ext4 /dev/vdN +func fdisk(ctx context.Context, ga agent.Interface, devPath, fs string) error { + var cmds = [][]string{ + {"parted", "-s", devPath, "mklabel", "gpt"}, + {"parted", "-s", devPath, "mkpart", "primary", "1049K", "--", "-1"}, + {"mkfs", "-F", "-t", fs, devPath}, + } + return ExecCommands(ctx, ga, cmds) +} + +func isFormatted(ctx context.Context, ga agent.Interface, name string) (bool, error) { + return ga.IsFile(ctx, formattedFlagPath(name)) +} + +func formattedFlagPath(name string) string { + return fmt.Sprintf("/etc/%s", name) +} + +func IsAmplifying(ctx context.Context, ga agent.Interface, devPath, mountDir string) (bool, error) { + mbs, err := getMountedBlocks(ctx, ga, mountDir) + if err != nil { + return false, errors.Wrap(err, "") + } + + cap, err := agent.NewParted(ga, devPath).GetSize(ctx) //nolint + if err != nil { + return false, errors.Wrap(err, "") + } + + mbs = int64(float64(mbs) * (1 + configs.Conf.ResizeVolumeMinRatio)) + cap >>= 10 //nolint // in bytes, aka. 1K-blocks. + + return cap > mbs, nil +} + +func getMountedBlocks(ctx context.Context, ga agent.Interface, mountDir string) (int64, error) { + df, err := ga.GetDiskfree(ctx, mountDir) + if err != nil { + return 0, errors.Wrap(err, "") + } + return df.Blocks, nil +} + +func AmplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface, devPath string) error { + devname := filepath.Base(devPath) + if err := dom.AmplifyVolume(devname, uint64(newCap)); err != nil { + return errors.Wrap(err, "") + } + + ctx, cancel := context.WithTimeout(context.Background(), configs.Conf.GADiskTimeout) + defer cancel() + return AmplifyDiskInGuest(ctx, ga, devPath) +} + +func AmplifyDiskInGuest(ctx context.Context, ga agent.Interface, devPath string) error { + // NOTICE: + // Actually, volume raw devices aren't necessary for re-parting. + + stoppedServices, err := StopSystemdServices(ctx, ga, devPath) + if err != nil { + return errors.Wrap(err, "") + } + + cmds := [][]string{ + {"umount", devPath}, + {"partprobe"}, + {"e2fsck", "-fy", devPath}, + {"resize2fs", devPath}, + {"mount", "-a"}, + } + + if err := ExecCommands(ctx, ga, cmds); err != nil { + return errors.Wrap(err, "") + } + + if err := RestartSystemdServices(ctx, ga, stoppedServices); err != nil { + return errors.Wrap(err, "") + } + + return nil +} + func ResetUserImage(gfs guestfs.Guestfs) error { if err := ResetFstab(gfs); err != nil { return errors.Wrap(err, "") diff --git a/internal/volume/base/volume.go b/internal/volume/base/volume.go index cea59cb..3845bed 100644 --- a/internal/volume/base/volume.go +++ b/internal/volume/base/volume.go @@ -2,25 +2,38 @@ package base import ( "context" + "fmt" "github.com/cockroachdb/errors" "github.com/projecteru2/yavirt/internal/meta" + "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/pkg/idgen" "github.com/projecteru2/yavirt/pkg/store" ) +type VolumeType int + +const ( + VolumeTypeUndefined VolumeType = iota + VolumeTypeRBD + VolumeTypeLocal + VolumeTypeHostDir +) + type Volume struct { *meta.Generic `mapstructure:",squash"` - SysImage string `json:"sys_image,omitempty" mapstructure:"sys_image"` // for sys volume - Device string `json:"device" mapstructure:"device"` // vda, vdb, vdc etc. - Hostname string `json:"host" mapstructure:"host"` - GuestID string `json:"guest" mapstructure:"guest"` + SysImage string `json:"sys_image,omitempty" mapstructure:"sys_image"` // for sys volume + Device string `json:"device" mapstructure:"device"` // vda, vdb, vdc etc. + Hostname string `json:"host" mapstructure:"host"` + GuestID string `json:"guest" mapstructure:"guest"` + Type VolumeType `json:"type" mapstructure:"type"` } -func New() *Volume { +func New(ty VolumeType) *Volume { return &Volume{ Generic: meta.NewGeneric(), + Type: ty, } } @@ -53,6 +66,10 @@ func (v *Volume) GetGuestID() string { return v.GuestID } +func (v *Volume) GetXMLQStr() string { + return fmt.Sprintf("//devices/disk[target[@dev='%s']]", v.Device) +} + // Delete . func (v *Volume) Delete(force bool) error { if err := v.SetStatus(meta.StatusDestroyed, force); err != nil { @@ -67,3 +84,8 @@ func (v *Volume) Delete(force bool) error { return store.Delete(ctx, keys, vers) } + +func (v *Volume) Umount(ctx context.Context, ga agent.Interface) error { + devPath := GetDevicePathByName(v.Device) + return UmountDevice(ctx, ga, devPath) +} diff --git a/internal/volume/factory/factory.go b/internal/volume/factory/factory.go index 520a3c9..5cacbb5 100644 --- a/internal/volume/factory/factory.go +++ b/internal/volume/factory/factory.go @@ -10,6 +10,7 @@ import ( gfsmocks "github.com/projecteru2/yavirt/internal/virt/guestfs/mocks" "github.com/projecteru2/yavirt/internal/volume" "github.com/projecteru2/yavirt/internal/volume/base" + "github.com/projecteru2/yavirt/internal/volume/hostdir" "github.com/projecteru2/yavirt/internal/volume/local" "github.com/projecteru2/yavirt/internal/volume/rbd" "github.com/projecteru2/yavirt/pkg/utils" @@ -26,10 +27,27 @@ func LoadVolumes(ids []string) (vols Volumes, err error) { return nil, errors.Wrap(err, "") } var vol volume.Volume - if _, ok := rawVal["pool"]; ok { + ty := base.VolumeTypeUndefined + if rawType := rawVal["type"]; rawType != nil { + // golang json will convert all integer to float64 + ty = base.VolumeType(rawType.(float64)) + } + switch ty { + case base.VolumeTypeRBD: vol = rbd.New() - } else { + case base.VolumeTypeLocal: vol = local.NewVolume() + case base.VolumeTypeHostDir: + vol = hostdir.New() + case base.VolumeTypeUndefined: + // for compatibility + if _, ok := rawVal["pool"]; ok { + vol = rbd.New() + } else if _, ok := rawVal["format"]; ok { + vol = local.NewVolume() + } else { + vol = hostdir.New() + } } if err := mapstructure.Decode(rawVal, &vol); err != nil { diff --git a/internal/volume/factory/guest.go b/internal/volume/factory/guest.go index 35be86d..960ee9c 100644 --- a/internal/volume/factory/guest.go +++ b/internal/volume/factory/guest.go @@ -1,33 +1,19 @@ package factory import ( - "bytes" "context" - "fmt" - "path/filepath" - "strings" "github.com/cockroachdb/errors" "github.com/projecteru2/core/log" "github.com/projecteru2/yavirt/configs" "github.com/projecteru2/yavirt/internal/types" - interutils "github.com/projecteru2/yavirt/internal/utils" "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/internal/volume" - "github.com/projecteru2/yavirt/internal/volume/base" "github.com/projecteru2/yavirt/pkg/libvirt" "github.com/projecteru2/yavirt/pkg/terrors" "github.com/projecteru2/yavirt/pkg/utils" ) -const ( - fs = "ext4" - // Disable backing up of the device/partition - backupDump = 0 - // Enable fsck checking the device/partition for errors at boot time. - fsckPass = 2 -) - // Undefine . func Undefine(vol volume.Volume) error { if err := vol.Lock(); err != nil { @@ -73,7 +59,7 @@ func Create(vol volume.Volume) (func(), error) { } // Amplify . -func Amplify(vol volume.Volume, delta int64, dom libvirt.Domain, ga agent.Interface, devPath string) (normDelta int64, err error) { +func Amplify(vol volume.Volume, delta int64, dom libvirt.Domain, ga agent.Interface) (normDelta int64, err error) { if err := vol.Lock(); err != nil { return 0, errors.Wrap(err, "") } @@ -101,9 +87,9 @@ func Amplify(vol volume.Volume, delta int64, dom libvirt.Domain, ga agent.Interf } switch st { case libvirt.DomainShutoff: - err = interutils.AmplifyImage(context.Background(), vol.QemuImagePath(), delta) + err = vol.AmplifyOffline(context.Background(), delta) case libvirt.DomainRunning: - err = amplifyOnline(newCap, dom, ga, devPath) + err = vol.AmplifyOnline(newCap, dom, ga) default: err = types.NewDomainStatesErr(st, libvirt.DomainShutoff, libvirt.DomainRunning) } @@ -194,8 +180,8 @@ func FSFreeze(ctx context.Context, ga agent.Interface, v volume.Volume, unfreeze return nil } -// Unmount . -func Unmount(vol volume.Volume, ga agent.Interface, devPath string) error { +// Umount . +func Umount(vol volume.Volume, ga agent.Interface) error { if err := vol.Lock(); err != nil { return errors.Wrap(err, "") } @@ -203,24 +189,7 @@ func Unmount(vol volume.Volume, ga agent.Interface, devPath string) error { var ctx, cancel = context.WithTimeout(context.Background(), configs.Conf.GADiskTimeout) defer cancel() - - log.WithFunc("volume.Umount").Debugf(ctx, "Umount: umount %s", devPath) - cmds := []string{"umount", devPath} - st := <-ga.ExecOutput(ctx, cmds[0], cmds[1:]...) - if err := st.Error(); err != nil { - log.WithFunc("volume.Umount").Warnf(ctx, "failed to run `%s`: %s", strings.Join(cmds, " "), err) - } - - log.Debugf(ctx, "Umount: save fstab") - escapeDir := strings.ReplaceAll(vol.GetMountDir(), "/", "\\/") - regex := fmt.Sprintf("/%s/d", escapeDir) - cmds = []string{"sed", "-i", regex, "/etc/fstab"} - st = <-ga.ExecOutput(ctx, cmds[0], cmds[1:]...) - if err := st.Error(); err != nil { - return errors.Wrapf(err, "failed to run `%v`", strings.Join(cmds, " ")) - } - return nil - + return vol.Umount(ctx, ga) } // Mount . @@ -232,166 +201,5 @@ func Mount(vol volume.Volume, ga agent.Interface, devPath string) error { var ctx, cancel = context.WithTimeout(context.Background(), configs.Conf.GADiskTimeout) defer cancel() - - log.Debugf(ctx, "Mount: format") - if err := format(ctx, ga, vol, devPath); err != nil { - return errors.Wrap(err, "") - } - - log.Debugf(ctx, "Mount: mount") - if err := mount(ctx, ga, vol, devPath); err != nil { - return errors.Wrap(err, "") - } - - log.Debugf(ctx, "Mount: save fstab") - if err := saveFstab(ctx, ga, vol, devPath); err != nil { - return errors.Wrap(err, "") - } - - log.Debugf(ctx, "Mount: amplify if necessary") - switch amplified, err := isAmplifying(ctx, ga, vol, devPath); { - case err != nil: - return errors.Wrap(err, "") - - case amplified: - return amplifyDiskInGuest(ctx, ga, devPath) - - default: - return nil - } -} - -func mount(ctx context.Context, ga agent.Interface, v volume.Volume, devPath string) error { - var mnt = v.GetMountDir() - var st = <-ga.Exec(ctx, "mkdir", "-p", mnt) - if err := st.Error(); err != nil { - return errors.Wrapf(err, "mkdir %s failed", mnt) - } - - st = <-ga.ExecOutput(ctx, "mount", "-t", fs, devPath, mnt) - _, _, err := st.CheckStdio(func(_, se []byte) bool { - return bytes.Contains(se, []byte("already mounted")) - }) - if err != nil { - return errors.Wrapf(err, "mount %s failed", mnt) - } - return nil -} - -func saveFstab(ctx context.Context, ga agent.Interface, v volume.Volume, devPath string) error { - var blkid, err = ga.Blkid(ctx, devPath) - if err != nil { - return errors.Wrap(err, "") - } - - switch exists, err := ga.Grep(ctx, blkid, types.FstabFile); { - case err != nil: - return errors.Wrap(err, "") - case exists: - return nil - } - - var line = fmt.Sprintf("\nUUID=%s %s %s defaults %d %d", - blkid, v.GetMountDir(), fs, backupDump, fsckPass) - - return ga.AppendLine(ctx, types.FstabFile, []byte(line)) -} - -func format(ctx context.Context, ga agent.Interface, v volume.Volume, devPath string) error { - switch formatted, err := isFormatted(ctx, ga, v); { - case err != nil: - return errors.Wrap(err, "") - case formatted: - return nil - } - - if err := fdisk(ctx, ga, devPath); err != nil { - return errors.Wrap(err, "") - } - - return ga.Touch(ctx, formattedFlagPath(v)) -} - -// parted -s /dev/vdN mklabel gpt -// parted -s /dev/vdN mkpart primary 1049K -- -1 -// mkfs -F -t ext4 /dev/vdN -func fdisk(ctx context.Context, ga agent.Interface, devPath string) error { - var cmds = [][]string{ - {"parted", "-s", devPath, "mklabel", "gpt"}, - {"parted", "-s", devPath, "mkpart", "primary", "1049K", "--", "-1"}, - {"mkfs", "-F", "-t", fs, devPath}, - } - return base.ExecCommands(ctx, ga, cmds) -} - -func isFormatted(ctx context.Context, ga agent.Interface, v volume.Volume) (bool, error) { - return ga.IsFile(ctx, formattedFlagPath(v)) -} - -func formattedFlagPath(v volume.Volume) string { - return fmt.Sprintf("/etc/%s", v.Name()) -} - -func isAmplifying(ctx context.Context, ga agent.Interface, v volume.Volume, devPath string) (bool, error) { - mbs, err := getMountedBlocks(ctx, ga, v) - if err != nil { - return false, errors.Wrap(err, "") - } - - cap, err := agent.NewParted(ga, devPath).GetSize(ctx) //nolint - if err != nil { - return false, errors.Wrap(err, "") - } - - mbs = int64(float64(mbs) * (1 + configs.Conf.ResizeVolumeMinRatio)) - cap >>= 10 //nolint // in bytes, aka. 1K-blocks. - - return cap > mbs, nil -} - -func getMountedBlocks(ctx context.Context, ga agent.Interface, v volume.Volume) (int64, error) { - df, err := ga.GetDiskfree(ctx, v.GetMountDir()) - if err != nil { - return 0, errors.Wrap(err, "") - } - return df.Blocks, nil -} - -func amplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface, devPath string) error { - devname := filepath.Base(devPath) - if err := dom.AmplifyVolume(devname, uint64(newCap)); err != nil { - return errors.Wrap(err, "") - } - - ctx, cancel := context.WithTimeout(context.Background(), configs.Conf.GADiskTimeout) - defer cancel() - return amplifyDiskInGuest(ctx, ga, devPath) -} - -func amplifyDiskInGuest(ctx context.Context, ga agent.Interface, devPath string) error { - // NOTICE: - // Actually, volume raw devices aren't necessary for re-parting. - - stoppedServices, err := base.StopSystemdServices(ctx, ga, devPath) - if err != nil { - return errors.Wrap(err, "") - } - - cmds := [][]string{ - {"umount", devPath}, - {"partprobe"}, - {"e2fsck", "-fy", devPath}, - {"resize2fs", devPath}, - {"mount", "-a"}, - } - - if err := base.ExecCommands(ctx, ga, cmds); err != nil { - return errors.Wrap(err, "") - } - - if err := base.RestartSystemdServices(ctx, ga, stoppedServices); err != nil { - return errors.Wrap(err, "") - } - - return nil + return vol.Mount(ctx, ga, devPath) } diff --git a/internal/volume/hostdir/snapshot.go b/internal/volume/hostdir/snapshot.go new file mode 100644 index 0000000..d41ab70 --- /dev/null +++ b/internal/volume/hostdir/snapshot.go @@ -0,0 +1 @@ +package hostdir diff --git a/internal/volume/hostdir/snapshot_api.go b/internal/volume/hostdir/snapshot_api.go new file mode 100644 index 0000000..ceaf3b9 --- /dev/null +++ b/internal/volume/hostdir/snapshot_api.go @@ -0,0 +1,43 @@ +package hostdir + +import "github.com/projecteru2/yavirt/internal/volume/base" + +// SnapshotAPI . +type SnapshotAPI struct { + vol *Volume +} + +// New . +func newSnapshotAPI(v *Volume) *SnapshotAPI { + return &SnapshotAPI{ + vol: v, + } +} + +func (api *SnapshotAPI) List() base.Snapshots { + return nil +} +func (api *SnapshotAPI) Create() error { + return nil +} +func (api *SnapshotAPI) Commit(rootID string) error { //nolint + return nil +} +func (api *SnapshotAPI) CommitByDay(day int) error { //nolint + return nil +} +func (api *SnapshotAPI) Delete(id string) error { //nolint + return nil +} +func (api *SnapshotAPI) DeleteAll() error { + return nil +} +func (api *SnapshotAPI) Restore(rootID string) error { //nolint + return nil +} +func (api *SnapshotAPI) Upload(id string, force bool) error { //nolint + return nil +} +func (api *SnapshotAPI) Download(id string) error { //nolint + return nil +} diff --git a/internal/volume/hostdir/templates/disk.xml b/internal/volume/hostdir/templates/disk.xml new file mode 100644 index 0000000..62a2330 --- /dev/null +++ b/internal/volume/hostdir/templates/disk.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/internal/volume/hostdir/volume.go b/internal/volume/hostdir/volume.go new file mode 100644 index 0000000..c6ef886 --- /dev/null +++ b/internal/volume/hostdir/volume.go @@ -0,0 +1,183 @@ +package hostdir + +import ( + "context" + "fmt" + "path/filepath" + + _ "embed" + + "github.com/cockroachdb/errors" + "github.com/projecteru2/yavirt/configs" + "github.com/projecteru2/yavirt/internal/meta" + "github.com/projecteru2/yavirt/internal/utils/template" + "github.com/projecteru2/yavirt/internal/virt/agent" + "github.com/projecteru2/yavirt/internal/virt/guestfs" + "github.com/projecteru2/yavirt/internal/volume/base" + "github.com/projecteru2/yavirt/pkg/libvirt" + vmitypes "github.com/projecteru2/yavirt/pkg/vmimage/types" + hostdirTypes "github.com/yuyang0/resource-hostdir/hostdir/types" +) + +var ( + //go:embed templates/disk.xml + diskXML string +) + +type Volume struct { + base.Volume `mapstructure:",squash"` + hostdirTypes.VolumeBinding `mapstructure:",squash"` +} + +func New() *Volume { + return &Volume{ + Volume: *base.New(base.VolumeTypeHostDir), + } +} + +func NewFromStr(ss string) (*Volume, error) { + vb, err := hostdirTypes.NewVolumeBinding(ss) + if err != nil { + return nil, err + } + return &Volume{ + Volume: *base.New(base.VolumeTypeHostDir), + VolumeBinding: *vb, + }, nil +} + +func (v *Volume) Name() string { + return fmt.Sprintf("hostdir-%s", v.ID) +} + +func (v *Volume) QemuImagePath() string { + return "" +} + +func (v *Volume) GetSize() int64 { + return v.SizeInBytes +} + +func (v *Volume) SetSize(sz int64) { + v.SizeInBytes = sz +} + +func (v *Volume) GetMountDir() string { + return v.Destination +} + +func (v *Volume) GetXMLQStr() string { + return fmt.Sprintf("//devices/filesystem[target[@dir='%s']]", v.Destination) +} + +func (v *Volume) IsSys() bool { + return false +} + +func (v *Volume) PrepareSysDisk(_ context.Context, _ *vmitypes.Image, _ ...base.Option) error { + if !v.IsSys() { + panic("not a sys disk") + } + return nil +} + +func (v *Volume) PrepareDataDisk(_ context.Context) error { + if v.IsSys() { + panic("not a data disk") + } + return nil +} + +// qemu-img doesn't support checking rbd +func (v *Volume) Check() error { + return nil +} + +func (v *Volume) Repair() error { + return nil +} + +// mount -t virtiofs mount_tag /mnt/mount/path +// we use destination as mount tag +func (v *Volume) Mount(ctx context.Context, ga agent.Interface, _ string) error { + const ( + fs = "virtiofs" + backupDump = 0 + fsckPass = 0 + ) + devPath := v.Destination + mountDir := v.GetMountDir() + if err := base.Mount(ctx, ga, devPath, mountDir, fs); err != nil { + return errors.Wrap(err, "") + } + if err := base.SaveFstab(ctx, ga, devPath, mountDir, fs, backupDump, fsckPass); err != nil { + return errors.Wrap(err, "") + } + return nil +} + +func (v *Volume) Umount(ctx context.Context, ga agent.Interface) error { + return base.UmountDevice(ctx, ga, v.Destination) +} + +func (v *Volume) AmplifyOffline(_ context.Context, _ int64) error { + return nil +} + +func (v *Volume) AmplifyOnline(_ int64, _ libvirt.Domain, _ agent.Interface) error { + return nil +} + +func (v *Volume) GenerateXML() ([]byte, error) { + args := map[string]any{ + "src": v.Source, + "dst": v.Destination, + } + tmplFile := filepath.Join(configs.Conf.VirtTmplDir, "hostdir.xml") + return template.Render(tmplFile, diskXML, args) +} + +// Cleanup is mainly used to clean old sys disk before reiniitializing sys disk +func (v *Volume) Cleanup() error { + return nil +} + +func (v *Volume) CaptureImage(imgName string) (uimg *vmitypes.Image, err error) { //nolint + return +} + +func (v *Volume) Save() error { + if v.GetVer() == 0 { + return meta.Create(meta.Resources{v}) + } + return meta.Save(meta.Resources{v}) +} + +func (v *Volume) Lock() error { + return nil +} + +func (v *Volume) Unlock() {} + +func (v *Volume) NewSnapshotAPI() base.SnapshotAPI { + return newSnapshotAPI(v) +} + +func (v *Volume) GetGfx() (guestfs.Guestfs, error) { + // opts := &libguestfs.OptargsAdd_drive{ + // Readonly_is_set: false, + // Format_is_set: true, + // Format: "raw", + // Protocol_is_set: true, + // Protocol: "rbd", + // Server_is_set: true, + // // TODO add servers, user and secret for ceph + // Server: []string{}, + // Username_is_set: true, + // // User: "eru", + // Secret_is_set: true, + // Secret: "", + // } + // return gfsx.NewFromOpts(v.GetSource(), opts) + return nil, nil //nolint:nilnil +} diff --git a/internal/volume/hostdir/volume_test.go b/internal/volume/hostdir/volume_test.go new file mode 100644 index 0000000..97591cb --- /dev/null +++ b/internal/volume/hostdir/volume_test.go @@ -0,0 +1,37 @@ +package hostdir + +import ( + "fmt" + "testing" + + "github.com/projecteru2/yavirt/pkg/test/assert" + "github.com/stretchr/testify/require" +) + +func TestNewVolume(t *testing.T) { + cases := []struct { + in string + src string + dst string + }{ + {"/pool/image1:/data1", "/pool/image1", "/data1"}, + {"/pool/image1:/data1", "/pool/image1", "/data1"}, + } + + for _, c := range cases { + vol, err := NewFromStr(c.in) + assert.NilErr(t, err) + + assert.Equal(t, c.dst, vol.GetMountDir()) + assert.Equal(t, c.src, vol.Source) + } +} + +func TestGenerateXML(t *testing.T) { + vol, err := NewFromStr("/pool/image1:/data1") + vol.SetDevice("vda") + require.Nil(t, err) + bs, err := vol.GenerateXML() + assert.NilErr(t, err) + fmt.Printf("%s\n", string(bs)) +} diff --git a/internal/volume/local/volume.go b/internal/volume/local/volume.go index 9de8d8c..aec41a3 100644 --- a/internal/volume/local/volume.go +++ b/internal/volume/local/volume.go @@ -16,9 +16,11 @@ import ( "github.com/projecteru2/yavirt/configs" "github.com/projecteru2/yavirt/internal/meta" interutils "github.com/projecteru2/yavirt/internal/utils" + "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/internal/virt/guestfs" "github.com/projecteru2/yavirt/internal/virt/guestfs/gfsx" "github.com/projecteru2/yavirt/internal/volume/base" + "github.com/projecteru2/yavirt/pkg/libvirt" "github.com/projecteru2/yavirt/pkg/sh" "github.com/projecteru2/yavirt/pkg/terrors" "github.com/projecteru2/yavirt/pkg/utils" @@ -69,7 +71,7 @@ func NewVolumeFromStr(s string) (*Volume, error) { } return &Volume{ Format: VolQcow2Format, - Volume: *base.New(), + Volume: *base.New(base.VolumeTypeLocal), VolumeBinding: *vb, }, nil } @@ -106,7 +108,7 @@ func NewDataVolume(mnt string, cap int64) (*Volume, error) { func NewVolume() *Volume { return &Volume{ - Volume: *base.New(), + Volume: *base.New(base.VolumeTypeLocal), Format: VolQcow2Format, } } @@ -125,7 +127,7 @@ func (v *Volume) Unlock() { v.flock.Close() } -func (v *Volume) QemuImagePath() string { +func (v *Volume) qemuImagePath() string { return v.Filepath() } @@ -244,6 +246,19 @@ func (v *Volume) Repair() error { return interutils.Repair(context.Background(), v.Filepath()) } +func (v *Volume) Mount(ctx context.Context, ga agent.Interface, devPath string) error { + return base.MountBlockDevice(ctx, ga, v.Name(), devPath, v.GetMountDir()) +} + +func (v *Volume) AmplifyOffline(ctx context.Context, delta int64) error { + return interutils.AmplifyImage(ctx, v.qemuImagePath(), delta) +} + +func (v *Volume) AmplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface) error { + devPath := base.GetDevicePathByName(v.GetDevice()) + return base.AmplifyOnline(newCap, dom, ga, devPath) +} + // IsSys . func (v *Volume) IsSys() bool { return strings.Contains(v.Flags, "s") diff --git a/internal/volume/mocks/Volume.go b/internal/volume/mocks/Volume.go index b7fb720..03d3648 100644 --- a/internal/volume/mocks/Volume.go +++ b/internal/volume/mocks/Volume.go @@ -3,12 +3,15 @@ package mocks import ( - context "context" - + agent "github.com/projecteru2/yavirt/internal/virt/agent" base "github.com/projecteru2/yavirt/internal/volume/base" + context "context" + guestfs "github.com/projecteru2/yavirt/internal/virt/guestfs" + libvirt "github.com/projecteru2/yavirt/pkg/libvirt" + mock "github.com/stretchr/testify/mock" types "github.com/projecteru2/yavirt/pkg/vmimage/types" @@ -19,6 +22,42 @@ type Volume struct { mock.Mock } +// AmplifyOffline provides a mock function with given fields: ctx, delta +func (_m *Volume) AmplifyOffline(ctx context.Context, delta int64) error { + ret := _m.Called(ctx, delta) + + if len(ret) == 0 { + panic("no return value specified for AmplifyOffline") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, delta) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AmplifyOnline provides a mock function with given fields: newCap, dom, ga +func (_m *Volume) AmplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface) error { + ret := _m.Called(newCap, dom, ga) + + if len(ret) == 0 { + panic("no return value specified for AmplifyOnline") + } + + var r0 error + if rf, ok := ret.Get(0).(func(int64, libvirt.Domain, agent.Interface) error); ok { + r0 = rf(newCap, dom, ga) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // CaptureImage provides a mock function with given fields: imgName func (_m *Volume) CaptureImage(imgName string) (*types.Image, error) { ret := _m.Called(imgName) @@ -330,6 +369,24 @@ func (_m *Volume) GetVer() int64 { return r0 } +// GetXMLQStr provides a mock function with given fields: +func (_m *Volume) GetXMLQStr() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetXMLQStr") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // IncrVer provides a mock function with given fields: func (_m *Volume) IncrVer() { _m.Called() @@ -389,6 +446,24 @@ func (_m *Volume) MetaKey() string { return r0 } +// Mount provides a mock function with given fields: ctx, ga, devPath +func (_m *Volume) Mount(ctx context.Context, ga agent.Interface, devPath string) error { + ret := _m.Called(ctx, ga, devPath) + + if len(ret) == 0 { + panic("no return value specified for Mount") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, agent.Interface, string) error); ok { + r0 = rf(ctx, ga, devPath) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Name provides a mock function with given fields: func (_m *Volume) Name() string { ret := _m.Called() @@ -470,24 +545,6 @@ func (_m *Volume) PrepareSysDisk(_a0 context.Context, _a1 *types.Image, _a2 ...b return r0 } -// QemuImagePath provides a mock function with given fields: -func (_m *Volume) QemuImagePath() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for QemuImagePath") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - // Repair provides a mock function with given fields: func (_m *Volume) Repair() error { ret := _m.Called() @@ -567,6 +624,24 @@ func (_m *Volume) SetVer(_a0 int64) { _m.Called(_a0) } +// Umount provides a mock function with given fields: ctx, ga +func (_m *Volume) Umount(ctx context.Context, ga agent.Interface) error { + ret := _m.Called(ctx, ga) + + if len(ret) == 0 { + panic("no return value specified for Umount") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, agent.Interface) error); ok { + r0 = rf(ctx, ga) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Unlock provides a mock function with given fields: func (_m *Volume) Unlock() { _m.Called() diff --git a/internal/volume/rbd/volume.go b/internal/volume/rbd/volume.go index 7415086..13eac08 100644 --- a/internal/volume/rbd/volume.go +++ b/internal/volume/rbd/volume.go @@ -17,9 +17,11 @@ import ( "github.com/projecteru2/yavirt/configs" "github.com/projecteru2/yavirt/internal/meta" interutils "github.com/projecteru2/yavirt/internal/utils" + "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/internal/virt/guestfs" "github.com/projecteru2/yavirt/internal/virt/guestfs/gfsx" "github.com/projecteru2/yavirt/internal/volume/base" + "github.com/projecteru2/yavirt/pkg/libvirt" vmiFact "github.com/projecteru2/yavirt/pkg/vmimage/factory" vmitypes "github.com/projecteru2/yavirt/pkg/vmimage/types" libguestfs "github.com/projecteru2/yavirt/third_party/guestfs" @@ -39,7 +41,7 @@ type Volume struct { func New() *Volume { return &Volume{ - Volume: *base.New(), + Volume: *base.New(base.VolumeTypeRBD), } } @@ -49,7 +51,7 @@ func NewFromStr(ss string) (*Volume, error) { return nil, err } return &Volume{ - Volume: *base.New(), + Volume: *base.New(base.VolumeTypeRBD), VolumeBinding: *vb, }, nil } @@ -58,7 +60,7 @@ func (v *Volume) Name() string { return fmt.Sprintf("rbd-%s", v.ID) } -func (v *Volume) QemuImagePath() string { +func (v *Volume) qemuImagePath() string { rbdDisk := fmt.Sprintf("rbd:%s/%s:id=%s", v.Pool, v.Image, configs.Conf.Storage.Ceph.Username) return rbdDisk } @@ -145,6 +147,19 @@ func (v *Volume) Repair() error { return nil } +func (v *Volume) Mount(ctx context.Context, ga agent.Interface, devPath string) error { + return base.MountBlockDevice(ctx, ga, v.Name(), devPath, v.GetMountDir()) +} + +func (v *Volume) AmplifyOffline(ctx context.Context, delta int64) error { + return interutils.AmplifyImage(ctx, v.qemuImagePath(), delta) +} + +func (v *Volume) AmplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface) error { + devPath := base.GetDevicePathByName(v.GetDevice()) + return base.AmplifyOnline(newCap, dom, ga, devPath) +} + func (v *Volume) GenerateXML() ([]byte, error) { // prepare monitor addresses cephMonitorAddrs := []map[string]string{} diff --git a/internal/volume/volume.go b/internal/volume/volume.go index 07aba66..8e7a74b 100644 --- a/internal/volume/volume.go +++ b/internal/volume/volume.go @@ -5,8 +5,10 @@ import ( "github.com/cockroachdb/errors" "github.com/projecteru2/yavirt/internal/meta" + "github.com/projecteru2/yavirt/internal/virt/agent" "github.com/projecteru2/yavirt/internal/virt/guestfs" "github.com/projecteru2/yavirt/internal/volume/base" + "github.com/projecteru2/yavirt/pkg/libvirt" vmitypes "github.com/projecteru2/yavirt/pkg/vmimage/types" ) @@ -15,12 +17,13 @@ type Volume interface { //nolint:interfacebloat // getters Name() string - QemuImagePath() string GetMountDir() string GetSize() int64 GetDevice() string GetHostname() string GetGuestID() string + // when detach volume, we need provide a query string to find the associated device in domain xml + GetXMLQStr() string // setters SetDevice(dev string) SetHostname(name string) @@ -32,6 +35,12 @@ type Volume interface { //nolint:interfacebloat Check() error Repair() error IsSys() bool + + Mount(ctx context.Context, ga agent.Interface, devPath string) error + Umount(ctx context.Context, ga agent.Interface) error + AmplifyOffline(ctx context.Context, delta int64) error + AmplifyOnline(newCap int64, dom libvirt.Domain, ga agent.Interface) error + // prepare the volume, run before create guest. PrepareSysDisk(context.Context, *vmitypes.Image, ...base.Option) error PrepareDataDisk(context.Context) error