mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-30 18:23:35 +08:00
Automated cherry pick of #24012: feat(host): mount post overlay layers by shortest path order (#24018)
* fix(host): mount and umount order for post overlay - support host_lower_map for post overlay image * feat(host): tar dirs of pod with include patterns
This commit is contained in:
@@ -126,6 +126,7 @@ type DiskBackupDetails struct {
|
||||
|
||||
type DiskBackupAsTarInput struct {
|
||||
IncludeFiles []string `json:"include_files"`
|
||||
IncludePatterns []string `json:"include_patterns"`
|
||||
ExcludeFiles []string `json:"exclude_files"`
|
||||
ContainerId string `json:"container_id"`
|
||||
IgnoreNotExistFile bool `json:"ignore_not_exist_file"`
|
||||
|
||||
@@ -218,7 +218,8 @@ type ContainerSaveVolumeMountToImageInput struct {
|
||||
Dirs []string `json:"dirs"`
|
||||
UsedByPostOverlay bool `json:"used_by_post_overlay"`
|
||||
|
||||
DirPrefix string `json:"dir_prefix"`
|
||||
DirPrefix string `json:"dir_prefix"`
|
||||
ExcludePaths []string `json:"exclude_paths"`
|
||||
}
|
||||
|
||||
type ContainerExecInfoOutput struct {
|
||||
|
||||
@@ -262,9 +262,16 @@ func (o ContainerVolumeMountDiskOverlay) IsValid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type HostLowerPath struct {
|
||||
PrePath string `json:"pre_path"`
|
||||
PostPath string `json:"post_path"`
|
||||
}
|
||||
|
||||
type ContainerVolumeMountDiskPostImageOverlay struct {
|
||||
Id string `json:"id"`
|
||||
PathMap map[string]string `json:"path_map"`
|
||||
// 宿主机底层目录映射, key 为 PathMap 的 key,value 为 overlay lower 格式,多目录以 ":" 分隔
|
||||
HostLowerMap map[string]*HostLowerPath `json:"host_lower_map"`
|
||||
}
|
||||
|
||||
type ContainerVolumeMountDiskPostImageOverlayUnpacker ContainerVolumeMountDiskPostImageOverlay
|
||||
@@ -277,6 +284,7 @@ func (ov *ContainerVolumeMountDiskPostImageOverlay) UnmarshalJSON(data []byte) e
|
||||
ov.Id = nov.Id
|
||||
// 防止 PathMap 被合并,总是用 Unarmshal data 里面的 path_map
|
||||
ov.PathMap = nov.PathMap
|
||||
ov.HostLowerMap = nov.HostLowerMap
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -295,6 +303,7 @@ type ContainerVolumeMountDiskPostOverlay struct {
|
||||
Image *ContainerVolumeMountDiskPostImageOverlay `json:"image"`
|
||||
FsUser *int64 `json:"fs_user,omitempty"`
|
||||
FsGroup *int64 `json:"fs_group,omitempty"`
|
||||
FlattenLayers bool `json:"flatten_layers"`
|
||||
}
|
||||
|
||||
func (o ContainerVolumeMountDiskPostOverlay) IsEqual(input ContainerVolumeMountDiskPostOverlay) bool {
|
||||
|
||||
@@ -147,7 +147,8 @@ type ContainerSaveVolumeMountToImageInput struct {
|
||||
VolumeMount *ContainerVolumeMount `json:"volume_mount"`
|
||||
VolumeMountDirs []string `json:"volume_mount_dirs"`
|
||||
|
||||
VolumeMountPrefix string `json:"volume_mount_prefix"`
|
||||
VolumeMountPrefix string `json:"volume_mount_prefix"`
|
||||
ExcludePaths []string `json:"exclude_paths"`
|
||||
}
|
||||
|
||||
type ContainerCommitInput struct {
|
||||
|
||||
@@ -71,6 +71,14 @@ func (p povImage) validateData(ctx context.Context, userCred mcclient.TokenCrede
|
||||
if len(pov.Image.PathMap) == 0 {
|
||||
pov.Image.PathMap = pathMap
|
||||
}
|
||||
if len(pov.Image.HostLowerMap) != 0 {
|
||||
for hostPath, _ := range pov.Image.HostLowerMap {
|
||||
_, ok := pov.Image.PathMap[hostPath]
|
||||
if !ok {
|
||||
return httperrors.NewNotFoundError("host_path %s of host_lower_map doesn't found in path_map", hostPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"yunion.io/x/pkg/util/fileutils"
|
||||
"yunion.io/x/pkg/util/osprofile"
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
@@ -716,3 +717,10 @@ func (base *SBaseGuestDriver) BeforeAttachIsolatedDevice(ctx context.Context, cr
|
||||
func (base *SBaseGuestDriver) RequestUploadGuestStatus(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestUploadGuestStatus")
|
||||
}
|
||||
|
||||
func (base *SBaseGuestDriver) CanStop(guest *models.SGuest) error {
|
||||
if utils.IsInStringArray(guest.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED, api.POD_STATUS_CRASH_LOOP_BACK_OFF, api.POD_STATUS_CONTAINER_EXITED, api.VM_KICKSTART_INSTALLING, api.VM_KICKSTART_FAILED, api.VM_KICKSTART_COMPLETED}) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(errors.ErrInvalidStatus, "Cannot stop server in status %s", guest.Status)
|
||||
}
|
||||
|
||||
@@ -329,6 +329,13 @@ func (p *SPodDriver) OnGuestDeployTaskDataReceived(ctx context.Context, guest *m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SPodDriver) CanStop(guest *models.SGuest) error {
|
||||
if guest.PowerStates == api.VM_POWER_STATES_ON {
|
||||
return nil
|
||||
}
|
||||
return p.SKVMGuestDriver.CanStop(guest)
|
||||
}
|
||||
|
||||
func (p *SPodDriver) StartGuestStopTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error {
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "PodStopTask", guest, userCred, params, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -482,10 +482,15 @@ func (c *SContainer) StartCreateTask(ctx context.Context, userCred mcclient.Toke
|
||||
return task.ScheduleRun(nil)
|
||||
}
|
||||
|
||||
func (c *SContainer) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ContainerUpdateInput) (*api.ContainerUpdateInput, error) {
|
||||
// func (c *SContainer) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ContainerUpdateInput) (*api.ContainerUpdateInput, error) {
|
||||
func (c *SContainer) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (*api.ContainerUpdateInput, error) {
|
||||
if !api.ContainerExitedStatus.Has(c.GetStatus()) {
|
||||
return nil, httperrors.NewInvalidStatusError("current status %s is not in %v", c.GetStatus(), api.ContainerExitedStatus.List())
|
||||
}
|
||||
input := new(api.ContainerUpdateInput)
|
||||
if err := data.Unmarshal(input); err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal")
|
||||
}
|
||||
|
||||
baseInput, err := c.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.VirtualResourceBaseUpdateInput)
|
||||
if err != nil {
|
||||
@@ -968,6 +973,7 @@ func (c *SContainer) PerformSaveVolumeMountImage(ctx context.Context, userCred m
|
||||
|
||||
VolumeMountDirs: cleanupDirPaths(input.Dirs),
|
||||
VolumeMountPrefix: cleanupDirPath(input.DirPrefix),
|
||||
ExcludePaths: cleanupDirPaths(input.ExcludePaths),
|
||||
}
|
||||
|
||||
return hostInput, c.StartSaveVolumeMountImage(ctx, userCred, hostInput, "")
|
||||
|
||||
@@ -3857,14 +3857,20 @@ func (self *SGuest) PerformStatus(ctx context.Context, userCred mcclient.TokenCr
|
||||
// 关机
|
||||
func (self *SGuest) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
|
||||
input api.ServerStopInput) (jsonutils.JSONObject, error) {
|
||||
// XXX if is force, force stop guest
|
||||
if input.IsForce || utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED, api.POD_STATUS_CRASH_LOOP_BACK_OFF, api.POD_STATUS_CONTAINER_EXITED, api.VM_KICKSTART_INSTALLING, api.VM_KICKSTART_FAILED, api.VM_KICKSTART_COMPLETED}) {
|
||||
if err := self.ValidateEncryption(ctx, userCred); err != nil {
|
||||
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
|
||||
}
|
||||
return nil, self.StartGuestStopTask(ctx, userCred, input.TimeoutSecs, input.IsForce, input.StopCharging, "")
|
||||
drv, err := self.GetDriver()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetDriver")
|
||||
}
|
||||
return nil, httperrors.NewInvalidStatusError("Cannot stop server in status %s", self.Status)
|
||||
if !input.IsForce {
|
||||
err := drv.CanStop(self)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Cannot stop server")
|
||||
}
|
||||
}
|
||||
if err := self.ValidateEncryption(ctx, userCred); err != nil {
|
||||
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
|
||||
}
|
||||
return nil, self.StartGuestStopTask(ctx, userCred, input.TimeoutSecs, input.IsForce, input.StopCharging, "")
|
||||
}
|
||||
|
||||
// 冻结虚拟机
|
||||
|
||||
@@ -84,6 +84,7 @@ type IGuestDriver interface {
|
||||
|
||||
RequestGuestCreateInsertIso(ctx context.Context, imageId string, bootIndex *int8, task taskman.ITask, guest *SGuest) error
|
||||
|
||||
CanStop(guest *SGuest) error
|
||||
StartGuestStopTask(guest *SGuest, ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error
|
||||
StartGuestResetTask(guest *SGuest, ctx context.Context, userCred mcclient.TokenCredential, isHard bool, parentTaskId string) error
|
||||
StartGuestRestartTask(guest *SGuest, ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error
|
||||
|
||||
@@ -88,7 +88,7 @@ func UnmountWithSubDirs(devPath string) error {
|
||||
return err
|
||||
}
|
||||
// found mountpoints starts with devPath
|
||||
out, err2 := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", fmt.Sprintf("mount | grep ' %s/' | awk '{print $3}'", devPath)).Output()
|
||||
out, err2 := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", fmt.Sprintf("mount | grep ' %s/' | awk '{print $3}' | tac", devPath)).Output()
|
||||
if err2 == nil && len(out) != 0 {
|
||||
mntPoints := strings.Split(string(out), "\n")
|
||||
for _, mntPoint := range mntPoints {
|
||||
|
||||
@@ -185,6 +185,39 @@ func (d disk) mountDisk(devPath string, mntPoint string, fs string, resUid int,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d disk) Connect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, bool, error) {
|
||||
iDisk, _, err := d.getPodDisk(pod, vm)
|
||||
if err != nil {
|
||||
return "", false, errors.Wrap(err, "get pod disk interface")
|
||||
}
|
||||
return d.connectDisk(iDisk)
|
||||
}
|
||||
|
||||
func (d disk) Disconnect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
|
||||
iDisk, _, err := d.getPodDisk(pod, vm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get pod disk interface")
|
||||
}
|
||||
drv, err := iDisk.GetContainerStorageDriver()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get disk storage driver")
|
||||
}
|
||||
mntPoint := pod.GetDiskMountPoint(iDisk)
|
||||
_, isConnected, err := drv.CheckConnect(iDisk.GetPath())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "CheckConnect %s", iDisk.GetPath())
|
||||
}
|
||||
if isConnected {
|
||||
if err := drv.DisconnectDisk(iDisk.GetPath(), mntPoint); err != nil {
|
||||
return errors.Wrapf(err, "DisconnectDisk %s %s", iDisk.GetPath(), mntPoint)
|
||||
}
|
||||
}
|
||||
if err := volume_mount.RemoveDir(mntPoint); err != nil {
|
||||
return errors.Wrapf(err, "remove dir %s", mntPoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d disk) connectDiskAndMount(drv container_storage.IContainerStorage, pod volume_mount.IPodInfo, iDisk storageman.IDisk, fs string, resUid, resGid int) (string, error) {
|
||||
devPath, isConnected, err := d.connectDisk(iDisk)
|
||||
if err != nil {
|
||||
@@ -282,15 +315,19 @@ func (d disk) createStorageSizeFile(iDisk storageman.IDisk, mntPoint string, inp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d disk) UnmountWithoutDisconnect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
|
||||
return d.unmount(pod, ctrId, vm, false)
|
||||
}
|
||||
|
||||
func (d disk) Unmount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
|
||||
return d.unmount(pod, ctrId, vm, true)
|
||||
}
|
||||
|
||||
func (d disk) unmount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, disconnect bool) error {
|
||||
iDisk, _, err := d.getPodDisk(pod, vm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get pod disk interface")
|
||||
}
|
||||
drv, err := iDisk.GetContainerStorageDriver()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get disk storage driver")
|
||||
}
|
||||
if len(vm.Disk.PostOverlay) != 0 {
|
||||
if err := d.UnmountPostOverlays(pod, ctrId, vm, vm.Disk.PostOverlay, false, false); err != nil {
|
||||
return errors.Wrap(err, "umount post overlay dirs")
|
||||
@@ -303,20 +340,13 @@ func (d disk) Unmount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.Conta
|
||||
}
|
||||
mntPoint := pod.GetDiskMountPoint(iDisk)
|
||||
if err := container_storage.UnmountWithSubDirs(mntPoint); err != nil {
|
||||
return errors.Wrapf(err, "unmount %s", mntPoint)
|
||||
return errors.Wrapf(err, "UnmountWithSubDirs %s", mntPoint)
|
||||
}
|
||||
_, isConnected, err := drv.CheckConnect(iDisk.GetPath())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "CheckConnect %s", iDisk.GetPath())
|
||||
}
|
||||
if isConnected {
|
||||
if err := drv.DisconnectDisk(iDisk.GetPath(), mntPoint); err != nil {
|
||||
return errors.Wrapf(err, "DisconnectDisk %s %s", iDisk.GetPath(), mntPoint)
|
||||
if disconnect {
|
||||
if err := d.Disconnect(pod, ctrId, vm); err != nil {
|
||||
return errors.Wrapf(err, "disconnect disk")
|
||||
}
|
||||
}
|
||||
if err := volume_mount.RemoveDir(mntPoint); err != nil {
|
||||
return errors.Wrapf(err, "remove dir %s", mntPoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"math"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
@@ -97,8 +101,34 @@ func (d diskPostOverlay) getDriver(ov *apis.ContainerVolumeMountDiskPostOverlay)
|
||||
return getPostOverlayDriver(ov.GetType())
|
||||
}
|
||||
|
||||
// getMinPathMapKeyLength 获取 PathMap 中最短 key 的长度
|
||||
// 如果 Image 为 nil 或 PathMap 为空,返回一个很大的值,确保排在后面
|
||||
func getMinPathMapKeyLength(ov *apis.ContainerVolumeMountDiskPostOverlay) int {
|
||||
if ov.Image == nil || len(ov.Image.PathMap) == 0 {
|
||||
return math.MaxInt // 返回一个很大的值,确保没有 PathMap 的排在后面
|
||||
}
|
||||
minLen := math.MaxInt
|
||||
for key := range ov.Image.PathMap {
|
||||
if len(key) < minLen {
|
||||
minLen = len(key)
|
||||
}
|
||||
}
|
||||
return minLen
|
||||
}
|
||||
|
||||
func sortPostOverlayByPathMapLength(ovs []*apis.ContainerVolumeMountDiskPostOverlay) {
|
||||
sort.Slice(ovs, func(i, j int) bool {
|
||||
lenI := getMinPathMapKeyLength(ovs[i])
|
||||
lenJ := getMinPathMapKeyLength(ovs[j])
|
||||
return lenI < lenJ
|
||||
})
|
||||
}
|
||||
|
||||
func (d diskPostOverlay) mountPostOverlays(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ovs []*apis.ContainerVolumeMountDiskPostOverlay) error {
|
||||
// 根据 PathMap 中最短路径长度排序,路径短的排在前面
|
||||
sortPostOverlayByPathMapLength(ovs)
|
||||
for _, ov := range ovs {
|
||||
log.Debugf("mount container %s post overlay dir: %s", ctrId, jsonutils.Marshal(ov).PrettyString())
|
||||
if err := d.getDriver(ov).Mount(d, pod, ctrId, vm, ov); err != nil {
|
||||
return errors.Wrapf(err, "mount container %s post overlay dir: %#v", ctrId, ov)
|
||||
}
|
||||
@@ -107,7 +137,11 @@ func (d diskPostOverlay) mountPostOverlays(pod volume_mount.IPodInfo, ctrId stri
|
||||
}
|
||||
|
||||
func (d diskPostOverlay) unmountPostOverlays(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ovs []*apis.ContainerVolumeMountDiskPostOverlay, useLazy bool, clearLayers bool) error {
|
||||
for _, ov := range ovs {
|
||||
//(与 mount 顺序相反)
|
||||
sortPostOverlayByPathMapLength(ovs)
|
||||
for i := len(ovs) - 1; i >= 0; i-- {
|
||||
ov := ovs[i]
|
||||
log.Debugf("-- unmount container %s post overlay dir: %s", ctrId, jsonutils.Marshal(ov).PrettyString())
|
||||
if err := d.getDriver(ov).Unmount(d, pod, ctrId, vm, ov, useLazy, clearLayers); err != nil {
|
||||
return errors.Wrapf(err, "unmount container %s post overlay dir: %#v", ctrId, ov)
|
||||
}
|
||||
@@ -128,6 +162,9 @@ func (d diskPostOverlay) getPostOverlayDirWithPrefix(
|
||||
}
|
||||
|
||||
workDir := filepath.Join(rootPath, ov.ContainerTargetDir)
|
||||
if ov.FlattenLayers {
|
||||
workDir = filepath.Join(rootPath, strings.ReplaceAll(ov.ContainerTargetDir, "/", "_"))
|
||||
}
|
||||
if ensure {
|
||||
if err := volume_mount.EnsureDir(workDir); err != nil {
|
||||
return "", errors.Wrapf(err, "makeDir %s", workDir)
|
||||
|
||||
@@ -17,6 +17,7 @@ package disk
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
@@ -44,25 +45,57 @@ func (i postOverlayImage) getImageInput(ov *apis.ContainerVolumeMountDiskPostOve
|
||||
return ov.Image
|
||||
}
|
||||
|
||||
func (i postOverlayImage) getCachedImagePaths(d diskPostOverlay, pod volume_mount.IPodInfo, img *apis.ContainerVolumeMountDiskPostImageOverlay, accquire bool) (map[string]string, error) {
|
||||
func (i postOverlayImage) getCachedImagePaths(d diskPostOverlay, pod volume_mount.IPodInfo, img *apis.ContainerVolumeMountDiskPostImageOverlay, accquire bool) (map[string]string, map[string]*apis.HostLowerPath, error) {
|
||||
cachedImgDir, err := d.disk.getCachedImageDir(context.Background(), pod, img.Id, accquire)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "disk.getCachedImageDir")
|
||||
return nil, nil, errors.Wrap(err, "disk.getCachedImageDir")
|
||||
}
|
||||
hostLowerMap := make(map[string]*apis.HostLowerPath)
|
||||
if img.HostLowerMap != nil {
|
||||
hostLowerMap = img.HostLowerMap
|
||||
}
|
||||
result := make(map[string]string)
|
||||
lowerResult := make(map[string]*apis.HostLowerPath)
|
||||
for hostPathSuffix, ctrPath := range img.PathMap {
|
||||
hostPath := filepath.Join(cachedImgDir, hostPathSuffix)
|
||||
result[hostPath] = ctrPath
|
||||
hostLowerPath := hostLowerMap[hostPathSuffix]
|
||||
if hostLowerPath != nil {
|
||||
lowerResult[hostPath] = hostLowerPath
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
return result, lowerResult, nil
|
||||
}
|
||||
|
||||
func (i postOverlayImage) convertToDiskOV(ov *apis.ContainerVolumeMountDiskPostOverlay, hostPath, ctrPath string) *apis.ContainerVolumeMountDiskPostOverlay {
|
||||
// parseColonSeparatedPaths 解析冒号分隔的路径字符串,过滤空字符串并返回路径列表
|
||||
func parseColonSeparatedPaths(pathStr string) []string {
|
||||
var paths []string
|
||||
for _, path := range strings.Split(pathStr, ":") {
|
||||
if path != "" {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (i postOverlayImage) convertToDiskOV(ov *apis.ContainerVolumeMountDiskPostOverlay, hostPath, ctrPath string, hostLowerPath *apis.HostLowerPath) *apis.ContainerVolumeMountDiskPostOverlay {
|
||||
hostLowerDir := []string{}
|
||||
if hostLowerPath != nil {
|
||||
// 添加 PrePath 中的路径
|
||||
hostLowerDir = append(hostLowerDir, parseColonSeparatedPaths(hostLowerPath.PrePath)...)
|
||||
}
|
||||
// 添加当前 hostPath
|
||||
hostLowerDir = append(hostLowerDir, hostPath)
|
||||
if hostLowerPath != nil {
|
||||
// 添加 PostPath 中的路径
|
||||
hostLowerDir = append(hostLowerDir, parseColonSeparatedPaths(hostLowerPath.PostPath)...)
|
||||
}
|
||||
return &apis.ContainerVolumeMountDiskPostOverlay{
|
||||
HostLowerDir: []string{hostPath},
|
||||
HostLowerDir: hostLowerDir,
|
||||
ContainerTargetDir: ctrPath,
|
||||
FsUser: ov.FsUser,
|
||||
FsGroup: ov.FsGroup,
|
||||
FlattenLayers: ov.FlattenLayers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +104,13 @@ func (i postOverlayImage) withAction(
|
||||
af func(iDiskPostOverlayDriver, *apis.ContainerVolumeMountDiskPostOverlay) error,
|
||||
accquire bool) error {
|
||||
img := i.getImageInput(ov)
|
||||
paths, err := i.getCachedImagePaths(d, pod, img, accquire)
|
||||
paths, hostLowerPaths, err := i.getCachedImagePaths(d, pod, img, accquire)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get cached image paths")
|
||||
}
|
||||
for hostPath, ctrPath := range paths {
|
||||
dov := i.convertToDiskOV(ov, hostPath, ctrPath)
|
||||
hostLowerPath := hostLowerPaths[hostPath]
|
||||
dov := i.convertToDiskOV(ov, hostPath, ctrPath, hostLowerPath)
|
||||
drv := d.getDriver(dov)
|
||||
if err := af(drv, dov); err != nil {
|
||||
return errors.Wrapf(err, "host path %s to %s", hostPath, ctrPath)
|
||||
|
||||
@@ -58,6 +58,14 @@ type IVolumeMount interface {
|
||||
Unmount(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error
|
||||
}
|
||||
|
||||
type IConnectedVolumeMount interface {
|
||||
IVolumeMount
|
||||
|
||||
UnmountWithoutDisconnect(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error
|
||||
Connect(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, bool, error)
|
||||
Disconnect(pod IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error
|
||||
}
|
||||
|
||||
type ContainerVolumeMountUsage struct {
|
||||
Id string
|
||||
HostPath string
|
||||
|
||||
+102
-10
@@ -691,12 +691,12 @@ func (s *sPodGuestInstance) umountRootFs(ctrId string, rootFs *hostapi.Container
|
||||
if rootFs == nil {
|
||||
return nil
|
||||
}
|
||||
drv := volume_mount.GetDriver(rootFs.Type)
|
||||
drv := volume_mount.GetDriver(rootFs.Type).(volume_mount.IConnectedVolumeMount)
|
||||
vol := s.ConvertRootFsToVolumeMount(rootFs)
|
||||
if err := s.clearContainerRootFs(ctrId, rootFs); err != nil {
|
||||
return errors.Wrapf(err, "clear rootfs of container %s", ctrId)
|
||||
}
|
||||
if err := drv.Unmount(s, ctrId, vol); err != nil {
|
||||
if err := drv.UnmountWithoutDisconnect(s, ctrId, vol); err != nil {
|
||||
return errors.Wrapf(err, "unmount root fs %s, ctrId %s", jsonutils.Marshal(rootFs), ctrId)
|
||||
}
|
||||
return nil
|
||||
@@ -747,6 +747,30 @@ func (s *sPodGuestInstance) mountPodVolumes() error {
|
||||
}
|
||||
|
||||
func (s *sPodGuestInstance) umountPodVolumes() error {
|
||||
disConnectFuncs := make([]func() error, 0)
|
||||
for ctrId, vols := range s.getContainerVolumeMounts() {
|
||||
for _, vol := range vols {
|
||||
drv := volume_mount.GetDriver(vol.Type)
|
||||
connectedDrv, ok := drv.(volume_mount.IConnectedVolumeMount)
|
||||
if !ok {
|
||||
if err := drv.Unmount(s, ctrId, vol); err != nil {
|
||||
return errors.Wrapf(err, "Unmount volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
|
||||
}
|
||||
} else {
|
||||
if err := connectedDrv.UnmountWithoutDisconnect(s, ctrId, vol); err != nil {
|
||||
return errors.Wrapf(err, "UnmountWithoutDisconnect volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
|
||||
}
|
||||
tmpCtrId := ctrId
|
||||
tmpVol := vol
|
||||
disConnectFuncs = append(disConnectFuncs, func() error {
|
||||
if err := connectedDrv.Disconnect(s, tmpCtrId, tmpVol); err != nil {
|
||||
return errors.Wrapf(err, "Disconnect volume %s, ctrId %s", jsonutils.Marshal(tmpVol), tmpCtrId)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ctr := range s.GetDesc().Containers {
|
||||
if ctr.Spec.Rootfs == nil {
|
||||
continue
|
||||
@@ -755,11 +779,9 @@ func (s *sPodGuestInstance) umountPodVolumes() error {
|
||||
return errors.Wrapf(err, "umount root fs %s, ctrId %s", jsonutils.Marshal(ctr.Spec.Rootfs), ctr.Id)
|
||||
}
|
||||
}
|
||||
for ctrId, vols := range s.getContainerVolumeMounts() {
|
||||
for _, vol := range vols {
|
||||
if err := volume_mount.GetDriver(vol.Type).Unmount(s, ctrId, vol); err != nil {
|
||||
return errors.Wrapf(err, "Unmount volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
|
||||
}
|
||||
for _, disConnectFunc := range disConnectFuncs {
|
||||
if err := disConnectFunc(); err != nil {
|
||||
return errors.Wrapf(err, "disconnect volume")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -2616,7 +2638,38 @@ func (s *sPodGuestInstance) tarGzDir(input *hostapi.ContainerSaveVolumeMountToIm
|
||||
}
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("tar -czf %s -C %s %s", outputFp, hostPath, dirPath)
|
||||
// 减去 excludePaths 的大小
|
||||
for _, exclude := range input.ExcludePaths {
|
||||
// exclude 路径是相对于 hostPath 的
|
||||
sizeCmd := fmt.Sprintf("du -sb '%s' 2>/dev/null | awk '{print $1}'", exclude)
|
||||
cmd := fmt.Sprintf("cd %s && %s", hostPath, sizeCmd)
|
||||
out, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
// exclude 路径不存在时忽略错误,继续处理下一个
|
||||
continue
|
||||
}
|
||||
outStr := strings.TrimSpace(string(out))
|
||||
if outStr == "" {
|
||||
continue
|
||||
}
|
||||
excludeSize, err := strconv.ParseInt(outStr, 10, 64)
|
||||
if err != nil {
|
||||
// 解析失败时忽略,继续处理下一个
|
||||
continue
|
||||
}
|
||||
totalSize -= excludeSize
|
||||
// 确保 totalSize 不为负数
|
||||
if totalSize < 0 {
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
|
||||
baseCmd := "tar -czf"
|
||||
// 添加 exclude 选项
|
||||
for _, exclude := range input.ExcludePaths {
|
||||
baseCmd = fmt.Sprintf("%s --exclude='%s'", baseCmd, exclude)
|
||||
}
|
||||
cmd := fmt.Sprintf("%s %s -C %s %s", baseCmd, outputFp, hostPath, dirPath)
|
||||
if input.VolumeMountPrefix != "" {
|
||||
cmd += fmt.Sprintf(" --transform 's,^,%s/,'", input.VolumeMountPrefix)
|
||||
}
|
||||
@@ -2851,7 +2904,7 @@ func (s *sPodGuestInstance) createSnapshot(params *SDiskSnapshot, hostPath strin
|
||||
snapshotPath := s.getSnapshotPath(d, params.SnapshotId)
|
||||
// tar hostPath to snapshotPath
|
||||
input := params.BackupDiskConfig.BackupAsTar
|
||||
if err := s.tarHostDir(hostPath, snapshotPath, input.IncludeFiles, input.ExcludeFiles, input.IgnoreNotExistFile); err != nil {
|
||||
if err := s.tarHostDir(hostPath, snapshotPath, input.IncludeFiles, input.ExcludeFiles, input.IgnoreNotExistFile, input.IncludePatterns); err != nil {
|
||||
return "", errors.Wrapf(err, "tar host dir %s to %s", hostPath, snapshotPath)
|
||||
}
|
||||
return snapshotPath, nil
|
||||
@@ -2859,7 +2912,8 @@ func (s *sPodGuestInstance) createSnapshot(params *SDiskSnapshot, hostPath strin
|
||||
|
||||
func (s *sPodGuestInstance) tarHostDir(srcDir, targetPath string,
|
||||
includeFiles, excludeFiles []string,
|
||||
ignoreNotExistFile bool) error {
|
||||
ignoreNotExistFile bool,
|
||||
includePatterns []string) error {
|
||||
baseCmd := "tar"
|
||||
filterNotExistFiles := func(files []string) []string {
|
||||
result := []string{}
|
||||
@@ -2876,6 +2930,44 @@ func (s *sPodGuestInstance) tarHostDir(srcDir, targetPath string,
|
||||
includeFiles = filterNotExistFiles(includeFiles)
|
||||
excludeFiles = filterNotExistFiles(excludeFiles)
|
||||
}
|
||||
|
||||
// 如果有 includePatterns,使用 find -name 找出匹配的路径,添加到 includeFiles 中
|
||||
if len(includePatterns) > 0 {
|
||||
findPatterns := []string{}
|
||||
for _, pattern := range includePatterns {
|
||||
// 转义特殊字符,但保留 glob 通配符
|
||||
escapedPattern := strings.ReplaceAll(pattern, "'", "'\"'\"'")
|
||||
findPatterns = append(findPatterns, fmt.Sprintf("-name '%s'", escapedPattern))
|
||||
}
|
||||
|
||||
// 构建 find 命令来查找匹配的路径
|
||||
findCmd := fmt.Sprintf("find . \\( %s \\)", strings.Join(findPatterns, " -o "))
|
||||
cmd := fmt.Sprintf("cd '%s' && %s", srcDir, findCmd)
|
||||
log.Infof("[%s] find cmd: %s", s.GetName(), cmd)
|
||||
out, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "find matching paths: %s", out)
|
||||
}
|
||||
|
||||
// 解析 find 的输出,将匹配的路径添加到 includeFiles 中
|
||||
// find 输出的路径格式为 ./path,需要去掉开头的 ./
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// 去掉开头的 ./
|
||||
if strings.HasPrefix(line, "./") {
|
||||
line = line[2:]
|
||||
}
|
||||
// 添加到 includeFiles 中
|
||||
includeFiles = append(includeFiles, line)
|
||||
}
|
||||
}
|
||||
|
||||
// 没有 includePatterns 时,使用原来的逻辑
|
||||
// 添加 --exclude 选项
|
||||
for _, exclude := range excludeFiles {
|
||||
baseCmd = fmt.Sprintf("%s --exclude='%s'", baseCmd, exclude)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ type DiskBackupCreateOptions struct {
|
||||
options.BaseCreateOptions
|
||||
AsTarContainerId string `help:"container id of tar process"`
|
||||
AsTarIncludeFile []string `help:"include file path of tar process"`
|
||||
AsTarIncludePattern []string `help:"include pattern of tar process"`
|
||||
AsTarExcludeFile []string `help:"exclude file path of tar process"`
|
||||
AsTarIgnoreNotExistFile bool `help:"ignore not exist file when using tar"`
|
||||
|
||||
@@ -82,6 +83,9 @@ func (opts *DiskBackupCreateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
if len(opts.AsTarIncludeFile) > 0 {
|
||||
input.BackupAsTar.IncludeFiles = opts.AsTarIncludeFile
|
||||
}
|
||||
if len(opts.AsTarIncludePattern) > 0 {
|
||||
input.BackupAsTar.IncludePatterns = opts.AsTarIncludePattern
|
||||
}
|
||||
if len(opts.AsTarExcludeFile) > 0 {
|
||||
input.BackupAsTar.ExcludeFiles = opts.AsTarExcludeFile
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user