diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index fe19660a84..b224c984b2 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -765,19 +765,29 @@ func (o ServerDelExtraOptionInput) Validate() error { } type ServerSnapshotAndCloneInput struct { - Name string - GenerateName string - Count int - AutoStart bool - AutoDeleteInstanceSnapshot bool + ServerCreateSnapshotParams + + // number of cloned servers + // 数量 + Count *int `json:"count"` + + // Whether auto start the cloned server + // 是否自动启动 + AutoStart *bool `json:"auto_start"` + + // Whether delete instance snapshot automatically + // 是否自动删除主机快照 + AutoDeleteInstanceSnapshot *bool `json:"auto_delete_instance_snapshot"` + + // ignore + InstanceSnapshotId string `json:"instance_snapshot_id"` } -func (o *ServerSnapshotAndCloneInput) Validate() error { - if len(o.Name) == 0 { - return httperrors.NewMissingParameterError("name") - } - if o.Count <= 1 { - o.Count = 1 - } - return nil +type ServerInstanceSnapshot struct { + ServerCreateSnapshotParams +} + +type ServerCreateSnapshotParams struct { + Name string `json:"name"` + GenerateName string `json:"generate_name"` } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index d49766d132..76db830d6c 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1737,16 +1737,20 @@ func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info return nil, errors.Wrap(err, "fillDiskConfigBySnapshot") } } - if info.ImageId != "" { - if err := fillDiskConfigByImage(ctx, userCred, info, info.ImageId); err != nil { - return nil, errors.Wrap(err, "fillDiskConfigByImage") - } - } if info.BackupId != "" { if err := fillDiskConfigByBackup(ctx, userCred, info, info.BackupId); err != nil { return nil, errors.Wrap(err, "fillDiskConfigByBackup") } } + if info.ImageId != "" { + if err := fillDiskConfigByImage(ctx, userCred, info, info.ImageId); err != nil { + if len(info.SnapshotId) == 0 && len(info.BackupId) == 0 { + // return error only if no valid snapshotId and backId + // otherwise, the disk was crated by snapshot or backup, not depends on vald image info + return nil, errors.Wrap(err, "fillDiskConfigByImage") + } + } + } // XXX: do not set default disk size here, set it by each hypervisor driver // if len(diskConfig.ImageId) > 0 && diskConfig.SizeMb == 0 { // diskConfig.SizeMb = options.Options.DefaultDiskSize // MB @@ -1914,6 +1918,14 @@ func parseIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageI } func (self *SDisk) fetchDiskInfo(diskConfig *api.DiskConfig) { + if len(diskConfig.SnapshotId) > 0 { + self.SnapshotId = diskConfig.SnapshotId + self.DiskType = diskConfig.DiskType + } + if len(diskConfig.BackupId) > 0 { + self.BackupId = diskConfig.BackupId + self.DiskType = diskConfig.DiskType + } if len(diskConfig.ImageId) > 0 { self.TemplateId = diskConfig.ImageId // support for create vm from guest image @@ -1922,12 +1934,6 @@ func (self *SDisk) fetchDiskInfo(diskConfig *api.DiskConfig) { } else { self.DiskType = diskConfig.DiskType } - } else if len(diskConfig.SnapshotId) > 0 { - self.SnapshotId = diskConfig.SnapshotId - self.DiskType = diskConfig.DiskType - } else if len(diskConfig.BackupId) > 0 { - self.BackupId = diskConfig.BackupId - self.DiskType = diskConfig.DiskType } if len(diskConfig.Fs) > 0 { self.FsFormat = diskConfig.Fs diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 738f7fa39a..dca800980f 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -4569,38 +4569,37 @@ func (self *SGuest) validateCreateInstanceSnapshot( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject, -) (*SRegionQuota, error) { + input api.ServerCreateSnapshotParams, +) (*SRegionQuota, api.ServerCreateSnapshotParams, error) { if !utils.IsInStringArray(self.Hypervisor, supportInstanceSnapshotHypervisors) { - return nil, httperrors.NewBadRequestError("guest hypervisor %s can't create instance snapshot", self.Hypervisor) + return nil, input, httperrors.NewBadRequestError("guest hypervisor %s can't create instance snapshot", self.Hypervisor) } if len(self.BackupHostId) > 0 { - return nil, httperrors.NewBadRequestError("Can't do instance snapshot with backup guest") + return nil, input, httperrors.NewBadRequestError("Can't do instance snapshot with backup guest") } if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) { - return nil, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status) + return nil, input, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status) } - var name string ownerId := self.GetOwnerId() - dataDict := data.(*jsonutils.JSONDict) - nameHint, err := dataDict.GetString("generate_name") - if err == nil { - name, err = db.GenerateName(ctx, InstanceSnapshotManager, ownerId, nameHint) + // dataDict := data.(*jsonutils.JSONDict) + // nameHint, err := dataDict.GetString("generate_name") + if len(input.GenerateName) > 0 { + name, err := db.GenerateName(ctx, InstanceSnapshotManager, ownerId, input.GenerateName) if err != nil { - return nil, err + return nil, input, errors.Wrap(err, "GenerateName") } - dataDict.Set("name", jsonutils.NewString(name)) - } else if name, err = dataDict.GetString("name"); err != nil { - return nil, httperrors.NewMissingParameterError("name") + input.Name = name + } else if len(input.Name) == 0 { + return nil, input, httperrors.NewMissingParameterError("name") } - err = db.NewNameValidator(InstanceSnapshotManager, ownerId, name, nil) + err := db.NewNameValidator(InstanceSnapshotManager, ownerId, input.Name, nil) if err != nil { - return nil, err + return nil, input, errors.Wrap(err, "NewNameValidator") } // construct Quota @@ -4610,16 +4609,16 @@ func (self *SGuest) validateCreateInstanceSnapshot( if utils.IsInStringArray(provider, ProviderHasSubSnapshot) { disks, err := self.GetDisks() if err != nil { - return nil, errors.Wrapf(err, "GetDisks") + 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, httperrors.NewInternalServerError("%v", err) + return nil, input, httperrors.NewInternalServerError("%v", err) } if count >= options.Options.DefaultMaxManualSnapshotCount { - return nil, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i) + return nil, input, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i) } } } @@ -4627,14 +4626,14 @@ func (self *SGuest) validateCreateInstanceSnapshot( } keys, err := self.GetRegionalQuotaKeys() if err != nil { - return nil, err + return nil, input, errors.Wrap(err, "GetRegionalQuotaKeys") } pendingUsage.SetKeys(keys) err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsage) if err != nil { - return nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err) + return nil, input, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err) } - return pendingUsage, nil + return pendingUsage, input, nil } func (self *SGuest) validateCreateInstanceBackup( @@ -4680,16 +4679,16 @@ func (self *SGuest) validateCreateInstanceBackup( // 2. validate every disk manual snapshot count // 3. validate snapshot quota with disk count func (self *SGuest) PerformInstanceSnapshot( - ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject, + ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerInstanceSnapshot, ) (jsonutils.JSONObject, error) { lockman.LockClass(ctx, InstanceSnapshotManager, userCred.GetProjectId()) defer lockman.ReleaseClass(ctx, InstanceSnapshotManager, userCred.GetProjectId()) - pendingUsage, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, data) + pendingUsage, params, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, input.ServerCreateSnapshotParams) if err != nil { - return nil, err + return nil, errors.Wrap(err, "validateCreateInstanceSnapshot") } - name, _ := data.GetString("name") - instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, userCred, self, name, false) + input.ServerCreateSnapshotParams = params + instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, userCred, self, input.Name, false) if err != nil { quotas.CancelPendingUsage( ctx, userCred, pendingUsage, pendingUsage, false) @@ -4790,27 +4789,35 @@ func (self *SGuest) StartSnapshotResetTask(ctx context.Context, userCred mcclien } func (self *SGuest) PerformSnapshotAndClone( - ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerSnapshotAndCloneInput, + ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSnapshotAndCloneInput, ) (jsonutils.JSONObject, error) { - err := input.Validate() - if err != nil { - return nil, err + newlyGuestName := input.Name + if len(input.Name) == 0 { + return nil, httperrors.NewMissingParameterError("name") + } + count := 1 + if input.Count != nil { + count = *input.Count + if count <= 0 { + return nil, httperrors.NewInputParameterError("count must > 0") + } } lockman.LockRawObject(ctx, InstanceSnapshotManager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, InstanceSnapshotManager.Keyword(), "name") // validate create instance snapshot and set snapshot pending usage - snapshotUsage, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, jsonutils.Marshal(input)) + snapshotUsage, params, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, input.ServerCreateSnapshotParams) if err != nil { - return nil, err + return nil, errors.Wrap(err, "validateCreateInstanceSnapshot") } + input.ServerCreateSnapshotParams = params // set guest pending usage - pendingUsage, pendingRegionUsage, err := self.getGuestUsage(input.Count) + pendingUsage, pendingRegionUsage, err := self.getGuestUsage(count) keys, err := self.GetQuotaKeys() if err != nil { quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false) - return nil, err + return nil, errors.Wrap(err, "GetQuotaKeys") } pendingUsage.SetKeys(keys) err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingUsage) @@ -4822,14 +4829,14 @@ func (self *SGuest) PerformSnapshotAndClone( if err != nil { quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false) quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false) - return nil, err + return nil, errors.Wrap(err, "GetRegionalQuotaKeys") } pendingRegionUsage.SetKeys(regionKeys) err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingRegionUsage) if err != nil { quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false) quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false) - return nil, err + return nil, errors.Wrap(err, "CheckSetPendingQuota") } // migrate snapshotUsage into regionUsage, then discard snapshotUsage pendingRegionUsage.Snapshot = snapshotUsage.Snapshot @@ -4843,7 +4850,7 @@ func (self *SGuest) PerformSnapshotAndClone( } instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot( ctx, userCred, self, instanceSnapshotName, - input.AutoDeleteInstanceSnapshot) + input.AutoDeleteInstanceSnapshot != nil && *input.AutoDeleteInstanceSnapshot) if err != nil { quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false) quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false) @@ -4854,7 +4861,7 @@ func (self *SGuest) PerformSnapshotAndClone( } err = self.StartInstanceSnapshotAndCloneTask( - ctx, userCred, input.Name, &pendingUsage, &pendingRegionUsage, instanceSnapshot, jsonutils.Marshal(input).(*jsonutils.JSONDict)) + ctx, userCred, newlyGuestName, &pendingUsage, &pendingRegionUsage, instanceSnapshot, input) if err != nil { quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false) quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false) @@ -4865,10 +4872,11 @@ func (self *SGuest) PerformSnapshotAndClone( func (self *SGuest) StartInstanceSnapshotAndCloneTask( ctx context.Context, userCred mcclient.TokenCredential, newlyGuestName string, - pendingUsage *SQuota, pendingRegionUsage *SRegionQuota, instanceSnapshot *SInstanceSnapshot, data *jsonutils.JSONDict) error { - + pendingUsage *SQuota, pendingRegionUsage *SRegionQuota, instanceSnapshot *SInstanceSnapshot, + input api.ServerSnapshotAndCloneInput, +) error { params := jsonutils.NewDict() - params.Set("guest_params", data) + params.Set("guest_params", jsonutils.Marshal(input)) if task, err := taskman.TaskManager.NewTask( ctx, "InstanceSnapshotAndCloneTask", instanceSnapshot, userCred, params, "", "", pendingUsage, pendingRegionUsage); err != nil { return err @@ -4880,24 +4888,24 @@ func (self *SGuest) StartInstanceSnapshotAndCloneTask( } func (manager *SGuestManager) CreateGuestFromInstanceSnapshot( - ctx context.Context, userCred mcclient.TokenCredential, guestParams *jsonutils.JSONDict, isp *SInstanceSnapshot, + ctx context.Context, userCred mcclient.TokenCredential, input api.ServerSnapshotAndCloneInput, isp *SInstanceSnapshot, ) (*SGuest, *jsonutils.JSONDict, error) { lockman.LockRawObject(ctx, manager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name") - guestName, err := guestParams.GetString("name") - if err != nil { - return nil, nil, fmt.Errorf("No new guest name provider") - } - if guestName, err = db.GenerateName(ctx, manager, isp.GetOwnerId(), guestName); err != nil { - return nil, nil, err + if guestName, err := db.GenerateName(ctx, manager, isp.GetOwnerId(), input.Name); err != nil { + return nil, nil, errors.Wrap(err, "db.GenerateName") + } else { + input.Name = guestName } - guestParams.Set("name", jsonutils.NewString(guestName)) - guestParams.Set("instance_snapshot_id", jsonutils.NewString(isp.Id)) - iGuest, err := db.DoCreate(manager, ctx, userCred, nil, guestParams, isp.GetOwnerId()) + input.InstanceSnapshotId = isp.Id + + params := jsonutils.Marshal(input).(*jsonutils.JSONDict) + + iGuest, err := db.DoCreate(manager, ctx, userCred, nil, params, isp.GetOwnerId()) if err != nil { - return nil, nil, err + return nil, nil, errors.Wrap(err, "db.DoCreate") } guest := iGuest.(*SGuest) notes := map[string]string{ @@ -4910,10 +4918,10 @@ func (manager *SGuestManager) CreateGuestFromInstanceSnapshot( lockman.LockObject(ctx, guest) defer lockman.ReleaseObject(ctx, guest) - guest.PostCreate(ctx, userCred, guest.GetOwnerId(), nil, guestParams) + guest.PostCreate(ctx, userCred, guest.GetOwnerId(), nil, params) }() - return guest, guestParams, nil + return guest, params, nil } func (self *SGuest) GetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index f3f015afb5..2c2fa90f75 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -1178,10 +1178,10 @@ func (manager *SGuestManager) validateCreateData( if inputMem > 0 { input.VcpuCount = inputCpu } - input.InstanceType = inputInstaceType - } - - if len(input.InstanceBackupId) > 0 { + if len(inputInstaceType) > 0 { + input.InstanceType = inputInstaceType + } + } else if len(input.InstanceBackupId) > 0 { inputMem := input.VmemSize inputCpu := input.VcpuCount inputInstaceType := input.InstanceType @@ -1196,7 +1196,9 @@ func (manager *SGuestManager) validateCreateData( if inputMem > 0 { input.VcpuCount = inputCpu } - input.InstanceType = inputInstaceType + if len(inputInstaceType) > 0 { + input.InstanceType = inputInstaceType + } } resetPassword := true diff --git a/pkg/compute/models/instance_snapshots.go b/pkg/compute/models/instance_snapshots.go index 916ceb6f79..44a3d33b57 100644 --- a/pkg/compute/models/instance_snapshots.go +++ b/pkg/compute/models/instance_snapshots.go @@ -77,6 +77,8 @@ type SInstanceSnapshot struct { InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"` // 主机快照磁盘容量和 SizeMb int `nullable:"false"` + // 镜像ID + ImageId string `width:"36" charset:"ascii" nullable:"true" list:"user"` } type SInstanceSnapshotManager struct { @@ -291,6 +293,9 @@ func (manager *SInstanceSnapshotManager) fillInstanceSnapshot(ctx context.Contex instanceSnapshot.ProjectId = guest.ProjectId instanceSnapshot.DomainId = guest.DomainId instanceSnapshot.GuestId = guest.Id + instanceSnapshot.InstanceType = guest.InstanceType + instanceSnapshot.ImageId = guest.GetTemplateId() + guestSchedInput := guest.ToSchedDesc() host, _ := guest.GetHost() @@ -312,7 +317,10 @@ func (manager *SInstanceSnapshotManager) fillInstanceSnapshot(ctx context.Contex } instanceSnapshot.ServerConfig = jsonutils.Marshal(guestSchedInput.ServerConfig) if len(guest.KeypairId) > 0 { - instanceSnapshot.KeypairId = guest.KeypairId + keypair, _ := KeypairManager.FetchById(guest.KeypairId) + if keypair != nil { + instanceSnapshot.KeypairId = guest.KeypairId + } } serverMetadata := jsonutils.NewDict() if loginAccount := guest.GetMetadata(ctx, "login_account", nil); len(loginAccount) > 0 { @@ -351,7 +359,6 @@ func (manager *SInstanceSnapshotManager) fillInstanceSnapshot(ctx context.Contex instanceSnapshot.OsType = guest.OsType instanceSnapshot.OsArch = guest.OsArch instanceSnapshot.ServerMetadata = serverMetadata - instanceSnapshot.InstanceType = guest.InstanceType } func (manager *SInstanceSnapshotManager) CreateInstanceSnapshot(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, name string, autoDelete bool) (*SInstanceSnapshot, error) { @@ -397,6 +404,10 @@ func (self *SInstanceSnapshot) ToInstanceCreateInput( index := serverConfig.Disks[i].Index if index < len(isjs) { serverConfig.Disks[i].SnapshotId = isjs[index].SnapshotId + if i == 0 && len(self.ImageId) > 0 { + // system disk, save ImageId + serverConfig.Disks[i].ImageId = self.ImageId + } } } } diff --git a/pkg/compute/tasks/disk_create_task.go b/pkg/compute/tasks/disk_create_task.go index e75c91b788..f59014a664 100644 --- a/pkg/compute/tasks/disk_create_task.go +++ b/pkg/compute/tasks/disk_create_task.go @@ -41,7 +41,8 @@ func (self *DiskCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, storage, _ := disk.GetStorage() storagecache := storage.GetStoragecache() imageId := disk.GetTemplateId() - if len(imageId) > 0 { + // use image only if disk not created from snapshot or backup + if len(imageId) > 0 && len(disk.SnapshotId) == 0 && len(disk.BackupId) == 0 { self.SetStage("OnStorageCacheImageComplete", nil) input := api.CacheImageInput{ ImageId: imageId, diff --git a/pkg/compute/tasks/instance_snapshot_and_clone_task.go b/pkg/compute/tasks/instance_snapshot_and_clone_task.go index 2a2ecb8c21..eeeb60146b 100644 --- a/pkg/compute/tasks/instance_snapshot_and_clone_task.go +++ b/pkg/compute/tasks/instance_snapshot_and_clone_task.go @@ -93,16 +93,20 @@ func (self *InstanceSnapshotAndCloneTask) OnInit( func (self *InstanceSnapshotAndCloneTask) OnCreateInstanceSnapshot( ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) { // start create server - params, err := self.Params.Get("guest_params") + var input compute.ServerSnapshotAndCloneInput + err := self.Params.Unmarshal(&input, "guest_params") if err != nil { self.taskFailed(ctx, isp, jsonutils.NewString("Failed get new guest params")) return } - count, _ := params.Int("count") + count := 0 + if input.Count != nil { + count = *input.Count + } if count == 0 { count = 1 } - err = self.doGuestCreate(ctx, isp, params, int(count)) + err = self.doGuestCreate(ctx, isp, input, count) if err != nil { self.taskFailed(ctx, isp, jsonutils.NewString(err.Error())) return @@ -111,22 +115,21 @@ func (self *InstanceSnapshotAndCloneTask) OnCreateInstanceSnapshot( } func (self *InstanceSnapshotAndCloneTask) doGuestCreate( - ctx context.Context, isp *models.SInstanceSnapshot, params jsonutils.JSONObject, count int) error { + ctx context.Context, isp *models.SInstanceSnapshot, cloneInput compute.ServerSnapshotAndCloneInput, count int) error { var ( - dictParmas = params.(*jsonutils.JSONDict) - errStr string + errStr string ) for i := 0; i < count; i++ { - newGuest, input, err := models.GuestManager.CreateGuestFromInstanceSnapshot( - ctx, self.UserCred, dictParmas.DeepCopy().(*jsonutils.JSONDict), isp) + newGuest, params, err := models.GuestManager.CreateGuestFromInstanceSnapshot( + ctx, self.UserCred, cloneInput, isp) if err != nil { log.Errorln(err) errStr += err.Error() + "\n" continue } isp.AddRefCount(ctx) - models.GuestManager.OnCreateComplete(ctx, []db.IModel{newGuest}, self.UserCred, self.UserCred, nil, input) + models.GuestManager.OnCreateComplete(ctx, []db.IModel{newGuest}, self.UserCred, self.UserCred, nil, params) } if len(errStr) > 0 { return fmt.Errorf(errStr)