diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index 0c25e409a0..cb4af1b473 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -556,9 +556,9 @@ func init() { return nil }) - R(&options.ServerSaveImageOptions{}, "server-save-guest-image", + R(&options.ServerSaveGuestImageOptions{}, "server-save-guest-image", "save root disk and data disks to new images and upload to glance.", func(s *mcclient.ClientSession, - opts *options.ServerSaveImageOptions) error { + opts *options.ServerSaveGuestImageOptions) error { params, err := options.StructToParams(opts) if err != nil { diff --git a/pkg/apis/image/consts.go b/pkg/apis/image/consts.go index 1b7210fdac..447a4c5436 100644 --- a/pkg/apis/image/consts.go +++ b/pkg/apis/image/consts.go @@ -47,6 +47,8 @@ const ( IMAGE_IS_READONLY = "is_readonly" IMAGE_PARTITION_TYPE = "partition_type" IMAGE_INSTALLED_CLOUDINIT = "installed_cloud_init" + + IMAGE_STATUS_UPDATING = "updating" ) var ( diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go index d4e8f26639..5edecaf1ac 100644 --- a/pkg/compute/guestdrivers/virtualization.go +++ b/pkg/compute/guestdrivers/virtualization.go @@ -300,7 +300,7 @@ func (self *SVirtualizedGuestDriver) StartGuestSaveImage(ctx context.Context, us func (self *SVirtualizedGuestDriver) StartGuestSaveGuestImage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, params *jsonutils.JSONDict, parentTaskId string) error { - + guest.SetStatus(userCred, api.VM_START_SAVE_DISK, "") if task, err := taskman.TaskManager.NewTask(ctx, "GuestSaveGuestImageTask", guest, userCred, params, parentTaskId, "", nil); err != nil { return err diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 4e0ded79ac..8eb1cad439 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1511,7 +1511,12 @@ func parseIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageI func (self *SDisk) fetchDiskInfo(diskConfig *api.DiskConfig) { if len(diskConfig.ImageId) > 0 { self.TemplateId = diskConfig.ImageId - self.DiskType = api.DISK_TYPE_SYS + // support for create vm from guest image + if len(diskConfig.DiskType) == 0 { + self.DiskType = api.DISK_TYPE_SYS + } else { + self.DiskType = diskConfig.DiskType + } } else if len(diskConfig.SnapshotId) > 0 { self.SnapshotId = diskConfig.SnapshotId self.DiskType = diskConfig.DiskType diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 7e029bbcec..fda6945ab7 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -198,7 +198,7 @@ func (self *SGuest) PerformSaveGuestImage(ctx context.Context, userCred mcclient if !utils.IsInStringArray(self.Status, []string{api.VM_READY}) { return nil, httperrors.NewBadRequestError("Cannot save image in status %s", self.Status) } - if !data.Contains("name") { + if !data.Contains("name") && !data.Contains("generate_name") { return nil, httperrors.NewMissingParameterError("Image name is required") } if self.Hypervisor != api.HYPERVISOR_KVM { @@ -237,7 +237,7 @@ func (self *SGuest) PerformSaveGuestImage(ctx context.Context, userCred mcclient kwargs.Add(images, "images") - s := auth.GetAdminSession(ctx, options.Options.Region, "") + s := auth.GetSession(ctx, userCred, options.Options.Region, "") ret, err := modules.GuestImages.Create(s, kwargs) if err != nil { return nil, err diff --git a/pkg/compute/tasks/guest_save_instance_image_task.go b/pkg/compute/tasks/guest_save_instance_image_task.go index 6fc090e16d..79feb82fdd 100644 --- a/pkg/compute/tasks/guest_save_instance_image_task.go +++ b/pkg/compute/tasks/guest_save_instance_image_task.go @@ -66,10 +66,11 @@ func (self *GuestSaveGuestImageTask) OnSaveRootImageComplete(ctx context.Context self.taskFailed(ctx, guest, "subtask failed") } - if restart, _ := self.GetParams().Bool("restart"); restart { + if restart, _ := self.GetParams().Bool("auto_start"); restart { self.SetStage("on_start_server_complete", nil) guest.StartGueststartTask(ctx, self.GetUserCred(), nil, self.GetTaskId()) } else { + guest.SetStatus(self.UserCred, api.VM_READY, "") self.taskSuc(ctx, guest) } } diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index 1aa234dedc..6c53509971 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -554,7 +554,7 @@ func (self *SImage) ImageProbeAndCustomization( func (self *SImage) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { if self.Status != api.IMAGE_STATUS_QUEUED { - if self.IsGuestImage.IsTrue() && !self.CanUpdate(data) { + if !self.CanUpdate(data) { return nil, httperrors.NewForbiddenError("image is the part of guest imgae") } appParams := appsrv.AppContextGetParams(ctx) @@ -1263,8 +1263,6 @@ func (self *SImage) PerformUpdateTorrentStatus(ctx context.Context, userCred mcc func (self *SImage) CanUpdate(data jsonutils.JSONObject) bool { dict := data.(*jsonutils.JSONDict) - if dict.Length() == 1 && !dict.Contains("description") { - return false - } - return true + // Only allow update description for now when Image is part of guest image + return self.IsGuestImage.IsFalse() || (dict.Length() == 1 && dict.Contains("description")) } diff --git a/pkg/image/tasks/guest_image_update_task.go b/pkg/image/tasks/guest_image_update_task.go index c7d4291444..a6dc3e8ba1 100644 --- a/pkg/image/tasks/guest_image_update_task.go +++ b/pkg/image/tasks/guest_image_update_task.go @@ -21,6 +21,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/sqlchemy" + "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/image/models" @@ -37,6 +38,10 @@ func init() { func (self *GuestImageUpdateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guestImage := obj.(*models.SGuestImage) + + self.Params.Add(jsonutils.NewString(guestImage.Status), "old_status") + guestImage.SetStatus(self.UserCred, image.IMAGE_STATUS_UPDATING, "") + subImages, err := models.GuestImageJointManager.GetImagesByFilter(guestImage.GetId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery { return q.Asc("name") }) @@ -75,10 +80,14 @@ func (self *GuestImageUpdateTask) OnInit(ctx context.Context, obj db.IStandalone return } } + oldStatus, _ := self.Params.GetString("old_status") + guestImage.SetStatus(self.UserCred, oldStatus, "") self.SetStageComplete(ctx, nil) } func (self *GuestImageUpdateTask) taskFailed(ctx context.Context, guestImage *models.SGuestImage, reason string) { + oldStatus, _ := self.Params.GetString("old_status") + guestImage.SetStatus(self.UserCred, oldStatus, "") db.OpsLog.LogEvent(guestImage, db.ACT_SUBIMAGE_UPDATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithContext(ctx, guestImage, logclient.ACT_SUBIMAGE_UPDATE, reason, self.UserCred, false) self.SetStageFailed(ctx, reason) diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 7268e260d0..29d2755e43 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -538,6 +538,12 @@ type ServerSaveImageOptions struct { AutoStart *bool `help:"Auto start server after image saved"` } +type ServerSaveGuestImageOptions struct { + ID string `help:"ID or name of server" json:"-"` + IMAGE string `help:"Image name" json:"name"` + AutoStart *bool `help:"Auto start server after image saved"` +} + type ServerRebuildRootOptions struct { ID string `help:"Server to rebuild root" json:"-"` ImageId string `help:"New root Image template ID" json:"image_id" token:"image"`