...

Source file src/k8s.io/kubernetes/pkg/volume/hostpath/host_path.go

Documentation: k8s.io/kubernetes/pkg/volume/hostpath

     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  }
   531  

View as plain text