From 653f32358849a95b2340b0af47badd27807c379c Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Mon, 8 Apr 2024 11:19:34 +0800 Subject: [PATCH] feat(glance,region,host,climc): support using tar.gz image as overlay layer --- cmd/climc/shell/image/images.go | 2 +- pkg/apis/compute/container.go | 14 +- pkg/apis/compute/disk.go | 1 + pkg/apis/compute/guests.go | 5 +- pkg/apis/container.go | 49 +++++-- pkg/apis/host/container.go | 37 ++++- pkg/apis/image/consts.go | 11 ++ .../container_drivers/volume_mount/disk.go | 61 ++++++++- .../volume_mount/host_local.go | 6 +- pkg/compute/guestdrivers/pod.go | 10 +- pkg/compute/models/containers.go | 74 +++++++--- pkg/compute/models/disks.go | 8 +- pkg/hostman/container/volume_mount/disk.go | 128 +++++++++++++++--- .../container/volume_mount/host_path.go | 13 +- .../container/volume_mount/interface.go | 7 +- pkg/hostman/guestman/desc/desc.go | 3 +- pkg/hostman/guestman/pod.go | 6 +- pkg/hostman/storageman/disk_local.go | 8 +- pkg/hostman/storageman/imagecache_base.go | 2 + pkg/hostman/storageman/imagecache_local.go | 34 ++++- pkg/hostman/storageman/imagecache_lvm.go | 4 + pkg/hostman/storageman/imagecache_rbd.go | 4 + pkg/hostman/storageman/storage_base.go | 3 +- pkg/image/models/images.go | 7 +- pkg/mcclient/options/compute/storagecache.go | 2 +- pkg/util/fileutils2/fileutils.go | 22 +++ pkg/util/qemuimg/qemuimg.go | 4 + 27 files changed, 421 insertions(+), 104 deletions(-) diff --git a/cmd/climc/shell/image/images.go b/cmd/climc/shell/image/images.go index ab34401205..3e4c92291f 100644 --- a/cmd/climc/shell/image/images.go +++ b/cmd/climc/shell/image/images.go @@ -34,7 +34,7 @@ import ( ) type ImageOptionalOptions struct { - Format string `help:"Image format" choices:"raw|qcow2|iso|vmdk|docker|vhd"` + Format string `help:"Image format" choices:"raw|qcow2|iso|vmdk|docker|vhd|tgz"` Protected bool `help:"Prevent image from being deleted"` Unprotected bool `help:"Allow image to be deleted"` Standard bool `help:"Mark image as a standard image"` diff --git a/pkg/apis/compute/container.go b/pkg/apis/compute/container.go index 359d784a90..7c86652776 100644 --- a/pkg/apis/compute/container.go +++ b/pkg/apis/compute/container.go @@ -47,6 +47,8 @@ const ( CONTAINER_STATUS_PULLED_IMAGE = "pulled_image" CONTAINER_STATUS_CREATING = "creating" CONTAINER_STATUS_CREATE_FAILED = "create_failed" + CONTAINER_STATUS_CACHING_IMAGE = "caching_image" + CONTAINER_STATUS_CACHE_IMAGE_FAILED = "cache_image_failed" CONTAINER_STATUS_STARTING = "starting" CONTAINER_STATUS_START_FAILED = "start_failed" CONTAINER_STATUS_STOPPING = "stopping" @@ -67,9 +69,9 @@ const ( type ContainerSpec struct { apis.ContainerSpec - // Mounts for the container. - // Mounts []*ContainerMount `json:"mounts"` - Devices []*ContainerDevice `json:"devices"` + // Volume mounts + VolumeMounts []*apis.ContainerVolumeMount `json:"volume_mounts"` + Devices []*ContainerDevice `json:"devices"` } func (c *ContainerSpec) String() string { @@ -110,12 +112,6 @@ type ContainerSyncStatusResponse struct { Status string `json:"status"` } -type ContainerDesc struct { - Id string `json:"id"` - Name string `json:"name"` - Spec *ContainerSpec `json:"spec"` -} - type ContainerHostDevice struct { // Path of the device within the container. ContainerPath string `json:"container_path"` diff --git a/pkg/apis/compute/disk.go b/pkg/apis/compute/disk.go index 8a88c86021..ccb96dd96d 100644 --- a/pkg/apis/compute/disk.go +++ b/pkg/apis/compute/disk.go @@ -284,6 +284,7 @@ type DiskAllocateInput struct { Format string DiskSizeMb int ImageId string + ImageFormat string FsFormat string Rebuild bool BackingDiskId string diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 15951b0b16..7af9921776 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/onecloud/pkg/apis" "yunion.io/x/onecloud/pkg/apis/billing" + "yunion.io/x/onecloud/pkg/apis/host" imageapi "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/httperrors" ) @@ -906,8 +907,8 @@ type GuestJsonDesc struct { LightMode bool `json:"light_mode"` - Hypervisor string `json:"hypervisor"` - Containers []*ContainerDesc `json:"containers"` + Hypervisor string `json:"hypervisor"` + Containers []*host.ContainerDesc `json:"containers"` } type ServerSetBootIndexInput struct { diff --git a/pkg/apis/container.go b/pkg/apis/container.go index 3886c3bace..88dad4a73b 100644 --- a/pkg/apis/container.go +++ b/pkg/apis/container.go @@ -14,7 +14,10 @@ package apis -import "yunion.io/x/pkg/util/sets" +import ( + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/sets" +) type ContainerKeyValue struct { Key string `json:"key"` @@ -54,14 +57,12 @@ type ContainerSpec struct { // List of environment variable to set in the container. Envs []*ContainerKeyValue `json:"envs"` // Enable lxcfs - EnableLxcfs bool `json:"enable_lxcfs"` - // Volume mounts - VolumeMounts []*ContainerVolumeMount `json:"volume_mounts"` - Capabilities *ContainerCapability `json:"capabilities"` - Privileged bool `json:"privileged"` - Lifecyle *ContainerLifecyle `json:"lifecyle"` - CgroupDevicesAllow []string `json:"cgroup_devices_allow"` - SimulateCpu bool `json:"simulate_cpu"` + EnableLxcfs bool `json:"enable_lxcfs"` + Capabilities *ContainerCapability `json:"capabilities"` + Privileged bool `json:"privileged"` + Lifecyle *ContainerLifecyle `json:"lifecyle"` + CgroupDevicesAllow []string `json:"cgroup_devices_allow"` + SimulateCpu bool `json:"simulate_cpu"` } type ContainerCapability struct { @@ -122,10 +123,36 @@ type ContainerVolumeMount struct { Propagation ContainerMountPropagation `json:"propagation,omitempty"` } +type ContainerOverlayDiskImage struct { + DiskId string `json:"disk_id"` + ImageId string `json:"image_id"` +} + +type ContainerDiskOverlayType string + +const ( + CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY ContainerDiskOverlayType = "directory" + CONTAINER_DISK_OVERLAY_TYPE_UNKNOWN ContainerDiskOverlayType = "unknown" +) + type ContainerVolumeMountDiskOverlay struct { LowerDir []string `json:"lower_dir"` } +func (o ContainerVolumeMountDiskOverlay) GetType() ContainerDiskOverlayType { + if len(o.LowerDir) != 0 { + return CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY + } + return CONTAINER_DISK_OVERLAY_TYPE_UNKNOWN +} + +func (o ContainerVolumeMountDiskOverlay) IsValid() error { + if o.GetType() == CONTAINER_DISK_OVERLAY_TYPE_UNKNOWN { + return errors.ErrNotSupported + } + return nil +} + type ContainerVolumeMountDisk struct { Index *int `json:"index,omitempty"` Id string `json:"id"` @@ -137,8 +164,8 @@ type ContainerVolumeMountDisk struct { type ContainerVolumeMountHostPathType string const ( - ContainerVolumeMountHostPathTypeDirectory ContainerVolumeMountHostPathType = "directory" - ContainerVolumeMountHostPathTypeFile ContainerVolumeMountHostPathType = "file" + CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY ContainerVolumeMountHostPathType = "directory" + CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE ContainerVolumeMountHostPathType = "file" ) type ContainerVolumeMountHostPath struct { diff --git a/pkg/apis/host/container.go b/pkg/apis/host/container.go index b932d81a7c..d3e1dc23ea 100644 --- a/pkg/apis/host/container.go +++ b/pkg/apis/host/container.go @@ -14,11 +14,38 @@ package host -import "yunion.io/x/onecloud/pkg/apis" +import ( + "yunion.io/x/onecloud/pkg/apis" +) + +type ContainerVolumeMountDisk struct { + Index *int `json:"index,omitempty"` + Id string `json:"id"` + TemplateId string `json:"template_id"` + SubDirectory string `json:"sub_directory"` + StorageSizeFile string `json:"storage_size_file"` + Overlay *apis.ContainerVolumeMountDiskOverlay `json:"overlay"` +} + +type ContainerVolumeMount struct { + Type apis.ContainerVolumeMountType `json:"type"` + Disk *ContainerVolumeMountDisk `json:"disk"` + HostPath *apis.ContainerVolumeMountHostPath `json:"host_path"` + // Mounted read-only if true, read-write otherwise (false or unspecified). + ReadOnly bool `json:"read_only"` + // Path within the container at which the volume should be mounted. Must + // not contain ':'. + MountPath string `json:"mount_path"` + // If set, the mount needs SELinux relabeling. + SelinuxRelabel bool `json:"selinux_relabel,omitempty"` + // Requested propagation mode. + Propagation apis.ContainerMountPropagation `json:"propagation,omitempty"` +} type ContainerSpec struct { apis.ContainerSpec - Devices []*ContainerDevice `json:"devices"` + VolumeMounts []*ContainerVolumeMount `json:"volume_mounts"` + Devices []*ContainerDevice `json:"devices"` } type ContainerDevice struct { @@ -69,3 +96,9 @@ type ContainerPullImageInput struct { PullPolicy apis.ImagePullPolicy `json:"pull_policy"` Auth *ContainerPullImageAuthConfig `json:"auth"` } + +type ContainerDesc struct { + Id string `json:"id"` + Name string `json:"name"` + Spec *ContainerSpec `json:"spec"` +} diff --git a/pkg/apis/image/consts.go b/pkg/apis/image/consts.go index bc7380ace6..925f4044c3 100644 --- a/pkg/apis/image/consts.go +++ b/pkg/apis/image/consts.go @@ -45,6 +45,7 @@ const ( ImageTypeTemplate = TImageType("image") ImageTypeISO = TImageType("iso") + ImageTypeTarGzip = TImageType("tgz") LocalFilePrefix = "file://" S3Prefix = "s3://" @@ -72,3 +73,13 @@ const ( var ( ImageDeadStatus = []string{IMAGE_STATUS_DEACTIVATED, IMAGE_STATUS_KILLED, IMAGE_STATUS_DELETED, IMAGE_STATUS_PENDING_DELETE} ) + +const ( + IMAGE_DISK_FORMAT_RAW = "raw" + IMAGE_DISK_FORMAT_QCOW2 = "qcow2" + IMAGE_DISK_FORMAT_ISO = "iso" + IMAGE_DISK_FORMAT_VMDK = "vmdk" + IMAGE_DISK_FORMAT_DOCKER = "docker" + IMAGE_DISK_FORMAT_VHD = "vhd" + IMAGE_DISK_FORMAT_TGZ = "tgz" +) diff --git a/pkg/compute/container_drivers/volume_mount/disk.go b/pkg/compute/container_drivers/volume_mount/disk.go index bd63cbaef6..aff6e29d1d 100644 --- a/pkg/compute/container_drivers/volume_mount/disk.go +++ b/pkg/compute/container_drivers/volume_mount/disk.go @@ -16,10 +16,20 @@ func init() { models.RegisterContainerVolumeMountDriver(newDisk()) } -type disk struct{} +type iDiskOverlay interface { + validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay) error +} + +type disk struct { + overlayDrivers map[apis.ContainerDiskOverlayType]iDiskOverlay +} func newDisk() models.IContainerVolumeMountDriver { - return &disk{} + return &disk{ + overlayDrivers: map[apis.ContainerDiskOverlayType]iDiskOverlay{ + apis.CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY: newDiskOverlayDir(), + }, + } } func (d disk) GetType() apis.ContainerVolumeMountType { @@ -56,9 +66,15 @@ func (d disk) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCre if diskIndex >= len(disks) { return nil, httperrors.NewInputParameterError("disk.index %d is large than disk size %d", diskIndex, len(disks)) } - vm.Disk.Id = disks[diskIndex].GetId() + diskObj := disks[diskIndex] + vm.Disk.Id = diskObj.GetId() // remove index vm.Disk.Index = nil + if diskObj.TemplateId != "" { + if vm.Disk.SubDirectory == "" { + return nil, httperrors.NewInputParameterError("sub_directory is required when disk has template_id %s", diskObj.TemplateId) + } + } } else { if disk.Id == "" { return nil, httperrors.NewNotEmptyError("disk.id is empty") @@ -67,6 +83,11 @@ func (d disk) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCre for _, d := range disks { if d.GetId() == disk.Id || d.GetName() == disk.Id { disk.Id = d.GetId() + if d.TemplateId != "" { + if vm.Disk.SubDirectory == "" { + return nil, httperrors.NewInputParameterError("sub_directory is required when disk has template_id %s", d.TemplateId) + } + } foundDisk = true break } @@ -75,7 +96,7 @@ func (d disk) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCre return nil, httperrors.NewNotFoundError("not found pod disk by %s", disk.Id) } } - if err := d.validateOverlay(vm); err != nil { + if err := d.validateOverlay(ctx, userCred, vm); err != nil { return nil, errors.Wrapf(err, "validate overlay") } return vm, nil @@ -100,18 +121,44 @@ func (d disk) ValidatePodCreateData(ctx context.Context, userCred mcclient.Token if diskIndex >= len(disks) { return httperrors.NewInputParameterError("disk.index %d is large than disk size %d", diskIndex, len(disks)) } + inputDisk := disks[diskIndex] + if inputDisk.ImageId != "" { + if disk.SubDirectory == "" { + return httperrors.NewInputParameterError("sub_directory is required when disk has image_id %s", inputDisk.ImageId) + } + } return nil } -func (d disk) validateOverlay(vm *apis.ContainerVolumeMount) error { +func (d disk) getOverlayDriver(ov *apis.ContainerVolumeMountDiskOverlay) iDiskOverlay { + return d.overlayDrivers[ov.GetType()] +} + +func (d disk) validateOverlay(ctx context.Context, userCred mcclient.TokenCredential, vm *apis.ContainerVolumeMount) error { if vm.Disk.Overlay == nil { return nil } ov := vm.Disk.Overlay - if len(ov.LowerDir) == 0 { + if err := ov.IsValid(); err != nil { + return httperrors.NewInputParameterError("invalid overlay input: %v", err) + } + if err := d.getOverlayDriver(ov).validateCreateData(ctx, userCred, ov); err != nil { + return errors.Wrapf(err, "validate overlay %s", ov.GetType()) + } + return nil +} + +type diskOverlayDir struct{} + +func newDiskOverlayDir() iDiskOverlay { + return &diskOverlayDir{} +} + +func (d diskOverlayDir) validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay) error { + if len(input.LowerDir) == 0 { return httperrors.NewNotEmptyError("lower_dir is required") } - for idx, ld := range ov.LowerDir { + for idx, ld := range input.LowerDir { if ld == "" { return httperrors.NewNotEmptyError("empty %d dir", idx) } diff --git a/pkg/compute/container_drivers/volume_mount/host_local.go b/pkg/compute/container_drivers/volume_mount/host_local.go index c98b4c28f5..062524e649 100644 --- a/pkg/compute/container_drivers/volume_mount/host_local.go +++ b/pkg/compute/container_drivers/volume_mount/host_local.go @@ -36,11 +36,11 @@ func (h hostLocal) ValidatePodCreateData(ctx context.Context, userCred mcclient. return httperrors.NewNotEmptyError("host_path is nil") } if hp.Type == "" { - hp.Type = apis.ContainerVolumeMountHostPathTypeFile + hp.Type = apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE } if !sets.NewString( - string(apis.ContainerVolumeMountHostPathTypeFile), - string(apis.ContainerVolumeMountHostPathTypeDirectory)).Has(string(hp.Type)) { + string(apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE), + string(apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY)).Has(string(hp.Type)) { return httperrors.NewInputParameterError("unsupported type %s", hp.Type) } if hp.Path == "" { diff --git a/pkg/compute/guestdrivers/pod.go b/pkg/compute/guestdrivers/pod.go index 763c87d218..630a20ba94 100644 --- a/pkg/compute/guestdrivers/pod.go +++ b/pkg/compute/guestdrivers/pod.go @@ -338,9 +338,13 @@ func (p *SPodDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.To if err != nil { return nil, errors.Wrap(err, "GetContainersByPod") } - ctrDescs := make([]*api.ContainerDesc, len(ctrs)) + ctrDescs := make([]*hostapi.ContainerDesc, len(ctrs)) for idx, ctr := range ctrs { - ctrDescs[idx] = ctr.GetJsonDescAtHost() + desc, err := ctr.GetJsonDescAtHost() + if err != nil { + return nil, errors.Wrapf(err, "GetJsonDescAtHost of container %s", ctr.GetId()) + } + ctrDescs[idx] = desc } desc.Containers = ctrDescs return jsonutils.Marshal(desc), nil @@ -398,7 +402,7 @@ func (p *SPodDriver) performContainerAction(ctx context.Context, userCred mcclie } func (p *SPodDriver) getContainerCreateInput(ctx context.Context, userCred mcclient.TokenCredential, ctr *models.SContainer) (*hostapi.ContainerCreateInput, error) { - spec, err := ctr.ToHostContainerSpec(ctx, userCred) + spec, err := ctr.ToHostContainerSpec() if err != nil { return nil, errors.Wrap(err, "ToHostContainerSpec") } diff --git a/pkg/compute/models/containers.go b/pkg/compute/models/containers.go index 427395291b..ebdb9fbef4 100644 --- a/pkg/compute/models/containers.go +++ b/pkg/compute/models/containers.go @@ -185,7 +185,7 @@ func (m *SContainerManager) ValidateSpecDevice(ctx context.Context, userCred mcc } func (m *SContainerManager) ValidateSpecVolumeMounts(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, spec *api.ContainerSpec) error { - relation, err := m.GetVolumeMountRelations(ctx, userCred, pod, spec) + relation, err := m.GetVolumeMountRelations(pod, spec) if err != nil { return errors.Wrap(err, "GetVolumeMountRelations") } @@ -288,11 +288,39 @@ type ContainerVolumeMountRelation struct { pod *SGuest } -func (vm *ContainerVolumeMountRelation) ToHostMount() (*apis.ContainerVolumeMount, error) { - return vm.VolumeMount, nil +func (vm *ContainerVolumeMountRelation) toHostDiskMount(disk *apis.ContainerVolumeMountDisk) (*hostapi.ContainerVolumeMountDisk, error) { + diskObj := DiskManager.FetchDiskById(disk.Id) + if diskObj == nil { + return nil, errors.Errorf("fetch disk by id %s", disk.Id) + } + ret := &hostapi.ContainerVolumeMountDisk{ + Index: disk.Index, + Id: disk.Id, + TemplateId: diskObj.TemplateId, + SubDirectory: disk.SubDirectory, + StorageSizeFile: disk.StorageSizeFile, + Overlay: disk.Overlay, + } + return ret, nil } -func (m *SContainerManager) GetVolumeMountRelations(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, spec *api.ContainerSpec) ([]*ContainerVolumeMountRelation, error) { +func (vm *ContainerVolumeMountRelation) ToHostMount() (*hostapi.ContainerVolumeMount, error) { + disk, err := vm.toHostDiskMount(vm.VolumeMount.Disk) + if err != nil { + return nil, errors.Wrap(err, "toHostDiskMount") + } + return &hostapi.ContainerVolumeMount{ + Type: vm.VolumeMount.Type, + Disk: disk, + HostPath: vm.VolumeMount.HostPath, + ReadOnly: vm.VolumeMount.ReadOnly, + MountPath: vm.VolumeMount.MountPath, + SelinuxRelabel: vm.VolumeMount.SelinuxRelabel, + Propagation: vm.VolumeMount.Propagation, + }, nil +} + +func (m *SContainerManager) GetVolumeMountRelations(pod *SGuest, spec *api.ContainerSpec) ([]*ContainerVolumeMountRelation, error) { relation := make([]*ContainerVolumeMountRelation, len(spec.VolumeMounts)) for idx, vm := range spec.VolumeMounts { tmpVm := vm @@ -304,8 +332,8 @@ func (m *SContainerManager) GetVolumeMountRelations(ctx context.Context, userCre return relation, nil } -func (c *SContainer) GetVolumeMountRelations(ctx context.Context, userCred mcclient.TokenCredential) ([]*ContainerVolumeMountRelation, error) { - return GetContainerManager().GetVolumeMountRelations(ctx, userCred, c.GetPod(), c.Spec) +func (c *SContainer) GetVolumeMountRelations() ([]*ContainerVolumeMountRelation, error) { + return GetContainerManager().GetVolumeMountRelations(c.GetPod(), c.Spec) } func (c *SContainer) PerformStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { @@ -379,8 +407,8 @@ func (c *SContainer) RealDelete(ctx context.Context, userCred mcclient.TokenCred return c.SVirtualResourceBase.Delete(ctx, userCred) } -func (m *SContainerManager) ConvertVolumeMountRelationToSpec(relation []*ContainerVolumeMountRelation) ([]*apis.ContainerVolumeMount, error) { - mounts := make([]*apis.ContainerVolumeMount, 0) +func (m *SContainerManager) ConvertVolumeMountRelationToSpec(relation []*ContainerVolumeMountRelation) ([]*hostapi.ContainerVolumeMount, error) { + mounts := make([]*hostapi.ContainerVolumeMount, 0) for _, r := range relation { mount, err := r.ToHostMount() if err != nil { @@ -393,8 +421,8 @@ func (m *SContainerManager) ConvertVolumeMountRelationToSpec(relation []*Contain return mounts, nil } -func (c *SContainer) ToHostContainerSpec(ctx context.Context, userCred mcclient.TokenCredential) (*hostapi.ContainerSpec, error) { - vmRelation, err := c.GetVolumeMountRelations(ctx, userCred) +func (c *SContainer) ToHostContainerSpec() (*hostapi.ContainerSpec, error) { + vmRelation, err := c.GetVolumeMountRelations() if err != nil { return nil, errors.Wrap(err, "GetVolumeMountRelations") } @@ -402,12 +430,6 @@ func (c *SContainer) ToHostContainerSpec(ctx context.Context, userCred mcclient. if err != nil { return nil, errors.Wrap(err, "ConvertVolumeRelationToSpec") } - - spec := c.Spec.ContainerSpec - spec.VolumeMounts = mounts - hSpec := &hostapi.ContainerSpec{ - ContainerSpec: spec, - } ctrDevs := make([]*hostapi.ContainerDevice, 0) for _, dev := range c.Spec.Devices { ctrDev, err := GetContainerDeviceDriver(dev.Type).ToHostDevice(dev) @@ -416,14 +438,24 @@ func (c *SContainer) ToHostContainerSpec(ctx context.Context, userCred mcclient. } ctrDevs = append(ctrDevs, ctrDev) } - hSpec.Devices = ctrDevs + + spec := c.Spec.ContainerSpec + hSpec := &hostapi.ContainerSpec{ + ContainerSpec: spec, + VolumeMounts: mounts, + Devices: ctrDevs, + } return hSpec, nil } -func (c *SContainer) GetJsonDescAtHost() *api.ContainerDesc { - return &api.ContainerDesc{ +func (c *SContainer) GetJsonDescAtHost() (*hostapi.ContainerDesc, error) { + spec, err := c.ToHostContainerSpec() + if err != nil { + return nil, errors.Wrap(err, "ToHostContainerSpec") + } + return &hostapi.ContainerDesc{ Id: c.GetId(), Name: c.GetName(), - Spec: c.Spec, - } + Spec: spec, + }, nil } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index dbf7d518df..8a12c84d94 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -828,6 +828,12 @@ func (self *SDisk) StartAllocate(ctx context.Context, host *SHost, storage *SSto } } else if len(templateId) > 0 { input.ImageId = templateId + s := auth.GetAdminSession(ctx, options.Options.Region) + img, err := image.Images.Get(s, templateId, nil) + if err != nil { + return errors.Wrapf(err, "get image details from glance") + } + input.ImageFormat, _ = img.GetString("disk_format") } if len(fsFormat) > 0 { input.FsFormat = fsFormat @@ -1204,7 +1210,7 @@ func (self *SDisk) validateDeleteCondition(ctx context.Context, isPurge bool) er if !isPurge { storage, _ := self.GetStorage() if storage == nil { - // storage is empty, a dirty data, allow delete + // storage is empty, a dirty data, allow to delete return nil } host, _ := storage.GetMasterHost() diff --git a/pkg/hostman/container/volume_mount/disk.go b/pkg/hostman/container/volume_mount/disk.go index bac893cc49..0fd8faa386 100644 --- a/pkg/hostman/container/volume_mount/disk.go +++ b/pkg/hostman/container/volume_mount/disk.go @@ -1,6 +1,7 @@ package volume_mount import ( + "context" "fmt" "path/filepath" "strings" @@ -8,6 +9,8 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/apis" + computeapi "yunion.io/x/onecloud/pkg/apis/compute" + hostapi "yunion.io/x/onecloud/pkg/apis/host" "yunion.io/x/onecloud/pkg/hostman/container/storage" container_storage "yunion.io/x/onecloud/pkg/hostman/container/storage" "yunion.io/x/onecloud/pkg/hostman/guestman/desc" @@ -20,17 +23,28 @@ func init() { RegisterDriver(newDisk()) } -type disk struct{} +type iDiskOverlay interface { + mount(d disk, pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error + unmount(d disk, pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error +} + +type disk struct { + overlayDrivers map[apis.ContainerDiskOverlayType]iDiskOverlay +} func newDisk() IVolumeMount { - return &disk{} + return &disk{ + overlayDrivers: map[apis.ContainerDiskOverlayType]iDiskOverlay{ + apis.CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY: newDiskOverlayDir(), + }, + } } func (d disk) GetType() apis.ContainerVolumeMountType { return apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK } -func (d disk) getRuntimeMountHostPath(pod IPodInfo, vm *apis.ContainerVolumeMount) (string, error) { +func (d disk) getRuntimeMountHostPath(pod IPodInfo, vm *hostapi.ContainerVolumeMount) (string, error) { diskInput := vm.Disk if diskInput == nil { return "", httperrors.NewNotEmptyError("disk is nil") @@ -45,19 +59,19 @@ func (d disk) getRuntimeMountHostPath(pod IPodInfo, vm *apis.ContainerVolumeMoun return hostPath, nil } -func (d disk) GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) (string, error) { +func (d disk) GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, error) { hostPath, err := d.getRuntimeMountHostPath(pod, vm) if err != nil { return "", errors.Wrap(err, "get runtime mount host_path") } overlay := vm.Disk.Overlay - if overlay == nil { + if overlay == nil && vm.Disk.TemplateId == "" { return hostPath, nil } return d.getOverlayMergedDir(pod, ctrId, vm, hostPath), nil } -func (d disk) getPodDisk(pod IPodInfo, vm *apis.ContainerVolumeMount) (storageman.IDisk, *desc.SGuestDisk, error) { +func (d disk) getPodDisk(pod IPodInfo, vm *hostapi.ContainerVolumeMount) (storageman.IDisk, *desc.SGuestDisk, error) { var disk *desc.SGuestDisk = nil disks := pod.GetDisks() volDisk := vm.Disk @@ -82,7 +96,7 @@ func (d disk) getPodDisk(pod IPodInfo, vm *apis.ContainerVolumeMount) (storagema return iDisk, disk, nil } -func (d disk) getDiskStorageDriver(pod IPodInfo, vm *apis.ContainerVolumeMount) (storage.IContainerStorage, error) { +func (d disk) getDiskStorageDriver(pod IPodInfo, vm *hostapi.ContainerVolumeMount) (storage.IContainerStorage, error) { iDisk, _, err := d.getPodDisk(pod, vm) if err != nil { return nil, errors.Wrap(err, "get pod disk interface") @@ -94,7 +108,7 @@ func (d disk) getDiskStorageDriver(pod IPodInfo, vm *apis.ContainerVolumeMount) return drv, nil } -func (d disk) getOverlayDir(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount, upperDir string, suffix string) string { +func (d disk) getOverlayDir(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, upperDir string, suffix string) string { return filepath.Join(pod.GetVolumesOverlayDir(), vm.Disk.Id, ctrId, fmt.Sprintf("%s-%s", filepath.Base(upperDir), suffix)) } @@ -102,11 +116,11 @@ func (d disk) getOverlayWorkDir(upperDir string) string { return fmt.Sprintf("%s-work", upperDir) } -func (d disk) getOverlayMergedDir(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount, upperDir string) string { +func (d disk) getOverlayMergedDir(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, upperDir string) string { return d.getOverlayDir(pod, ctrId, vm, upperDir, "merged") } -func (d disk) Mount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { +func (d disk) Mount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { iDisk, gd, err := d.getPodDisk(pod, vm) if err != nil { return errors.Wrap(err, "get pod disk interface") @@ -146,10 +160,15 @@ func (d disk) Mount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) e return errors.Wrapf(err, "mount container %s overlay dir: %#v", ctrId, vmDisk.Overlay) } } + if vmDisk.TemplateId != "" { + if err := d.mountTemplateOverlay(context.Background(), pod, ctrId, vm); err != nil { + return errors.Wrapf(err, "mount container %s template overlay: %#v", ctrId, vmDisk.TemplateId) + } + } return nil } -func (d disk) createStorageSizeFile(iDisk storageman.IDisk, mntPoint string, input *apis.ContainerVolumeMountDisk) error { +func (d disk) createStorageSizeFile(iDisk storageman.IDisk, mntPoint string, input *hostapi.ContainerVolumeMountDisk) error { desc := iDisk.GetDiskDesc() diskSizeMB, err := desc.Int("disk_size") if err != nil { @@ -164,7 +183,7 @@ func (d disk) createStorageSizeFile(iDisk storageman.IDisk, mntPoint string, inp return nil } -func (d disk) Unmount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { +func (d disk) Unmount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { iDisk, _, err := d.getPodDisk(pod, vm) if err != nil { return errors.Wrap(err, "get pod disk interface") @@ -178,6 +197,11 @@ func (d disk) Unmount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) return errors.Wrapf(err, "umount overlay") } } + if vm.Disk.TemplateId != "" { + if err := d.unmountTemplateOverlay(context.Background(), pod, ctrId, vm); err != nil { + return errors.Wrapf(err, "unmount container %s template overlay: %#v", ctrId, vm.Disk.TemplateId) + } + } mntPoint := pod.GetDiskMountPoint(iDisk) if err := container_storage.Unmount(mntPoint); err != nil { return errors.Wrapf(err, "unmount %s", mntPoint) @@ -194,16 +218,71 @@ func (d disk) Unmount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) return nil } -func (d disk) unmoutOverlay(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { - upperDir, err := d.getRuntimeMountHostPath(pod, vm) - if err != nil { - return errors.Wrap(err, "getRuntimeMountHostPath") - } - overlayDir := d.getOverlayMergedDir(pod, ctrId, vm, upperDir) - return container_storage.Unmount(overlayDir) +func (d disk) getOverlayDriver(ov *apis.ContainerVolumeMountDiskOverlay) iDiskOverlay { + return d.overlayDrivers[ov.GetType()] } -func (d disk) mountOverlay(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { +func (d disk) unmoutOverlay(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { + return d.getOverlayDriver(vm.Disk.Overlay).unmount(d, pod, ctrId, vm) +} + +func (d disk) mountOverlay(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { + return d.getOverlayDriver(vm.Disk.Overlay).mount(d, pod, ctrId, vm) +} + +func (d disk) doTemplateOverlayAction( + ctx context.Context, + pod IPodInfo, ctrId string, + vm *hostapi.ContainerVolumeMount, + ovAction func(d disk, pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error) error { + templateId := vm.Disk.TemplateId + input := computeapi.CacheImageInput{ + ImageId: templateId, + } + cachedImgMan := storageman.GetManager().LocalStorageImagecacheManager + cachedImg, err := cachedImgMan.AcquireImage(ctx, input, nil) + if err != nil { + return errors.Wrapf(err, "Get cache image %s", templateId) + } + defer cachedImgMan.ReleaseImage(ctx, templateId) + cachedImageDir, err := cachedImg.GetAccessDirectory() + if err != nil { + return errors.Wrapf(err, "GetAccessDirectory of cached image %s", cachedImg.GetPath()) + } + vm.Disk.Overlay = &apis.ContainerVolumeMountDiskOverlay{ + LowerDir: []string{cachedImageDir}, + } + if err := ovAction(d, pod, ctrId, vm); err != nil { + return errors.Wrapf(err, "overlay dir %s", cachedImageDir) + } + return nil +} + +func (d disk) mountTemplateOverlay( + ctx context.Context, + pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { + if err := d.doTemplateOverlayAction(ctx, pod, ctrId, vm, newDiskOverlayDir().mount); err != nil { + return errors.Wrapf(err, "mount template overlay") + } + return nil +} + +func (d disk) unmountTemplateOverlay( + ctx context.Context, + pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { + if err := d.doTemplateOverlayAction(ctx, pod, ctrId, vm, newDiskOverlayDir().unmount); err != nil { + return errors.Wrapf(err, "unmount template overlay") + } + return nil +} + +type diskOverlayDir struct{} + +func newDiskOverlayDir() iDiskOverlay { + return &diskOverlayDir{} +} + +func (dod diskOverlayDir) mount(d disk, pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { vmDisk := vm.Disk lowerDir := vmDisk.Overlay.LowerDir upperDir, err := d.getRuntimeMountHostPath(pod, vm) @@ -226,3 +305,12 @@ func (d disk) mountOverlay(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeM return nil } + +func (dod diskOverlayDir) unmount(d disk, pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { + upperDir, err := d.getRuntimeMountHostPath(pod, vm) + if err != nil { + return errors.Wrap(err, "getRuntimeMountHostPath") + } + overlayDir := d.getOverlayMergedDir(pod, ctrId, vm, upperDir) + return container_storage.Unmount(overlayDir) +} diff --git a/pkg/hostman/container/volume_mount/host_path.go b/pkg/hostman/container/volume_mount/host_path.go index 62acaa8f8a..04e209d1fe 100644 --- a/pkg/hostman/container/volume_mount/host_path.go +++ b/pkg/hostman/container/volume_mount/host_path.go @@ -4,6 +4,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/apis" + hostapi "yunion.io/x/onecloud/pkg/apis/host" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/util/procutils" ) @@ -14,11 +15,11 @@ func init() { type hostLocal struct{} -func (h hostLocal) Mount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { +func (h hostLocal) Mount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { return nil } -func (h hostLocal) Unmount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error { +func (h hostLocal) Unmount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error { return nil } @@ -30,22 +31,22 @@ func (h hostLocal) GetType() apis.ContainerVolumeMountType { return apis.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH } -func (h hostLocal) GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) (string, error) { +func (h hostLocal) GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, error) { host := vm.HostPath if host == nil { return "", httperrors.NewNotEmptyError("host_local is nil") } switch host.Type { - case "", apis.ContainerVolumeMountHostPathTypeFile: + case "", apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE: return host.Path, nil - case apis.ContainerVolumeMountHostPathTypeDirectory: + case apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY: return h.getDirectoryPath(host) } return "", httperrors.NewInputParameterError("unsupported type %q", host.Type) } func (h hostLocal) getDirectoryPath(input *apis.ContainerVolumeMountHostPath) (string, error) { - if input.Type != apis.ContainerVolumeMountHostPathTypeDirectory { + if input.Type != apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY { return "", httperrors.NewInputParameterError("unsupported type %q", input.Type) } dirPath := input.Path diff --git a/pkg/hostman/container/volume_mount/interface.go b/pkg/hostman/container/volume_mount/interface.go index c2f837ac20..62a77858bf 100644 --- a/pkg/hostman/container/volume_mount/interface.go +++ b/pkg/hostman/container/volume_mount/interface.go @@ -6,6 +6,7 @@ import ( runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "yunion.io/x/onecloud/pkg/apis" + hostapi "yunion.io/x/onecloud/pkg/apis/host" "yunion.io/x/onecloud/pkg/hostman/guestman/desc" "yunion.io/x/onecloud/pkg/hostman/storageman" ) @@ -35,9 +36,9 @@ type IPodInfo interface { type IVolumeMount interface { GetType() apis.ContainerVolumeMountType - GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) (string, error) - Mount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error - Unmount(pod IPodInfo, ctrId string, vm *apis.ContainerVolumeMount) error + GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, error) + Mount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error + Unmount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error } func GetRuntimeVolumeMountPropagation(input apis.ContainerMountPropagation) runtimeapi.MountPropagation { diff --git a/pkg/hostman/guestman/desc/desc.go b/pkg/hostman/guestman/desc/desc.go index c31669944e..031ced4c37 100644 --- a/pkg/hostman/guestman/desc/desc.go +++ b/pkg/hostman/guestman/desc/desc.go @@ -18,6 +18,7 @@ import ( "yunion.io/x/jsonutils" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/apis/host" ) type SGuestCpu struct { @@ -381,7 +382,7 @@ type SGuestMetaDesc struct { } type SGuestContainerDesc struct { - Containers []*api.ContainerDesc + Containers []*host.ContainerDesc } type SGuestDesc struct { diff --git a/pkg/hostman/guestman/pod.go b/pkg/hostman/guestman/pod.go index 1449904dec..9cc6ccadb8 100644 --- a/pkg/hostman/guestman/pod.go +++ b/pkg/hostman/guestman/pod.go @@ -223,12 +223,12 @@ func (s *sPodGuestInstance) umountPodVolumes() error { return nil } -func (s *sPodGuestInstance) getContainerVolumeMounts() map[string][]*apis.ContainerVolumeMount { - result := make(map[string][]*apis.ContainerVolumeMount, 0) +func (s *sPodGuestInstance) getContainerVolumeMounts() map[string][]*hostapi.ContainerVolumeMount { + result := make(map[string][]*hostapi.ContainerVolumeMount, 0) for _, ctr := range s.GetDesc().Containers { mnts, ok := result[ctr.Id] if !ok { - mnts = make([]*apis.ContainerVolumeMount, 0) + mnts = make([]*hostapi.ContainerVolumeMount, 0) } for _, vol := range ctr.Spec.VolumeMounts { tmp := vol diff --git a/pkg/hostman/storageman/disk_local.go b/pkg/hostman/storageman/disk_local.go index a413ad8567..62c9bed0e4 100644 --- a/pkg/hostman/storageman/disk_local.go +++ b/pkg/hostman/storageman/disk_local.go @@ -516,18 +516,16 @@ func (d *SLocalDisk) DeleteSnapshot(snapshotId, convertSnapshot string) error { func (d *SLocalDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { if err := d.Probe(); err != nil { - return nil, err + return nil, errors.Wrap(err, "do probe") } destDir := d.Storage.GetImgsaveBackupPath() if err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil { - log.Errorln(err) - return nil, err + return nil, errors.Wrapf(err, "mkdir -p %s", destDir) } backupPath := path.Join(destDir, fmt.Sprintf("%s.%s", d.Id, appctx.AppContextTaskId(ctx))) if err := procutils.NewCommand("cp", "--sparse=always", "-f", d.GetPath(), backupPath).Run(); err != nil { - log.Errorln(err) procutils.NewCommand("rm", "-f", backupPath).Run() - return nil, err + return nil, errors.Wrapf(err, "cp %s to %s", d.getPath(), backupPath) } res := jsonutils.NewDict() res.Set("backup", jsonutils.NewString(backupPath)) diff --git a/pkg/hostman/storageman/imagecache_base.go b/pkg/hostman/storageman/imagecache_base.go index 69a44efa8d..413b0cf610 100644 --- a/pkg/hostman/storageman/imagecache_base.go +++ b/pkg/hostman/storageman/imagecache_base.go @@ -31,4 +31,6 @@ type IImageCache interface { GetImageId() string GetDesc() *remotefile.SImageDesc + // GetAccessDirectory is used by container's volume mount + GetAccessDirectory() (string, error) } diff --git a/pkg/hostman/storageman/imagecache_local.go b/pkg/hostman/storageman/imagecache_local.go index 988a1d418e..fb4777ee26 100644 --- a/pkg/hostman/storageman/imagecache_local.go +++ b/pkg/hostman/storageman/imagecache_local.go @@ -31,11 +31,13 @@ import ( "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" + imageapi "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" "yunion.io/x/onecloud/pkg/mcclient/auth" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemuimg" ) @@ -56,7 +58,8 @@ type SLocalImageCache struct { cond *sync.Cond lastCheckTime time.Time - remoteFile *remotefile.SRemoteFile + remoteFile *remotefile.SRemoteFile + accessDirLock sync.Mutex } func NewLocalImageCache(imageId string, imagecacheManager IImageCacheManger) *SLocalImageCache { @@ -64,6 +67,7 @@ func NewLocalImageCache(imageId string, imagecacheManager IImageCacheManger) *SL imageCache.imageId = imageId imageCache.Manager = imagecacheManager imageCache.cond = sync.NewCond(new(sync.Mutex)) + imageCache.accessDirLock = sync.Mutex{} return imageCache } @@ -286,3 +290,31 @@ func (l *SLocalImageCache) GetSize() int64 { return fi.Size() } } + +func (l *SLocalImageCache) getAccessDirPath() string { + return fmt.Sprintf("%s-dir", l.GetPath()) +} + +func (l *SLocalImageCache) GetAccessDirectory() (string, error) { + if l.Desc.Format != imageapi.IMAGE_DISK_FORMAT_TGZ { + return "", errors.Wrapf(errors.ErrNotSupported, "format %s", l.Desc.Format) + } + + l.accessDirLock.Lock() + defer l.accessDirLock.Unlock() + dir := l.getAccessDirPath() + if fileutils2.Exists(dir) { + return dir, nil + } + // untar cached image + out, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", dir).Output() + if err != nil { + return "", errors.Wrapf(err, "mkdir %s: %s", dir, out) + } + out, err = procutils.NewRemoteCommandAsFarAsPossible("tar", "xf", l.GetPath(), "-C", dir).Output() + if err != nil { + return "", errors.Wrapf(err, "untar to %s: %s", dir, out) + } + + return dir, nil +} diff --git a/pkg/hostman/storageman/imagecache_lvm.go b/pkg/hostman/storageman/imagecache_lvm.go index 280c191ba0..7d808e4a6a 100644 --- a/pkg/hostman/storageman/imagecache_lvm.go +++ b/pkg/hostman/storageman/imagecache_lvm.go @@ -157,3 +157,7 @@ func (c *SLVMImageCache) Remove(ctx context.Context) error { }() return nil } + +func (c *SLVMImageCache) GetAccessDirectory() (string, error) { + return "", errors.ErrNotImplemented +} diff --git a/pkg/hostman/storageman/imagecache_rbd.go b/pkg/hostman/storageman/imagecache_rbd.go index 14b3f395c2..b1f92f4cca 100644 --- a/pkg/hostman/storageman/imagecache_rbd.go +++ b/pkg/hostman/storageman/imagecache_rbd.go @@ -161,3 +161,7 @@ func (r *SRbdImageCache) GetDesc() *remotefile.SImageDesc { func (r *SRbdImageCache) GetImageId() string { return r.imageId } + +func (r *SRbdImageCache) GetAccessDirectory() (string, error) { + return "", errors.ErrNotImplemented +} diff --git a/pkg/hostman/storageman/storage_base.go b/pkg/hostman/storageman/storage_base.go index f838bc93b3..0cd788338b 100644 --- a/pkg/hostman/storageman/storage_base.go +++ b/pkg/hostman/storageman/storage_base.go @@ -29,6 +29,7 @@ import ( "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" hostapi "yunion.io/x/onecloud/pkg/apis/host" + imageapi "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/hostman/guestman/desc" "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/hostman/options" @@ -366,7 +367,7 @@ func (s *SBaseStorage) CreateDiskByDiskinfo(ctx context.Context, params interfac case len(createParams.DiskInfo.SnapshotId) > 0: log.Infof("CreateDiskFromSnpashot %s", createParams) return s.CreateDiskFromSnpashot(ctx, disk, createParams) - case len(createParams.DiskInfo.ImageId) > 0: + case len(createParams.DiskInfo.ImageId) > 0 && createParams.DiskInfo.ImageFormat != imageapi.IMAGE_DISK_FORMAT_TGZ: log.Infof("CreateDiskFromTemplate %s", createParams) return s.CreateDiskFromTemplate(ctx, disk, createParams) case createParams.DiskInfo.Backup != nil: diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index 0853d33fb2..adfa1e0651 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -228,7 +228,7 @@ func (self *SImage) CustomizedGetDetailsBody(ctx context.Context, userCred mccli if self.IsGuestImage.IsFalse() { formatStr := jsonutils.GetAnyString(query, []string{"format", "disk_format"}) - if len(formatStr) > 0 { + if len(formatStr) > 0 && formatStr != api.IMAGE_DISK_FORMAT_TGZ { subimg := ImageSubformatManager.FetchSubImage(self.Id, formatStr) if subimg != nil { if strings.HasPrefix(subimg.Location, api.LocalFilePrefix) { @@ -546,7 +546,6 @@ func (self *SImage) SaveImageFromStream(reader io.Reader, totalSize int64, calCh if err != nil { return err } - format = string(img.Format) virtualSizeBytes = img.SizeBytes var fastChksum string @@ -1035,6 +1034,8 @@ func (manager *SImageManager) Usage(ctx context.Context, scope rbacscope.TRbacSc func (self *SImage) GetImageType() api.TImageType { if self.DiskFormat == string(qemuimgfmt.ISO) { return api.ImageTypeISO + } else if self.DiskFormat == api.IMAGE_DISK_FORMAT_TGZ { + return api.ImageTypeTarGzip } else { return api.ImageTypeTemplate } @@ -1112,7 +1113,7 @@ func (img *SImage) isEncrypted() bool { } func (self *SImage) makeSubImages(ctx context.Context) error { - if self.GetImageType() == api.ImageTypeISO { + if self.GetImageType() == api.ImageTypeISO || self.GetImageType() == api.ImageTypeTarGzip { // do not convert iso return nil } diff --git a/pkg/mcclient/options/compute/storagecache.go b/pkg/mcclient/options/compute/storagecache.go index 4771845e39..7810d87db3 100644 --- a/pkg/mcclient/options/compute/storagecache.go +++ b/pkg/mcclient/options/compute/storagecache.go @@ -34,7 +34,7 @@ type StorageCacheImageActionOptions struct { options.BaseIdOptions IMAGE string `help:"ID or name of image"` Force bool `help:"Force refresh cache, even if the image exists in cache"` - Format string `help:"Image force" choices:"iso|vmdk|qcow2|vhd"` + Format string `help:"Image force" choices:"iso|vmdk|qcow2|vhd|tgz"` } func (opts *StorageCacheImageActionOptions) Params() (jsonutils.JSONObject, error) { diff --git a/pkg/util/fileutils2/fileutils.go b/pkg/util/fileutils2/fileutils.go index 3aa5432ead..0fb95776bd 100644 --- a/pkg/util/fileutils2/fileutils.go +++ b/pkg/util/fileutils2/fileutils.go @@ -15,8 +15,10 @@ package fileutils2 import ( + "archive/tar" "bufio" "bytes" + "compress/gzip" "fmt" "io/ioutil" "os" @@ -402,3 +404,23 @@ func IsIsoFile(sPath string) bool { } return bytes.Equal(buffer, []byte("CD001")) } + +func IsTarGzipFile(fPath string) bool { + f, err := os.Open(fPath) + if err != nil { + return false + } + defer f.Close() + + gzf, err := gzip.NewReader(f) + if err != nil { + return false + } + + tarReader := tar.NewReader(gzf) + _, err = tarReader.Next() + if err != nil { + return false + } + return true +} diff --git a/pkg/util/qemuimg/qemuimg.go b/pkg/util/qemuimg/qemuimg.go index 53db71de28..a652ca6ee1 100644 --- a/pkg/util/qemuimg/qemuimg.go +++ b/pkg/util/qemuimg/qemuimg.go @@ -29,6 +29,7 @@ import ( "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" + imageapi "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemutils" @@ -215,6 +216,9 @@ func (img *SQemuImage) parse() error { if img.Format == qemuimgfmt.RAW && fileutils2.IsFile(img.Path) && fileutils2.IsIsoFile(img.Path) { img.Format = qemuimgfmt.ISO } + if img.Format == qemuimgfmt.RAW && fileutils2.IsFile(img.Path) && fileutils2.IsTarGzipFile(img.Path) { + img.Format = imageapi.IMAGE_DISK_FORMAT_TGZ + } return nil }