disk.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 volume_mount
  15. import (
  16. "context"
  17. "yunion.io/x/jsonutils"
  18. "yunion.io/x/pkg/errors"
  19. "yunion.io/x/pkg/util/sets"
  20. "yunion.io/x/onecloud/pkg/apis"
  21. api "yunion.io/x/onecloud/pkg/apis/compute"
  22. hostapi "yunion.io/x/onecloud/pkg/apis/host"
  23. "yunion.io/x/onecloud/pkg/compute/models"
  24. "yunion.io/x/onecloud/pkg/httperrors"
  25. "yunion.io/x/onecloud/pkg/mcclient"
  26. )
  27. func init() {
  28. models.RegisterContainerVolumeMountDriver(newDisk())
  29. }
  30. type iDiskOverlay interface {
  31. validatePodCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, disk *api.DiskConfig) error
  32. validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, obj *models.SDisk) error
  33. }
  34. type iDiskPostOverlay interface {
  35. validateData(ctx context.Context, userCred mcclient.TokenCredential, pov *apis.ContainerVolumeMountDiskPostOverlay) error
  36. getContainerTargetDirs(ov *apis.ContainerVolumeMountDiskPostOverlay) []string
  37. }
  38. type disk struct {
  39. overlayDrivers map[apis.ContainerDiskOverlayType]iDiskOverlay
  40. postOverlayDrivers map[apis.ContainerVolumeMountDiskPostOverlayType]iDiskPostOverlay
  41. }
  42. func newDisk() models.IContainerVolumeMountDiskDriver {
  43. return &disk{
  44. overlayDrivers: map[apis.ContainerDiskOverlayType]iDiskOverlay{
  45. apis.CONTAINER_DISK_OVERLAY_TYPE_DIRECTORY: newDiskOverlayDir(),
  46. apis.CONTAINER_DISK_OVERLAY_TYPE_DISK_IMAGE: newDiskOverlayImage(),
  47. },
  48. postOverlayDrivers: map[apis.ContainerVolumeMountDiskPostOverlayType]iDiskPostOverlay{
  49. apis.CONTAINER_VOLUME_MOUNT_DISK_POST_OVERLAY_HOSTPATH: newDiskPostOverlayHostPath(),
  50. apis.CONTAINER_VOLUME_MOUNT_DISK_POST_OVERLAY_IMAGE: newDiskPostOverlayImage(),
  51. },
  52. }
  53. }
  54. func (d disk) GetType() apis.ContainerVolumeMountType {
  55. return apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK
  56. }
  57. func (d disk) validateDiskCreateData(ctx context.Context, userCred mcclient.TokenCredential, disk *apis.ContainerVolumeMountDisk) (*apis.ContainerVolumeMountDisk, error) {
  58. if disk == nil {
  59. return nil, httperrors.NewNotEmptyError("disk is nil")
  60. }
  61. if disk.Index == nil && disk.Id == "" {
  62. return nil, httperrors.NewNotEmptyError("one of index or id is required")
  63. }
  64. if disk.Index != nil {
  65. if *disk.Index < 0 {
  66. return nil, httperrors.NewInputParameterError("index is less than 0")
  67. }
  68. }
  69. return disk, nil
  70. }
  71. func (d disk) validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, vm *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error) {
  72. disk, err := d.validateDiskCreateData(ctx, userCred, vm.Disk)
  73. if err != nil {
  74. return nil, err
  75. }
  76. vm.Disk = disk
  77. return vm, nil
  78. }
  79. func (d disk) validateCaseInsensitive(disk *models.SDisk, vm *apis.ContainerVolumeMountDisk) error {
  80. if len(vm.CaseInsensitivePaths) == 0 {
  81. return nil
  82. }
  83. if disk.FsFeatures == nil {
  84. return httperrors.NewInputParameterError("disk(%s) fs_features is not set", disk.GetId())
  85. }
  86. if disk.FsFeatures.Ext4 == nil {
  87. return httperrors.NewInputParameterError("disk(%s) fs_features.ext4 is not set", disk.GetId())
  88. }
  89. if !disk.FsFeatures.Ext4.CaseInsensitive {
  90. return httperrors.NewInputParameterError("disk(%s) fs_features.ext4.case_insensitive is not set", disk.GetId())
  91. }
  92. if disk.FsFeatures.F2fs == nil {
  93. return httperrors.NewInputParameterError("disk(%s) fs_features.f2fs is not set", disk.GetId())
  94. }
  95. if !disk.FsFeatures.F2fs.CaseInsensitive {
  96. return httperrors.NewInputParameterError("disk(%s) fs_features.f2fs.case_insensitive is not set", disk.GetId())
  97. }
  98. if vm.Overlay != nil {
  99. return httperrors.NewInputParameterError("can't use case_insensitive and overlay at the same time")
  100. }
  101. if vm.SubDirectory == "" {
  102. return httperrors.NewInputParameterError("sub_directory must set to use case_insensitive")
  103. }
  104. return nil
  105. }
  106. func (d disk) ValidateRootFsCreateData(ctx context.Context, userCred mcclient.TokenCredential, pod *models.SGuest, rootFs *apis.ContainerRootfs) error {
  107. disk, err := d.validateDiskCreateData(ctx, userCred, rootFs.Disk)
  108. if err != nil {
  109. return err
  110. }
  111. disk, _, err = d.validateDiskIndex(ctx, userCred, pod, disk)
  112. if err != nil {
  113. return err
  114. }
  115. rootFs.Disk = disk
  116. if rootFs.Disk.Overlay != nil {
  117. return httperrors.NewInputParameterError("can't use overlay and root_fs at the same time")
  118. }
  119. if len(rootFs.Disk.PostOverlay) > 0 {
  120. return httperrors.NewInputParameterError("can't use post_overlay and root_fs at the same time")
  121. }
  122. return nil
  123. }
  124. func (d disk) ToHostRootFs(rootFs *apis.ContainerRootfs) (*hostapi.ContainerRootfs, error) {
  125. diskObj := models.DiskManager.FetchDiskById(rootFs.Disk.Id)
  126. if diskObj == nil {
  127. return nil, errors.Wrapf(httperrors.ErrNotFound, "fetch disk %s", rootFs.Disk.Id)
  128. }
  129. disk := rootFs.Disk
  130. return &hostapi.ContainerRootfs{
  131. Type: rootFs.Type,
  132. Disk: &hostapi.ContainerVolumeMountDisk{
  133. Index: disk.Index,
  134. Id: disk.Id,
  135. SubDirectory: disk.SubDirectory,
  136. },
  137. Persistent: rootFs.Persistent,
  138. }, nil
  139. }
  140. func (d disk) validateDiskIndex(ctx context.Context, userCred mcclient.TokenCredential, pod *models.SGuest, disk *apis.ContainerVolumeMountDisk) (*apis.ContainerVolumeMountDisk, *models.SDisk, error) {
  141. disks, err := pod.GetDisks()
  142. if err != nil {
  143. return nil, nil, errors.Wrap(err, "get pod disks")
  144. }
  145. var diskObj models.SDisk
  146. if disk.Index != nil {
  147. diskIndex := *disk.Index
  148. if diskIndex >= len(disks) {
  149. return nil, nil, httperrors.NewInputParameterError("disk.index %d is large than disk size %d", diskIndex, len(disks))
  150. }
  151. diskObj = disks[diskIndex]
  152. disk.Id = diskObj.GetId()
  153. // remove index
  154. disk.Index = nil
  155. if err := d.validateCaseInsensitive(&diskObj, disk); err != nil {
  156. return nil, nil, err
  157. }
  158. } else {
  159. if disk.Id == "" {
  160. return nil, nil, httperrors.NewNotEmptyError("disk.id is empty")
  161. }
  162. foundDisk := false
  163. for _, d := range disks {
  164. if d.GetId() == disk.Id || d.GetName() == disk.Id {
  165. disk.Id = d.GetId()
  166. diskObj = d
  167. foundDisk = true
  168. break
  169. }
  170. }
  171. if !foundDisk {
  172. return nil, nil, httperrors.NewNotFoundError("not found pod disk by %s", disk.Id)
  173. }
  174. if err := d.validateCaseInsensitive(&diskObj, disk); err != nil {
  175. return nil, nil, err
  176. }
  177. }
  178. return disk, &diskObj, nil
  179. }
  180. func (d disk) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, pod *models.SGuest, vm *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error) {
  181. if _, err := d.validateCreateData(ctx, userCred, vm); err != nil {
  182. return nil, err
  183. }
  184. disk, diskObj, err := d.validateDiskIndex(ctx, userCred, pod, vm.Disk)
  185. if err != nil {
  186. return nil, err
  187. }
  188. vm.Disk = disk
  189. if err := d.validateOverlay(ctx, userCred, vm, diskObj); err != nil {
  190. return nil, errors.Wrapf(err, "validate overlay")
  191. }
  192. if err := d.ValidatePostOverlay(ctx, userCred, vm); err != nil {
  193. return nil, errors.Wrap(err, "validate post overlay")
  194. }
  195. return vm, nil
  196. }
  197. func (d disk) ValidatePodCreateData(ctx context.Context, userCred mcclient.TokenCredential, vm *apis.ContainerVolumeMount, input *api.ServerCreateInput) error {
  198. if _, err := d.validateCreateData(ctx, userCred, vm); err != nil {
  199. return err
  200. }
  201. disk := vm.Disk
  202. if disk.Id != "" {
  203. return httperrors.NewInputParameterError("can't specify disk_id %s when creating pod", disk.Id)
  204. }
  205. if disk.Index == nil {
  206. return httperrors.NewNotEmptyError("disk.index is required")
  207. }
  208. diskIndex := *disk.Index
  209. disks := input.Disks
  210. if diskIndex < 0 {
  211. return httperrors.NewInputParameterError("disk.index %d is less than 0", diskIndex)
  212. }
  213. if diskIndex >= len(disks) {
  214. return httperrors.NewInputParameterError("disk.index %d is large than disk size %d", diskIndex, len(disks))
  215. }
  216. inputDisk := disks[diskIndex]
  217. if vm.Disk.Overlay != nil {
  218. if err := d.getOverlayDriver(vm.Disk.Overlay).validatePodCreateData(ctx, userCred, vm.Disk.Overlay, inputDisk); err != nil {
  219. return httperrors.NewInputParameterError("valid overlay %v", err)
  220. }
  221. }
  222. return nil
  223. }
  224. func (d disk) getOverlayDriver(ov *apis.ContainerVolumeMountDiskOverlay) iDiskOverlay {
  225. return d.overlayDrivers[ov.GetType()]
  226. }
  227. func (d disk) getPostOverlayDriver(pov *apis.ContainerVolumeMountDiskPostOverlay) iDiskPostOverlay {
  228. return d.postOverlayDrivers[pov.GetType()]
  229. }
  230. func (d disk) validateOverlay(ctx context.Context, userCred mcclient.TokenCredential, vm *apis.ContainerVolumeMount, diskObj *models.SDisk) error {
  231. if vm.Disk.Overlay == nil {
  232. return nil
  233. }
  234. ov := vm.Disk.Overlay
  235. if err := ov.IsValid(); err != nil {
  236. return httperrors.NewInputParameterError("invalid overlay input: %v", err)
  237. }
  238. if err := d.getOverlayDriver(ov).validateCreateData(ctx, userCred, ov, diskObj); err != nil {
  239. return errors.Wrapf(err, "validate overlay %s", ov.GetType())
  240. }
  241. return nil
  242. }
  243. func (d disk) ValidatePostSingleOverlay(ctx context.Context, userCred mcclient.TokenCredential, ov *apis.ContainerVolumeMountDiskPostOverlay) error {
  244. drv := d.getPostOverlayDriver(ov)
  245. if err := drv.validateData(ctx, userCred, ov); err != nil {
  246. return errors.Wrapf(err, "validate post overlay %s", ov.GetType())
  247. }
  248. return nil
  249. }
  250. func (d disk) ValidatePostOverlayTargetDirs(ovs []*apis.ContainerVolumeMountDiskPostOverlay) error {
  251. ctrTargetDirs := sets.NewString()
  252. for i := range ovs {
  253. ov := ovs[i]
  254. drv := d.getPostOverlayDriver(ov)
  255. ovCtrTargetDirs := drv.getContainerTargetDirs(ov)
  256. if ctrTargetDirs.HasAny(ovCtrTargetDirs...) {
  257. return httperrors.NewInputParameterError("duplicated container target dirs %v of ov %s", ctrTargetDirs, jsonutils.Marshal(ov))
  258. } else {
  259. ctrTargetDirs.Insert(ovCtrTargetDirs...)
  260. }
  261. }
  262. return nil
  263. }
  264. func (d disk) ValidatePostOverlay(ctx context.Context, userCred mcclient.TokenCredential, vm *apis.ContainerVolumeMount) error {
  265. if len(vm.Disk.PostOverlay) == 0 {
  266. return nil
  267. }
  268. ovs := vm.Disk.PostOverlay
  269. for i := range ovs {
  270. ov := ovs[i]
  271. if err := d.ValidatePostSingleOverlay(ctx, userCred, ov); err != nil {
  272. return errors.Wrap(err, "validate post single overlay")
  273. }
  274. vm.Disk.PostOverlay[i] = ov
  275. }
  276. if err := d.ValidatePostOverlayTargetDirs(vm.Disk.PostOverlay); err != nil {
  277. return errors.Wrap(err, "validate post overlay target dirs")
  278. }
  279. if vm.Propagation == "" {
  280. // 设置默认 propagation 为 rslave
  281. vm.Propagation = apis.MOUNTPROPAGATION_PROPAGATION_HOST_TO_CONTAINER
  282. }
  283. return nil
  284. }
  285. type diskOverlayDir struct{}
  286. func newDiskOverlayDir() iDiskOverlay {
  287. return &diskOverlayDir{}
  288. }
  289. func (d diskOverlayDir) validateCommonCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay) error {
  290. if len(input.LowerDir) == 0 {
  291. return httperrors.NewNotEmptyError("lower_dir is required")
  292. }
  293. for idx, ld := range input.LowerDir {
  294. if ld == "" {
  295. return httperrors.NewNotEmptyError("empty %d dir", idx)
  296. }
  297. if ld == "/" {
  298. return httperrors.NewInputParameterError("can't use '/' as lower_dir")
  299. }
  300. }
  301. return nil
  302. }
  303. func (d diskOverlayDir) validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, _ *models.SDisk) error {
  304. return d.validateCommonCreateData(ctx, userCred, input)
  305. }
  306. func (d diskOverlayDir) validatePodCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, disk *api.DiskConfig) error {
  307. return d.validateCommonCreateData(ctx, userCred, input)
  308. }
  309. type diskOverlayImage struct{}
  310. func newDiskOverlayImage() iDiskOverlay {
  311. return &diskOverlayImage{}
  312. }
  313. func (d diskOverlayImage) validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, diskObj *models.SDisk) error {
  314. if !input.UseDiskImage {
  315. return nil
  316. }
  317. if diskObj.TemplateId == "" {
  318. return httperrors.NewInputParameterError("disk %s must have template_id", diskObj.GetId())
  319. }
  320. return nil
  321. }
  322. func (d diskOverlayImage) validatePodCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerVolumeMountDiskOverlay, disk *api.DiskConfig) error {
  323. if !input.UseDiskImage {
  324. return nil
  325. }
  326. if disk.ImageId == "" {
  327. return httperrors.NewInputParameterError("disk %#v must have image_id", disk)
  328. }
  329. return nil
  330. }