...

Source file src/k8s.io/kubernetes/pkg/volume/downwardapi/downwardapi.go

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

     1  /*
     2  Copyright 2015 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 downwardapi
    18  
    19  import (
    20  	"fmt"
    21  	"path/filepath"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/klog/v2"
    27  	"k8s.io/kubernetes/pkg/api/v1/resource"
    28  	"k8s.io/kubernetes/pkg/fieldpath"
    29  	"k8s.io/kubernetes/pkg/volume"
    30  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    31  	utilstrings "k8s.io/utils/strings"
    32  )
    33  
    34  // ProbeVolumePlugins is the entry point for plugin detection in a package.
    35  func ProbeVolumePlugins() []volume.VolumePlugin {
    36  	return []volume.VolumePlugin{&downwardAPIPlugin{}}
    37  }
    38  
    39  const (
    40  	downwardAPIPluginName = "kubernetes.io/downward-api"
    41  )
    42  
    43  // downwardAPIPlugin implements the VolumePlugin interface.
    44  type downwardAPIPlugin struct {
    45  	host volume.VolumeHost
    46  }
    47  
    48  var _ volume.VolumePlugin = &downwardAPIPlugin{}
    49  
    50  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    51  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(downwardAPIPluginName), volName)
    52  }
    53  
    54  func wrappedVolumeSpec() volume.Spec {
    55  	return volume.Spec{
    56  		Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}}},
    57  	}
    58  }
    59  
    60  func (plugin *downwardAPIPlugin) Init(host volume.VolumeHost) error {
    61  	plugin.host = host
    62  	return nil
    63  }
    64  
    65  func (plugin *downwardAPIPlugin) GetPluginName() string {
    66  	return downwardAPIPluginName
    67  }
    68  
    69  func (plugin *downwardAPIPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    70  	volumeSource, _ := getVolumeSource(spec)
    71  	if volumeSource == nil {
    72  		return "", fmt.Errorf("Spec does not reference a DownwardAPI volume type")
    73  	}
    74  
    75  	// Return user defined volume name, since this is an ephemeral volume type
    76  	return spec.Name(), nil
    77  }
    78  
    79  func (plugin *downwardAPIPlugin) CanSupport(spec *volume.Spec) bool {
    80  	return spec.Volume != nil && spec.Volume.DownwardAPI != nil
    81  }
    82  
    83  func (plugin *downwardAPIPlugin) RequiresRemount(spec *volume.Spec) bool {
    84  	return true
    85  }
    86  
    87  func (plugin *downwardAPIPlugin) SupportsMountOption() bool {
    88  	return false
    89  }
    90  
    91  func (plugin *downwardAPIPlugin) SupportsBulkVolumeVerification() bool {
    92  	return false
    93  }
    94  
    95  func (plugin *downwardAPIPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
    96  	return false, nil
    97  }
    98  
    99  func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
   100  	v := &downwardAPIVolume{
   101  		volName:         spec.Name(),
   102  		items:           spec.Volume.DownwardAPI.Items,
   103  		pod:             pod,
   104  		podUID:          pod.UID,
   105  		plugin:          plugin,
   106  		MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))),
   107  	}
   108  	return &downwardAPIVolumeMounter{
   109  		downwardAPIVolume: v,
   110  		source:            *spec.Volume.DownwardAPI,
   111  		opts:              &opts,
   112  	}, nil
   113  }
   114  
   115  func (plugin *downwardAPIPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   116  	return &downwardAPIVolumeUnmounter{
   117  		&downwardAPIVolume{
   118  			volName:         volName,
   119  			podUID:          podUID,
   120  			plugin:          plugin,
   121  			MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))),
   122  		},
   123  	}, nil
   124  }
   125  
   126  func (plugin *downwardAPIPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   127  	downwardAPIVolume := &v1.Volume{
   128  		Name: volumeName,
   129  		VolumeSource: v1.VolumeSource{
   130  			DownwardAPI: &v1.DownwardAPIVolumeSource{},
   131  		},
   132  	}
   133  	return volume.ReconstructedVolume{
   134  		Spec: volume.NewSpecFromVolume(downwardAPIVolume),
   135  	}, nil
   136  }
   137  
   138  // downwardAPIVolume retrieves downward API data and placing them into the volume on the host.
   139  type downwardAPIVolume struct {
   140  	volName string
   141  	items   []v1.DownwardAPIVolumeFile
   142  	pod     *v1.Pod
   143  	podUID  types.UID // TODO: remove this redundancy as soon NewUnmounter func will have *v1.POD and not only types.UID
   144  	plugin  *downwardAPIPlugin
   145  	volume.MetricsProvider
   146  }
   147  
   148  // downwardAPIVolumeMounter fetches info from downward API from the pod
   149  // and dumps it in files
   150  type downwardAPIVolumeMounter struct {
   151  	*downwardAPIVolume
   152  	source v1.DownwardAPIVolumeSource
   153  	opts   *volume.VolumeOptions
   154  }
   155  
   156  // downwardAPIVolumeMounter implements volume.Mounter interface
   157  var _ volume.Mounter = &downwardAPIVolumeMounter{}
   158  
   159  // downward API volumes are always ReadOnlyManaged
   160  func (d *downwardAPIVolume) GetAttributes() volume.Attributes {
   161  	return volume.Attributes{
   162  		ReadOnly:       true,
   163  		Managed:        true,
   164  		SELinuxRelabel: true,
   165  	}
   166  }
   167  
   168  // SetUp puts in place the volume plugin.
   169  // This function is not idempotent by design. We want the data to be refreshed periodically.
   170  // The internal sync interval of kubelet will drive the refresh of data.
   171  // TODO: Add volume specific ticker and refresh loop
   172  func (b *downwardAPIVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   173  	return b.SetUpAt(b.GetPath(), mounterArgs)
   174  }
   175  
   176  func (b *downwardAPIVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   177  	klog.V(3).Infof("Setting up a downwardAPI volume %v for pod %v/%v at %v", b.volName, b.pod.Namespace, b.pod.Name, dir)
   178  	// Wrap EmptyDir. Here we rely on the idempotency of the wrapped plugin to avoid repeatedly mounting
   179  	wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), b.pod, *b.opts)
   180  	if err != nil {
   181  		klog.Errorf("Couldn't setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   182  		return err
   183  	}
   184  
   185  	data, err := CollectData(b.source.Items, b.pod, b.plugin.host, b.source.DefaultMode)
   186  	if err != nil {
   187  		klog.Errorf("Error preparing data for downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   188  		return err
   189  	}
   190  
   191  	setupSuccess := false
   192  	if err := wrapped.SetUpAt(dir, mounterArgs); err != nil {
   193  		klog.Errorf("Unable to setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   194  		return err
   195  	}
   196  
   197  	if err := volumeutil.MakeNestedMountpoints(b.volName, dir, *b.pod); err != nil {
   198  		return err
   199  	}
   200  
   201  	defer func() {
   202  		// Clean up directories if setup fails
   203  		if !setupSuccess {
   204  			unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID)
   205  			if unmountCreateErr != nil {
   206  				klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr)
   207  				return
   208  			}
   209  			tearDownErr := unmounter.TearDown()
   210  			if tearDownErr != nil {
   211  				klog.Errorf("error tearing down volume %s with : %v", b.volName, tearDownErr)
   212  			}
   213  		}
   214  	}()
   215  
   216  	writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
   217  	writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
   218  	if err != nil {
   219  		klog.Errorf("Error creating atomic writer: %v", err)
   220  		return err
   221  	}
   222  
   223  	setPerms := func(_ string) error {
   224  		// This may be the first time writing and new files get created outside the timestamp subdirectory:
   225  		// change the permissions on the whole volume and not only in the timestamp directory.
   226  		return volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
   227  	}
   228  	err = writer.Write(data, setPerms)
   229  	if err != nil {
   230  		klog.Errorf("Error writing payload to dir: %v", err)
   231  		return err
   232  	}
   233  
   234  	setupSuccess = true
   235  	return nil
   236  }
   237  
   238  // CollectData collects requested downwardAPI in data map.
   239  // Map's key is the requested name of file to dump
   240  // Map's value is the (sorted) content of the field to be dumped in the file.
   241  //
   242  // Note: this function is exported so that it can be called from the projection volume driver
   243  func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.VolumeHost, defaultMode *int32) (map[string]volumeutil.FileProjection, error) {
   244  	if defaultMode == nil {
   245  		return nil, fmt.Errorf("no defaultMode used, not even the default value for it")
   246  	}
   247  
   248  	errlist := []error{}
   249  	data := make(map[string]volumeutil.FileProjection)
   250  	for _, fileInfo := range items {
   251  		var fileProjection volumeutil.FileProjection
   252  		fPath := filepath.Clean(fileInfo.Path)
   253  		if fileInfo.Mode != nil {
   254  			fileProjection.Mode = *fileInfo.Mode
   255  		} else {
   256  			fileProjection.Mode = *defaultMode
   257  		}
   258  		if fileInfo.FieldRef != nil {
   259  			// TODO: unify with Kubelet.podFieldSelectorRuntimeValue
   260  			if values, err := fieldpath.ExtractFieldPathAsString(pod, fileInfo.FieldRef.FieldPath); err != nil {
   261  				klog.Errorf("Unable to extract field %s: %s", fileInfo.FieldRef.FieldPath, err.Error())
   262  				errlist = append(errlist, err)
   263  			} else {
   264  				fileProjection.Data = []byte(values)
   265  			}
   266  		} else if fileInfo.ResourceFieldRef != nil {
   267  			containerName := fileInfo.ResourceFieldRef.ContainerName
   268  			nodeAllocatable, err := host.GetNodeAllocatable()
   269  			if err != nil {
   270  				errlist = append(errlist, err)
   271  			} else if values, err := resource.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil {
   272  				klog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
   273  				errlist = append(errlist, err)
   274  			} else {
   275  				fileProjection.Data = []byte(values)
   276  			}
   277  		}
   278  
   279  		data[fPath] = fileProjection
   280  	}
   281  	return data, utilerrors.NewAggregate(errlist)
   282  }
   283  
   284  func (d *downwardAPIVolume) GetPath() string {
   285  	return d.plugin.host.GetPodVolumeDir(d.podUID, utilstrings.EscapeQualifiedName(downwardAPIPluginName), d.volName)
   286  }
   287  
   288  // downwardAPIVolumeCleaner handles cleaning up downwardAPI volumes
   289  type downwardAPIVolumeUnmounter struct {
   290  	*downwardAPIVolume
   291  }
   292  
   293  // downwardAPIVolumeUnmounter implements volume.Unmounter interface
   294  var _ volume.Unmounter = &downwardAPIVolumeUnmounter{}
   295  
   296  func (c *downwardAPIVolumeUnmounter) TearDown() error {
   297  	return c.TearDownAt(c.GetPath())
   298  }
   299  
   300  func (c *downwardAPIVolumeUnmounter) TearDownAt(dir string) error {
   301  	return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID)
   302  }
   303  
   304  func getVolumeSource(spec *volume.Spec) (*v1.DownwardAPIVolumeSource, bool) {
   305  	var readOnly bool
   306  	var volumeSource *v1.DownwardAPIVolumeSource
   307  
   308  	if spec.Volume != nil && spec.Volume.DownwardAPI != nil {
   309  		volumeSource = spec.Volume.DownwardAPI
   310  		readOnly = spec.ReadOnly
   311  	}
   312  
   313  	return volumeSource, readOnly
   314  }
   315  

View as plain text