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  }