k8s.io/kubernetes@v1.29.3/pkg/volume/hostpath/host_path.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // ProbeVolumePlugins is the primary entrypoint for volume plugins. 41 // The volumeConfig arg provides the ability to configure volume behavior. It is implemented as a pointer to allow nils. 42 // The hostPathPlugin is used to store the volumeConfig and give it, when needed, to the func that Recycles. 43 // Tests that exercise recycling should not use this func but instead use ProbeRecyclablePlugins() to override default behavior. 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 // Recycle recycles/scrubs clean a HostPath volume. 157 // Recycle blocks until the pod has completed or any error occurs. 158 // HostPath recycling only works in single node clusters and is meant for testing purposes only. 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 // overrides 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 // HostPath volumes represent a bare host file or directory mount. 214 // The direct at the specified path will be directly exposed to the container. 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 // SetUp does nothing. 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 // SetUpAt does not make sense for host paths - probably programmer error. 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 // TearDown does nothing. 276 func (c *hostPathUnmounter) TearDown() error { 277 return nil 278 } 279 280 // TearDownAt does not make sense for host paths - probably programmer error. 281 func (c *hostPathUnmounter) TearDownAt(dir string) error { 282 return fmt.Errorf("TearDownAt() does not make sense for host paths") 283 } 284 285 // hostPathProvisioner implements a Provisioner for the HostPath plugin 286 // This implementation is meant for testing only and only works in a single node cluster. 287 type hostPathProvisioner struct { 288 host volume.VolumeHost 289 options volume.VolumeOptions 290 plugin *hostPathPlugin 291 basePath string 292 } 293 294 // Create for hostPath simply creates a local /tmp/%/%s directory as a new PersistentVolume, default /tmp/hostpath_pv/%s. 295 // This Provisioner is meant for development and testing only and WILL NOT WORK in a multi-node cluster. 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 // hostPathDeleter deletes a hostPath PV from the cluster. 342 // This deleter only works on a single host cluster and is for testing purposes only. 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 // Delete for hostPath removes the local directory so long as it is beneath /tmp/*. 355 // THIS IS FOR TESTING AND LOCAL DEVELOPMENT ONLY! This message should scare you away from using 356 // this deleter for anything other than development and testing. 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 // checkType checks whether the given path is the exact pathType 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 // makeDir creates a new directory. 505 // If pathname already exists as a directory, no error is returned. 506 // If pathname already exists as a file, an error is returned. 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 // makeFile creates an empty file. 518 // If pathname already exists, whether a file or directory, no error is returned. 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 }