diff --git a/pkg/apis/compute/disk.go b/pkg/apis/compute/disk.go index 8f9149ef1f..8a88c86021 100644 --- a/pkg/apis/compute/disk.go +++ b/pkg/apis/compute/disk.go @@ -233,11 +233,6 @@ type DiskDetails struct { // 自动快照策略 Snapshotpolicies []SimpleSnapshotPolicy `json:"snapshotpolicies"` - - // 手动快照数量 - ManualSnapshotCount int `json:"manual_snapshot_count"` - // 最多可创建手动快照数量 - MaxManualSnapshotCount int `json:"max_manual_snapshot_count"` } type DiskResourceInfoBase struct { diff --git a/pkg/apis/compute/storage_const.go b/pkg/apis/compute/storage_const.go index 6c34c2678a..535da8bff5 100644 --- a/pkg/apis/compute/storage_const.go +++ b/pkg/apis/compute/storage_const.go @@ -175,7 +175,8 @@ var ( HOST_STORAGE_LOCAL_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_ZSTACK_LOCAL_STORAGE, STORAGE_OPENSTACK_NOVA} - STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS, STORAGE_GPFS, STORAGE_VSAN, STORAGE_CIFS} + STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, + STORAGE_NFS, STORAGE_GPFS, STORAGE_VSAN, STORAGE_CIFS, STORAGE_CLVM, STORAGE_SLVM} SHARED_FILE_STORAGE = []string{STORAGE_NFS, STORAGE_GPFS} FIEL_STORAGE = []string{STORAGE_LOCAL, STORAGE_NFS, STORAGE_GPFS} diff --git a/pkg/compute/hostdrivers/kvm.go b/pkg/compute/hostdrivers/kvm.go index 7a740d4382..ab2497f80f 100644 --- a/pkg/compute/hostdrivers/kvm.go +++ b/pkg/compute/hostdrivers/kvm.go @@ -287,7 +287,7 @@ func (self *SKVMHostDriver) RequestAllocateDiskOnStorage(ctx context.Context, us } snapshot := snapObj.(*models.SSnapshot) snapshotStorage := models.StorageManager.FetchStorageById(snapshot.StorageId) - if snapshotStorage.StorageType == api.STORAGE_LOCAL { + if snapshotStorage.StorageType == api.STORAGE_LOCAL || snapshotStorage.StorageType == api.STORAGE_LVM { snapshotHost, err := snapshotStorage.GetMasterHost() if err != nil { return errors.Wrapf(err, "GetMasterHost") diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 9d67b51c00..dbf7d518df 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -851,37 +851,6 @@ func (self *SDisk) StartAllocate(ctx context.Context, host *SHost, storage *SSto } } -func (self *SDisk) GetDetailsConvertSnapshot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - needs, err := SnapshotManager.IsDiskSnapshotsNeedConvert(self.Id) - if err != nil { - return nil, httperrors.NewInternalServerError("Fetch snapshot count failed %s", err) - } - if !needs { - return nil, httperrors.NewBadRequestError("Disk %s don't need convert snapshots", self.Id) - } - - deleteSnapshot := SnapshotManager.GetDiskFirstSnapshot(self.Id) - if deleteSnapshot == nil { - return nil, httperrors.NewNotFoundError("Can not get disk snapshot") - } - convertSnapshot, err := SnapshotManager.GetConvertSnapshot(deleteSnapshot) - if err != nil { - return nil, httperrors.NewBadRequestError("Get convert snapshot failed: %s", err.Error()) - } - if convertSnapshot == nil { - return nil, httperrors.NewBadRequestError("Snapshot %s dose not have convert snapshot", deleteSnapshot.Id) - } - var FakeDelete bool - if deleteSnapshot.CreatedBy == api.SNAPSHOT_MANUAL && !deleteSnapshot.FakeDeleted { - FakeDelete = true - } - ret := jsonutils.NewDict() - ret.Set("delete_snapshot", jsonutils.NewString(deleteSnapshot.Id)) - ret.Set("convert_snapshot", jsonutils.NewString(convertSnapshot.Id)) - ret.Set("pending_delete", jsonutils.NewBool(FakeDelete)) - return ret, nil -} - // make snapshot after reset out of chain func (self *SDisk) CleanUpDiskSnapshots(ctx context.Context, userCred mcclient.TokenCredential, snapshot *SSnapshot) error { dest := make([]SSnapshot, 0) @@ -2382,60 +2351,6 @@ func (manager *SDiskManager) FetchCustomizeColumns( }) } - storageSQ := StorageManager.Query().SubQuery() - diskSQ := DiskManager.Query().SubQuery() - q = storageSQ.Query( - storageSQ.Field("storage_type"), - diskSQ.Field("id").Label("disk_id"), - ).Join(diskSQ, sqlchemy.Equals(diskSQ.Field("storage_id"), storageSQ.Field("id"))). - Filter(sqlchemy.In(diskSQ.Field("id"), diskIds)) - - storageInfo := []struct { - StorageType string - DiskId string - }{} - err = q.All(&storageInfo) - if err != nil { - log.Errorf("query disk storage info error: %v", err) - return rows - } - - storages := map[string]string{} - for _, storage := range storageInfo { - storages[storage.DiskId] = storage.StorageType - } - - snapshotSQ := SnapshotManager.Query().SubQuery() - q = snapshotSQ.Query( - snapshotSQ.Field("id"), - diskSQ.Field("id").Label("disk_id"), - ).Join(diskSQ, sqlchemy.Equals(diskSQ.Field("id"), snapshotSQ.Field("disk_id"))). - Filter( - sqlchemy.AND( - sqlchemy.In(diskSQ.Field("id"), diskIds), - sqlchemy.Equals(snapshotSQ.Field("created_by"), api.SNAPSHOT_MANUAL), - sqlchemy.Equals(snapshotSQ.Field("fake_deleted"), false), - ), - ) - - snapshotInfo := []struct { - Id string - DiskId string - }{} - err = q.All(&snapshotInfo) - if err != nil { - log.Errorf("query disk snapshot info error: %v", err) - return rows - } - snapshots := map[string][]string{} - for _, snapshot := range snapshotInfo { - _, ok := snapshots[snapshot.DiskId] - if !ok { - snapshots[snapshot.DiskId] = []string{} - } - snapshots[snapshot.DiskId] = append(snapshots[snapshot.DiskId], snapshot.Id) - } - for i := range rows { rows[i].Guests, _ = guests[diskIds[i]] names, status := []string{}, []string{} @@ -2449,12 +2364,6 @@ func (manager *SDiskManager) FetchCustomizeColumns( rows[i].Snapshotpolicies, _ = policies[diskIds[i]] - storageType, ok := storages[diskIds[i]] - if ok && utils.IsInStringArray(storageType, append(api.SHARED_FILE_STORAGE, api.STORAGE_LOCAL)) { - rows[i].MaxManualSnapshotCount = options.Options.DefaultMaxManualSnapshotCount - snps, _ := snapshots[diskIds[i]] - rows[i].ManualSnapshotCount = len(snps) - } disk := objs[i].(*SDisk) if len(disk.StorageId) == 0 && disk.Status == api.VM_SCHEDULE_FAILED { rows[i].Brand = "Unknown" @@ -2704,6 +2613,9 @@ func (disk *SDisk) validateDiskAutoCreateSnapshot() error { return fmt.Errorf("Guest(%s) in status(%s) cannot do disk snapshot", guests[0].Id, guests[0].Status) } } + if storageFree := storage.GetFreeCapacity(); storageFree < int64(disk.DiskSize) { + return fmt.Errorf("Storage(%s) space not enough", storage.GetName()) + } return nil } @@ -2721,7 +2633,6 @@ func (manager *SDiskManager) AutoDiskSnapshot(ctx context.Context, userCred mccl log.Errorf("get disk error: %v", err) continue } - autoSnapshotCount := options.Options.DefaultMaxSnapshotCount - options.Options.DefaultMaxManualSnapshotCount err = func() error { policy, err := disks[i].GetSnapshotPolicy() @@ -2741,16 +2652,8 @@ func (manager *SDiskManager) AutoDiskSnapshot(ctx context.Context, userCred mccl return errors.Wrapf(err, "CreateSnapshotAuto") } - snapCount, err := SnapshotManager.Query().Equals("fake_deleted", false). - Equals("disk_id", disk.Id).Equals("created_by", api.SNAPSHOT_AUTO). - CountWithError() - if err != nil { - return errors.Wrap(err, "get snapshot count") - } - // if auto snapshot count gt max auto snapshot count, do clean overdued snapshots - cleanOverdueSnapshots := snapCount > autoSnapshotCount - if cleanOverdueSnapshots { - disk.CleanOverdueSnapshots(ctx, userCred, policy, now) + if err = disk.CleanOverduedSnapshots(ctx, userCred, policy, now); err != nil { + log.Errorf("failed clean overdued snapshots %s", err) } db.OpsLog.LogEvent(disk, db.ACT_DISK_AUTO_SNAPSHOT, snapshot.Name, userCred) policy.ExecuteNotify(ctx, userCred, disk.GetName()) @@ -2817,15 +2720,21 @@ func (self *SDisk) CreateSnapshotAuto( return snapshot, nil } -func (self *SDisk) CleanOverdueSnapshots(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, now time.Time) error { - kwargs := jsonutils.NewDict() - kwargs.Set("retention_day", jsonutils.NewInt(int64(sp.RetentionDays))) - kwargs.Set("start_time", jsonutils.NewTimeString(now)) - task, err := taskman.TaskManager.NewTask(ctx, "DiskCleanOverduedSnapshots", self, userCred, kwargs, "", "", nil) +func (self *SDisk) CleanOverduedSnapshots(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, now time.Time) error { + snapshot := new(SSnapshot) + err := SnapshotManager.Query().Equals("disk_id", self.Id). + Equals("created_by", api.SNAPSHOT_AUTO).Equals("fake_deleted", false).Asc("created_at").First(snapshot) if err != nil { - return errors.Wrapf(err, "NewTask") + return errors.Wrap(err, "get snapshot") } - return task.ScheduleRun(nil) + snapshot.SetModelManager(SnapshotManager, snapshot) + if snapshot.ExpiredAt.Before(now) { + err = snapshot.StartSnapshotDeleteTask(ctx, userCred, false, self.Id) + if err != nil { + return err + } + } + return nil } func (self *SDisk) StartCreateBackupTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index bf47b291c3..caf98abbb5 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -3785,7 +3785,7 @@ func (self *SGuest) guestDisksStorageTypeIsLocal() bool { disks, _ := self.GetDisks() for _, disk := range disks { storage, _ := disk.GetStorage() - if storage.StorageType != api.STORAGE_LOCAL { + if storage.StorageType != api.STORAGE_LOCAL && storage.StorageType != api.STORAGE_LVM { return false } } @@ -3796,7 +3796,7 @@ func (self *SGuest) guestDisksStorageTypeIsShared() bool { disks, _ := self.GetDisks() for _, disk := range disks { storage, _ := disk.GetStorage() - if storage.StorageType == api.STORAGE_LOCAL { + if storage.StorageType == api.STORAGE_LOCAL || storage.StorageType == api.STORAGE_LVM { return false } } @@ -4858,17 +4858,17 @@ func (self *SGuest) validateCreateInstanceSnapshot( if err != nil { return nil, input, errors.Wrapf(err, "GetDisks") } - for i := 0; i < len(disks); i++ { - if storage, _ := disks[i].GetStorage(); utils.IsInStringArray(storage.StorageType, api.FIEL_STORAGE) { - count, err := SnapshotManager.GetDiskManualSnapshotCount(disks[i].Id) - if err != nil { - return nil, input, httperrors.NewInternalServerError("%v", err) - } - if count >= options.Options.DefaultMaxManualSnapshotCount { - return nil, input, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i) - } - } - } + //for i := 0; i < len(disks); i++ { + // if storage, _ := disks[i].GetStorage(); utils.IsInStringArray(storage.StorageType, api.FIEL_STORAGE) { + // count, err := SnapshotManager.GetDiskManualSnapshotCount(disks[i].Id) + // if err != nil { + // return nil, input, httperrors.NewInternalServerError("%v", err) + // } + // if count >= options.Options.DefaultMaxManualSnapshotCount { + // return nil, input, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i) + // } + // } + //} pendingUsage.Snapshot = len(disks) } keys, err := self.GetRegionalQuotaKeys() diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 4375b017ae..f17c881025 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -6076,7 +6076,7 @@ func (self *SGuest) FillDiskSchedDesc(desc *api.ServerConfigs) { for i := 0; i < len(guestDisks); i++ { diskConf := guestDisks[i].ToDiskConfig() // HACK: storage used by self, so earse it - if diskConf.Backend == api.STORAGE_LOCAL { + if !utils.IsInStringArray(diskConf.Backend, api.SHARED_STORAGE) { diskConf.Storage = "" } desc.Disks = append(desc.Disks, diskConf) diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index 307caa2f3d..9a3cc5a327 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -27,7 +27,6 @@ import ( "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/util/compare" "yunion.io/x/pkg/util/rbacscope" - "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -582,7 +581,7 @@ func (self *SSnapshot) GetFuseUrl() (string, error) { return "", errors.Wrapf(err, "StorageManager.FetchById(%s)", self.StorageId) } storage := iStorage.(*SStorage) - if storage.StorageType != api.STORAGE_LOCAL { + if storage.StorageType != api.STORAGE_LOCAL && storage.StorageType != api.STORAGE_LVM { return "", nil } host, err := storage.GetMasterHost() @@ -635,16 +634,6 @@ func (self *SSnapshotManager) GetDiskManualSnapshotCount(diskId string) (int, er return self.Query().Equals("disk_id", diskId).Equals("fake_deleted", false).CountWithError() } -func (self *SSnapshotManager) IsDiskSnapshotsNeedConvert(diskId string) (bool, error) { - count, err := self.Query().Equals("disk_id", diskId). - In("status", []string{api.SNAPSHOT_READY, api.SNAPSHOT_DELETING}). - Equals("out_of_chain", false).CountWithError() - if err != nil { - return false, err - } - return count >= options.Options.DefaultMaxSnapshotCount, nil -} - func (self *SSnapshotManager) GetDiskFirstSnapshot(diskId string) *SSnapshot { dest := &SSnapshot{} q := self.Query().SubQuery() @@ -806,12 +795,6 @@ func (self *SSnapshot) PerformSyncstatus(ctx context.Context, userCred mcclient. return nil, StartResourceSyncStatusTask(ctx, userCred, self, "SnapshotSyncstatusTask", "") } -func (self *SSnapshotManager) GetPropertyMaxCount(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - ret := jsonutils.NewDict() - ret.Set("max_count", jsonutils.NewInt(int64(options.Options.DefaultMaxSnapshotCount))) - return ret, nil -} - func (self *SSnapshotManager) GetConvertSnapshot(deleteSnapshot *SSnapshot) (*SSnapshot, error) { dest := &SSnapshot{} q := self.Query() @@ -913,18 +896,6 @@ func (self *SSnapshot) GetBackingDisks() ([]string, error) { } } -func (self *SSnapshot) FakeDelete(userCred mcclient.TokenCredential) error { - _, err := db.Update(self, func() error { - self.FakeDeleted = true - self.Name += timeutils.IsoTime(time.Now()) - return nil - }) - if err == nil { - db.OpsLog.LogEvent(self, db.ACT_SNAPSHOT_FAKE_DELETE, "snapshot fake delete", userCred) - } - return err -} - func (self *SSnapshot) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index dd47b67fad..b9c9d96a3c 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -27,7 +27,6 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/util/httputils" "yunion.io/x/pkg/util/rbacscope" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -81,7 +80,7 @@ type SStorage struct { // we always expect actual capacity great or equal than zero, otherwise something wrong ActualCapacityUsed int64 `nullable:"true" list:"user" update:"domain" create:"domain_optional"` // 预留容量大小 - Reserved int64 `nullable:"true" default:"0" list:"domain" update:"domain"` + Reserved int64 `nullable:"true" default:"0" list:"domain" update:"domain" create:"domain_optional"` // 存储类型 // example: local StorageType string `width:"64" charset:"ascii" nullable:"false" list:"user" create:"domain_required"` @@ -1856,31 +1855,6 @@ func (self *SStorage) GetSchedtagJointManager() ISchedtagJointManager { return StorageschedtagManager } -func (manager *SStorageManager) StorageSnapshotsRecycle(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { - storages := []SStorage{} - q := manager.Query().Equals("enabled", true). - In("status", []string{api.STORAGE_ENABLED, api.STORAGE_ONLINE}). - In("storage_type", api.SHARED_FILE_STORAGE) - err := db.FetchModelObjects(manager, q, &storages) - if err != nil { - log.Errorf("Get shared file storage failed %s", err) - return - } - for i := 0; i < len(storages); i++ { - host, err := storages[i].GetMasterHost() - if err != nil { - log.Errorf("get master host for storage %s(%s) failed: %v", storages[i].Name, storages[i].Id, err) - continue - } - url := fmt.Sprintf("%s/storages/%s/snapshots-recycle", host.ManagerUri, storages[i].Id) - headers := mcclient.GetTokenHeaders(userCred) - _, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, headers, nil, false) - if err != nil { - log.Errorf("Storage request snapshots recycle failed %s", err) - } - } -} - func (self *SStorage) StartDeleteRbdDisks(ctx context.Context, userCred mcclient.TokenCredential, disksId []string) error { data := jsonutils.NewDict() data.Add(jsonutils.NewStringArray(disksId), "disks_id") diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 93e2d0d943..0e47a8ad12 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -100,10 +100,8 @@ type ComputeOptions struct { BaremetalPreparePackageUrl string `help:"Baremetal online register package"` // snapshot options - AutoSnapshotDay int `default:"1" help:"Days auto snapshot disks, default 1 day"` - AutoSnapshotHour int `default:"2" help:"What hour take sanpshot, default 02:00"` - DefaultMaxSnapshotCount int `default:"9" help:"Per Disk max snapshot count, default 9"` - DefaultMaxManualSnapshotCount int `default:"2" help:"Per Disk max manual snapshot count, default 2"` + AutoSnapshotDay int `default:"1" help:"Days auto snapshot disks, default 1 day"` + AutoSnapshotHour int `default:"2" help:"What hour take sanpshot, default 02:00"` //snapshot policy options RetentionDaysLimit int `default:"49" help:"Days of snapshot retention, default 49 days"` diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index eef8e1f483..612bb4dd2e 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -187,7 +187,6 @@ func StartServiceWithJobs(jobs func(cron *cronman.SCronJobManager)) { cron.AddJobEveryFewDays("SyncNatSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncNatSkus, true) cron.AddJobEveryFewDays("SyncNasSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncNasSkus, true) cron.AddJobEveryFewDays("SyncElasticCacheSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncElasticCacheSkus, true) - cron.AddJobEveryFewDays("StorageSnapshotsRecycle", 1, 2, 0, 0, models.StorageManager.StorageSnapshotsRecycle, false) cron.AddJobEveryFewDays("SnapshotDataCleaning", 1, 0, 0, 0, models.SnapshotManager.DataCleaning, true) diff --git a/pkg/compute/storagedrivers/base.go b/pkg/compute/storagedrivers/base.go index e6bc72a1dc..9e96d3584f 100644 --- a/pkg/compute/storagedrivers/base.go +++ b/pkg/compute/storagedrivers/base.go @@ -20,14 +20,12 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" - "yunion.io/x/sqlchemy" - "yunion.io/x/onecloud/pkg/apis/compute" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -56,16 +54,6 @@ func (self *SBaseStorageDriver) ValidateSnapshotDelete(ctx context.Context, snap if snapshot.RefCount > 0 { return httperrors.NewBadRequestError("Snapshot reference(by disk) count > 0, can not delete") } - - if !snapshot.OutOfChain && snapshot.FakeDeleted { - disk, _ := snapshot.GetDisk() - if disk != nil { - _, err := models.SnapshotManager.GetConvertSnapshot(snapshot) - if err != nil { - return httperrors.NewBadRequestError("disk need at least one of snapshot as backing file") - } - } - } return nil } @@ -82,16 +70,6 @@ func (self *SBaseStorageDriver) ValidateCreateSnapshotData(ctx context.Context, if !utils.IsInStringArray(guest.Status, []string{api.VM_RUNNING, api.VM_READY}) { return httperrors.NewInvalidStatusError("Cannot do snapshot when VM in status %s", guest.Status) } - q := models.SnapshotManager.Query() - cnt, err := q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), disk.Id), - sqlchemy.Equals(q.Field("created_by"), api.SNAPSHOT_MANUAL), - sqlchemy.IsFalse(q.Field("fake_deleted")))).CountWithError() - if err != nil { - return httperrors.NewInternalServerError("check disk snapshot count fail %s", err) - } - if cnt >= options.Options.DefaultMaxManualSnapshotCount { - return httperrors.NewBadRequestError("Disk %s snapshot full, cannot take any more", disk.Id) - } return nil } @@ -125,27 +103,20 @@ func (self *SBaseStorageDriver) RequestDeleteSnapshot(ctx context.Context, snaps params.Set("disk_id", jsonutils.NewString(snapshot.DiskId)) return guest.GetDriver().RequestReloadDiskSnapshot(ctx, guest, task, params) } else { - if !snapshot.FakeDeleted { - snapshot.SetStatus(ctx, task.GetUserCred(), compute.SNAPSHOT_READY, "snapshot fake_delete") - task.SetStageComplete(ctx, nil) - return snapshot.FakeDelete(task.GetUserCred()) - } - - convertSnapshot, _ := models.SnapshotManager.GetConvertSnapshot(snapshot) - if convertSnapshot == nil { - return fmt.Errorf("snapshot dose not have convert snapshot") + convertSnapshot, err := models.SnapshotManager.GetConvertSnapshot(snapshot) + if err != nil && err != sql.ErrNoRows { + return errors.Wrap(err, "get convert snapshot") } snapshot.SetStatus(ctx, task.GetUserCred(), api.SNAPSHOT_DELETING, "On SnapshotDeleteTask StartDeleteSnapshot") params := jsonutils.NewDict() params.Set("delete_snapshot", jsonutils.NewString(snapshot.Id)) params.Set("disk_id", jsonutils.NewString(snapshot.DiskId)) if !snapshot.OutOfChain { - params.Set("convert_snapshot", jsonutils.NewString(convertSnapshot.Id)) - var FakeDelete = jsonutils.JSONFalse - if snapshot.CreatedBy == api.SNAPSHOT_MANUAL && snapshot.FakeDeleted == false { - FakeDelete = jsonutils.JSONTrue + if convertSnapshot != nil { + params.Set("convert_snapshot", jsonutils.NewString(convertSnapshot.Id)) + } else { + params.Set("block_stream", jsonutils.JSONTrue) } - params.Set("pending_delete", FakeDelete) } else { params.Set("auto_deleted", jsonutils.JSONTrue) } diff --git a/pkg/compute/storagedrivers/clvm.go b/pkg/compute/storagedrivers/clvm.go index e64ce57366..a13427275d 100644 --- a/pkg/compute/storagedrivers/clvm.go +++ b/pkg/compute/storagedrivers/clvm.go @@ -21,7 +21,6 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -72,7 +71,7 @@ func (self *SCLVMStorageDriver) ValidateSnapshotDelete(ctx context.Context, snap } func (s *SCLVMStorageDriver) ValidateCreateSnapshotData(ctx context.Context, userCred mcclient.TokenCredential, disk *models.SDisk, input *api.SnapshotCreateInput) error { - return errors.Errorf("lvm storage unsupported create snapshot") + return nil } func (s *SCLVMStorageDriver) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, storage *models.SStorage, data jsonutils.JSONObject) { diff --git a/pkg/compute/storagedrivers/local.go b/pkg/compute/storagedrivers/local.go index 310f386a64..3ec56a6c3b 100644 --- a/pkg/compute/storagedrivers/local.go +++ b/pkg/compute/storagedrivers/local.go @@ -73,7 +73,7 @@ func (self *SLVMStorageDriver) PostCreate(ctx context.Context, userCred mcclient } func (self *SLVMStorageDriver) ValidateCreateSnapshotData(ctx context.Context, userCred mcclient.TokenCredential, disk *models.SDisk, input *api.SnapshotCreateInput) error { - return errors.Errorf("lvm storage unsupported create snapshot") + return nil } func (self *SLVMStorageDriver) ValidateSnapshotDelete(ctx context.Context, snapshot *models.SSnapshot) error { diff --git a/pkg/compute/storagedrivers/slvm.go b/pkg/compute/storagedrivers/slvm.go index df77a76ba0..2a5a2047d2 100644 --- a/pkg/compute/storagedrivers/slvm.go +++ b/pkg/compute/storagedrivers/slvm.go @@ -21,7 +21,6 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -89,7 +88,7 @@ func (self *SSLVMStorageDriver) ValidateSnapshotDelete(ctx context.Context, snap } func (s *SSLVMStorageDriver) ValidateCreateSnapshotData(ctx context.Context, userCred mcclient.TokenCredential, disk *models.SDisk, input *api.SnapshotCreateInput) error { - return errors.Errorf("lvm storage unsupported create snapshot") + return nil } func (s *SSLVMStorageDriver) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, storage *models.SStorage, data jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/disk_clean_overdued_snapshots.go b/pkg/compute/tasks/disk_clean_overdued_snapshots.go index ad920ad8a1..dee3c89999 100644 --- a/pkg/compute/tasks/disk_clean_overdued_snapshots.go +++ b/pkg/compute/tasks/disk_clean_overdued_snapshots.go @@ -26,7 +26,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/compute/options" ) type DiskCleanOverduedSnapshots struct { @@ -34,64 +33,9 @@ type DiskCleanOverduedSnapshots struct { } func init() { - taskman.RegisterTask(DiskCleanOverduedSnapshots{}) taskman.RegisterTask(SnapshotCleanupTask{}) } -func (self *DiskCleanOverduedSnapshots) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - disk := obj.(*models.SDisk) - retentionDays, _ := self.Params.Int("retention_days") - - now, err := self.Params.GetTime("start_time") - if err != nil { - self.SetStageFailed(ctx, jsonutils.NewString("failed to get start time")) - return - } - - var ( - snapCount int - cleanOverdueSnapshots bool - ) - - snapCount, err = models.SnapshotManager.Query().Equals("fake_deleted", false).Equals("disk_id", disk.Id). - Equals("created_by", compute.SNAPSHOT_AUTO).CountWithError() - if err != nil { - err = fmt.Errorf("GetSnapshotCount fail %s", err) - return - } - cleanOverdueSnapshots = snapCount > (options.Options.DefaultMaxSnapshotCount - options.Options.DefaultMaxManualSnapshotCount) - - if retentionDays > 0 && !cleanOverdueSnapshots { - t := now.AddDate(0, 0, -1*int(retentionDays)) - snapCount, err = models.SnapshotManager.Query().Equals("fake_deleted", false).Equals("disk_id", disk.Id). - Equals("created_by", compute.SNAPSHOT_AUTO).LT("created_at", t).CountWithError() - if err != nil { - self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) - return - } - cleanOverdueSnapshots = snapCount > 0 - } - - if !cleanOverdueSnapshots { - self.SetStageComplete(ctx, nil) - return - } - - snapshot := new(models.SSnapshot) - err = models.SnapshotManager.Query().Equals("disk_id", disk.Id). - Equals("created_by", compute.SNAPSHOT_AUTO).Equals("fake_deleted", false).Asc("created_at").First(snapshot) - if err != nil { - self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) - return - } - snapshot.SetModelManager(models.SnapshotManager, snapshot) - err = snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.Id) - if err != nil { - self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) - return - } -} - type SnapshotCleanupTask struct { taskman.STask } diff --git a/pkg/compute/tasks/guest_sync_task.go b/pkg/compute/tasks/guest_sync_task.go index 76f10a0398..c723fe9d43 100644 --- a/pkg/compute/tasks/guest_sync_task.go +++ b/pkg/compute/tasks/guest_sync_task.go @@ -57,9 +57,10 @@ func (self *GuestSyncConfTask) OnInit(ctx context.Context, obj db.IStandaloneMod func (self *GuestSyncConfTask) OnSyncComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) + fwOnly, _ := self.GetParams().Bool("fw_only") if restart, _ := self.Params.Bool("restart_network"); restart { self.StartRestartNetworkTask(ctx, guest) - } else if data.Contains("task") { + } else if !fwOnly && data.Contains("task") { // XXX this is only applied to KVM, which will call task_complete twice self.SetStage("OnDiskSyncComplete", nil) } else { diff --git a/pkg/compute/tasks/snapshot_delete_task.go b/pkg/compute/tasks/snapshot_delete_task.go index 39dfa7fff4..5cccbce8e1 100644 --- a/pkg/compute/tasks/snapshot_delete_task.go +++ b/pkg/compute/tasks/snapshot_delete_task.go @@ -91,24 +91,8 @@ func (self *SnapshotDeleteTask) OnDeleteSnapshot(ctx context.Context, snapshot * return } snapshot.SetStatus(ctx, self.UserCred, api.SNAPSHOT_READY, "OnDeleteSnapshot") - if snapshot.OutOfChain { - snapshot.RealDelete(ctx, self.UserCred) - self.TaskComplete(ctx, snapshot, nil) - } else { - var FakeDelete = false - if snapshot.CreatedBy == api.SNAPSHOT_MANUAL && snapshot.FakeDeleted == false { - FakeDelete = true - } - if FakeDelete { - db.Update(snapshot, func() error { - snapshot.OutOfChain = true - return nil - }) - } else { - snapshot.RealDelete(ctx, self.UserCred) - } - self.TaskComplete(ctx, snapshot, nil) - } + snapshot.RealDelete(ctx, self.UserCred) + self.TaskComplete(ctx, snapshot, nil) } func (self *SnapshotDeleteTask) OnDeleteSnapshotFailed(ctx context.Context, snapshot *models.SSnapshot, data jsonutils.JSONObject) { diff --git a/pkg/hostimage/host_image_service.go b/pkg/hostimage/host_image_service.go index 62bc536d03..3beb7f8338 100644 --- a/pkg/hostimage/host_image_service.go +++ b/pkg/hostimage/host_image_service.go @@ -41,6 +41,7 @@ import ( type SHostImageOptions struct { common_options.CommonOptions LocalImagePath []string `help:"Local Image Paths"` + LVMVolumeGroups []string `help:"LVM Volume Groups(vgs)"` SnapshotDirSuffix string `help:"Snapshot dir name equal diskId concat snapshot dir suffix" default:"_snap"` CommonConfigFile string `help:"common config file for container"` StreamChunkSize int `help:"Download stream chunk size KB" default:"4096"` @@ -96,6 +97,12 @@ func getDiskPath(diskId string) string { return diskPath } } + for _, vg := range HostImageOptions.LVMVolumeGroups { + diskPath := path.Join("/dev", vg, diskId) + if _, err := os.Stat(diskPath); !os.IsNotExist(err) { + return diskPath + } + } return "" } @@ -107,6 +114,12 @@ func getSnapshotPath(diskId, snapshotId string) string { return diskPath } } + for _, vg := range HostImageOptions.LVMVolumeGroups { + diskPath := path.Join("/dev", vg, "snap_"+diskId+snapshotId) + if _, err := os.Stat(diskPath); !os.IsNotExist(err) { + return diskPath + } + } return "" } diff --git a/pkg/hostman/guestman/guesthandlers/guesthandler.go b/pkg/hostman/guestman/guesthandlers/guesthandler.go index 73e7512aa2..9d72d19968 100644 --- a/pkg/hostman/guestman/guesthandlers/guesthandler.go +++ b/pkg/hostman/guestman/guesthandlers/guesthandler.go @@ -693,18 +693,18 @@ func guestDeleteSnapshot(ctx context.Context, userCred mcclient.TokenCredential, Disk: disk, } - if !jsonutils.QueryBoolean(body, "auto_deleted", false) { + // blockStream indicate snapshot<-disk + blockStream := jsonutils.QueryBoolean(body, "block_stream", false) + autoDeleted := jsonutils.QueryBoolean(body, "auto_deleted", false) + + if !blockStream && !autoDeleted { convertSnapshot, err := body.GetString("convert_snapshot") if err != nil { return nil, httperrors.NewMissingParameterError("convert_snapshot") } params.ConvertSnapshot = convertSnapshot - pendingDelete, err := body.Bool("pending_delete") - if err != nil { - return nil, httperrors.NewMissingParameterError("pending_delete") - } - params.PendingDelete = pendingDelete } + params.BlockStream = blockStream hostutils.DelayTask(ctx, guestman.GetGuestManager().DeleteSnapshot, params) return nil, nil } diff --git a/pkg/hostman/guestman/guesthelper.go b/pkg/hostman/guestman/guesthelper.go index f0875c5384..1ca8bc52a5 100644 --- a/pkg/hostman/guestman/guesthelper.go +++ b/pkg/hostman/guestman/guesthelper.go @@ -140,7 +140,7 @@ type SDeleteDiskSnapshot struct { DeleteSnapshot string Disk storageman.IDisk ConvertSnapshot string - PendingDelete bool + BlockStream bool } type SLibvirtServer struct { diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go index 8134591571..37a0826e20 100644 --- a/pkg/hostman/guestman/guestman.go +++ b/pkg/hostman/guestman/guestman.go @@ -1102,8 +1102,12 @@ func (m *SGuestManager) DestPrepareMigrate(ctx context.Context, params interface for _, disk := range guest.Desc.Disks { if disk.Path != "" { + d, err := storageman.GetManager().GetDiskByPath(disk.Path) + if err != nil { + return nil, errors.Wrapf(err, "GetDiskByPath(%s)", disk.Path) + } if disk.StorageType == compute.STORAGE_SLVM { - if err := lvmutils.LVActive(disk.Path, true, false); err != nil { + if err := lvmutils.LVActive(disk.Path, d.GetStorage().Lvmlockd(), false); err != nil { return nil, errors.Wrap(err, "lvm active with shared") } _, err := storageman.GetManager().GetDiskByPath(disk.Path) @@ -1291,14 +1295,14 @@ func (m *SGuestManager) DeleteSnapshot(ctx context.Context, params interface{}) return nil, hostutils.ParamsError } - if len(delParams.ConvertSnapshot) > 0 { + if len(delParams.ConvertSnapshot) > 0 || delParams.BlockStream { guest, _ := m.GetServer(delParams.Sid) return guest.ExecDeleteSnapshotTask(ctx, delParams.Disk, delParams.DeleteSnapshot, - delParams.ConvertSnapshot, delParams.PendingDelete) + delParams.ConvertSnapshot, delParams.BlockStream) } else { res := jsonutils.NewDict() res.Set("deleted", jsonutils.JSONTrue) - return res, delParams.Disk.DeleteSnapshot(delParams.DeleteSnapshot, "", false) + return res, delParams.Disk.DeleteSnapshot(delParams.DeleteSnapshot, "") } } diff --git a/pkg/hostman/guestman/guesttasks.go b/pkg/hostman/guestman/guesttasks.go index 3f8e7faf42..3118c2d92b 100644 --- a/pkg/hostman/guestman/guesttasks.go +++ b/pkg/hostman/guestman/guesttasks.go @@ -43,7 +43,6 @@ import ( "yunion.io/x/onecloud/pkg/hostman/options" "yunion.io/x/onecloud/pkg/hostman/storageman" "yunion.io/x/onecloud/pkg/util/cgrouputils/cpuset" - "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemuimg" "yunion.io/x/onecloud/pkg/util/timeutils2" @@ -1845,7 +1844,7 @@ func (s *SGuestStreamDisksTask) OnGetBlockJobs(jobs []monitor.BlockJob) { func (s *SGuestStreamDisksTask) taskComplete() { hostutils.UpdateServerProgress(context.Background(), s.Id, 100.0, 0.0) - s.SyncStatus("") + s.SyncStatus("Guest Disks Block Stream Complete") if s.callback != nil { s.callback() @@ -2026,63 +2025,59 @@ type SGuestSnapshotDeleteTask struct { *SGuestReloadDiskTask deleteSnapshot string convertSnapshot string - pendingDelete bool + blockStream bool tmpPath string } func NewGuestSnapshotDeleteTask( ctx context.Context, s *SKVMGuestInstance, disk storageman.IDisk, - deleteSnapshot, convertSnapshot string, pendingDelete bool, + deleteSnapshot, convertSnapshot string, blockStream bool, ) *SGuestSnapshotDeleteTask { return &SGuestSnapshotDeleteTask{ SGuestReloadDiskTask: NewGuestReloadDiskTask(ctx, s, disk), deleteSnapshot: deleteSnapshot, convertSnapshot: convertSnapshot, - pendingDelete: pendingDelete, + blockStream: blockStream, } } func (s *SGuestSnapshotDeleteTask) Start() { + if s.blockStream { + s.startBlockStream() + return + } + if err := s.doDiskConvert(); err != nil { s.taskFailed(err.Error()) + return } s.fetchDisksInfo(s.doReloadDisk) } -func (s *SGuestSnapshotDeleteTask) doDiskConvert() error { - snapshotDir := s.disk.GetSnapshotDir() - snapshotPath := path.Join(snapshotDir, s.convertSnapshot) - img, err := qemuimg.NewQemuImage(snapshotPath) - if err != nil { - log.Errorln(err) - return err - } - convertedDisk := snapshotPath + ".tmp" - if err = img.Convert2Qcow2To(convertedDisk, true, "", "", ""); err != nil { - log.Errorln(err) - if fileutils2.Exists(convertedDisk) { - os.Remove(convertedDisk) +func (s *SGuestSnapshotDeleteTask) startBlockStream() { + diskIdx := []int{} + for i := range s.Desc.Disks { + if s.Desc.Disks[i].DiskId == s.disk.GetId() { + diskIdx = append(diskIdx, int(s.Desc.Disks[i].Index)) } - return err } + s.StreamDisks(s.ctx, s.onStreamDiskComplete, diskIdx) +} - s.tmpPath = snapshotPath + ".swap" - if output, err := procutils.NewCommand("mv", "-f", snapshotPath, s.tmpPath).Output(); err != nil { - log.Errorf("mv %s to %s failed: %s, %s", snapshotPath, s.tmpPath, err, output) - if fileutils2.Exists(s.tmpPath) { - procutils.NewCommand("mv", "-f", s.tmpPath, snapshotPath).Output() - } - return err +func (s *SGuestSnapshotDeleteTask) onStreamDiskComplete() { + // remove snapshot file + if err := s.disk.DoDeleteSnapshot(s.deleteSnapshot); err != nil { + hostutils.TaskFailed(s.ctx, err.Error()) + return } - if output, err := procutils.NewCommand("mv", "-f", convertedDisk, snapshotPath).Output(); err != nil { - log.Errorf("mv %s to %s failed: %s, %s", convertedDisk, snapshotPath, err, output) - if fileutils2.Exists(s.tmpPath) { - procutils.NewCommand("mv", "-f", s.tmpPath, snapshotPath).Output() - } - return err - } - return nil + body := jsonutils.NewDict() + body.Set("deleted", jsonutils.JSONTrue) + hostutils.TaskComplete(s.ctx, body) +} + +func (s *SGuestSnapshotDeleteTask) doDiskConvert() error { + return s.disk.ConvertSnapshot(s.convertSnapshot) } func (s *SGuestSnapshotDeleteTask) doReloadDisk(device string) { @@ -2115,9 +2110,7 @@ func (s *SGuestSnapshotDeleteTask) onResumeSucc(res string) { log.Errorf("rm %s failed: %s, %s", s.tmpPath, err, output) } } - if !s.pendingDelete { - s.disk.DoDeleteSnapshot(s.deleteSnapshot) - } + s.disk.DoDeleteSnapshot(s.deleteSnapshot) body := jsonutils.NewDict() body.Set("deleted", jsonutils.JSONTrue) hostutils.TaskComplete(s.ctx, body) diff --git a/pkg/hostman/guestman/qemu-kvm.go b/pkg/hostman/guestman/qemu-kvm.go index c3b12df299..284c257eee 100644 --- a/pkg/hostman/guestman/qemu-kvm.go +++ b/pkg/hostman/guestman/qemu-kvm.go @@ -3235,28 +3235,25 @@ func (s *SKVMGuestInstance) StaticSaveSnapshot( func (s *SKVMGuestInstance) ExecDeleteSnapshotTask( ctx context.Context, disk storageman.IDisk, - deleteSnapshot string, convertSnapshot string, pendingDelete bool, + deleteSnapshot string, convertSnapshot string, blockStream bool, ) (jsonutils.JSONObject, error) { if s.IsRunning() { if s.isLiveSnapshotEnabled() { - task := NewGuestSnapshotDeleteTask(ctx, s, disk, - deleteSnapshot, convertSnapshot, pendingDelete) + task := NewGuestSnapshotDeleteTask(ctx, s, disk, deleteSnapshot, convertSnapshot, blockStream) task.Start() return nil, nil } else { return nil, fmt.Errorf("Guest dosen't support live snapshot delete") } } else { - return s.deleteStaticSnapshotFile(ctx, disk, deleteSnapshot, - convertSnapshot, pendingDelete) + return s.deleteStaticSnapshotFile(ctx, disk, deleteSnapshot, convertSnapshot, blockStream) } } func (s *SKVMGuestInstance) deleteStaticSnapshotFile( - ctx context.Context, disk storageman.IDisk, - deleteSnapshot string, convertSnapshot string, pendingDelete bool, + ctx context.Context, disk storageman.IDisk, deleteSnapshot, convertSnapshot string, blockStream bool, ) (jsonutils.JSONObject, error) { - if err := disk.DeleteSnapshot(deleteSnapshot, convertSnapshot, pendingDelete); err != nil { + if err := disk.DeleteSnapshot(deleteSnapshot, convertSnapshot); err != nil { log.Errorln(err) return nil, err } @@ -3353,7 +3350,7 @@ func (s *SKVMGuestInstance) PrepareDisksMigrate(liveMigrate bool) (*jsonutils.JS if err != nil { return nil, nil, false, errors.Wrapf(err, "GetDiskByPath(%s)", disk.Path) } - if d.GetType() == api.STORAGE_LOCAL { + if d.GetType() == api.STORAGE_LOCAL || d.GetType() == api.STORAGE_LVM { snaps, back, hasTemplate, err := d.PrepareMigrate(liveMigrate) if err != nil { return nil, nil, false, err @@ -3369,7 +3366,7 @@ func (s *SKVMGuestInstance) PrepareDisksMigrate(liveMigrate bool) (*jsonutils.JS } } else if d.GetType() == api.STORAGE_SLVM { if d.GetStorage().Lvmlockd() { - if err := lvmutils.LVActive(d.GetPath(), true, false); err != nil { + if err := lvmutils.LVActive(d.GetPath(), d.GetStorage().Lvmlockd(), false); err != nil { return nil, nil, false, errors.Wrap(err, "lvm active with share") } } diff --git a/pkg/hostman/guestman/qemu-kvmhelper.go b/pkg/hostman/guestman/qemu-kvmhelper.go index 88c0cddd54..68a8089be9 100644 --- a/pkg/hostman/guestman/qemu-kvmhelper.go +++ b/pkg/hostman/guestman/qemu-kvmhelper.go @@ -15,7 +15,6 @@ package guestman import ( - "context" "fmt" "net" "path" @@ -591,12 +590,9 @@ func (s *SKVMGuestInstance) slaveDiskPrepare(input *qemu.GenerateStartOptionsInp if err != nil { return errors.Wrapf(err, "GetDiskByPath(%s)", diskPath) } - if output, err := procutils.NewCommand("rm", "-f", diskPath).Output(); err != nil { - return errors.Errorf("failed delete slave top disk file %s %s", output, err) - } - diskUrl := fmt.Sprintf("%s/%s", diskUri, input.GuestDesc.Disks[i].DiskId) - if err := d.CreateFromImageFuse(context.Background(), diskUrl, 0, nil); err != nil { - return errors.Wrap(err, "failed create slave disk") + err = d.RebuildSlaveDisk(diskUri) + if err != nil { + return errors.Wrap(err, "RebuildSlaveDisk") } } return nil diff --git a/pkg/hostman/guestman/qemu/generate.go b/pkg/hostman/guestman/qemu/generate.go index 7f3d9ccc37..60687a6b12 100644 --- a/pkg/hostman/guestman/qemu/generate.go +++ b/pkg/hostman/guestman/qemu/generate.go @@ -321,7 +321,7 @@ func getDiskDriveOption(drvOpt QemuOptions, disk *desc.SGuestDisk, isEncrypt boo } func isLocalStorage(disk *desc.SGuestDisk) bool { - if disk.StorageType == api.STORAGE_LOCAL || len(disk.StorageType) == 0 { + if disk.StorageType == api.STORAGE_LOCAL || disk.StorageType == api.STORAGE_LVM || len(disk.StorageType) == 0 { return true } else { return false diff --git a/pkg/hostman/storageman/core.go b/pkg/hostman/storageman/core.go index 41e632a20b..a11074c698 100644 --- a/pkg/hostman/storageman/core.go +++ b/pkg/hostman/storageman/core.go @@ -74,7 +74,6 @@ func NewStorageManager(host hostutils.IHost) (*SStorageManager, error) { for i, d := range options.HostOptions.LocalImagePath { s := NewLocalStorage(ret, d, i) if err := s.Accessible(); err == nil { - StartSnapshotRecycle(s) ret.Storages = append(ret.Storages, s) if allFull && s.GetFreeSizeMb() > MINIMAL_FREE_SPACE { allFull = false @@ -265,6 +264,10 @@ func (s *SStorageManager) GetDiskByPath(diskPath string) (IDisk, error) { if pos > 0 { diskId = diskId[:pos] } + + if strings.HasPrefix(sPath, "/dev/") { + sPath = strings.TrimPrefix(sPath, "/dev/") + } storages, err := s.GetStoragesByPath(sPath) if err != nil { return nil, errors.Wrapf(err, "GetStoragesByPath") diff --git a/pkg/hostman/storageman/disk_base.go b/pkg/hostman/storageman/disk_base.go index f720bb160b..512d5df4c9 100644 --- a/pkg/hostman/storageman/disk_base.go +++ b/pkg/hostman/storageman/disk_base.go @@ -45,6 +45,7 @@ type IDisk interface { GetSnapshotDir() string DoDeleteSnapshot(snapshotId string) error GetSnapshotLocation() string + GetSnapshotPath(snapshotId string) string GetStorage() IStorage @@ -58,6 +59,7 @@ type IDisk interface { CleanupSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) PrepareMigrate(liveMigrate bool) ([]string, string, bool, error) + RebuildSlaveDisk(diskUri string) error CreateFromUrl(ctx context.Context, url string, size int64, callback func(progress, progressMbps float64, totalSizeMb int64)) error CreateFromTemplate(context.Context, string, string, int64, *apis.SEncryptInfo) (jsonutils.JSONObject, error) CreateFromSnapshotLocation(ctx context.Context, location string, size int64, encryptInfo *apis.SEncryptInfo) error @@ -67,9 +69,10 @@ type IDisk interface { encryptInfo *apis.SEncryptInfo, diskId string, back string) (jsonutils.JSONObject, error) PostCreateFromImageFuse() CreateSnapshot(snapshotId string, encryptKey string, encFormat qemuimg.TEncryptFormat, encAlg seclib2.TSymEncAlg) error - DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error + DeleteSnapshot(snapshotId, convertSnapshot string) error DeployGuestFs(diskInfo *deployapi.DiskInfo, guestDesc *desc.SGuestDesc, deployInfo *deployapi.DeployInfo) (jsonutils.JSONObject, error) + ConvertSnapshot(convertSnapshotId string) error // GetBackupDir() string DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) @@ -133,7 +136,11 @@ func (d *SBaseDisk) CreateSnapshot(snapshotId string, encryptKey string, encForm return errors.Errorf("unsupported operation") } -func (d *SBaseDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error { +func (d *SBaseDisk) ConvertSnapshot(convertSnapshotId string) error { + return errors.Errorf("unsupported operation") +} + +func (d *SBaseDisk) DeleteSnapshot(snapshotId, convertSnapshot string) error { return errors.Errorf("unsupported operation") } @@ -157,6 +164,10 @@ func (d *SBaseDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, er return nil, "", false, errors.Errorf("unsupported operation") } +func (d *SBaseDisk) RebuildSlaveDisk(diskUri string) error { + return nil +} + func (d *SBaseDisk) PostCreateFromImageFuse() { } @@ -195,6 +206,10 @@ func (d *SBaseDisk) GetSnapshotLocation() string { return "" } +func (d *SBaseDisk) GetSnapshotPath(snapshotId string) string { + return "" +} + func (d *SBaseDisk) FormatFs(fsFormat, uuid string, diskInfo *deployapi.DiskInfo) { log.Infof("Make disk %s fs %s", uuid, fsFormat) _, err := deployclient.GetDeployClient().FormatFs( diff --git a/pkg/hostman/storageman/disk_clvm.go b/pkg/hostman/storageman/disk_clvm.go index d931253c8a..5a03bac56f 100644 --- a/pkg/hostman/storageman/disk_clvm.go +++ b/pkg/hostman/storageman/disk_clvm.go @@ -17,259 +17,29 @@ package storageman import ( "context" "fmt" - "path" - - "yunion.io/x/cloudmux/pkg/cloudprovider" - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/appctx" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/qemuimgfmt" - "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" - deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" - "yunion.io/x/onecloud/pkg/hostman/hostutils" - "yunion.io/x/onecloud/pkg/hostman/storageman/lvmutils" - "yunion.io/x/onecloud/pkg/hostman/storageman/storageutils" - "yunion.io/x/onecloud/pkg/util/fileutils2" - "yunion.io/x/onecloud/pkg/util/procutils" - "yunion.io/x/onecloud/pkg/util/qemuimg" ) type SCLVMDisk struct { - SBaseDisk -} - -func (d *SCLVMDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - return nil, errors.ErrNotImplemented + SLVMDisk } func NewCLVMDisk(storage IStorage, id string) *SCLVMDisk { return &SCLVMDisk{ - SBaseDisk: *NewBaseDisk(storage, id), + SLVMDisk: *NewLVMDisk(storage, id), } } +func (d *SCLVMDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, error) { + return nil, "", false, fmt.Errorf("Not support") +} + +func (d *SCLVMDisk) CreateFromImageFuse(ctx context.Context, url string, size int64, encryptInfo *apis.SEncryptInfo) error { + return fmt.Errorf("Not support") +} + func (d *SCLVMDisk) GetType() string { return api.STORAGE_CLVM } - -// /dev// -func (d *SCLVMDisk) GetLvPath() string { - return path.Join("/dev", d.Storage.GetPath(), d.Id) -} - -func (d *SCLVMDisk) GetPath() string { - return path.Join("/dev", d.Storage.GetPath(), d.Id) -} - -func (d *SCLVMDisk) GetDiskSetupScripts(idx int) string { - return fmt.Sprintf("DISK_%d='%s'\n", idx, d.GetPath()) -} - -func (d *SCLVMDisk) GetDiskDesc() jsonutils.JSONObject { - qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) - if err != nil { - log.Errorln(err) - return nil - } - - var desc = jsonutils.NewDict() - desc.Set("disk_id", jsonutils.NewString(d.Id)) - desc.Set("disk_size", jsonutils.NewInt(qemuImg.SizeBytes/1024/1024)) - desc.Set("format", jsonutils.NewString(string(qemuImg.Format))) - desc.Set("disk_path", jsonutils.NewString(d.Storage.GetPath())) - return desc -} - -func (d *SCLVMDisk) CreateRaw( - ctx context.Context, sizeMb int, diskFormat string, fsFormat string, - encryptInfo *apis.SEncryptInfo, diskId string, back string, -) (jsonutils.JSONObject, error) { - if fileutils2.Exists(d.GetPath()) { - if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { - return nil, errors.Wrap(err, "CreateRaw lvremove") - } - } - if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, int64(sizeMb)*1024*1024); err != nil { - return nil, errors.Wrap(err, "CreateRaw") - } - - diskInfo := &deployapi.DiskInfo{ - Path: d.GetPath(), - } - if utils.IsInStringArray(fsFormat, []string{"swap", "ext2", "ext3", "ext4", "xfs"}) { - d.FormatFs(fsFormat, diskId, diskInfo) - } - return d.GetDiskDesc(), nil -} - -func (d *SCLVMDisk) CreateFromTemplate( - ctx context.Context, imageId, format string, sizeMb int64, encryptInfo *apis.SEncryptInfo, -) (jsonutils.JSONObject, error) { - if fileutils2.Exists(d.GetPath()) { - if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { - return nil, errors.Wrap(err, "CreateRaw lvremove") - } - } - - var imageCacheManager = storageManager.GetStoragecacheById(d.Storage.GetStoragecacheId()) - ret, err := d.createFromTemplate(ctx, imageId, format, sizeMb, imageCacheManager, encryptInfo) - if err != nil { - return nil, err - } - retSize, _ := ret.Int("disk_size") - log.Infof("REQSIZE: %d, RETSIZE: %d", sizeMb, retSize) - if sizeMb > retSize { - params := jsonutils.NewDict() - params.Set("size", jsonutils.NewInt(sizeMb)) - if encryptInfo != nil { - params.Set("encrypt_info", jsonutils.Marshal(encryptInfo)) - } - return d.Resize(ctx, params) - } - return ret, nil -} - -func (d *SCLVMDisk) createFromTemplate( - ctx context.Context, imageId, format string, sizeMb int64, imageCacheManager IImageCacheManger, encryptInfo *apis.SEncryptInfo, -) (jsonutils.JSONObject, error) { - input := api.CacheImageInput{ImageId: imageId, Zone: d.GetZoneId()} - imageCache, err := imageCacheManager.AcquireImage(ctx, input, nil) - if err != nil { - return nil, errors.Wrapf(err, "AcquireImage") - } - - defer imageCacheManager.ReleaseImage(ctx, imageId) - cacheImagePath := imageCache.GetPath() - - lvSizeMb := d.getQcow2LvSize(imageCache.GetDesc().Size) - if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSizeMb*1024*1024); err != nil { - return nil, errors.Wrap(err, "CreateRaw") - } - newImg, err := qemuimg.NewQemuImage(d.GetPath()) - if err != nil { - return nil, errors.Wrapf(err, "NewQemuImage(%s)", d.GetPath()) - } - err = newImg.CreateQcow2(0, false, cacheImagePath, "", "", "") - if err != nil { - return nil, errors.Wrapf(err, "CreateQcow2(%s)", cacheImagePath) - } - - return d.GetDiskDesc(), nil -} - -func (d *SCLVMDisk) Probe() error { - if !fileutils2.Exists(d.GetPath()) { - return errors.Wrapf(cloudprovider.ErrNotFound, "%s", d.GetPath()) - } - return nil -} - -func (d *SCLVMDisk) GetSnapshotDir() string { - return "" -} - -func (d *SCLVMDisk) OnRebuildRoot(ctx context.Context, params api.DiskAllocateInput) error { - _, err := d.Delete(ctx, api.DiskDeleteInput{}) - return err -} - -func (d *SCLVMDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { - return nil, errors.Wrap(err, "Delete lvremove") - } - d.Storage.RemoveDisk(d) - return nil, nil -} - -func (d *SCLVMDisk) getQcow2LvSize(sizeMb int64) int64 { - // Qcow2 cluster size 2M, 100G reserve 1M for qcow2 metadata - metaSize := sizeMb/1024/100 + 2 - return sizeMb + metaSize -} - -func (d *SCLVMDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - diskInfo, ok := params.(*jsonutils.JSONDict) - if !ok { - return nil, hostutils.ParamsError - } - sizeMb, _ := diskInfo.Int("size") - - qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) - if err != nil { - return nil, errors.Wrap(err, "lvm qemuimg.NewQemuImage") - } - - lvsize := sizeMb - if qemuImg.Format == qemuimgfmt.QCOW2 { - lvsize = d.getQcow2LvSize(sizeMb) - } - - err = lvmutils.LvResize(d.Storage.GetPath(), d.GetPath(), lvsize*1024*1024) - if err != nil { - return nil, errors.Wrap(err, "lv resize") - } - err = qemuImg.Resize(int(sizeMb)) - if err != nil { - return nil, errors.Wrap(err, "qemuImg resize") - } - - resizeFsInfo := &deployapi.DiskInfo{ - Path: d.GetPath(), - } - if err := d.ResizeFs(resizeFsInfo); err != nil { - log.Errorf("Resize fs %s fail %s", d.GetPath(), err) - } - return d.GetDiskDesc(), nil -} - -func (d *SCLVMDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - if err := d.Probe(); err != nil { - return nil, err - } - destDir := d.Storage.GetImgsaveBackupPath() - if err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil { - log.Errorln(err) - return nil, err - } - freeSizeMb, err := storageutils.GetFreeSizeMb(destDir) - if err != nil { - return nil, errors.Wrap(err, "lvm storageutils.GetFreeSizeMb") - } - qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) - if err != nil { - return nil, errors.Wrap(err, "lvm qemuimg.NewQemuImage") - } - if int(qemuImg.SizeBytes/1024/1024) >= freeSizeMb*4/5 { - return nil, errors.Errorf("image cache dir free size is not enough") - } - - backupPath := path.Join(destDir, fmt.Sprintf("%s.%s", d.Id, appctx.AppContextTaskId(ctx))) - srcInfo := qemuimg.SImageInfo{ - Path: d.GetPath(), - Format: qemuImg.Format, - IoLevel: qemuimg.IONiceNone, - Password: "", - } - destInfo := qemuimg.SImageInfo{ - Path: backupPath, - Format: qemuimgfmt.QCOW2, - IoLevel: qemuimg.IONiceNone, - Password: "", - } - if err = qemuimg.Convert(srcInfo, destInfo, true, nil); err != nil { - log.Errorln(err) - procutils.NewCommand("rm", "-f", backupPath).Run() - return nil, err - } - res := jsonutils.NewDict() - res.Set("backup", jsonutils.NewString(backupPath)) - return res, nil -} - -func (d *SCLVMDisk) IsFile() bool { - return false -} diff --git a/pkg/hostman/storageman/disk_local.go b/pkg/hostman/storageman/disk_local.go index 7e779d5eb7..655becc9b6 100644 --- a/pkg/hostman/storageman/disk_local.go +++ b/pkg/hostman/storageman/disk_local.go @@ -448,8 +448,32 @@ func (d *SLocalDisk) CreateSnapshot(snapshotId string, encryptKey string, encFor return nil } -func (d *SLocalDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error { +func (d *SLocalDisk) ConvertSnapshot(convertSnapshotId string) error { snapshotDir := d.GetSnapshotDir() + snapshotPath := path.Join(snapshotDir, convertSnapshotId) + img, err := qemuimg.NewQemuImage(snapshotPath) + if err != nil { + log.Errorln(err) + return err + } + convertedDisk := snapshotPath + ".tmp" + if err = img.Convert2Qcow2To(convertedDisk, false, "", "", ""); err != nil { + log.Errorln(err) + if fileutils2.Exists(convertedDisk) { + os.Remove(convertedDisk) + } + return err + } + if output, err := procutils.NewCommand("mv", "-f", convertedDisk, snapshotPath).Output(); err != nil { + log.Errorf("mv %s to %s failed: %s, %s", convertedDisk, snapshotPath, err, output) + return err + } + return nil +} + +func (d *SLocalDisk) DeleteSnapshot(snapshotId, convertSnapshot string) error { + snapshotDir := d.GetSnapshotDir() + snapshotPath := path.Join(snapshotDir, snapshotId) if len(convertSnapshot) > 0 { if !fileutils2.Exists(snapshotDir) { err := procutils.NewCommand("mkdir", "-p", snapshotDir).Run() @@ -465,38 +489,28 @@ func (d *SLocalDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingD } img, err := qemuimg.NewQemuImage(convertSnapshotPath) if err != nil { - log.Errorln(err) - return err + return errors.Wrap(err, "NewQemuImage") } - if err = img.Convert2Qcow2To(output, true, "", "", ""); err != nil { - log.Errorln(err) + if err = img.Convert2Qcow2To(output, false, "", "", ""); err != nil { + log.Errorf("convert image %s to %s: %s", img.Path, output, err) procutils.NewCommand("rm", "-f", output).Run() return err } if err = procutils.NewCommand("rm", "-f", convertSnapshotPath).Run(); err != nil { - log.Errorln(err) + log.Errorf("rm convert snapshot file %s: %s", convertSnapshotPath, err) return err } if err = procutils.NewCommand("mv", "-f", output, convertSnapshotPath).Run(); err != nil { - log.Errorln(err) + log.Errorf("mv snapshot file %s to %s: %s", output, convertSnapshotPath, err) return err } - if !pendingDelete { - err = procutils.NewCommand("rm", "-f", path.Join(snapshotDir, snapshotId)).Run() - if err != nil { - log.Errorln(err) - return err - } - } - return nil - } else { - err := procutils.NewCommand("rm", "-f", path.Join(snapshotDir, snapshotId)).Run() - if err != nil { - log.Errorln(err) - return err - } - return nil } + err := procutils.NewCommand("rm", "-f", snapshotPath).Run() + if err != nil { + log.Errorf("rm snapshot file: %s", err) + return errors.Wrap(err, "rm snapshot file") + } + return nil } func (d *SLocalDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { @@ -548,6 +562,12 @@ func (d *SLocalDisk) ResetFromSnapshot(ctx context.Context, params interface{}) } func (d *SLocalDisk) resetFromSnapshot(snapshotPath string, outOfChain bool, encryptInfo *apis.SEncryptInfo) (jsonutils.JSONObject, error) { + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + return nil, err + } + diskSizeMB := int(img.SizeBytes / 1024 / 1024) + diskTmpPath := d.GetPath() + "_reset.tmp" if output, err := procutils.NewCommand("mv", "-f", d.GetPath(), diskTmpPath).Output(); err != nil { err = errors.Wrapf(err, "mv disk to tmp failed: %s", output) @@ -570,7 +590,7 @@ func (d *SLocalDisk) resetFromSnapshot(snapshotPath string, outOfChain bool, enc encFmt = qemuimg.EncryptFormatLuks encAlg = encryptInfo.Alg } - if err := img.CreateQcow2(0, false, snapshotPath, encKey, encFmt, encAlg); err != nil { + if err := img.CreateQcow2(diskSizeMB, false, snapshotPath, encKey, encFmt, encAlg); err != nil { err = errors.Wrap(err, "qemu-img create disk by snapshot") procutils.NewCommand("mv", "-f", diskTmpPath, d.GetPath()).Run() return nil, err @@ -686,6 +706,18 @@ func (d *SLocalDisk) IsFile() bool { return true } +func (d *SLocalDisk) RebuildSlaveDisk(diskUri string) error { + diskPath := d.getPath() + if output, err := procutils.NewCommand("rm", "-f", diskPath).Output(); err != nil { + return errors.Errorf("failed delete slave top disk file %s %s", output, err) + } + diskUrl := fmt.Sprintf("%s/%s", diskUri, d.Id) + if err := d.CreateFromImageFuse(context.Background(), diskUrl, 0, nil); err != nil { + return errors.Wrap(err, "failed create slave disk") + } + return nil +} + func (d *SLocalDisk) fallocate() error { img, err := qemuimg.NewQemuImage(d.GetPath()) if err != nil { diff --git a/pkg/hostman/storageman/disk_lvm.go b/pkg/hostman/storageman/disk_lvm.go index a1685592d2..8eded53d51 100644 --- a/pkg/hostman/storageman/disk_lvm.go +++ b/pkg/hostman/storageman/disk_lvm.go @@ -17,7 +17,9 @@ package storageman import ( "context" "fmt" + "io/ioutil" "path" + "strings" "yunion.io/x/cloudmux/pkg/cloudprovider" "yunion.io/x/jsonutils" @@ -31,11 +33,15 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" "yunion.io/x/onecloud/pkg/hostman/storageman/lvmutils" "yunion.io/x/onecloud/pkg/hostman/storageman/storageutils" + "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/fuseutils" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemuimg" + "yunion.io/x/onecloud/pkg/util/seclib2" ) var _ IDisk = (*SLVMDisk)(nil) @@ -45,7 +51,15 @@ type SLVMDisk struct { } func (d *SLVMDisk) GetSnapshotDir() string { - return "" + return d.GetSnapshotPrefix() +} + +func (d *SLVMDisk) GetSnapshotPrefix() string { + return path.Join("/dev", d.Storage.GetPath(), "snap_") +} + +func (d *SLVMDisk) GetImageCachePrefix() string { + return path.Join("/dev", d.Storage.GetPath(), "imagecache_") } func (d *SLVMDisk) GetType() string { @@ -57,28 +71,17 @@ func (d *SLVMDisk) GetLvPath() string { return path.Join("/dev", d.Storage.GetPath(), d.Id) } -func (d *SLVMDisk) GetDevMapperDiskId() string { - return "dm_" + d.Id -} - -func (d *SLVMDisk) GetDevMapperPath() string { - return path.Join("/dev/mapper", "dm_"+d.Id) -} - -func (d *SLVMDisk) GetExtendDiskId() string { - return "ex_" + d.Id -} - -func (d *SLVMDisk) GetSysDiskExtendPath() string { - return path.Join("/dev", d.Storage.GetPath(), d.GetExtendDiskId()) -} - func (d *SLVMDisk) GetPath() string { - var diskPath = d.GetLvPath() - if fileutils2.Exists(d.GetDevMapperPath()) { - diskPath = d.GetDevMapperPath() - } - return diskPath + return path.Join("/dev", d.Storage.GetPath(), d.Id) +} + +// The LVM logical volume name is limited to 64 characters. +func (d *SLVMDisk) GetSnapshotName(snapshotId string) string { + return "snap_" + d.Id + snapshotId +} + +func (d *SLVMDisk) GetSnapshotPath(snapshotId string) string { + return path.Join("/dev", d.Storage.GetPath(), d.GetSnapshotName(snapshotId)) } func (d *SLVMDisk) GetDiskSetupScripts(idx int) string { @@ -88,56 +91,57 @@ func (d *SLVMDisk) GetDiskSetupScripts(idx int) string { func (d *SLVMDisk) GetDiskDesc() jsonutils.JSONObject { qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) if err != nil { - log.Errorln(err) + log.Errorf("qemuimg.NewQemuImage %s: %s", d.GetPath(), err) return nil } var desc = jsonutils.NewDict() desc.Set("disk_id", jsonutils.NewString(d.Id)) desc.Set("disk_size", jsonutils.NewInt(qemuImg.SizeBytes/1024/1024)) - desc.Set("format", jsonutils.NewString(string(qemuimgfmt.RAW))) - desc.Set("disk_path", jsonutils.NewString(d.Storage.GetPath())) + desc.Set("format", jsonutils.NewString(string(qemuImg.Format))) + desc.Set("disk_path", jsonutils.NewString(d.GetPath())) return desc } -func (d *SLVMDisk) CleanUpDisk() error { - // device mapper /dev/mapper/dm_ - if fileutils2.Exists(d.GetDevMapperPath()) { - if err := lvmutils.DmRemove(d.GetDevMapperPath()); err != nil { - return err - } - } - // sys disk extend lv /dev//ex_ - if fileutils2.Exists(d.GetSysDiskExtendPath()) { - if err := lvmutils.LvRemove(d.GetSysDiskExtendPath()); err != nil { - return err - } - } - // disk path /dev// - if fileutils2.Exists(d.GetLvPath()) { - if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { - return err - } - } - return nil -} - func (d *SLVMDisk) CreateRaw( - ctx context.Context, sizeMb int, diskFormat string, fsFormat string, + ctx context.Context, sizeMB int, diskFormat string, fsFormat string, encryptInfo *apis.SEncryptInfo, diskId string, back string, ) (jsonutils.JSONObject, error) { if fileutils2.Exists(d.GetPath()) { - if err := d.CleanUpDisk(); err != nil { - return nil, errors.Wrap(err, "failed remove exists lvm") + if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { + return nil, errors.Wrap(err, "CreateRaw lvremove") } } - if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, int64(sizeMb)*1024*1024); err != nil { + + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, err + } + + qcow2Size := lvmutils.GetQcow2LvSize(int64(sizeMB)) + err = lvmutils.LvCreate(d.Storage.GetPath(), d.Id, qcow2Size*1024*1024) + if err != nil { return nil, errors.Wrap(err, "CreateRaw") } + if encryptInfo != nil { + err = img.CreateQcow2(sizeMB, false, back, encryptInfo.Key, qemuimg.EncryptFormatLuks, encryptInfo.Alg) + } else { + err = img.CreateQcow2(sizeMB, false, back, "", "", "") + } + + if err != nil { + return nil, fmt.Errorf("create_raw: Fail to create disk: %s", err) + } + diskInfo := &deployapi.DiskInfo{ Path: d.GetPath(), } + if encryptInfo != nil { + diskInfo.EncryptPassword = encryptInfo.Key + diskInfo.EncryptAlg = string(encryptInfo.Alg) + } if utils.IsInStringArray(fsFormat, []string{"swap", "ext2", "ext3", "ext4", "xfs"}) { d.FormatFs(fsFormat, diskId, diskInfo) } @@ -145,18 +149,80 @@ func (d *SLVMDisk) CreateRaw( } func (d *SLVMDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - if err := d.CleanUpDisk(); err != nil { - return nil, errors.Wrap(err, "Delete") + if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { + return nil, errors.Wrap(err, "Delete lvremove") } d.Storage.RemoveDisk(d) return nil, nil } func (d *SLVMDisk) PostCreateFromImageFuse() { + mntPath := path.Join(d.Storage.GetFuseMountPath(), d.Id) + if output, err := procutils.NewCommand("umount", mntPath).Output(); err != nil { + log.Errorf("umount %s failed: %s, %s", mntPath, err, output) + } + if output, err := procutils.NewCommand("rm", "-rf", mntPath).Output(); err != nil { + log.Errorf("rm %s failed: %s, %s", mntPath, err, output) + } + tmpPath := d.Storage.GetFuseTmpPath() + tmpFiles, err := ioutil.ReadDir(tmpPath) + if err != nil { + for _, f := range tmpFiles { + if strings.HasPrefix(f.Name(), d.Id) { + procutils.NewCommand("rm", "-f", path.Join(tmpPath, f.Name())) + } + } + } +} + +func (d *SLVMDisk) CreateFromImageFuse(ctx context.Context, url string, size int64, encryptInfo *apis.SEncryptInfo) error { + log.Infof("Create from image fuse %s", url) + + localPath := d.Storage.GetFuseTmpPath() + mntPath := path.Join(d.Storage.GetFuseMountPath(), d.Id) + contentPath := path.Join(mntPath, "content") + newImg, err := qemuimg.NewQemuImage(d.GetPath()) + + if err != nil { + log.Errorf("qemuimg.NewQemuImage %s fail: %s", d.GetPath(), err) + return err + } + + if newImg.IsValid() && newImg.IsChained() && newImg.BackFilePath != contentPath { + if err := lvmutils.LvRemove(d.GetPath()); err != nil { + return errors.Wrap(err, "remove disk") + } + } + if !newImg.IsValid() || newImg.IsChained() { + if err := fuseutils.MountFusefs( + options.HostOptions.FetcherfsPath, url, localPath, + auth.GetTokenString(), mntPath, options.HostOptions.FetcherfsBlockSize, encryptInfo, + ); err != nil { + log.Errorln(err) + return err + } + } + if !newImg.IsValid() { + lvSize := lvmutils.GetQcow2LvSize(size) + if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSize*1024*1024); err != nil { + return errors.Wrap(err, "lvcreate") + } + + if encryptInfo != nil { + err = newImg.CreateQcow2(0, false, contentPath, encryptInfo.Key, qemuimg.EncryptFormatLuks, encryptInfo.Alg) + } else { + err = newImg.CreateQcow2(0, false, contentPath, "", "", "") + } + if err != nil { + return errors.Wrapf(err, "create from fuse") + } + } + + return nil } func (d *SLVMDisk) IsFile() bool { - return false + return true } func (d *SLVMDisk) Probe() error { @@ -177,8 +243,24 @@ func (d *SLVMDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JS return nil, hostutils.ParamsError } sizeMb, _ := diskInfo.Int("size") - if err := d.resize(sizeMb * 1024 * 1024); err != nil { - return nil, err + + qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + return nil, errors.Wrap(err, "lvm qemuimg.NewQemuImage") + } + + lvsize := sizeMb + if qemuImg.Format == qemuimgfmt.QCOW2 { + lvsize = lvmutils.GetQcow2LvSize(sizeMb) + } + + err = lvmutils.LvResize(d.Storage.GetPath(), d.GetPath(), lvsize*1024*1024) + if err != nil { + return nil, errors.Wrap(err, "lv resize") + } + err = qemuImg.Resize(int(sizeMb)) + if err != nil { + return nil, errors.Wrap(err, "qemuImg resize") } resizeFsInfo := &deployapi.DiskInfo{ @@ -190,79 +272,25 @@ func (d *SLVMDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JS return d.GetDiskDesc(), nil } -func (d *SLVMDisk) resize(newSize int64) error { - qemuImg, err := qemuimg.NewQemuImage(d.GetPath()) - if err != nil { - return errors.Wrapf(err, "Open image %s", d.GetPath()) - } - if qemuImg.SizeBytes >= newSize { - return nil - } - - resizePath := d.GetLvPath() - origin, err := lvmutils.GetLvOrigin(resizePath) - if err != nil { - return errors.Wrap(err, "get lv origin") - } - - if origin != "" { - // lv created from snapshot - if !fileutils2.Exists(d.GetDevMapperPath()) { - // create an lvm extend disk directly. - err = lvmutils.LvCreate(d.Storage.GetPath(), d.GetExtendDiskId(), newSize-qemuImg.SizeBytes) - if err != nil { - return errors.Wrap(err, "lv create") - } - // create a device mapper disk - err = lvmutils.DmCreate(d.GetLvPath(), d.GetSysDiskExtendPath(), d.GetDevMapperDiskId()) - if err != nil { - if errT := lvmutils.LvRemove(d.GetSysDiskExtendPath()); errT != nil { - log.Errorf("failed remove extend disk path %s", errT) - } - return errors.Wrap(err, "dm create") - } - } else { - // resize extend disk - resizePath = d.GetSysDiskExtendPath() - extendImg, err := qemuimg.NewQemuImage(resizePath) - if err != nil { - return errors.Wrapf(err, "Open image %s", resizePath) - } - - newSize = newSize - qemuImg.SizeBytes + extendImg.SizeBytes - err = lvmutils.LvResize(d.Storage.GetPath(), resizePath, newSize) - if err != nil { - return errors.Wrap(err, "lv resize") - } - } - } else { - err = lvmutils.LvResize(d.Storage.GetPath(), resizePath, newSize) - if err != nil { - return errors.Wrap(err, "lv resize") - } - } - return nil -} - func (d *SLVMDisk) CreateFromTemplate( - ctx context.Context, imageId, format string, size int64, encryptInfo *apis.SEncryptInfo, + ctx context.Context, imageId, format string, sizeMb int64, encryptInfo *apis.SEncryptInfo, ) (jsonutils.JSONObject, error) { if fileutils2.Exists(d.GetPath()) { - if err := d.CleanUpDisk(); err != nil { - return nil, errors.Wrap(err, "failed remove exists lvm") + if err := lvmutils.LvRemove(d.GetLvPath()); err != nil { + return nil, errors.Wrap(err, "CreateRaw lvremove") } } var imageCacheManager = storageManager.GetStoragecacheById(d.Storage.GetStoragecacheId()) - ret, err := d.createFromTemplate(ctx, imageId, format, size, imageCacheManager, encryptInfo) + ret, err := d.createFromTemplate(ctx, imageId, format, sizeMb, imageCacheManager, encryptInfo) if err != nil { return nil, err } retSize, _ := ret.Int("disk_size") - log.Infof("REQSIZE: %d, RETSIZE: %d", size, retSize) - if size > retSize { + log.Infof("REQSIZE: %d, RETSIZE: %d", sizeMb, retSize) + if sizeMb > retSize { params := jsonutils.NewDict() - params.Set("size", jsonutils.NewInt(size)) + params.Set("size", jsonutils.NewInt(sizeMb)) if encryptInfo != nil { params.Set("encrypt_info", jsonutils.Marshal(encryptInfo)) } @@ -271,6 +299,38 @@ func (d *SLVMDisk) CreateFromTemplate( return ret, nil } +func (d *SLVMDisk) createFromTemplate( + ctx context.Context, imageId, format string, sizeMb int64, imageCacheManager IImageCacheManger, encryptInfo *apis.SEncryptInfo, +) (jsonutils.JSONObject, error) { + input := api.CacheImageInput{ImageId: imageId, Zone: d.GetZoneId()} + imageCache, err := imageCacheManager.AcquireImage(ctx, input, nil) + if err != nil { + return nil, errors.Wrapf(err, "AcquireImage") + } + + defer imageCacheManager.ReleaseImage(ctx, imageId) + cacheImagePath := imageCache.GetPath() + + lvSizeMb := lvmutils.GetQcow2LvSize(imageCache.GetDesc().Size) + if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSizeMb*1024*1024); err != nil { + return nil, errors.Wrap(err, "CreateRaw") + } + newImg, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + return nil, errors.Wrapf(err, "NewQemuImage(%s)", d.GetPath()) + } + if encryptInfo != nil { + err = newImg.CreateQcow2(0, false, cacheImagePath, encryptInfo.Key, qemuimg.EncryptFormatLuks, encryptInfo.Alg) + } else { + err = newImg.CreateQcow2(0, false, cacheImagePath, "", "", "") + } + if err != nil { + return nil, errors.Wrapf(err, "CreateQcow2(%s)", cacheImagePath) + } + + return d.GetDiskDesc(), nil +} + func (d *SLVMDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { if err := d.Probe(); err != nil { return nil, err @@ -295,7 +355,7 @@ func (d *SLVMDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) backupPath := path.Join(destDir, fmt.Sprintf("%s.%s", d.Id, appctx.AppContextTaskId(ctx))) srcInfo := qemuimg.SImageInfo{ Path: d.GetPath(), - Format: qemuimgfmt.RAW, + Format: qemuImg.Format, IoLevel: qemuimg.IONiceNone, Password: "", } @@ -315,33 +375,222 @@ func (d *SLVMDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) return res, nil } -func (d *SLVMDisk) createFromTemplate( - ctx context.Context, imageId, format string, sizeMb int64, imageCacheManager IImageCacheManger, encryptInfo *apis.SEncryptInfo, -) (jsonutils.JSONObject, error) { - input := api.CacheImageInput{ImageId: imageId, Zone: d.GetZoneId()} - imageCache, err := imageCacheManager.AcquireImage(ctx, input, nil) - if err != nil { - return nil, errors.Wrapf(err, "AcquireImage") - } - - defer imageCacheManager.ReleaseImage(ctx, imageId) - cacheImagePath := imageCache.GetPath() - cacheImage, err := qemuimg.NewQemuImage(cacheImagePath) - if err != nil { - return nil, errors.Wrapf(err, "NewQemuImage(%s)", cacheImagePath) - } - - if err = lvmutils.LvCreateFromSnapshot(d.GetLvPath(), cacheImagePath, cacheImage.SizeBytes); err != nil { - return nil, errors.Wrap(err, "lv create from snapshot") - } - - return d.GetDiskDesc(), nil -} - func (d *SLVMDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { return nil, errors.ErrNotImplemented } +func (d *SLVMDisk) CreateSnapshot(snapshotId string, encryptKey string, encFormat qemuimg.TEncryptFormat, encAlg seclib2.TSymEncAlg) error { + snapName := d.GetSnapshotName(snapshotId) + log.Infof("Start create snapshot %s of lvm Disk %s", snapName, d.Id) + lvSize, err := lvmutils.GetLvSize(d.GetPath()) + if err != nil { + return err + } + + err = lvmutils.LvRename(d.Storage.GetPath(), d.Id, snapName) + if err != nil { + return err + } + if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSize); err != nil { + return errors.Wrap(err, "snapshot LvCreate") + } + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + lvmutils.LvRemove(d.GetPath()) + lvmutils.LvRename(d.Storage.GetPath(), snapName, d.Id) + return errors.Wrapf(err, "failed qemuimg.NewQemuImage(%s))", d.GetPath()) + } + + snapPath := d.GetSnapshotPath(snapshotId) + err = img.CreateQcow2(0, false, snapPath, "", "", "") + if err != nil { + lvmutils.LvRemove(d.GetPath()) + lvmutils.LvRename(d.Storage.GetPath(), snapName, d.Id) + return errors.Wrapf(err, "CreateQcow2(%s)", snapPath) + } + return nil +} + +func (d *SLVMDisk) ResetFromSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + resetParams, ok := params.(*SDiskReset) + if !ok { + return nil, hostutils.ParamsError + } + + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + return nil, err + } + diskSizeMB := int(img.SizeBytes / 1024 / 1024) + + lvSize, err := lvmutils.GetLvSize(d.GetPath()) + if err != nil { + return nil, err + } + // rename disk to temp logical volume + tmpVolume := d.Id + "-reset.tmp" + err = lvmutils.LvRename(d.Storage.GetPath(), d.Id, tmpVolume) + if err != nil { + return nil, err + } + if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSize); err != nil { + return nil, errors.Wrap(err, "reset snapshot LvCreate") + } + + imgNew, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + lvmutils.LvRemove(d.GetPath()) + lvmutils.LvRename(d.Storage.GetPath(), tmpVolume, d.Id) + return nil, errors.Wrapf(err, "failed qemuimg.NewQemuImage(%s))", d.GetPath()) + } + + snapPath := d.GetSnapshotPath(resetParams.SnapshotId) + err = imgNew.CreateQcow2(diskSizeMB, false, snapPath, "", "", "") + if err != nil { + lvmutils.LvRemove(d.GetPath()) + lvmutils.LvRename(d.Storage.GetPath(), tmpVolume, d.Id) + return nil, errors.Wrapf(err, "CreateQcow2(%s)", snapPath) + } + tmpVolumePath := path.Join("/dev", d.Storage.GetPath(), tmpVolume) + err = lvmutils.LvRemove(tmpVolumePath) + if err != nil { + return nil, errors.Wrap(err, "failed remove tmp volume") + } + return nil, nil +} + +func (d *SLVMDisk) DeleteSnapshot(snapshotId, convertSnapshot string) error { + if len(convertSnapshot) > 0 { + if err := d.ConvertSnapshot(convertSnapshot); err != nil { + return err + } + } + return lvmutils.LvRemove(d.GetSnapshotPath(snapshotId)) +} + +func (d *SLVMDisk) DeleteAllSnapshot(skipRecycle bool) error { + lvNames, err := lvmutils.GetLvNames(d.Storage.GetPath()) + if err != nil { + log.Errorf("failed get lvm %s lvs %s", d.Storage.GetPath(), err) + return nil + } + + snapPrefix := "snap_" + d.Id + for _, f := range lvNames { + if strings.HasPrefix(f, snapPrefix) { + if err := lvmutils.LvRemove(path.Join("/dev", d.Storage.GetPath(), f)); err != nil { + return errors.Wrap(err, "delele lvm snapshots") + } + } + } + return nil +} + +func (d *SLVMDisk) ConvertSnapshot(convertSnapshot string) error { + convertSnapshotPath := d.GetSnapshotPath(convertSnapshot) + qemuImg, err := qemuimg.NewQemuImage(convertSnapshotPath) + if err != nil { + log.Errorln(err) + return err + } + lvSize, err := lvmutils.GetLvSize(convertSnapshotPath) + if err != nil { + return err + } + + tmpVolume := d.Id + "-convert.tmp" + tmpVolumePath := path.Join("/dev", d.Storage.GetPath(), tmpVolume) + // create /dev/vg/snapshot-convert.tmp + if err := lvmutils.LvCreate(d.Storage.GetPath(), d.Id, lvSize); err != nil { + return errors.Wrap(err, "delete snapshot LvCreate") + } + srcInfo := qemuimg.SImageInfo{ + Path: convertSnapshotPath, + Format: qemuImg.Format, + IoLevel: qemuimg.IONiceNone, + Password: "", + } + destInfo := qemuimg.SImageInfo{ + Path: tmpVolumePath, + Format: qemuimgfmt.QCOW2, + IoLevel: qemuimg.IONiceNone, + Password: "", + } + // convert /dev/vg/snapshot to /dev/vg/snapshot-convert.tmp + if err = qemuimg.Convert(srcInfo, destInfo, false, nil); err != nil { + lvmutils.LvRemove(tmpVolumePath) + return errors.Wrap(err, "failed convert tmp disk") + } + + tmpVolume2 := d.Id + "-convert.tmp2" + tmpVolume2Path := path.Join("/dev", d.Storage.GetPath(), tmpVolume2) + // rename /dev/vg/snapshot to /dev/vg/snapshot-convert.tmp2 + err = lvmutils.LvRename(d.Storage.GetPath(), convertSnapshot, tmpVolume2) + if err != nil { + return errors.Wrap(err, "failed rename disk to tmp") + } + // rename /dev/vg/snapshot-convert.tmp to /dev/vg/snapshot + err = lvmutils.LvRename(d.Storage.GetPath(), tmpVolume, convertSnapshot) + if err != nil { + return errors.Wrap(err, "failed rename tmp to disk") + } + // delete /dev/vg/snapshot-convert.tmp2 + err = lvmutils.LvRemove(tmpVolume2Path) + if err != nil { + return errors.Wrap(err, "failed remove tmp disk") + } + return nil +} + +func (d *SLVMDisk) DoDeleteSnapshot(snapshotId string) error { + snapshotPath := d.GetSnapshotPath(snapshotId) + return lvmutils.LvRemove(snapshotPath) +} + +func (d *SLVMDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, error) { + disk, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, "", false, err + } + ret, err := disk.WholeChainFormatIs("qcow2") + if err != nil { + log.Errorln(err) + return nil, "", false, err + } + if liveMigrate && !ret { + return nil, "", false, fmt.Errorf("Disk format doesn't support live migrate") + } + if disk.IsChained() { + backingChain, err := disk.GetBackingChain() + if err != nil { + return nil, "", false, err + } + snapshots := []string{} + for i := range backingChain { + if strings.HasPrefix(backingChain[i], d.GetSnapshotDir()) { + snapshots = append(snapshots, path.Base(backingChain[i])) + } else if !strings.HasPrefix(backingChain[i], d.GetImageCachePrefix()) { + return nil, "", false, errors.Errorf("backing file path %s unsupported", backingChain[i]) + } + } + hasTemplate := strings.HasPrefix(backingChain[len(backingChain)-1], d.GetImageCachePrefix()) + return snapshots, backingChain[0], hasTemplate, nil + } + return nil, "", false, nil +} + +func (d *SLVMDisk) RebuildSlaveDisk(diskUri string) error { + if err := lvmutils.LvRemove(d.GetPath()); err != nil { + return errors.Wrap(err, "lvremove") + } + diskUrl := fmt.Sprintf("%s/%s", diskUri, d.Id) + if err := d.CreateFromImageFuse(context.Background(), diskUrl, 0, nil); err != nil { + return errors.Wrap(err, "failed create slave disk") + } + return nil +} + func NewLVMDisk(storage IStorage, id string) *SLVMDisk { return &SLVMDisk{ SBaseDisk: *NewBaseDisk(storage, id), diff --git a/pkg/hostman/storageman/disk_rbd.go b/pkg/hostman/storageman/disk_rbd.go index 57a49e73f1..371f9d769d 100644 --- a/pkg/hostman/storageman/disk_rbd.go +++ b/pkg/hostman/storageman/disk_rbd.go @@ -256,7 +256,11 @@ func (d *SRBDDisk) CreateSnapshot(snapshotId string, encryptKey string, encForma return storage.createSnapshot(d.Id, snapshotId) } -func (d *SRBDDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error { +func (d *SRBDDisk) ConvertSnapshot(convertSnapshotId string) error { + return nil +} + +func (d *SRBDDisk) DeleteSnapshot(snapshotId, convertSnapshot string) error { storage := d.Storage.(*SRbdStorage) return storage.deleteSnapshot(d.Id, snapshotId) } @@ -274,7 +278,7 @@ func (d *SRBDDisk) DiskDeleteSnapshot(ctx context.Context, params interface{}) ( if !ok { return nil, hostutils.ParamsError } - err := d.DeleteSnapshot(snapshotId, "", false) + err := d.DeleteSnapshot(snapshotId, "") if err != nil { return nil, err } else { diff --git a/pkg/hostman/storageman/disk_slvm.go b/pkg/hostman/storageman/disk_slvm.go index d4f1b990ad..8fac375e43 100644 --- a/pkg/hostman/storageman/disk_slvm.go +++ b/pkg/hostman/storageman/disk_slvm.go @@ -32,12 +32,12 @@ import ( // shared lvm type SSLVMDisk struct { - SCLVMDisk + SLVMDisk } func NewSLVMDisk(storage IStorage, id string) *SSLVMDisk { return &SSLVMDisk{ - SCLVMDisk: *NewCLVMDisk(storage, id), + SLVMDisk: *NewLVMDisk(storage, id), } } @@ -92,11 +92,11 @@ func (d *SSLVMDisk) CreateRaw( ctx context.Context, sizeMb int, diskFormat string, fsFormat string, encryptInfo *apis.SEncryptInfo, diskId string, back string, ) (jsonutils.JSONObject, error) { - ret, err := d.SCLVMDisk.CreateRaw(ctx, sizeMb, diskFormat, fsFormat, encryptInfo, diskId, back) + ret, err := d.SLVMDisk.CreateRaw(ctx, sizeMb, diskFormat, fsFormat, encryptInfo, diskId, back) if err != nil { return ret, err } - err = lvmutils.LVActive(d.GetPath(), true, false) + err = lvmutils.LVActive(d.GetPath(), d.Storage.Lvmlockd(), false) if err != nil { return ret, errors.Wrap(err, "lvactive shared") } @@ -106,11 +106,11 @@ func (d *SSLVMDisk) CreateRaw( func (d *SSLVMDisk) CreateFromTemplate( ctx context.Context, imageId, format string, sizeMb int64, encryptInfo *apis.SEncryptInfo, ) (jsonutils.JSONObject, error) { - ret, err := d.SCLVMDisk.CreateFromTemplate(ctx, imageId, format, sizeMb, encryptInfo) + ret, err := d.SLVMDisk.CreateFromTemplate(ctx, imageId, format, sizeMb, encryptInfo) if err != nil { return ret, err } - err = lvmutils.LVActive(d.GetPath(), true, false) + err = lvmutils.LVActive(d.GetPath(), d.Storage.Lvmlockd(), false) if err != nil { return ret, errors.Wrap(err, "lvactive shared") } @@ -128,6 +128,5 @@ func (d *SSLVMDisk) Delete(ctx context.Context, params interface{}) (jsonutils.J return nil, errors.Wrap(err, "lv active") } } - d.SCLVMDisk.Delete(ctx, params) - return nil, nil + return d.SLVMDisk.Delete(ctx, params) } diff --git a/pkg/hostman/storageman/imagecache_lvm.go b/pkg/hostman/storageman/imagecache_lvm.go index 0160e8963b..280c191ba0 100644 --- a/pkg/hostman/storageman/imagecache_lvm.go +++ b/pkg/hostman/storageman/imagecache_lvm.go @@ -109,7 +109,8 @@ func (c *SLVMImageCache) Acquire( if err != nil { return errors.Wrapf(err, "NewQemuImage for local image path %s", localImageCache.GetPath()) } - err = lvmutils.LvCreate(c.Manager.GetPath(), c.GetName(), localImg.SizeBytes) + lvSize := lvmutils.GetQcow2LvSize(localImg.SizeBytes/1024/1024) * 1024 * 1024 + err = lvmutils.LvCreate(c.Manager.GetPath(), c.GetName(), lvSize) if err != nil { return errors.Wrap(err, "lvm image cache acquire") } @@ -123,7 +124,7 @@ func (c *SLVMImageCache) Acquire( log.Infof("convert local image %s to lvm %s", c.imageId, c.GetPath()) out, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuImg(), - "convert", "-W", "-m", "16", "-O", "raw", localImageCache.GetPath(), c.GetPath()).Output() + "convert", "-W", "-m", "16", "-O", "qcow2", localImageCache.GetPath(), c.GetPath()).Output() if err != nil { return errors.Wrapf(err, "convert local image %s to lvm %s: %s", c.imageId, c.GetPath(), out) } diff --git a/pkg/hostman/storageman/lvmutils/lvmutils.go b/pkg/hostman/storageman/lvmutils/lvmutils.go index 88382fef5f..56e675d28f 100644 --- a/pkg/hostman/storageman/lvmutils/lvmutils.go +++ b/pkg/hostman/storageman/lvmutils/lvmutils.go @@ -157,11 +157,12 @@ type VgReports struct { } `json:"report"` } +// lvm units https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/logical_volume_manager_administration/report_units func GetVgProps(vg string) (*VgProps, error) { cmd := fmt.Sprintf("lvm vgs --reportformat json -o vg_free,vg_size,vg_extent_size --units=B %s 2>/dev/null", vg) out, err := procutils.NewRemoteCommandAsFarAsPossible("bash", "-c", cmd).Output() if err != nil { - return nil, errors.Wrapf(err, "exec lvm command: %s", out) + return nil, errors.Wrapf(err, "exec lvm command %s: %s", cmd, out) } var vgReports VgReports err = json.Unmarshal(out, &vgReports) @@ -290,3 +291,32 @@ func VgActive(vgName string, active bool) error { } return nil } + +func LvRename(vgName, oldName, newName string) error { + out, err := procutils.NewRemoteCommandAsFarAsPossible("lvm", "lvrename", vgName, oldName, newName).Output() + if err != nil { + return errors.Wrapf(err, "lvrename vg: %s oldName: %s newName: %s failed: %s", vgName, oldName, newName, out) + } + return nil +} + +func GetQcow2LvSize(sizeMb int64) int64 { + // 100G reserve 1M for qcow2 metadata + metaSize := sizeMb/1024/100 + 10 + return sizeMb + metaSize +} + +// get lvsize unit byte +func GetLvSize(lvPath string) (int64, error) { + cmd := fmt.Sprintf("lvm lvs %s -o LV_SIZE --noheadings --units B --nosuffix 2>/dev/null", lvPath) + out, err := procutils.NewRemoteCommandAsFarAsPossible("bash", "-c", cmd).Output() + if err != nil { + return -1, errors.Wrapf(err, "exec lvm command %s: %s", cmd, out) + } + strSize := strings.TrimSpace(string(out)) + size, err := strconv.ParseInt(strSize, 10, 64) + if err != nil { + return -1, errors.Wrapf(err, "failed parse size %s", strSize) + } + return size, nil +} diff --git a/pkg/hostman/storageman/remotefile/remotefile.go b/pkg/hostman/storageman/remotefile/remotefile.go index cba306c616..77f8bf3ba1 100644 --- a/pkg/hostman/storageman/remotefile/remotefile.go +++ b/pkg/hostman/storageman/remotefile/remotefile.go @@ -222,10 +222,17 @@ func (r *SRemoteFile) downloadInternal(getData bool, preChksum string, callback defer resp.Body.Close() if resp.StatusCode < 300 { if getData { - os.Remove(r.tmpPath) - fi, err := os.Create(r.tmpPath) - if err != nil { - return errors.Wrapf(err, "os.Create(%s)", r.tmpPath) + var fi *os.File + if r.tmpPath == r.localPath && fileutils2.Exists(r.localPath) { + fi, err = os.Open(r.tmpPath) + if err != nil { + return errors.Wrapf(err, "os.Open(%s)", r.tmpPath) + } + } else { + fi, err = os.Create(r.tmpPath) + if err != nil { + return errors.Wrapf(err, "os.Create(%s)", r.tmpPath) + } } defer fi.Close() diff --git a/pkg/hostman/storageman/storage_base.go b/pkg/hostman/storageman/storage_base.go index 79a2b3beb6..f838bc93b3 100644 --- a/pkg/hostman/storageman/storage_base.go +++ b/pkg/hostman/storageman/storage_base.go @@ -17,13 +17,9 @@ package storageman import ( "context" "fmt" - "io/ioutil" "path" - "regexp" - "runtime/debug" "strings" "sync" - "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -33,16 +29,13 @@ import ( "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" hostapi "yunion.io/x/onecloud/pkg/apis/host" - "yunion.io/x/onecloud/pkg/cloudcommon/cronman" "yunion.io/x/onecloud/pkg/hostman/guestman/desc" "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/hostman/options" "yunion.io/x/onecloud/pkg/hostman/storageman/storageutils" "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/mcclient/modules/image" - "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemuimg" ) @@ -181,6 +174,14 @@ func (s *SBaseStorage) Lvmlockd() bool { return false } +func (s *SBaseStorage) GetFuseTmpPath() string { + return path.Join(s.Path, _FUSE_TMP_PATH_) +} + +func (s *SBaseStorage) GetFuseMountPath() string { + return path.Join(s.Path, _FUSE_MOUNT_PATH_) +} + func (s *SBaseStorage) GetStorageName() string { return s.StorageName } @@ -492,124 +493,6 @@ func (s *SBaseStorage) onSaveToGlanceFailed(ctx context.Context, imageId string, } } -/*************************Background delete snapshot job****************************/ - -func StartSnapshotRecycle(storage IStorage) { - log.Infof("Snapshot recyle job started") - if !fileutils2.Exists(storage.GetSnapshotDir()) { - procutils.NewCommand("mkdir", "-p", storage.GetSnapshotDir()).Run() - } - cronman.GetCronJobManager().AddJobAtIntervals( - "SnapshotRecycle", time.Hour*6, - func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { - snapshotRecycle(ctx, userCred, isStart, storage) - }) -} - -func StorageRequestSnapshotRecycle(ctx context.Context, userCred mcclient.TokenCredential, storage IStorage) { - defer func() { - if r := recover(); r != nil { - log.Errorf("On storage request snapshot recycle %s \n %s", r, debug.Stack()) - } - }() - - if !fileutils2.Exists(storage.GetSnapshotDir()) { - procutils.NewCommand("mkdir", "-p", storage.GetSnapshotDir()).Run() - } - snapshotRecycle(ctx, userCred, false, storage) -} - -func snapshotRecycle(ctx context.Context, userCred mcclient.TokenCredential, isStart bool, storage IStorage) { - log.Infof("Snapshot Recycle Job Start, storage is %s, ss dir is %s", storage.GetStorageName(), storage.GetSnapshotDir()) - res, err := modules.Snapshots.GetById(hostutils.GetComputeSession(ctx), "max-count", nil) - if err != nil { - log.Errorln(err) - return - } - maxSnapshotCount, err := res.Int("max_count") - if err != nil { - log.Errorln("Request region get snapshot max count failed") - return - } - files, err := ioutil.ReadDir(storage.GetSnapshotDir()) - if err != nil { - log.Errorln(err) - return - } - for _, file := range files { - checkSnapshots(storage, file.Name(), int(maxSnapshotCount)) - } -} - -func checkSnapshots(storage IStorage, snapshotDir string, maxSnapshotCount int) { - re := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}_snap$`) - if !re.MatchString(snapshotDir) { - log.Warningf("snapshot_dir got unexcept file %s", snapshotDir) - return - } - diskId := snapshotDir[:len(snapshotDir)-len(options.HostOptions.SnapshotDirSuffix)] - snapshotPath := path.Join(storage.GetSnapshotDir(), snapshotDir) - - // If disk is Deleted, request delete this disk all snapshots - if !fileutils2.Exists(path.Join(storage.GetPath(), diskId)) && fileutils2.Exists(snapshotPath) { - params := jsonutils.NewDict() - params.Set("disk_id", jsonutils.NewString(diskId)) - _, err := modules.Snapshots.PerformClassAction( - hostutils.GetComputeSession(context.Background()), - "delete-disk-snapshots", params) - if err != nil { - log.Infof("Request delele disk %s snapshots failed %s", diskId, err) - } - return - } - - snapshots, err := ioutil.ReadDir(snapshotPath) - if err != nil { - log.Errorln(err) - return - } - - // if snapshot count greater than maxsnapshot count, do convert - if len(snapshots) >= maxSnapshotCount { - requestConvertSnapshot(storage, snapshotPath, diskId) - } -} - -func requestConvertSnapshot(storage IStorage, snapshotPath, diskId string) { - log.Infof("SNPASHOT path %s", snapshotPath) - res, err := modules.Disks.GetSpecific( - hostutils.GetComputeSession(context.Background()), diskId, "convert-snapshot", nil) - if err != nil { - log.Errorln(err) - return - } - - var ( - deleteSnapshot, _ = res.GetString("delete_snapshot") - convertSnapshot, _ = res.GetString("convert_snapshot") - pendingDelete, _ = res.Bool("pending_delete") - ) - log.Infof("start convert disk(%s) snapshot(%s), delete_snapshot is %s", - diskId, convertSnapshot, deleteSnapshot) - convertSnapshotPath := path.Join(snapshotPath, convertSnapshot) - outfile := convertSnapshotPath + ".tmp" - img, err := qemuimg.NewQemuImage(convertSnapshotPath) - if err != nil { - log.Errorln(err) - return - } - log.Infof("convertSnapshot path %s", convertSnapshotPath) - err = img.Convert2Qcow2To(outfile, true, "", "", "") - if err != nil { - log.Errorln(err) - return - } - requestDeleteSnapshot( - storage, diskId, snapshotPath, deleteSnapshot, - convertSnapshotPath, outfile, pendingDelete, - ) -} - func requestDeleteSnapshot( storage IStorage, diskId, snapshotPath, deleteSnapshot, convertSnapshotPath, outfile string, pendingDelete bool, diff --git a/pkg/hostman/storageman/storage_local.go b/pkg/hostman/storageman/storage_local.go index 9d0f8001e1..d2f2d5e3d2 100644 --- a/pkg/hostman/storageman/storage_local.go +++ b/pkg/hostman/storageman/storage_local.go @@ -73,14 +73,6 @@ func NewLocalStorage(manager *SStorageManager, path string, index int) *SLocalSt return ret } -func (s *SLocalStorage) GetFuseTmpPath() string { - return path.Join(s.Path, _FUSE_TMP_PATH_) -} - -func (s *SLocalStorage) GetFuseMountPath() string { - return path.Join(s.Path, _FUSE_MOUNT_PATH_) -} - func (s *SLocalStorage) StorageType() string { return api.STORAGE_LOCAL } diff --git a/pkg/hostman/storageman/storage_lvm.go b/pkg/hostman/storageman/storage_lvm.go index 82150123e2..40bfe763c3 100644 --- a/pkg/hostman/storageman/storage_lvm.go +++ b/pkg/hostman/storageman/storage_lvm.go @@ -23,16 +23,23 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/qemuimgfmt" + "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" + hostapi "yunion.io/x/onecloud/pkg/apis/host" + "yunion.io/x/onecloud/pkg/hostman/guestman/desc" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient" "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/hostman/options" "yunion.io/x/onecloud/pkg/hostman/storageman/lvmutils" + "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" + "yunion.io/x/onecloud/pkg/httperrors" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/mcclient/modules/image" "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" ) type SLVMStorage struct { @@ -120,7 +127,9 @@ func (s *SLVMStorage) SyncStorageInfo() (jsonutils.JSONObject, error) { if err != nil { return nil, errors.Wrap(err, "GetUsedSizeMb") } + content.Set("capacity", jsonutils.NewInt(sizeMb)) + content.Set("actual_capacity_used", jsonutils.NewInt(usedSizeMb)) content.Set("storage_type", jsonutils.NewString(s.StorageType())) content.Set("zone", jsonutils.NewString(s.GetZoneId())) @@ -130,7 +139,6 @@ func (s *SLVMStorage) SyncStorageInfo() (jsonutils.JSONObject, error) { ) log.Infof("Sync storage info %s/%s", s.StorageId, name) - if len(s.StorageId) > 0 { res, err = modules.Storages.Put( hostutils.GetComputeSession(context.Background()), @@ -142,6 +150,12 @@ func (s *SLVMStorage) SyncStorageInfo() (jsonutils.JSONObject, error) { } else { content.Set("medium_type", jsonutils.NewString(mediumType)) } + // reserved for imagecache + reserved := sizeMb / 10 + if reserved > 1024*1024 { + reserved = 1024 * 1024 + } + content.Set("reserved", jsonutils.NewInt(reserved)) res, err = modules.Storages.Create(hostutils.GetComputeSession(context.Background()), content) if err == nil { @@ -171,7 +185,12 @@ func (s *SLVMStorage) GetSnapshotDir() string { } func (s *SLVMStorage) GetSnapshotPathByIds(diskId, snapshotId string) string { - return "" + disk, err := s.GetDiskById(diskId) + if err != nil { + log.Errorf("lvm failed get disk by id %s: %s", diskId, err) + return "" + } + return disk.GetSnapshotPath(snapshotId) } func (s *SLVMStorage) DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { @@ -295,24 +314,167 @@ func (s *SLVMStorage) saveToGlance(ctx context.Context, imageId, imagePath strin return nil } -func (s *SLVMStorage) CreateDiskFromSnapshot(context.Context, IDisk, *SDiskCreateByDiskinfo) error { - return errors.Errorf("unsupported operation") +func (s *SLVMStorage) DestinationPrepareMigrate( + ctx context.Context, liveMigrate bool, disksUri string, snapshotsUri string, + disksBackingFile, diskSnapsChain, outChainSnaps jsonutils.JSONObject, + rebaseDisks bool, + diskinfo *desc.SGuestDisk, + serverId string, idx, totalDiskCount int, + encInfo *apis.SEncryptInfo, sysDiskHasTemplate bool, +) error { + var ( + diskId = diskinfo.DiskId + snapshots, _ = diskSnapsChain.GetArray(diskId) + disk = s.CreateDisk(diskId) + diskOutChainSnaps, _ = outChainSnaps.GetArray(diskId) + ) + + if disk == nil { + return fmt.Errorf( + "Storage %s create disk %s failed", s.GetId(), diskId) + } + + templateId := diskinfo.TemplateId + // create snapshots form remote url + var ( + diskStorageId = diskinfo.StorageId + baseImagePath string + ) + for i, snapshotId := range snapshots { + snapId, _ := snapshotId.GetString() + snapshotUrl := fmt.Sprintf("%s/%s/%s/%s", + snapshotsUri, diskStorageId, diskId, snapId) + snapshotPath := path.Join("/dev", s.GetPath(), "snap_"+snapId) + log.Infof("Disk %s snapshot %s url: %s", diskId, snapId, snapshotUrl) + if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, diskId, snapshotPath); err != nil { + return errors.Wrap(err, "create from snapshot url failed") + } + if i == 0 && len(templateId) > 0 && sysDiskHasTemplate { + templatePath := path.Join("/dev", s.GetPath(), "imagecache_"+templateId) + // check if template is encrypted + img, err := qemuimg.NewQemuImage(templatePath) + if err != nil { + return errors.Wrap(err, "template image probe fail") + } + if img.Encrypted { + templatePath = qemuimg.GetQemuFilepath(templatePath, "sec0", qemuimg.EncryptFormatLuks) + } + if err := doRebaseDisk(snapshotPath, templatePath, encInfo); err != nil { + return err + } + } else if rebaseDisks && len(baseImagePath) > 0 { + if encInfo != nil { + baseImagePath = qemuimg.GetQemuFilepath(baseImagePath, "sec0", qemuimg.EncryptFormatLuks) + } + if err := doRebaseDisk(snapshotPath, baseImagePath, encInfo); err != nil { + return err + } + } + baseImagePath = snapshotPath + } + + for _, snapshotId := range diskOutChainSnaps { + snapId, _ := snapshotId.GetString() + snapshotUrl := fmt.Sprintf("%s/%s/%s/%s", + snapshotsUri, diskStorageId, diskId, snapId) + snapshotPath := disk.GetSnapshotPath(snapId) + log.Infof("Disk %s snapshot %s url: %s", diskId, snapId, snapshotUrl) + if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, diskId, snapshotPath); err != nil { + return errors.Wrap(err, "create from snapshot url failed") + } + } + + if liveMigrate { + // create local disk + backingFile, _ := disksBackingFile.GetString(diskId) + _, err := disk.CreateRaw(ctx, int(diskinfo.Size), "qcow2", "", encInfo, "", backingFile) + if err != nil { + log.Errorln(err) + return err + } + } else { + // download disk form remote url + diskUrl := fmt.Sprintf("%s/%s/%s", disksUri, diskStorageId, diskId) + err := disk.CreateFromUrl(ctx, diskUrl, 0, func(progress, progressMbps float64, totalSizeMb int64) { + log.Debugf("[%.2f / %d] disk %s create %.2f with speed %.2fMbps", progress*float64(totalSizeMb)/100, totalSizeMb, disk.GetId(), progress, progressMbps) + newProgress := float64(idx-1)/float64(totalDiskCount)*100.0 + 1/float64(totalDiskCount)*progress + if len(serverId) > 0 { + log.Debugf("server %s migrate %.2f with speed %.2fMbps", serverId, newProgress, progressMbps) + hostutils.UpdateServerProgress(context.Background(), serverId, newProgress, progressMbps) + } + }) + if err != nil { + return errors.Wrap(err, "CreateFromUrl") + } + } + if rebaseDisks && len(templateId) > 0 && len(baseImagePath) == 0 { + templatePath := path.Join(storageManager.LocalStorageImagecacheManager.GetPath(), templateId) + // check if template is encrypted + img, err := qemuimg.NewQemuImage(templatePath) + if err != nil { + return errors.Wrap(err, "template image probe fail") + } + if img.Encrypted { + templatePath = qemuimg.GetQemuFilepath(templatePath, "sec0", qemuimg.EncryptFormatLuks) + } + if err := doRebaseDisk(disk.GetPath(), templatePath, encInfo); err != nil { + return err + } + } else if rebaseDisks && len(baseImagePath) > 0 { + if encInfo != nil { + baseImagePath = qemuimg.GetQemuFilepath(baseImagePath, "sec0", qemuimg.EncryptFormatLuks) + } + if err := doRebaseDisk(disk.GetPath(), baseImagePath, encInfo); err != nil { + return err + } + } + diskinfo.Path = disk.GetPath() + return nil +} + +func (s *SLVMStorage) CreateDiskFromSnapshot(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error { + info := input.DiskInfo + if info.Protocol == "fuse" { + var encryptInfo *apis.SEncryptInfo + if info.Encryption { + encryptInfo = &info.EncryptInfo + } + err := disk.CreateFromImageFuse(ctx, info.SnapshotUrl, int64(info.DiskSizeMb), encryptInfo) + if err != nil { + return errors.Wrapf(err, "CreateFromImageFuse") + } + return nil + } + return httperrors.NewUnsupportOperationError("Unsupport protocol %s for lvm storage", info.Protocol) } func (s *SLVMStorage) CreateDiskFromExistingPath(context.Context, IDisk, *SDiskCreateByDiskinfo) error { return errors.Errorf("unsupported operation") } -func (s *SLVMStorage) CreateSnapshotFormUrl(ctx context.Context, snapshotUrl, diskId, snapshotPath string) error { - return errors.Errorf("unsupported operation") -} - func (s *SLVMStorage) GetFuseTmpPath() string { - return "" + localPath := options.HostOptions.ImageCachePath + if len(options.HostOptions.LocalImagePath) > 0 { + localPath = options.HostOptions.LocalImagePath[0] + } + + return path.Join(localPath, _FUSE_TMP_PATH_) } func (s *SLVMStorage) GetFuseMountPath() string { - return "" + localPath := options.HostOptions.ImageCachePath + if len(options.HostOptions.LocalImagePath) > 0 { + localPath = options.HostOptions.LocalImagePath[0] + } + + return path.Join(localPath, _FUSE_MOUNT_PATH_) +} + +func (s *SLVMStorage) CreateSnapshotFormUrl(ctx context.Context, snapshotUrl, diskId, snapshotPath string) error { + remoteFile := remotefile.NewRemoteFile(ctx, snapshotUrl, snapshotPath, + false, "", -1, nil, "", "") + err := remoteFile.Fetch(nil) + return errors.Wrapf(err, "fetch snapshot from %s", snapshotUrl) } func (s *SLVMStorage) GetImgsaveBackupPath() string { @@ -333,3 +495,40 @@ func (s *SLVMStorage) Accessible() error { func (s *SLVMStorage) Detach() error { return nil } + +func (s *SLVMStorage) CloneDiskFromStorage( + ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string, fullCopy bool, +) (*hostapi.ServerCloneDiskFromStorageResponse, error) { + srcDiskPath := srcDisk.GetPath() + srcImg, err := qemuimg.NewQemuImage(srcDiskPath) + if err != nil { + return nil, errors.Wrapf(err, "Get source image %q info", srcDiskPath) + } + + // create target disk lv + lvSize := lvmutils.GetQcow2LvSize(srcImg.SizeBytes/1024/1024) * 1024 * 1024 + if err = lvmutils.LvCreate(s.GetPath(), targetDiskId, lvSize); err != nil { + return nil, errors.Wrap(err, "lvcreate") + } + + // start create target disk. if full copy is false, just create + // empty target disk with same size and format + accessPath := path.Join("/dev", s.GetPath(), targetDiskId) + if fullCopy { + _, err = srcImg.Clone(accessPath, qemuimgfmt.QCOW2, false) + } else { + newImg, err := qemuimg.NewQemuImage(accessPath) + if err != nil { + return nil, errors.Wrap(err, "failed new qemu image") + } + + err = newImg.CreateQcow2(srcImg.GetSizeMB(), false, "", "", "", "") + } + if err != nil { + return nil, errors.Wrap(err, "Clone source disk to target local storage") + } + return &hostapi.ServerCloneDiskFromStorageResponse{ + TargetAccessPath: accessPath, + TargetFormat: qemuimgfmt.QCOW2.String(), + }, nil +} diff --git a/pkg/hostman/storageman/storagehandler/storagehandler.go b/pkg/hostman/storageman/storagehandler/storagehandler.go index 508aa94130..5e2bdf1576 100644 --- a/pkg/hostman/storageman/storagehandler/storagehandler.go +++ b/pkg/hostman/storageman/storagehandler/storagehandler.go @@ -56,9 +56,6 @@ func AddStorageHandler(prefix string, app *appsrv.Application) { app.AddHandler("POST", fmt.Sprintf("%s/%s//delete-snapshots", prefix, keyWords), auth.Authenticate(storageDeleteSnapshots)) - app.AddHandler("POST", - fmt.Sprintf("%s/%s//snapshots-recycle", prefix, keyWords), - auth.Authenticate(storageSnapshotsRecycle)) app.AddHandler("GET", fmt.Sprintf("%s/%s/is-mount-point", prefix, keyWords), auth.Authenticate(storageVerifyMountPoint)) @@ -453,15 +450,3 @@ func storageDeleteSnapshots(ctx context.Context, w http.ResponseWriter, r *http. hostutils.DelayTask(ctx, storage.DeleteSnapshots, diskId) hostutils.ResponseOk(ctx, w) } - -func storageSnapshotsRecycle(ctx context.Context, w http.ResponseWriter, r *http.Request) { - params, _, _ := appsrv.FetchEnv(ctx, w, r) - var storageId = params[""] - storage := storageman.GetManager().GetStorage(storageId) - if storage == nil { - hostutils.Response(ctx, w, httperrors.NewNotFoundError("Stroage Not found")) - return - } - go storageman.StorageRequestSnapshotRecycle(ctx, auth.AdminCredential(), storage) - hostutils.ResponseOk(ctx, w) -}