disk.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package disk
  15. import (
  16. "context"
  17. "fmt"
  18. "path/filepath"
  19. "strings"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/onecloud/pkg/apis"
  23. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  24. hostapi "yunion.io/x/onecloud/pkg/apis/host"
  25. imageapi "yunion.io/x/onecloud/pkg/apis/image"
  26. "yunion.io/x/onecloud/pkg/hostman/container/storage"
  27. container_storage "yunion.io/x/onecloud/pkg/hostman/container/storage"
  28. "yunion.io/x/onecloud/pkg/hostman/container/volume_mount"
  29. "yunion.io/x/onecloud/pkg/hostman/guestman/desc"
  30. "yunion.io/x/onecloud/pkg/hostman/storageman"
  31. "yunion.io/x/onecloud/pkg/httperrors"
  32. "yunion.io/x/onecloud/pkg/util/mountutils"
  33. "yunion.io/x/onecloud/pkg/util/procutils"
  34. )
  35. func init() {
  36. volume_mount.RegisterDriver(newDisk())
  37. }
  38. type IVolumeMountDisk interface {
  39. volume_mount.IUsageVolumeMount
  40. MountPostOverlays(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ovs []*apis.ContainerVolumeMountDiskPostOverlay) error
  41. UnmountPostOverlays(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, ovs []*apis.ContainerVolumeMountDiskPostOverlay, useLazy bool, clearLayers bool) error
  42. GetHostDiskRootPath(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount) (string, error)
  43. GetPostOverlayRootWorkDir(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount, ctrId string) (string, error)
  44. GetPostOverlayRootUpperDir(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount, ctrId string, pov *apis.ContainerVolumeMountDiskPostOverlay) (string, error)
  45. }
  46. type disk struct {
  47. overlayDrivers map[apis.ContainerDiskOverlayType]iDiskOverlay
  48. }
  49. func newDisk() IVolumeMountDisk {
  50. return &disk{
  51. overlayDrivers: map[apis.ContainerDiskOverlayType]iDiskOverlay{
  52. apis.CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY: newDiskOverlayDir(),
  53. apis.CONTAINER_DISK_OVERLAY_TYPE_DISK_IMAGE: newDiskOverlayImage(),
  54. },
  55. }
  56. }
  57. func (d disk) GetType() apis.ContainerVolumeMountType {
  58. return apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK
  59. }
  60. func (d disk) GetHostDiskRootPath(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount) (string, error) {
  61. diskInput := vm.Disk
  62. if diskInput == nil {
  63. return "", httperrors.NewNotEmptyError("disk is nil")
  64. }
  65. hostPath := filepath.Join(pod.GetVolumesDir(), diskInput.Id)
  66. return hostPath, nil
  67. }
  68. func (d disk) getRuntimeMountHostPath(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount) (string, error) {
  69. hostPath, err := d.GetHostDiskRootPath(pod, vm)
  70. if err != nil {
  71. return "", errors.Wrap(err, "get host disk root path")
  72. }
  73. diskInput := vm.Disk
  74. if diskInput.SubDirectory != "" {
  75. return filepath.Join(hostPath, diskInput.SubDirectory), nil
  76. }
  77. if diskInput.StorageSizeFile != "" {
  78. return filepath.Join(hostPath, diskInput.StorageSizeFile), nil
  79. }
  80. return hostPath, nil
  81. }
  82. func (d disk) GetRuntimeMountHostPath(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, error) {
  83. hostPath, err := d.getRuntimeMountHostPath(pod, vm)
  84. if err != nil {
  85. return "", errors.Wrap(err, "get runtime mount host_path")
  86. }
  87. overlay := vm.Disk.Overlay
  88. if overlay == nil && vm.Disk.TemplateId == "" {
  89. return hostPath, nil
  90. }
  91. return d.getOverlayMergedDir(pod, ctrId, vm, hostPath), nil
  92. }
  93. func (d disk) getPodDisk(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount) (storageman.IDisk, *desc.SGuestDisk, error) {
  94. var disk *desc.SGuestDisk = nil
  95. disks := pod.GetDisks()
  96. volDisk := vm.Disk
  97. if volDisk.Id == "" {
  98. return nil, nil, errors.Errorf("volume mount disk id is empty")
  99. }
  100. if volDisk.Id != "" {
  101. for _, gd := range disks {
  102. if gd.DiskId == volDisk.Id {
  103. disk = gd
  104. break
  105. }
  106. }
  107. }
  108. if disk == nil {
  109. return nil, nil, errors.Wrapf(errors.ErrNotFound, "not found disk by id %s", volDisk.Id)
  110. }
  111. iDisk, err := storageman.GetManager().GetDiskById(disk.DiskId)
  112. if err != nil {
  113. return nil, disk, errors.Wrapf(err, "GetDiskById %s", disk.Path)
  114. }
  115. return iDisk, disk, nil
  116. }
  117. func (d disk) getDiskStorageDriver(pod volume_mount.IPodInfo, vm *hostapi.ContainerVolumeMount) (storage.IContainerStorage, error) {
  118. iDisk, _, err := d.getPodDisk(pod, vm)
  119. if err != nil {
  120. return nil, errors.Wrap(err, "get pod disk interface")
  121. }
  122. drv, err := iDisk.GetContainerStorageDriver()
  123. if err != nil {
  124. return nil, errors.Wrap(err, "GetContainerStorageDriver")
  125. }
  126. return drv, nil
  127. }
  128. func (d disk) setDirCaseInsensitive(dir string) error {
  129. out, err := procutils.NewRemoteCommandAsFarAsPossible("chattr", "+F", dir).Output()
  130. if err != nil {
  131. return errors.Wrapf(err, "enable %q case_insensitive: %s", dir, out)
  132. }
  133. return nil
  134. }
  135. func (d disk) newPostOverlay() iDiskPostOverlay {
  136. return newDiskPostOverlay(d)
  137. }
  138. func (d disk) connectDisk(iDisk storageman.IDisk) (string, bool, error) {
  139. drv, err := iDisk.GetContainerStorageDriver()
  140. if err != nil {
  141. return "", false, errors.Wrap(err, "get disk storage driver")
  142. }
  143. devPath, isConnected, err := drv.CheckConnect(iDisk.GetPath())
  144. if err != nil {
  145. return "", false, errors.Wrapf(err, "CheckConnect %s", iDisk.GetPath())
  146. }
  147. if !isConnected {
  148. devPath, err = drv.ConnectDisk(iDisk.GetPath())
  149. if err != nil {
  150. return "", false, errors.Wrapf(err, "ConnectDisk %s", iDisk.GetPath())
  151. }
  152. }
  153. return devPath, isConnected, nil
  154. }
  155. func (d disk) mountDisk(devPath string, mntPoint string, fs string, resUid int, resGid int) error {
  156. if err := container_storage.MountWithResId(devPath, mntPoint, fs, resUid, resGid); err != nil {
  157. return errors.Wrapf(err, "mount %s to %s", devPath, mntPoint)
  158. }
  159. // make mount point shared
  160. if err := mountutils.MakeShared(mntPoint); err != nil {
  161. return errors.Wrapf(err, "mount %s to %s", devPath, mntPoint)
  162. }
  163. return nil
  164. }
  165. func (d disk) Connect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) (string, bool, error) {
  166. iDisk, _, err := d.getPodDisk(pod, vm)
  167. if err != nil {
  168. return "", false, errors.Wrap(err, "get pod disk interface")
  169. }
  170. return d.connectDisk(iDisk)
  171. }
  172. func (d disk) Disconnect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  173. iDisk, _, err := d.getPodDisk(pod, vm)
  174. if err != nil {
  175. return errors.Wrap(err, "get pod disk interface")
  176. }
  177. drv, err := iDisk.GetContainerStorageDriver()
  178. if err != nil {
  179. return errors.Wrap(err, "get disk storage driver")
  180. }
  181. mntPoint := pod.GetDiskMountPoint(iDisk)
  182. _, isConnected, err := drv.CheckConnect(iDisk.GetPath())
  183. if err != nil {
  184. return errors.Wrapf(err, "CheckConnect %s", iDisk.GetPath())
  185. }
  186. if isConnected {
  187. if err := drv.DisconnectDisk(iDisk.GetPath(), mntPoint); err != nil {
  188. return errors.Wrapf(err, "DisconnectDisk %s %s", iDisk.GetPath(), mntPoint)
  189. }
  190. }
  191. if err := volume_mount.RemoveDir(mntPoint); err != nil {
  192. return errors.Wrapf(err, "remove dir %s", mntPoint)
  193. }
  194. return nil
  195. }
  196. func (d disk) connectDiskAndMount(drv container_storage.IContainerStorage, pod volume_mount.IPodInfo, iDisk storageman.IDisk, fs string, resUid, resGid int) (string, error) {
  197. devPath, isConnected, err := d.connectDisk(iDisk)
  198. if err != nil {
  199. return "", errors.Wrap(err, "connect disk")
  200. }
  201. mntPoint := pod.GetDiskMountPoint(iDisk)
  202. mountErrs := []error{}
  203. if err := d.mountDisk(devPath, mntPoint, fs, resUid, resGid); err != nil {
  204. mountErrs = append(mountErrs, err)
  205. if isConnected && strings.Contains(err.Error(), fmt.Sprintf("%s already mounted or mount point busy.", devPath)) {
  206. // disconnect disk and mount agin
  207. if err := drv.DisconnectDisk(iDisk.GetPath(), mntPoint); err != nil {
  208. mountErrs = append(mountErrs, errors.Wrapf(err, "disconnect disk cause of mount point busy"))
  209. return mntPoint, errors.NewAggregate(mountErrs)
  210. }
  211. devPath, _, err = d.connectDisk(iDisk)
  212. if err != nil {
  213. return mntPoint, errors.Wrap(err, "connect disk after disconnect")
  214. }
  215. if err := d.mountDisk(devPath, mntPoint, fs, resUid, resGid); err != nil {
  216. mountErrs = append(mountErrs, errors.Wrapf(err, "mount disk after reconnect"))
  217. return mntPoint, errors.NewAggregate(mountErrs)
  218. }
  219. return mntPoint, nil
  220. }
  221. return mntPoint, errors.Wrapf(err, "mount %s to %s", devPath, mntPoint)
  222. }
  223. return mntPoint, nil
  224. }
  225. func (d disk) Mount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  226. iDisk, gd, err := d.getPodDisk(pod, vm)
  227. if err != nil {
  228. return errors.Wrap(err, "get pod disk interface")
  229. }
  230. drv, err := iDisk.GetContainerStorageDriver()
  231. if err != nil {
  232. return errors.Wrap(err, "get disk storage driver")
  233. }
  234. mntPoint, err := d.connectDiskAndMount(drv, pod, iDisk, gd.Fs, vm.Disk.ResUid, vm.Disk.ResGid)
  235. if err != nil {
  236. return errors.Wrap(err, "connect disk and mount disk")
  237. }
  238. vmDisk := vm.Disk
  239. if vmDisk.SubDirectory != "" {
  240. subDir := filepath.Join(mntPoint, vmDisk.SubDirectory)
  241. if err := volume_mount.EnsureDir(subDir); err != nil {
  242. return errors.Wrapf(err, "make sub_directory %s inside %s", vmDisk.SubDirectory, mntPoint)
  243. }
  244. for _, cd := range vmDisk.CaseInsensitivePaths {
  245. cdp := filepath.Join(subDir, cd)
  246. if err := volume_mount.EnsureDir(cdp); err != nil {
  247. return errors.Wrapf(err, "make %s inside %s", cdp, vmDisk.SubDirectory)
  248. }
  249. if err := d.setDirCaseInsensitive(cdp); err != nil {
  250. return errors.Wrapf(err, "enable case_insensitive %s", cdp)
  251. }
  252. }
  253. } else {
  254. if len(vmDisk.CaseInsensitivePaths) != 0 {
  255. return errors.Errorf("only sub_directory can use case_insensitive")
  256. }
  257. }
  258. if vmDisk.StorageSizeFile != "" {
  259. if err := d.createStorageSizeFile(iDisk, mntPoint, vmDisk); err != nil {
  260. return errors.Wrapf(err, "create storage file %s inside %s", vmDisk.StorageSizeFile, mntPoint)
  261. }
  262. }
  263. if vmDisk.Overlay != nil {
  264. if err := d.mountOverlay(pod, ctrId, vm); err != nil {
  265. return errors.Wrapf(err, "mount container %s overlay dir: %#v", ctrId, vmDisk.Overlay)
  266. }
  267. }
  268. if len(vmDisk.PostOverlay) != 0 {
  269. if err := d.MountPostOverlays(pod, ctrId, vm, vmDisk.PostOverlay); err != nil {
  270. return errors.Wrap(err, "mount post overlay dirs")
  271. }
  272. }
  273. return nil
  274. }
  275. func (d disk) createStorageSizeFile(iDisk storageman.IDisk, mntPoint string, input *hostapi.ContainerVolumeMountDisk) error {
  276. desc := iDisk.GetDiskDesc()
  277. diskSizeMB, err := desc.Int("disk_size")
  278. if err != nil {
  279. return errors.Wrapf(err, "get disk_size from %s", desc.String())
  280. }
  281. sp := filepath.Join(mntPoint, input.StorageSizeFile)
  282. sizeBytes := diskSizeMB * 1024
  283. out, err := procutils.NewRemoteCommandAsFarAsPossible("bash", "-c", fmt.Sprintf("echo %d > %s", sizeBytes, sp)).Output()
  284. if err != nil {
  285. return errors.Wrapf(err, "write %d to %s: %s", sizeBytes, sp, out)
  286. }
  287. return nil
  288. }
  289. func (d disk) UnmountWithoutDisconnect(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  290. return d.unmount(pod, ctrId, vm, false)
  291. }
  292. func (d disk) Unmount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  293. return d.unmount(pod, ctrId, vm, true)
  294. }
  295. func (d disk) unmount(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount, disconnect bool) error {
  296. iDisk, _, err := d.getPodDisk(pod, vm)
  297. if err != nil {
  298. return errors.Wrap(err, "get pod disk interface")
  299. }
  300. if len(vm.Disk.PostOverlay) != 0 {
  301. if err := d.UnmountPostOverlays(pod, ctrId, vm, vm.Disk.PostOverlay, false, false); err != nil {
  302. return errors.Wrap(err, "umount post overlay dirs")
  303. }
  304. }
  305. if vm.Disk.Overlay != nil {
  306. if err := d.unmoutOverlay(pod, ctrId, vm); err != nil {
  307. return errors.Wrapf(err, "umount overlay")
  308. }
  309. }
  310. mntPoint := pod.GetDiskMountPoint(iDisk)
  311. if err := container_storage.UnmountWithSubDirs(mntPoint); err != nil {
  312. return errors.Wrapf(err, "UnmountWithSubDirs %s", mntPoint)
  313. }
  314. if disconnect {
  315. if err := d.Disconnect(pod, ctrId, vm); err != nil {
  316. return errors.Wrapf(err, "disconnect disk")
  317. }
  318. }
  319. return nil
  320. }
  321. func (d disk) getOverlayDriver(ov *apis.ContainerVolumeMountDiskOverlay) iDiskOverlay {
  322. return d.overlayDrivers[ov.GetType()]
  323. }
  324. func (d disk) unmoutOverlay(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  325. return d.getOverlayDriver(vm.Disk.Overlay).unmount(d, pod, ctrId, vm)
  326. }
  327. func (d disk) mountOverlay(pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error {
  328. return d.getOverlayDriver(vm.Disk.Overlay).mount(d, pod, ctrId, vm)
  329. }
  330. func (d disk) getCachedImageDir(ctx context.Context, pod volume_mount.IPodInfo, imgId string, accquire bool) (string, error) {
  331. input := computeapi.CacheImageInput{
  332. ImageId: imgId,
  333. Format: imageapi.IMAGE_DISK_FORMAT_TGZ,
  334. SkipChecksumIfExists: true,
  335. }
  336. cachedImgMan := storageman.GetManager().LocalStorageImagecacheManager
  337. logPrefx := fmt.Sprintf("pod %s, image %s", pod.GetName(), imgId)
  338. var cachedImageDir string
  339. var err error
  340. if accquire {
  341. log.Infof("%s try to accuire image...", logPrefx)
  342. cachedImg, err := cachedImgMan.AcquireImage(ctx, input, nil)
  343. if err != nil {
  344. return "", errors.Wrapf(err, "Get cache image %s", imgId)
  345. }
  346. defer cachedImgMan.ReleaseImage(ctx, imgId)
  347. log.Infof("%s try to get access directory", logPrefx)
  348. cachedImageDir, err = cachedImg.GetAccessDirectory()
  349. if err != nil {
  350. return "", errors.Wrapf(err, "GetAccessDirectory of cached image %s", cachedImg.GetPath())
  351. }
  352. } else {
  353. cachedImg := cachedImgMan.(storageman.IImageCacheManagerGetter).GetImage(imgId)
  354. if cachedImg == nil {
  355. return "", errors.Wrapf(errors.ErrNotFound, "GetCachedImage %s", imgId)
  356. }
  357. cachedImageDir, err = cachedImg.GetAccessDirectory()
  358. if err != nil {
  359. return "", errors.Wrapf(err, "GetAccessDirectory of cached image %s", cachedImg.GetPath())
  360. }
  361. }
  362. log.Infof("%s got cached image dir %s, accquire: %v", logPrefx, cachedImageDir, accquire)
  363. return cachedImageDir, nil
  364. }
  365. func (d disk) doTemplateOverlayAction(
  366. ctx context.Context,
  367. pod volume_mount.IPodInfo, ctrId string,
  368. vm *hostapi.ContainerVolumeMount,
  369. ovAction func(d disk, pod volume_mount.IPodInfo, ctrId string, vm *hostapi.ContainerVolumeMount) error,
  370. accquire bool) error {
  371. templateId := vm.Disk.TemplateId
  372. cachedImageDir, err := d.getCachedImageDir(ctx, pod, templateId, accquire)
  373. if err != nil {
  374. return errors.Wrap(err, "get cached image dir")
  375. }
  376. vm.Disk.Overlay = &apis.ContainerVolumeMountDiskOverlay{
  377. LowerDir: []string{cachedImageDir},
  378. }
  379. if err := ovAction(d, pod, ctrId, vm); err != nil {
  380. return errors.Wrapf(err, "overlay dir %s", cachedImageDir)
  381. }
  382. return nil
  383. }
  384. func (d disk) InjectUsageTags(usage *volume_mount.ContainerVolumeMountUsage, vol *hostapi.ContainerVolumeMount) {
  385. usage.Tags["disk_id"] = vol.Disk.Id
  386. }