empty_dir.go 14 KB

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