Merge pull request #19926 from zexi/save-container-image

feat(glance,region,host,climc): support using tar.gz image as overlayer
This commit is contained in:
Zexi Li
2024-04-10 18:38:36 +08:00
committed by GitHub
27 changed files with 421 additions and 104 deletions
+1 -1
View File
@@ -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"`
+5 -9
View File
@@ -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"`
+1
View File
@@ -284,6 +284,7 @@ type DiskAllocateInput struct {
Format string
DiskSizeMb int
ImageId string
ImageFormat string
FsFormat string
Rebuild bool
BackingDiskId string
+3 -2
View File
@@ -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 {
+38 -11
View File
@@ -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 {
+35 -2
View File
@@ -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"`
}
+11
View File
@@ -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"
)
@@ -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)
}
@@ -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 == "" {
+7 -3
View File
@@ -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")
}
+53 -21
View File
@@ -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
}
+7 -1
View File
@@ -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()
+108 -20
View File
@@ -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)
}
@@ -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
@@ -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 {
+2 -1
View File
@@ -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 {
+3 -3
View File
@@ -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
+3 -5
View File
@@ -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))
@@ -31,4 +31,6 @@ type IImageCache interface {
GetImageId() string
GetDesc() *remotefile.SImageDesc
// GetAccessDirectory is used by container's volume mount
GetAccessDirectory() (string, error)
}
+33 -1
View File
@@ -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
}
+4
View File
@@ -157,3 +157,7 @@ func (c *SLVMImageCache) Remove(ctx context.Context) error {
}()
return nil
}
func (c *SLVMImageCache) GetAccessDirectory() (string, error) {
return "", errors.ErrNotImplemented
}
+4
View File
@@ -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
}
+2 -1
View File
@@ -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:
+4 -3
View File
@@ -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
}
+1 -1
View File
@@ -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) {
+22
View File
@@ -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
}
+4
View File
@@ -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
}