// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package disk import ( "path/filepath" "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/apis" hostapi "yunion.io/x/onecloud/pkg/apis/host" "yunion.io/x/onecloud/pkg/hostman/container/volume_mount" fileutils "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/mountutils" "yunion.io/x/onecloud/pkg/util/procutils" ) func init() { registerPostOverlayDriver(newPostOverlayHostPath()) } func newPostOverlayHostPath() iDiskPostOverlayDriver { return &postOverlayHostPath{} } type postOverlayHostPath struct { } func (p postOverlayHostPath) GetType() apis.ContainerVolumeMountDiskPostOverlayType { return apis.CONTAINER_VOLUME_MOUNT_DISK_POST_OVERLAY_HOSTPATH } func (p postOverlayHostPath) getSingleFilePath(ov *apis.ContainerVolumeMountDiskPostOverlay) string { if len(ov.HostLowerDir) == 1 && fileutils.IsFile(ov.HostLowerDir[0]) { return ov.HostLowerDir[0] } return "" } func (p postOverlayHostPath) mountDir(d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay) error { upperDir, err := d.getPostOverlayUpperDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay upper dir for container %s", ctrId) } workDir, err := d.getPostOverlayWorkDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay work dir for container %s", ctrId) } mergedDir, err := d.getPostOverlayMountpoint(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay mountpoint for container %s", ctrId) } if err := mountutils.MountOverlayWithFeatures(ov.HostLowerDir, upperDir, workDir, mergedDir, &mountutils.MountOverlayFeatures{ MetaCopy: true, }); err != nil { return errors.Wrapf(err, "mount overlay dir for container %s", ctrId) } if err := volume_mount.ChangeDirOwnerDirectly(mergedDir, ov.FsUser, ov.FsGroup); err != nil { return errors.Wrapf(err, "change dir owner") } return nil } func (p postOverlayHostPath) getSingleFileMergedFilePath(mergedDir string, singleFilePath string) string { return filepath.Join(mergedDir, filepath.Base(singleFilePath)) } func (p postOverlayHostPath) getSingleFileLowerDirFilePath(lowerDir string, singleFilePath string) string { return filepath.Join(lowerDir, filepath.Base(singleFilePath)) } func (p postOverlayHostPath) mountSingleFile(singleFilePath string, d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay) error { /* // 测试发现,如果 lowerDir 存在 bind mount 的文件,merge 后的目录中该文件为空,不是 bind mount 的 source 文件 // 如果是单文件,创建一个目录,再把该文件 mount bind 过去到 lowerDir 里面 lowerDir, err := d.getPostOverlayLowerDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get single file lower dir for container %s", ctrId) } lowerDirFilePath := p.getSingleFileLowerDirFilePath(lowerDir, singleFilePath) if !fileutils.Exists(lowerDirFilePath) { if err := volume_mount.TouchFile(lowerDirFilePath); err != nil { return errors.Wrapf(err, "touch file %s", lowerDirFilePath) } } if err := mountutils.MountBind(singleFilePath, lowerDirFilePath); err != nil { return errors.Wrapf(err, "mount bind %s to %s", singleFilePath, lowerDirFilePath) } */ // 单文件,直接把该文件的目录作为 lowerDir lowerDir := filepath.Dir(singleFilePath) // 把单文件的 lowerDir 挂载到 targetMergedDir,然后在把 singleFileMergedFilePath bind mount 到 mergedDir(mergedDir 其实是个文件路径) upperDir, err := d.getPostOverlayUpperDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay upper dir for container %s", ctrId) } workDir, err := d.getPostOverlayWorkDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay work dir for container %s", ctrId) } mergedDst, err := d.getPostOverlayMountpoint(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay mountpoint for container %s", ctrId) } targetMergedDir, err := d.getPostOverlayMergedDir(pod, ctrId, vm, ov, true) if err != nil { return errors.Wrapf(err, "get post overlay merged dir for container %s", ctrId) } if err := mountutils.MountOverlayWithFeatures([]string{lowerDir}, upperDir, workDir, targetMergedDir, &mountutils.MountOverlayFeatures{ MetaCopy: true, }); err != nil { return errors.Wrapf(err, "mount overlay dir for container %s", ctrId) } singleFileMergedFilePath := p.getSingleFileMergedFilePath(targetMergedDir, singleFilePath) if err := volume_mount.ChangeDirOwnerDirectly(singleFileMergedFilePath, ov.FsUser, ov.FsGroup); err != nil { return errors.Wrapf(err, "change file %s owner", singleFilePath) } if !fileutils.Exists(mergedDst) { if err := volume_mount.EnsureDir(filepath.Dir(mergedDst)); err != nil { return errors.Wrapf(err, "ensure dir %s", filepath.Dir(mergedDst)) } if err := volume_mount.ChangeDirOwnerDirectly(filepath.Dir(mergedDst), ov.FsUser, ov.FsGroup); err != nil { return errors.Wrapf(err, "change dir %s owner", filepath.Dir(mergedDst)) } if err := volume_mount.TouchFile(mergedDst); err != nil { return errors.Wrapf(err, "touch file %s", mergedDst) } if err := volume_mount.ChangeDirOwnerDirectly(mergedDst, ov.FsUser, ov.FsGroup); err != nil { return errors.Wrapf(err, "change file %s owner", mergedDst) } } if err := mountutils.MountBind(singleFileMergedFilePath, mergedDst); err != nil { return errors.Wrapf(err, "bind mount %s to %s", singleFileMergedFilePath, mergedDst) } return nil } func (p postOverlayHostPath) Mount(d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay) error { // 支持单文件挂载 singleFilePath := "" if len(ov.HostLowerDir) == 1 && fileutils.IsFile(ov.HostLowerDir[0]) { singleFilePath = ov.HostLowerDir[0] } if singleFilePath != "" { return p.mountSingleFile(singleFilePath, d, pod, ctrId, vm, ov) } else { return p.mountDir(d, pod, ctrId, vm, ov) } } func (p postOverlayHostPath) unmountDir(d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay, useLazy bool, cleanLayers bool) error { mergedDir, err := d.getPostOverlayMountpoint(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay mountpoint for container %s", ctrId) } if err := mountutils.Unmount(mergedDir, useLazy); err != nil { return errors.Wrapf(err, "unmount %s", mergedDir) } if cleanLayers { upperDir, err := d.getPostOverlayUpperDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay upper dir for container %s", ctrId) } if err := volume_mount.RemoveDir(upperDir); err != nil { return errors.Wrap(err, "remove upper dir") } workDir, err := d.getPostOverlayWorkDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay work dir for container %s", ctrId) } if err := volume_mount.RemoveDir(workDir); err != nil { return errors.Wrap(err, "remove work dir") } } return nil } func (p postOverlayHostPath) unmountSingleFile(singleFilePath string, d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay, useLazy bool, cleanLayers bool) error { mergedFile, err := d.getPostOverlayMountpoint(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay mountpoint for container %s", ctrId) } targetMergedDir, err := d.getPostOverlayMergedDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay merged dir for container %s", ctrId) } singleFileMergedFilePath := p.getSingleFileMergedFilePath(targetMergedDir, singleFilePath) // 先 unbind mergedFile if err := mountutils.Unmount(mergedFile, false); err != nil { return errors.Wrapf(err, "unmount %s of single file %s", mergedFile, singleFileMergedFilePath) } // 如果 mergedFile 是空文件,则删除这个空文件,因为空文件很可能是挂载时创建的 if procutils.IsEmptyFile(mergedFile) { if err := volume_mount.RemoveDir(mergedFile); err != nil { return errors.Wrapf(err, "remove empty file %s", mergedFile) } } if err := mountutils.Unmount(targetMergedDir, useLazy); err != nil { return errors.Wrapf(err, "unmount %s", targetMergedDir) } /* // 再 unbind lowerDir 里面的 singleFilePath lowerDir, err := d.getPostOverlayLowerDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay lower dir for container %s", ctrId) } lowerDirFilePath := p.getSingleFileLowerDirFilePath(lowerDir, singleFilePath) if err := mountutils.Unmount(lowerDirFilePath, false); err != nil { return errors.Wrapf(err, "unmount %s of single file %s", lowerDirFilePath, singleFileMergedFilePath) } */ if cleanLayers { upperDir, err := d.getPostOverlayUpperDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay upper dir for container %s", ctrId) } workDir, err := d.getPostOverlayWorkDir(pod, ctrId, vm, ov, false) if err != nil { return errors.Wrapf(err, "get post overlay work dir for container %s", ctrId) } for _, dir := range []string{upperDir, workDir, targetMergedDir} { if err := volume_mount.RemoveDir(dir); err != nil { return errors.Wrapf(err, "remove dir %s", dir) } } } return nil } func (p postOverlayHostPath) Unmount(d diskPostOverlay, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay, useLazy bool, cleanLayers bool) error { // 支持单文件卸载 singleFilePath := "" if len(ov.HostLowerDir) == 1 && fileutils.IsFile(ov.HostLowerDir[0]) { singleFilePath = ov.HostLowerDir[0] } if singleFilePath != "" { return p.unmountSingleFile(singleFilePath, d, pod, ctrId, vm, ov, useLazy, cleanLayers) } else { return p.unmountDir(d, pod, ctrId, vm, ov, useLazy, cleanLayers) } }