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