empty_dir.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package emptydir
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/api/resource"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/types"
  22. "k8s.io/klog"
  23. v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
  24. "k8s.io/kubernetes/pkg/util/mount"
  25. "k8s.io/kubernetes/pkg/volume"
  26. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  27. "k8s.io/kubernetes/pkg/volume/util/quota"
  28. utilstrings "k8s.io/utils/strings"
  29. )
  30. // TODO: in the near future, this will be changed to be more restrictive
  31. // and the group will be set to allow containers to use emptyDir volumes
  32. // from the group attribute.
  33. //
  34. // http://issue.k8s.io/2630
  35. const perm os.FileMode = 0777
  36. // ProbeVolumePlugins is the primary entrypoint for volume plugins.
  37. func ProbeVolumePlugins() []volume.VolumePlugin {
  38. return []volume.VolumePlugin{
  39. &emptyDirPlugin{nil},
  40. }
  41. }
  42. type emptyDirPlugin struct {
  43. host volume.VolumeHost
  44. }
  45. var _ volume.VolumePlugin = &emptyDirPlugin{}
  46. const (
  47. emptyDirPluginName = "kubernetes.io/empty-dir"
  48. hugePagesPageSizeMountOption = "pagesize"
  49. )
  50. func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
  51. return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(emptyDirPluginName), volName)
  52. }
  53. func (plugin *emptyDirPlugin) Init(host volume.VolumeHost) error {
  54. plugin.host = host
  55. return nil
  56. }
  57. func (plugin *emptyDirPlugin) GetPluginName() string {
  58. return emptyDirPluginName
  59. }
  60. func (plugin *emptyDirPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  61. volumeSource, _ := getVolumeSource(spec)
  62. if volumeSource == nil {
  63. return "", fmt.Errorf("Spec does not reference an EmptyDir volume type")
  64. }
  65. // Return user defined volume name, since this is an ephemeral volume type
  66. return spec.Name(), nil
  67. }
  68. func (plugin *emptyDirPlugin) CanSupport(spec *volume.Spec) bool {
  69. if spec.Volume != nil && spec.Volume.EmptyDir != nil {
  70. return true
  71. }
  72. return false
  73. }
  74. func (plugin *emptyDirPlugin) IsMigratedToCSI() bool {
  75. return false
  76. }
  77. func (plugin *emptyDirPlugin) RequiresRemount() bool {
  78. return false
  79. }
  80. func (plugin *emptyDirPlugin) SupportsMountOption() bool {
  81. return false
  82. }
  83. func (plugin *emptyDirPlugin) SupportsBulkVolumeVerification() bool {
  84. return false
  85. }
  86. func (plugin *emptyDirPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
  87. return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()), &realMountDetector{plugin.host.GetMounter(plugin.GetPluginName())}, opts)
  88. }
  89. func (plugin *emptyDirPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface, mountDetector mountDetector, opts volume.VolumeOptions) (volume.Mounter, error) {
  90. medium := v1.StorageMediumDefault
  91. if spec.Volume.EmptyDir != nil { // Support a non-specified source as EmptyDir.
  92. medium = spec.Volume.EmptyDir.Medium
  93. }
  94. return &emptyDir{
  95. pod: pod,
  96. volName: spec.Name(),
  97. medium: medium,
  98. mounter: mounter,
  99. mountDetector: mountDetector,
  100. plugin: plugin,
  101. MetricsProvider: volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host)),
  102. }, nil
  103. }
  104. func (plugin *emptyDirPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  105. // Inject real implementations here, test through the internal function.
  106. return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()), &realMountDetector{plugin.host.GetMounter(plugin.GetPluginName())})
  107. }
  108. func (plugin *emptyDirPlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface, mountDetector mountDetector) (volume.Unmounter, error) {
  109. ed := &emptyDir{
  110. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
  111. volName: volName,
  112. medium: v1.StorageMediumDefault, // might be changed later
  113. mounter: mounter,
  114. mountDetector: mountDetector,
  115. plugin: plugin,
  116. MetricsProvider: volume.NewMetricsDu(getPath(podUID, volName, plugin.host)),
  117. }
  118. return ed, nil
  119. }
  120. func (plugin *emptyDirPlugin) ConstructVolumeSpec(volName, mountPath string) (*volume.Spec, error) {
  121. emptyDirVolume := &v1.Volume{
  122. Name: volName,
  123. VolumeSource: v1.VolumeSource{
  124. EmptyDir: &v1.EmptyDirVolumeSource{},
  125. },
  126. }
  127. return volume.NewSpecFromVolume(emptyDirVolume), nil
  128. }
  129. // mountDetector abstracts how to find what kind of mount a path is backed by.
  130. type mountDetector interface {
  131. // GetMountMedium determines what type of medium a given path is backed
  132. // by and whether that path is a mount point. For example, if this
  133. // returns (v1.StorageMediumMemory, false, nil), the caller knows that the path is
  134. // on a memory FS (tmpfs on Linux) but is not the root mountpoint of
  135. // that tmpfs.
  136. GetMountMedium(path string) (v1.StorageMedium, bool, error)
  137. }
  138. // EmptyDir volumes are temporary directories exposed to the pod.
  139. // These do not persist beyond the lifetime of a pod.
  140. type emptyDir struct {
  141. pod *v1.Pod
  142. volName string
  143. medium v1.StorageMedium
  144. mounter mount.Interface
  145. mountDetector mountDetector
  146. plugin *emptyDirPlugin
  147. desiredSize int64
  148. volume.MetricsProvider
  149. }
  150. func (ed *emptyDir) GetAttributes() volume.Attributes {
  151. return volume.Attributes{
  152. ReadOnly: false,
  153. Managed: true,
  154. SupportsSELinux: true,
  155. }
  156. }
  157. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  158. // to mount the volume are available on the underlying node.
  159. // If not, it returns an error
  160. func (ed *emptyDir) CanMount() error {
  161. return nil
  162. }
  163. // SetUp creates new directory.
  164. func (ed *emptyDir) SetUp(mounterArgs volume.MounterArgs) error {
  165. return ed.SetUpAt(ed.GetPath(), mounterArgs)
  166. }
  167. // SetUpAt creates new directory.
  168. func (ed *emptyDir) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  169. notMnt, err := ed.mounter.IsLikelyNotMountPoint(dir)
  170. // Getting an os.IsNotExist err from is a contingency; the directory
  171. // may not exist yet, in which case, setup should run.
  172. if err != nil && !os.IsNotExist(err) {
  173. return err
  174. }
  175. // If the plugin readiness file is present for this volume, and the
  176. // storage medium is the default, then the volume is ready. If the
  177. // medium is memory, and a mountpoint is present, then the volume is
  178. // ready.
  179. if volumeutil.IsReady(ed.getMetaDir()) {
  180. if ed.medium == v1.StorageMediumMemory && !notMnt {
  181. return nil
  182. } else if ed.medium == v1.StorageMediumDefault {
  183. return nil
  184. }
  185. }
  186. switch ed.medium {
  187. case v1.StorageMediumDefault:
  188. err = ed.setupDir(dir)
  189. case v1.StorageMediumMemory:
  190. err = ed.setupTmpfs(dir)
  191. case v1.StorageMediumHugePages:
  192. err = ed.setupHugepages(dir)
  193. default:
  194. err = fmt.Errorf("unknown storage medium %q", ed.medium)
  195. }
  196. volume.SetVolumeOwnership(ed, mounterArgs.FsGroup)
  197. // If setting up the quota fails, just log a message but don't actually error out.
  198. // We'll use the old du mechanism in this case, at least until we support
  199. // enforcement.
  200. if err == nil {
  201. volumeutil.SetReady(ed.getMetaDir())
  202. if mounterArgs.DesiredSize != nil {
  203. // Deliberately shadow the outer use of err as noted
  204. // above.
  205. hasQuotas, err := quota.SupportsQuotas(ed.mounter, dir)
  206. if err != nil {
  207. klog.V(3).Infof("Unable to check for quota support on %s: %s", dir, err.Error())
  208. } else if hasQuotas {
  209. klog.V(4).Infof("emptydir trying to assign quota %v on %s", mounterArgs.DesiredSize, dir)
  210. err := quota.AssignQuota(ed.mounter, dir, mounterArgs.PodUID, mounterArgs.DesiredSize)
  211. if err != nil {
  212. klog.V(3).Infof("Set quota on %s failed %s", dir, err.Error())
  213. }
  214. }
  215. }
  216. }
  217. return err
  218. }
  219. // setupTmpfs creates a tmpfs mount at the specified directory.
  220. func (ed *emptyDir) setupTmpfs(dir string) error {
  221. if ed.mounter == nil {
  222. return fmt.Errorf("memory storage requested, but mounter is nil")
  223. }
  224. if err := ed.setupDir(dir); err != nil {
  225. return err
  226. }
  227. // Make SetUp idempotent.
  228. medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
  229. if err != nil {
  230. return err
  231. }
  232. // If the directory is a mountpoint with medium memory, there is no
  233. // work to do since we are already in the desired state.
  234. if isMnt && medium == v1.StorageMediumMemory {
  235. return nil
  236. }
  237. klog.V(3).Infof("pod %v: mounting tmpfs for volume %v", ed.pod.UID, ed.volName)
  238. return ed.mounter.Mount("tmpfs", dir, "tmpfs", nil /* options */)
  239. }
  240. // setupHugepages creates a hugepage mount at the specified directory.
  241. func (ed *emptyDir) setupHugepages(dir string) error {
  242. if ed.mounter == nil {
  243. return fmt.Errorf("memory storage requested, but mounter is nil")
  244. }
  245. if err := ed.setupDir(dir); err != nil {
  246. return err
  247. }
  248. // Make SetUp idempotent.
  249. medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
  250. if err != nil {
  251. return err
  252. }
  253. // If the directory is a mountpoint with medium hugepages, there is no
  254. // work to do since we are already in the desired state.
  255. if isMnt && medium == v1.StorageMediumHugePages {
  256. return nil
  257. }
  258. pageSizeMountOption, err := getPageSizeMountOptionFromPod(ed.pod)
  259. if err != nil {
  260. return err
  261. }
  262. klog.V(3).Infof("pod %v: mounting hugepages for volume %v", ed.pod.UID, ed.volName)
  263. return ed.mounter.Mount("nodev", dir, "hugetlbfs", []string{pageSizeMountOption})
  264. }
  265. // getPageSizeMountOptionFromPod retrieves pageSize mount option from Pod's resources
  266. // and validates pageSize options in all containers of given Pod.
  267. func getPageSizeMountOptionFromPod(pod *v1.Pod) (string, error) {
  268. pageSizeFound := false
  269. pageSize := resource.Quantity{}
  270. // In some rare cases init containers can also consume Huge pages.
  271. containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
  272. for _, container := range containers {
  273. // We can take request because limit and requests must match.
  274. for requestName := range container.Resources.Requests {
  275. if v1helper.IsHugePageResourceName(requestName) {
  276. currentPageSize, err := v1helper.HugePageSizeFromResourceName(requestName)
  277. if err != nil {
  278. return "", err
  279. }
  280. // PageSize for all volumes in a POD are equal, except for the first one discovered.
  281. if pageSizeFound && pageSize.Cmp(currentPageSize) != 0 {
  282. return "", fmt.Errorf("multiple pageSizes for huge pages in a single PodSpec")
  283. }
  284. pageSize = currentPageSize
  285. pageSizeFound = true
  286. }
  287. }
  288. }
  289. if !pageSizeFound {
  290. return "", fmt.Errorf("hugePages storage requested, but there is no resource request for huge pages")
  291. }
  292. return fmt.Sprintf("%s=%s", hugePagesPageSizeMountOption, pageSize.String()), nil
  293. }
  294. // setupDir creates the directory with the default permissions specified by the perm constant.
  295. func (ed *emptyDir) setupDir(dir string) error {
  296. // Create the directory if it doesn't already exist.
  297. if err := os.MkdirAll(dir, perm); err != nil {
  298. return err
  299. }
  300. // stat the directory to read permission bits
  301. fileinfo, err := os.Lstat(dir)
  302. if err != nil {
  303. return err
  304. }
  305. if fileinfo.Mode().Perm() != perm.Perm() {
  306. // If the permissions on the created directory are wrong, the
  307. // kubelet is probably running with a umask set. In order to
  308. // avoid clearing the umask for the entire process or locking
  309. // the thread, clearing the umask, creating the dir, restoring
  310. // the umask, and unlocking the thread, we do a chmod to set
  311. // the specific bits we need.
  312. err := os.Chmod(dir, perm)
  313. if err != nil {
  314. return err
  315. }
  316. fileinfo, err = os.Lstat(dir)
  317. if err != nil {
  318. return err
  319. }
  320. if fileinfo.Mode().Perm() != perm.Perm() {
  321. klog.Errorf("Expected directory %q permissions to be: %s; got: %s", dir, perm.Perm(), fileinfo.Mode().Perm())
  322. }
  323. }
  324. return nil
  325. }
  326. func (ed *emptyDir) GetPath() string {
  327. return getPath(ed.pod.UID, ed.volName, ed.plugin.host)
  328. }
  329. // TearDown simply discards everything in the directory.
  330. func (ed *emptyDir) TearDown() error {
  331. return ed.TearDownAt(ed.GetPath())
  332. }
  333. // TearDownAt simply discards everything in the directory.
  334. func (ed *emptyDir) TearDownAt(dir string) error {
  335. if pathExists, pathErr := mount.PathExists(dir); pathErr != nil {
  336. return fmt.Errorf("Error checking if path exists: %v", pathErr)
  337. } else if !pathExists {
  338. klog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir)
  339. return nil
  340. }
  341. // Figure out the medium.
  342. medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
  343. if err != nil {
  344. return err
  345. }
  346. if isMnt {
  347. if medium == v1.StorageMediumMemory {
  348. ed.medium = v1.StorageMediumMemory
  349. return ed.teardownTmpfsOrHugetlbfs(dir)
  350. } else if medium == v1.StorageMediumHugePages {
  351. ed.medium = v1.StorageMediumHugePages
  352. return ed.teardownTmpfsOrHugetlbfs(dir)
  353. }
  354. }
  355. // assume StorageMediumDefault
  356. return ed.teardownDefault(dir)
  357. }
  358. func (ed *emptyDir) teardownDefault(dir string) error {
  359. // Remove any quota
  360. err := quota.ClearQuota(ed.mounter, dir)
  361. if err != nil {
  362. klog.Warningf("Warning: Failed to clear quota on %s: %v", dir, err)
  363. }
  364. // Renaming the directory is not required anymore because the operation executor
  365. // now handles duplicate operations on the same volume
  366. err = os.RemoveAll(dir)
  367. if err != nil {
  368. return err
  369. }
  370. return nil
  371. }
  372. func (ed *emptyDir) teardownTmpfsOrHugetlbfs(dir string) error {
  373. if ed.mounter == nil {
  374. return fmt.Errorf("memory storage requested, but mounter is nil")
  375. }
  376. if err := ed.mounter.Unmount(dir); err != nil {
  377. return err
  378. }
  379. if err := os.RemoveAll(dir); err != nil {
  380. return err
  381. }
  382. return nil
  383. }
  384. func (ed *emptyDir) getMetaDir() string {
  385. return filepath.Join(ed.plugin.host.GetPodPluginDir(ed.pod.UID, utilstrings.EscapeQualifiedName(emptyDirPluginName)), ed.volName)
  386. }
  387. func getVolumeSource(spec *volume.Spec) (*v1.EmptyDirVolumeSource, bool) {
  388. var readOnly bool
  389. var volumeSource *v1.EmptyDirVolumeSource
  390. if spec.Volume != nil && spec.Volume.EmptyDir != nil {
  391. volumeSource = spec.Volume.EmptyDir
  392. readOnly = spec.ReadOnly
  393. }
  394. return volumeSource, readOnly
  395. }