diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 68ca4aa83b..85f8900200 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -196,6 +196,7 @@ func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.Toke input.Name = input.GenerateName } + logclient.AddSimpleActionLog(self, logclient.ACT_SAVE_IMAGE, input, userCred, true) return input, self.StartGuestSaveImage(ctx, userCred, input, "") } diff --git a/pkg/compute/tasks/guest_save_image_task.go b/pkg/compute/tasks/guest_save_image_task.go index 5d0044c175..e8c984c1aa 100644 --- a/pkg/compute/tasks/guest_save_image_task.go +++ b/pkg/compute/tasks/guest_save_image_task.go @@ -24,6 +24,7 @@ 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/util/logclient" ) type GuestSaveImageTask struct { @@ -72,7 +73,7 @@ func (self *GuestSaveImageTask) OnSaveRootImageComplete(ctx context.Context, gue } func (self *GuestSaveImageTask) OnSaveRootImageCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { - log.Errorf("Guest save root image failed: %s", data.PrettyString()) + logclient.AddActionLogWithStartable(self, guest, logclient.ACT_SAVE_IMAGE, data, self.UserCred, false) guest.SetStatus(self.GetUserCred(), api.VM_SAVE_DISK_FAILED, data.String()) self.SetStageFailed(ctx, data) } diff --git a/pkg/mcclient/modules/image/mod_images.go b/pkg/mcclient/modules/image/mod_images.go index 8694ac64e6..33467528ae 100644 --- a/pkg/mcclient/modules/image/mod_images.go +++ b/pkg/mcclient/modules/image/mod_images.go @@ -42,6 +42,8 @@ const ( IMAGE_META = "X-Image-Meta-" IMAGE_META_PROPERTY = "X-Image-Meta-Property-" + IMAGE_METADATA = "X-Image-Meta-Metadata" + IMAGE_META_COPY_FROM = "x-glance-api-copy-from" ) @@ -58,7 +60,12 @@ func FetchImageMeta(h http.Header) jsonutils.JSONObject { meta := jsonutils.NewDict() meta.Add(jsonutils.NewDict(), "properties") for k, v := range h { - if strings.HasPrefix(k, IMAGE_META_PROPERTY) { + if k == IMAGE_METADATA && len(v) == 1 { + metadata, _ := jsonutils.Parse([]byte(v[0])) + if metadata != nil { + meta.Add(metadata, "metadata") + } + } else if strings.HasPrefix(k, IMAGE_META_PROPERTY) { k := strings.ToLower(k[len(IMAGE_META_PROPERTY):]) meta.Add(jsonutils.NewString(decodeMeta(v[0])), "properties", k) if strings.IndexByte(k, '-') > 0 { diff --git a/pkg/multicloud/cloudpods/disk.go b/pkg/multicloud/cloudpods/disk.go index ee6870bde8..ee0ff457ef 100644 --- a/pkg/multicloud/cloudpods/disk.go +++ b/pkg/multicloud/cloudpods/disk.go @@ -163,7 +163,10 @@ func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error { } func (self *SDisk) Reset(ctx context.Context, snapId string) (string, error) { - return "", cloudprovider.ErrNotImplemented + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(snapId), "snapshot") + _, err := self.region.perform(&modules.Disks, self.Id, "reset", params) + return self.Id, err } func (self *SDisk) Rebuild(ctx context.Context) error { diff --git a/pkg/multicloud/cloudpods/image.go b/pkg/multicloud/cloudpods/image.go index 0e68452d44..98588f6d39 100644 --- a/pkg/multicloud/cloudpods/image.go +++ b/pkg/multicloud/cloudpods/image.go @@ -165,7 +165,12 @@ func (self *SRegion) GetImages() ([]SImage, error) { func (self *SRegion) GetImage(id string) (*SImage, error) { image := &SImage{} - return image, self.cli.get(&modules.Images, id, nil, image) + resp, err := modules.Images.GetById(self.cli.s, id, nil) + if err != nil { + return nil, err + } + log.Errorf("resp: %s", resp.PrettyString()) + return image, resp.Unmarshal(image) } func (self *SRegion) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, opts *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) { diff --git a/pkg/multicloud/cloudpods/instance.go b/pkg/multicloud/cloudpods/instance.go index e101f6d00e..4892c4d268 100644 --- a/pkg/multicloud/cloudpods/instance.go +++ b/pkg/multicloud/cloudpods/instance.go @@ -364,18 +364,22 @@ func (self *SInstance) ResetToInstanceSnapshot(ctx context.Context, idStr string } func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { + return self.host.zone.region.SaveImage(self.Id, opts.Name, opts.Notes) +} + +func (self *SRegion) SaveImage(id, imageName, notes string) (*SImage, error) { input := api.ServerSaveImageInput{} - input.GenerateName = opts.Name - input.Notes = opts.Notes - resp, err := self.host.zone.region.perform(&modules.Servers, self.Id, "save-image", input) + input.GenerateName = imageName + input.Notes = notes + resp, err := self.perform(&modules.Servers, id, "save-image", input) if err != nil { return nil, err } - err = resp.Unmarshal(&input) + imageId, err := resp.GetString("image_id") if err != nil { return nil, err } - return self.host.zone.region.GetImage(input.ImageId) + return self.GetImage(imageId) } func (self *SInstance) AllocatePublicIpAddress() (string, error) { diff --git a/pkg/multicloud/cloudpods/shell/image.go b/pkg/multicloud/cloudpods/shell/image.go new file mode 100644 index 0000000000..f8dca198dc --- /dev/null +++ b/pkg/multicloud/cloudpods/shell/image.go @@ -0,0 +1,47 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/cloudpods" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageListOptions struct { + } + shellutils.R(&ImageListOptions{}, "image-list", "List instances", func(cli *cloudpods.SRegion, args *ImageListOptions) error { + images, err := cli.GetImages() + if err != nil { + return err + } + printList(images, 0, 0, 0, nil) + return nil + }) + + type ImageIdOptions struct { + ID string + } + + shellutils.R(&ImageIdOptions{}, "image-show", "Show instance", func(cli *cloudpods.SRegion, args *ImageIdOptions) error { + image, err := cli.GetImage(args.ID) + if err != nil { + return err + } + printObject(image) + return nil + }) + +} diff --git a/pkg/multicloud/cloudpods/shell/instance.go b/pkg/multicloud/cloudpods/shell/instance.go new file mode 100644 index 0000000000..16eae75bfb --- /dev/null +++ b/pkg/multicloud/cloudpods/shell/instance.go @@ -0,0 +1,63 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/cloudpods" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceListOptions struct { + HostId string + } + shellutils.R(&InstanceListOptions{}, "instance-list", "List instances", func(cli *cloudpods.SRegion, args *InstanceListOptions) error { + instances, err := cli.GetInstances(args.HostId) + if err != nil { + return err + } + printList(instances, 0, 0, 0, nil) + return nil + }) + + type InstanceIdOptions struct { + ID string + } + + shellutils.R(&InstanceIdOptions{}, "instance-show", "Show instance", func(cli *cloudpods.SRegion, args *InstanceIdOptions) error { + instance, err := cli.GetInstance(args.ID) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + type InstanceSaveImageOptions struct { + ID string + NAME string + Note string + } + + shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance image", func(cli *cloudpods.SRegion, args *InstanceSaveImageOptions) error { + image, err := cli.SaveImage(args.ID, args.NAME, args.Note) + if err != nil { + return err + } + printObject(image) + return nil + }) + +} diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index b67daa35d6..988073b2e4 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -76,6 +76,7 @@ const ( ACT_CREATE_BACKUP = "create_backup" ACT_SWITCH_TO_BACKUP = "switch_to_backup" ACT_RENEW = "renew" + ACT_SAVE_IMAGE = "save_image" ACT_SET_AUTO_RENEW = "set_auto_renew" ACT_MIGRATE = "migrate" ACT_EIP_ASSOCIATE = "eip_associate"