host_path.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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 hostpath
  14. import (
  15. "fmt"
  16. "os"
  17. "regexp"
  18. "k8s.io/api/core/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/types"
  21. "k8s.io/apimachinery/pkg/util/uuid"
  22. "k8s.io/kubernetes/pkg/util/mount"
  23. "k8s.io/kubernetes/pkg/volume"
  24. "k8s.io/kubernetes/pkg/volume/util"
  25. "k8s.io/kubernetes/pkg/volume/util/recyclerclient"
  26. "k8s.io/kubernetes/pkg/volume/validation"
  27. )
  28. // ProbeVolumePlugins is the primary entrypoint for volume plugins.
  29. // The volumeConfig arg provides the ability to configure volume behavior. It is implemented as a pointer to allow nils.
  30. // The hostPathPlugin is used to store the volumeConfig and give it, when needed, to the func that Recycles.
  31. // Tests that exercise recycling should not use this func but instead use ProbeRecyclablePlugins() to override default behavior.
  32. func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
  33. return []volume.VolumePlugin{
  34. &hostPathPlugin{
  35. host: nil,
  36. config: volumeConfig,
  37. },
  38. }
  39. }
  40. type hostPathPlugin struct {
  41. host volume.VolumeHost
  42. config volume.VolumeConfig
  43. }
  44. var _ volume.VolumePlugin = &hostPathPlugin{}
  45. var _ volume.PersistentVolumePlugin = &hostPathPlugin{}
  46. var _ volume.RecyclableVolumePlugin = &hostPathPlugin{}
  47. var _ volume.DeletableVolumePlugin = &hostPathPlugin{}
  48. var _ volume.ProvisionableVolumePlugin = &hostPathPlugin{}
  49. const (
  50. hostPathPluginName = "kubernetes.io/host-path"
  51. )
  52. func (plugin *hostPathPlugin) Init(host volume.VolumeHost) error {
  53. plugin.host = host
  54. return nil
  55. }
  56. func (plugin *hostPathPlugin) GetPluginName() string {
  57. return hostPathPluginName
  58. }
  59. func (plugin *hostPathPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  60. volumeSource, _, err := getVolumeSource(spec)
  61. if err != nil {
  62. return "", err
  63. }
  64. return volumeSource.Path, nil
  65. }
  66. func (plugin *hostPathPlugin) CanSupport(spec *volume.Spec) bool {
  67. return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.HostPath != nil) ||
  68. (spec.Volume != nil && spec.Volume.HostPath != nil)
  69. }
  70. func (plugin *hostPathPlugin) IsMigratedToCSI() bool {
  71. return false
  72. }
  73. func (plugin *hostPathPlugin) RequiresRemount() bool {
  74. return false
  75. }
  76. func (plugin *hostPathPlugin) SupportsMountOption() bool {
  77. return false
  78. }
  79. func (plugin *hostPathPlugin) SupportsBulkVolumeVerification() bool {
  80. return false
  81. }
  82. func (plugin *hostPathPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  83. return []v1.PersistentVolumeAccessMode{
  84. v1.ReadWriteOnce,
  85. }
  86. }
  87. func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
  88. hostPathVolumeSource, readOnly, err := getVolumeSource(spec)
  89. if err != nil {
  90. return nil, err
  91. }
  92. path := hostPathVolumeSource.Path
  93. pathType := new(v1.HostPathType)
  94. if hostPathVolumeSource.Type == nil {
  95. *pathType = v1.HostPathUnset
  96. } else {
  97. pathType = hostPathVolumeSource.Type
  98. }
  99. return &hostPathMounter{
  100. hostPath: &hostPath{path: path, pathType: pathType},
  101. readOnly: readOnly,
  102. mounter: plugin.host.GetMounter(plugin.GetPluginName()),
  103. }, nil
  104. }
  105. func (plugin *hostPathPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  106. return &hostPathUnmounter{&hostPath{
  107. path: "",
  108. }}, nil
  109. }
  110. // Recycle recycles/scrubs clean a HostPath volume.
  111. // Recycle blocks until the pod has completed or any error occurs.
  112. // HostPath recycling only works in single node clusters and is meant for testing purposes only.
  113. func (plugin *hostPathPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
  114. if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.HostPath == nil {
  115. return fmt.Errorf("spec.PersistentVolume.Spec.HostPath is nil")
  116. }
  117. pod := plugin.config.RecyclerPodTemplate
  118. timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume)
  119. // overrides
  120. pod.Spec.ActiveDeadlineSeconds = &timeout
  121. pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{
  122. HostPath: &v1.HostPathVolumeSource{
  123. Path: spec.PersistentVolume.Spec.HostPath.Path,
  124. },
  125. }
  126. return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder)
  127. }
  128. func (plugin *hostPathPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
  129. return newDeleter(spec, plugin.host)
  130. }
  131. func (plugin *hostPathPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
  132. if !plugin.config.ProvisioningEnabled {
  133. return nil, fmt.Errorf("Provisioning in volume plugin %q is disabled", plugin.GetPluginName())
  134. }
  135. return newProvisioner(options, plugin.host, plugin)
  136. }
  137. func (plugin *hostPathPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
  138. hostPathVolume := &v1.Volume{
  139. Name: volumeName,
  140. VolumeSource: v1.VolumeSource{
  141. HostPath: &v1.HostPathVolumeSource{
  142. Path: volumeName,
  143. },
  144. },
  145. }
  146. return volume.NewSpecFromVolume(hostPathVolume), nil
  147. }
  148. func newDeleter(spec *volume.Spec, host volume.VolumeHost) (volume.Deleter, error) {
  149. if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.HostPath == nil {
  150. return nil, fmt.Errorf("spec.PersistentVolumeSource.HostPath is nil")
  151. }
  152. path := spec.PersistentVolume.Spec.HostPath.Path
  153. return &hostPathDeleter{name: spec.Name(), path: path, host: host}, nil
  154. }
  155. func newProvisioner(options volume.VolumeOptions, host volume.VolumeHost, plugin *hostPathPlugin) (volume.Provisioner, error) {
  156. return &hostPathProvisioner{options: options, host: host, plugin: plugin}, nil
  157. }
  158. // HostPath volumes represent a bare host file or directory mount.
  159. // The direct at the specified path will be directly exposed to the container.
  160. type hostPath struct {
  161. path string
  162. pathType *v1.HostPathType
  163. volume.MetricsNil
  164. }
  165. func (hp *hostPath) GetPath() string {
  166. return hp.path
  167. }
  168. type hostPathMounter struct {
  169. *hostPath
  170. readOnly bool
  171. mounter mount.Interface
  172. }
  173. var _ volume.Mounter = &hostPathMounter{}
  174. func (b *hostPathMounter) GetAttributes() volume.Attributes {
  175. return volume.Attributes{
  176. ReadOnly: b.readOnly,
  177. Managed: false,
  178. SupportsSELinux: false,
  179. }
  180. }
  181. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  182. // to mount the volume are available on the underlying node.
  183. // If not, it returns an error
  184. func (b *hostPathMounter) CanMount() error {
  185. return nil
  186. }
  187. // SetUp does nothing.
  188. func (b *hostPathMounter) SetUp(mounterArgs volume.MounterArgs) error {
  189. err := validation.ValidatePathNoBacksteps(b.GetPath())
  190. if err != nil {
  191. return fmt.Errorf("invalid HostPath `%s`: %v", b.GetPath(), err)
  192. }
  193. if *b.pathType == v1.HostPathUnset {
  194. return nil
  195. }
  196. return checkType(b.GetPath(), b.pathType, b.mounter)
  197. }
  198. // SetUpAt does not make sense for host paths - probably programmer error.
  199. func (b *hostPathMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  200. return fmt.Errorf("SetUpAt() does not make sense for host paths")
  201. }
  202. func (b *hostPathMounter) GetPath() string {
  203. return b.path
  204. }
  205. type hostPathUnmounter struct {
  206. *hostPath
  207. }
  208. var _ volume.Unmounter = &hostPathUnmounter{}
  209. // TearDown does nothing.
  210. func (c *hostPathUnmounter) TearDown() error {
  211. return nil
  212. }
  213. // TearDownAt does not make sense for host paths - probably programmer error.
  214. func (c *hostPathUnmounter) TearDownAt(dir string) error {
  215. return fmt.Errorf("TearDownAt() does not make sense for host paths")
  216. }
  217. // hostPathProvisioner implements a Provisioner for the HostPath plugin
  218. // This implementation is meant for testing only and only works in a single node cluster.
  219. type hostPathProvisioner struct {
  220. host volume.VolumeHost
  221. options volume.VolumeOptions
  222. plugin *hostPathPlugin
  223. }
  224. // Create for hostPath simply creates a local /tmp/hostpath_pv/%s directory as a new PersistentVolume.
  225. // This Provisioner is meant for development and testing only and WILL NOT WORK in a multi-node cluster.
  226. func (r *hostPathProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
  227. if util.CheckPersistentVolumeClaimModeBlock(r.options.PVC) {
  228. return nil, fmt.Errorf("%s does not support block volume provisioning", r.plugin.GetPluginName())
  229. }
  230. fullpath := fmt.Sprintf("/tmp/hostpath_pv/%s", uuid.NewUUID())
  231. capacity := r.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
  232. pv := &v1.PersistentVolume{
  233. ObjectMeta: metav1.ObjectMeta{
  234. Name: r.options.PVName,
  235. Annotations: map[string]string{
  236. util.VolumeDynamicallyCreatedByKey: "hostpath-dynamic-provisioner",
  237. },
  238. },
  239. Spec: v1.PersistentVolumeSpec{
  240. PersistentVolumeReclaimPolicy: r.options.PersistentVolumeReclaimPolicy,
  241. AccessModes: r.options.PVC.Spec.AccessModes,
  242. Capacity: v1.ResourceList{
  243. v1.ResourceName(v1.ResourceStorage): capacity,
  244. },
  245. PersistentVolumeSource: v1.PersistentVolumeSource{
  246. HostPath: &v1.HostPathVolumeSource{
  247. Path: fullpath,
  248. },
  249. },
  250. },
  251. }
  252. if len(r.options.PVC.Spec.AccessModes) == 0 {
  253. pv.Spec.AccessModes = r.plugin.GetAccessModes()
  254. }
  255. return pv, os.MkdirAll(pv.Spec.HostPath.Path, 0750)
  256. }
  257. // hostPathDeleter deletes a hostPath PV from the cluster.
  258. // This deleter only works on a single host cluster and is for testing purposes only.
  259. type hostPathDeleter struct {
  260. name string
  261. path string
  262. host volume.VolumeHost
  263. volume.MetricsNil
  264. }
  265. func (r *hostPathDeleter) GetPath() string {
  266. return r.path
  267. }
  268. // Delete for hostPath removes the local directory so long as it is beneath /tmp/*.
  269. // THIS IS FOR TESTING AND LOCAL DEVELOPMENT ONLY! This message should scare you away from using
  270. // this deleter for anything other than development and testing.
  271. func (r *hostPathDeleter) Delete() error {
  272. regexp := regexp.MustCompile("/tmp/.+")
  273. if !regexp.MatchString(r.GetPath()) {
  274. return fmt.Errorf("host_path deleter only supports /tmp/.+ but received provided %s", r.GetPath())
  275. }
  276. return os.RemoveAll(r.GetPath())
  277. }
  278. func getVolumeSource(spec *volume.Spec) (*v1.HostPathVolumeSource, bool, error) {
  279. if spec.Volume != nil && spec.Volume.HostPath != nil {
  280. return spec.Volume.HostPath, spec.ReadOnly, nil
  281. } else if spec.PersistentVolume != nil &&
  282. spec.PersistentVolume.Spec.HostPath != nil {
  283. return spec.PersistentVolume.Spec.HostPath, spec.ReadOnly, nil
  284. }
  285. return nil, false, fmt.Errorf("Spec does not reference an HostPath volume type")
  286. }
  287. type hostPathTypeChecker interface {
  288. Exists() bool
  289. IsFile() bool
  290. MakeFile() error
  291. IsDir() bool
  292. MakeDir() error
  293. IsBlock() bool
  294. IsChar() bool
  295. IsSocket() bool
  296. GetPath() string
  297. }
  298. type fileTypeChecker struct {
  299. path string
  300. exists bool
  301. mounter mount.Interface
  302. }
  303. func (ftc *fileTypeChecker) Exists() bool {
  304. exists, err := ftc.mounter.ExistsPath(ftc.path)
  305. return exists && err == nil
  306. }
  307. func (ftc *fileTypeChecker) IsFile() bool {
  308. if !ftc.Exists() {
  309. return false
  310. }
  311. return !ftc.IsDir()
  312. }
  313. func (ftc *fileTypeChecker) MakeFile() error {
  314. return ftc.mounter.MakeFile(ftc.path)
  315. }
  316. func (ftc *fileTypeChecker) IsDir() bool {
  317. if !ftc.Exists() {
  318. return false
  319. }
  320. pathType, err := ftc.mounter.GetFileType(ftc.path)
  321. if err != nil {
  322. return false
  323. }
  324. return string(pathType) == string(v1.HostPathDirectory)
  325. }
  326. func (ftc *fileTypeChecker) MakeDir() error {
  327. return ftc.mounter.MakeDir(ftc.path)
  328. }
  329. func (ftc *fileTypeChecker) IsBlock() bool {
  330. blkDevType, err := ftc.mounter.GetFileType(ftc.path)
  331. if err != nil {
  332. return false
  333. }
  334. return string(blkDevType) == string(v1.HostPathBlockDev)
  335. }
  336. func (ftc *fileTypeChecker) IsChar() bool {
  337. charDevType, err := ftc.mounter.GetFileType(ftc.path)
  338. if err != nil {
  339. return false
  340. }
  341. return string(charDevType) == string(v1.HostPathCharDev)
  342. }
  343. func (ftc *fileTypeChecker) IsSocket() bool {
  344. socketType, err := ftc.mounter.GetFileType(ftc.path)
  345. if err != nil {
  346. return false
  347. }
  348. return string(socketType) == string(v1.HostPathSocket)
  349. }
  350. func (ftc *fileTypeChecker) GetPath() string {
  351. return ftc.path
  352. }
  353. func newFileTypeChecker(path string, mounter mount.Interface) hostPathTypeChecker {
  354. return &fileTypeChecker{path: path, mounter: mounter}
  355. }
  356. // checkType checks whether the given path is the exact pathType
  357. func checkType(path string, pathType *v1.HostPathType, mounter mount.Interface) error {
  358. return checkTypeInternal(newFileTypeChecker(path, mounter), pathType)
  359. }
  360. func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error {
  361. switch *pathType {
  362. case v1.HostPathDirectoryOrCreate:
  363. if !ftc.Exists() {
  364. return ftc.MakeDir()
  365. }
  366. fallthrough
  367. case v1.HostPathDirectory:
  368. if !ftc.IsDir() {
  369. return fmt.Errorf("hostPath type check failed: %s is not a directory", ftc.GetPath())
  370. }
  371. case v1.HostPathFileOrCreate:
  372. if !ftc.Exists() {
  373. return ftc.MakeFile()
  374. }
  375. fallthrough
  376. case v1.HostPathFile:
  377. if !ftc.IsFile() {
  378. return fmt.Errorf("hostPath type check failed: %s is not a file", ftc.GetPath())
  379. }
  380. case v1.HostPathSocket:
  381. if !ftc.IsSocket() {
  382. return fmt.Errorf("hostPath type check failed: %s is not a socket file", ftc.GetPath())
  383. }
  384. case v1.HostPathCharDev:
  385. if !ftc.IsChar() {
  386. return fmt.Errorf("hostPath type check failed: %s is not a character device", ftc.GetPath())
  387. }
  388. case v1.HostPathBlockDev:
  389. if !ftc.IsBlock() {
  390. return fmt.Errorf("hostPath type check failed: %s is not a block device", ftc.GetPath())
  391. }
  392. default:
  393. return fmt.Errorf("%s is an invalid volume type", *pathType)
  394. }
  395. return nil
  396. }