1
16
17 package hostpath
18
19 import (
20 "fmt"
21 "k8s.io/klog/v2"
22 "os"
23 "regexp"
24
25 "github.com/opencontainers/selinux/go-selinux"
26
27 v1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/apimachinery/pkg/util/uuid"
31 "k8s.io/kubernetes/pkg/kubelet/config"
32 "k8s.io/kubernetes/pkg/volume"
33 "k8s.io/kubernetes/pkg/volume/util"
34 "k8s.io/kubernetes/pkg/volume/util/hostutil"
35 "k8s.io/kubernetes/pkg/volume/util/recyclerclient"
36 "k8s.io/kubernetes/pkg/volume/validation"
37 "k8s.io/mount-utils"
38 )
39
40
41
42
43
44 func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
45 return []volume.VolumePlugin{
46 &hostPathPlugin{
47 host: nil,
48 config: volumeConfig,
49 },
50 }
51 }
52
53 func FakeProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
54 return []volume.VolumePlugin{
55 &hostPathPlugin{
56 host: nil,
57 config: volumeConfig,
58 noTypeChecker: true,
59 },
60 }
61 }
62
63 type hostPathPlugin struct {
64 host volume.VolumeHost
65 config volume.VolumeConfig
66 noTypeChecker bool
67 }
68
69 var _ volume.VolumePlugin = &hostPathPlugin{}
70 var _ volume.PersistentVolumePlugin = &hostPathPlugin{}
71 var _ volume.RecyclableVolumePlugin = &hostPathPlugin{}
72 var _ volume.DeletableVolumePlugin = &hostPathPlugin{}
73 var _ volume.ProvisionableVolumePlugin = &hostPathPlugin{}
74
75 const (
76 hostPathPluginName = "kubernetes.io/host-path"
77 )
78
79 func (plugin *hostPathPlugin) Init(host volume.VolumeHost) error {
80 plugin.host = host
81 return nil
82 }
83
84 func (plugin *hostPathPlugin) GetPluginName() string {
85 return hostPathPluginName
86 }
87
88 func (plugin *hostPathPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
89 volumeSource, _, err := getVolumeSource(spec)
90 if err != nil {
91 return "", err
92 }
93
94 return volumeSource.Path, nil
95 }
96
97 func (plugin *hostPathPlugin) CanSupport(spec *volume.Spec) bool {
98 return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.HostPath != nil) ||
99 (spec.Volume != nil && spec.Volume.HostPath != nil)
100 }
101
102 func (plugin *hostPathPlugin) RequiresRemount(spec *volume.Spec) bool {
103 return false
104 }
105
106 func (plugin *hostPathPlugin) SupportsMountOption() bool {
107 return false
108 }
109
110 func (plugin *hostPathPlugin) SupportsBulkVolumeVerification() bool {
111 return false
112 }
113
114 func (plugin *hostPathPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
115 return false, nil
116 }
117
118 func (plugin *hostPathPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
119 return []v1.PersistentVolumeAccessMode{
120 v1.ReadWriteOnce,
121 }
122 }
123
124 func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
125 hostPathVolumeSource, readOnly, err := getVolumeSource(spec)
126 if err != nil {
127 return nil, err
128 }
129
130 path := hostPathVolumeSource.Path
131 pathType := new(v1.HostPathType)
132 if hostPathVolumeSource.Type == nil {
133 *pathType = v1.HostPathUnset
134 } else {
135 pathType = hostPathVolumeSource.Type
136 }
137 kvh, ok := plugin.host.(volume.KubeletVolumeHost)
138 if !ok {
139 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
140 }
141 return &hostPathMounter{
142 hostPath: &hostPath{path: path, pathType: pathType},
143 readOnly: readOnly,
144 mounter: plugin.host.GetMounter(plugin.GetPluginName()),
145 hu: kvh.GetHostUtil(),
146 noTypeChecker: plugin.noTypeChecker,
147 }, nil
148 }
149
150 func (plugin *hostPathPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
151 return &hostPathUnmounter{&hostPath{
152 path: "",
153 }}, nil
154 }
155
156
157
158
159 func (plugin *hostPathPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
160 if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.HostPath == nil {
161 return fmt.Errorf("spec.PersistentVolume.Spec.HostPath is nil")
162 }
163
164 pod := plugin.config.RecyclerPodTemplate
165 timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume)
166
167 pod.Spec.ActiveDeadlineSeconds = &timeout
168 pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{
169 HostPath: &v1.HostPathVolumeSource{
170 Path: spec.PersistentVolume.Spec.HostPath.Path,
171 },
172 }
173 return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder)
174 }
175
176 func (plugin *hostPathPlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) {
177 return newDeleter(spec, plugin.host)
178 }
179
180 func (plugin *hostPathPlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) {
181 if !plugin.config.ProvisioningEnabled {
182 return nil, fmt.Errorf("provisioning in volume plugin %q is disabled", plugin.GetPluginName())
183 }
184 return newProvisioner(options, plugin.host, plugin)
185 }
186
187 func (plugin *hostPathPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
188 hostPathVolume := &v1.Volume{
189 Name: volumeName,
190 VolumeSource: v1.VolumeSource{
191 HostPath: &v1.HostPathVolumeSource{
192 Path: volumeName,
193 },
194 },
195 }
196 return volume.ReconstructedVolume{
197 Spec: volume.NewSpecFromVolume(hostPathVolume),
198 }, nil
199 }
200
201 func newDeleter(spec *volume.Spec, host volume.VolumeHost) (volume.Deleter, error) {
202 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.HostPath == nil {
203 return nil, fmt.Errorf("spec.PersistentVolumeSource.HostPath is nil")
204 }
205 path := spec.PersistentVolume.Spec.HostPath.Path
206 return &hostPathDeleter{name: spec.Name(), path: path, host: host}, nil
207 }
208
209 func newProvisioner(options volume.VolumeOptions, host volume.VolumeHost, plugin *hostPathPlugin) (volume.Provisioner, error) {
210 return &hostPathProvisioner{options: options, host: host, plugin: plugin, basePath: "hostpath_pv"}, nil
211 }
212
213
214
215 type hostPath struct {
216 path string
217 pathType *v1.HostPathType
218 volume.MetricsNil
219 }
220
221 func (hp *hostPath) GetPath() string {
222 return hp.path
223 }
224
225 type hostPathMounter struct {
226 *hostPath
227 readOnly bool
228 mounter mount.Interface
229 hu hostutil.HostUtils
230 noTypeChecker bool
231 }
232
233 var _ volume.Mounter = &hostPathMounter{}
234
235 func (b *hostPathMounter) GetAttributes() volume.Attributes {
236 return volume.Attributes{
237 ReadOnly: b.readOnly,
238 Managed: false,
239 SELinuxRelabel: false,
240 }
241 }
242
243
244 func (b *hostPathMounter) SetUp(mounterArgs volume.MounterArgs) error {
245 err := validation.ValidatePathNoBacksteps(b.GetPath())
246 if err != nil {
247 return fmt.Errorf("invalid HostPath `%s`: %v", b.GetPath(), err)
248 }
249
250 if *b.pathType == v1.HostPathUnset {
251 return nil
252 }
253 if b.noTypeChecker {
254 return nil
255 } else {
256 return checkType(b.GetPath(), b.pathType, b.hu)
257 }
258 }
259
260
261 func (b *hostPathMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
262 return fmt.Errorf("SetUpAt() does not make sense for host paths")
263 }
264
265 func (b *hostPathMounter) GetPath() string {
266 return b.path
267 }
268
269 type hostPathUnmounter struct {
270 *hostPath
271 }
272
273 var _ volume.Unmounter = &hostPathUnmounter{}
274
275
276 func (c *hostPathUnmounter) TearDown() error {
277 return nil
278 }
279
280
281 func (c *hostPathUnmounter) TearDownAt(dir string) error {
282 return fmt.Errorf("TearDownAt() does not make sense for host paths")
283 }
284
285
286
287 type hostPathProvisioner struct {
288 host volume.VolumeHost
289 options volume.VolumeOptions
290 plugin *hostPathPlugin
291 basePath string
292 }
293
294
295
296 func (r *hostPathProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
297 if util.CheckPersistentVolumeClaimModeBlock(r.options.PVC) {
298 return nil, fmt.Errorf("%s does not support block volume provisioning", r.plugin.GetPluginName())
299 }
300
301 fullpath := fmt.Sprintf("/tmp/%s/%s", r.basePath, uuid.NewUUID())
302
303 capacity := r.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
304 pv := &v1.PersistentVolume{
305 ObjectMeta: metav1.ObjectMeta{
306 Name: r.options.PVName,
307 Annotations: map[string]string{
308 util.VolumeDynamicallyCreatedByKey: "hostpath-dynamic-provisioner",
309 },
310 },
311 Spec: v1.PersistentVolumeSpec{
312 PersistentVolumeReclaimPolicy: r.options.PersistentVolumeReclaimPolicy,
313 AccessModes: r.options.PVC.Spec.AccessModes,
314 Capacity: v1.ResourceList{
315 v1.ResourceName(v1.ResourceStorage): capacity,
316 },
317 PersistentVolumeSource: v1.PersistentVolumeSource{
318 HostPath: &v1.HostPathVolumeSource{
319 Path: fullpath,
320 },
321 },
322 },
323 }
324 if len(r.options.PVC.Spec.AccessModes) == 0 {
325 pv.Spec.AccessModes = r.plugin.GetAccessModes()
326 }
327
328 if err := os.MkdirAll(pv.Spec.HostPath.Path, 0750); err != nil {
329 return nil, err
330 }
331 if selinux.GetEnabled() {
332 err := selinux.SetFileLabel(pv.Spec.HostPath.Path, config.KubeletContainersSharedSELinuxLabel)
333 if err != nil {
334 return nil, fmt.Errorf("failed to set selinux label for %q: %v", pv.Spec.HostPath.Path, err)
335 }
336 }
337
338 return pv, nil
339 }
340
341
342
343 type hostPathDeleter struct {
344 name string
345 path string
346 host volume.VolumeHost
347 volume.MetricsNil
348 }
349
350 func (r *hostPathDeleter) GetPath() string {
351 return r.path
352 }
353
354
355
356
357 func (r *hostPathDeleter) Delete() error {
358 regexp := regexp.MustCompile("/tmp/.+")
359 if !regexp.MatchString(r.GetPath()) {
360 return fmt.Errorf("host_path deleter only supports /tmp/.+ but received provided %s", r.GetPath())
361 }
362 return os.RemoveAll(r.GetPath())
363 }
364
365 func getVolumeSource(spec *volume.Spec) (*v1.HostPathVolumeSource, bool, error) {
366 if spec.Volume != nil && spec.Volume.HostPath != nil {
367 return spec.Volume.HostPath, spec.ReadOnly, nil
368 } else if spec.PersistentVolume != nil &&
369 spec.PersistentVolume.Spec.HostPath != nil {
370 return spec.PersistentVolume.Spec.HostPath, spec.ReadOnly, nil
371 }
372
373 return nil, false, fmt.Errorf("spec does not reference an HostPath volume type")
374 }
375
376 type hostPathTypeChecker interface {
377 Exists() bool
378 IsFile() bool
379 MakeFile() error
380 IsDir() bool
381 MakeDir() error
382 IsBlock() bool
383 IsChar() bool
384 IsSocket() bool
385 GetPath() string
386 }
387
388 type fileTypeChecker struct {
389 path string
390 hu hostutil.HostUtils
391 }
392
393 func (ftc *fileTypeChecker) Exists() bool {
394 exists, err := ftc.hu.PathExists(ftc.path)
395 return exists && err == nil
396 }
397
398 func (ftc *fileTypeChecker) IsFile() bool {
399 if !ftc.Exists() {
400 return false
401 }
402 pathType, err := ftc.hu.GetFileType(ftc.path)
403 if err != nil {
404 return false
405 }
406 return string(pathType) == string(v1.HostPathFile)
407 }
408
409 func (ftc *fileTypeChecker) MakeFile() error {
410 return makeFile(ftc.path)
411 }
412
413 func (ftc *fileTypeChecker) IsDir() bool {
414 if !ftc.Exists() {
415 return false
416 }
417 pathType, err := ftc.hu.GetFileType(ftc.path)
418 if err != nil {
419 return false
420 }
421 return string(pathType) == string(v1.HostPathDirectory)
422 }
423
424 func (ftc *fileTypeChecker) MakeDir() error {
425 return makeDir(ftc.path)
426 }
427
428 func (ftc *fileTypeChecker) IsBlock() bool {
429 blkDevType, err := ftc.hu.GetFileType(ftc.path)
430 if err != nil {
431 return false
432 }
433 return string(blkDevType) == string(v1.HostPathBlockDev)
434 }
435
436 func (ftc *fileTypeChecker) IsChar() bool {
437 charDevType, err := ftc.hu.GetFileType(ftc.path)
438 if err != nil {
439 return false
440 }
441 return string(charDevType) == string(v1.HostPathCharDev)
442 }
443
444 func (ftc *fileTypeChecker) IsSocket() bool {
445 socketType, err := ftc.hu.GetFileType(ftc.path)
446 if err != nil {
447 return false
448 }
449 return string(socketType) == string(v1.HostPathSocket)
450 }
451
452 func (ftc *fileTypeChecker) GetPath() string {
453 return ftc.path
454 }
455
456 func newFileTypeChecker(path string, hu hostutil.HostUtils) hostPathTypeChecker {
457 return &fileTypeChecker{path: path, hu: hu}
458 }
459
460
461 func checkType(path string, pathType *v1.HostPathType, hu hostutil.HostUtils) error {
462 return checkTypeInternal(newFileTypeChecker(path, hu), pathType)
463 }
464
465 func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error {
466 switch *pathType {
467 case v1.HostPathDirectoryOrCreate:
468 if !ftc.Exists() {
469 return ftc.MakeDir()
470 }
471 fallthrough
472 case v1.HostPathDirectory:
473 if !ftc.IsDir() {
474 return fmt.Errorf("hostPath type check failed: %s is not a directory", ftc.GetPath())
475 }
476 case v1.HostPathFileOrCreate:
477 if !ftc.Exists() {
478 return ftc.MakeFile()
479 }
480 fallthrough
481 case v1.HostPathFile:
482 if !ftc.IsFile() {
483 return fmt.Errorf("hostPath type check failed: %s is not a file", ftc.GetPath())
484 }
485 case v1.HostPathSocket:
486 if !ftc.IsSocket() {
487 return fmt.Errorf("hostPath type check failed: %s is not a socket file", ftc.GetPath())
488 }
489 case v1.HostPathCharDev:
490 if !ftc.IsChar() {
491 return fmt.Errorf("hostPath type check failed: %s is not a character device", ftc.GetPath())
492 }
493 case v1.HostPathBlockDev:
494 if !ftc.IsBlock() {
495 return fmt.Errorf("hostPath type check failed: %s is not a block device", ftc.GetPath())
496 }
497 default:
498 return fmt.Errorf("%s is an invalid volume type", *pathType)
499 }
500
501 return nil
502 }
503
504
505
506
507 func makeDir(pathname string) error {
508 err := os.MkdirAll(pathname, os.FileMode(0755))
509 if err != nil {
510 if !os.IsExist(err) {
511 return err
512 }
513 }
514 return nil
515 }
516
517
518
519 func makeFile(pathname string) error {
520 f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
521 if f != nil {
522 f.Close()
523 }
524 if err != nil {
525 if !os.IsExist(err) {
526 return err
527 }
528 }
529 return nil
530 }
531
View as plain text