k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/portworx/portworx.go (about)

     1  /*
     2  Copyright 2017 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 portworx
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"os"
    23  	"strconv"
    24  
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/mount-utils"
    27  	utilstrings "k8s.io/utils/strings"
    28  
    29  	volumeclient "github.com/libopenstorage/openstorage/api/client/volume"
    30  	v1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    35  	"k8s.io/kubernetes/pkg/features"
    36  	"k8s.io/kubernetes/pkg/volume"
    37  	"k8s.io/kubernetes/pkg/volume/util"
    38  )
    39  
    40  const (
    41  	attachContextKey = "context"
    42  	attachHostKey    = "host"
    43  )
    44  
    45  // ProbeVolumePlugins is the primary entrypoint for volume plugins.
    46  func ProbeVolumePlugins() []volume.VolumePlugin {
    47  	return []volume.VolumePlugin{&portworxVolumePlugin{nil, nil}}
    48  }
    49  
    50  type portworxVolumePlugin struct {
    51  	host volume.VolumeHost
    52  	util *portworxVolumeUtil
    53  }
    54  
    55  var _ volume.VolumePlugin = &portworxVolumePlugin{}
    56  var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{}
    57  var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{}
    58  var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{}
    59  var _ volume.ExpandableVolumePlugin = &portworxVolumePlugin{}
    60  
    61  const (
    62  	portworxVolumePluginName = "kubernetes.io/portworx-volume"
    63  )
    64  
    65  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    66  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(portworxVolumePluginName), volName)
    67  }
    68  
    69  func (plugin *portworxVolumePlugin) IsMigratedToCSI() bool {
    70  	return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx)
    71  }
    72  
    73  func (plugin *portworxVolumePlugin) Init(host volume.VolumeHost) error {
    74  	client, err := volumeclient.NewDriverClient(
    75  		fmt.Sprintf("http://%s", net.JoinHostPort(host.GetHostName(), strconv.Itoa(osdMgmtDefaultPort))),
    76  		pxdDriverName, osdDriverVersion, pxDriverName)
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	plugin.host = host
    82  	plugin.util = &portworxVolumeUtil{
    83  		portworxClient: client,
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func (plugin *portworxVolumePlugin) GetPluginName() string {
    90  	return portworxVolumePluginName
    91  }
    92  
    93  func (plugin *portworxVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    94  	volumeSource, _, err := getVolumeSource(spec)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  
    99  	return volumeSource.VolumeID, nil
   100  }
   101  
   102  func (plugin *portworxVolumePlugin) CanSupport(spec *volume.Spec) bool {
   103  	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume != nil) ||
   104  		(spec.Volume != nil && spec.Volume.PortworxVolume != nil)
   105  }
   106  
   107  func (plugin *portworxVolumePlugin) RequiresRemount(spec *volume.Spec) bool {
   108  	return false
   109  }
   110  
   111  func (plugin *portworxVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
   112  	return []v1.PersistentVolumeAccessMode{
   113  		v1.ReadWriteOnce,
   114  		v1.ReadWriteMany,
   115  	}
   116  }
   117  
   118  func (plugin *portworxVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
   119  	return plugin.newMounterInternal(spec, pod.UID, plugin.util, plugin.host.GetMounter(plugin.GetPluginName()))
   120  }
   121  
   122  func (plugin *portworxVolumePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager portworxManager, mounter mount.Interface) (volume.Mounter, error) {
   123  	pwx, readOnly, err := getVolumeSource(spec)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	volumeID := pwx.VolumeID
   129  	fsType := pwx.FSType
   130  
   131  	return &portworxVolumeMounter{
   132  		portworxVolume: &portworxVolume{
   133  			podUID:          podUID,
   134  			volName:         spec.Name(),
   135  			volumeID:        volumeID,
   136  			manager:         manager,
   137  			mounter:         mounter,
   138  			plugin:          plugin,
   139  			MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)),
   140  		},
   141  		fsType:      fsType,
   142  		readOnly:    readOnly,
   143  		diskMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host)}, nil
   144  }
   145  
   146  func (plugin *portworxVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   147  	return plugin.newUnmounterInternal(volName, podUID, plugin.util, plugin.host.GetMounter(plugin.GetPluginName()))
   148  }
   149  
   150  func (plugin *portworxVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, manager portworxManager,
   151  	mounter mount.Interface) (volume.Unmounter, error) {
   152  	return &portworxVolumeUnmounter{
   153  		&portworxVolume{
   154  			podUID:          podUID,
   155  			volName:         volName,
   156  			manager:         manager,
   157  			mounter:         mounter,
   158  			plugin:          plugin,
   159  			MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
   160  		}}, nil
   161  }
   162  
   163  func (plugin *portworxVolumePlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) {
   164  	return plugin.newDeleterInternal(spec, plugin.util)
   165  }
   166  
   167  func (plugin *portworxVolumePlugin) newDeleterInternal(spec *volume.Spec, manager portworxManager) (volume.Deleter, error) {
   168  	if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume == nil {
   169  		return nil, fmt.Errorf("spec.PersistentVolumeSource.PortworxVolume is nil")
   170  	}
   171  
   172  	return &portworxVolumeDeleter{
   173  		portworxVolume: &portworxVolume{
   174  			volName:  spec.Name(),
   175  			volumeID: spec.PersistentVolume.Spec.PortworxVolume.VolumeID,
   176  			manager:  manager,
   177  			plugin:   plugin,
   178  		}}, nil
   179  }
   180  
   181  func (plugin *portworxVolumePlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) {
   182  	return plugin.newProvisionerInternal(options, plugin.util)
   183  }
   184  
   185  func (plugin *portworxVolumePlugin) newProvisionerInternal(options volume.VolumeOptions, manager portworxManager) (volume.Provisioner, error) {
   186  	return &portworxVolumeProvisioner{
   187  		portworxVolume: &portworxVolume{
   188  			manager: manager,
   189  			plugin:  plugin,
   190  		},
   191  		options: options,
   192  	}, nil
   193  }
   194  
   195  func (plugin *portworxVolumePlugin) RequiresFSResize() bool {
   196  	return false
   197  }
   198  
   199  func (plugin *portworxVolumePlugin) ExpandVolumeDevice(
   200  	spec *volume.Spec,
   201  	newSize resource.Quantity,
   202  	oldSize resource.Quantity) (resource.Quantity, error) {
   203  	klog.V(4).Infof("Expanding: %s from %v to %v", spec.Name(), oldSize, newSize)
   204  	err := plugin.util.ResizeVolume(spec, newSize, plugin.host)
   205  	if err != nil {
   206  		return oldSize, err
   207  	}
   208  
   209  	klog.V(4).Infof("Successfully resized %s to %v", spec.Name(), newSize)
   210  	return newSize, nil
   211  }
   212  
   213  func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   214  	portworxVolume := &v1.Volume{
   215  		Name: volumeName,
   216  		VolumeSource: v1.VolumeSource{
   217  			PortworxVolume: &v1.PortworxVolumeSource{
   218  				VolumeID: volumeName,
   219  			},
   220  		},
   221  	}
   222  	return volume.ReconstructedVolume{
   223  		Spec: volume.NewSpecFromVolume(portworxVolume),
   224  	}, nil
   225  }
   226  
   227  func (plugin *portworxVolumePlugin) SupportsMountOption() bool {
   228  	return false
   229  }
   230  
   231  func (plugin *portworxVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
   232  	return false, nil
   233  }
   234  
   235  func getVolumeSource(
   236  	spec *volume.Spec) (*v1.PortworxVolumeSource, bool, error) {
   237  	if spec.Volume != nil && spec.Volume.PortworxVolume != nil {
   238  		return spec.Volume.PortworxVolume, spec.Volume.PortworxVolume.ReadOnly, nil
   239  	} else if spec.PersistentVolume != nil &&
   240  		spec.PersistentVolume.Spec.PortworxVolume != nil {
   241  		return spec.PersistentVolume.Spec.PortworxVolume, spec.ReadOnly, nil
   242  	}
   243  
   244  	return nil, false, fmt.Errorf("Spec does not reference a Portworx Volume type")
   245  }
   246  
   247  // Abstract interface to PD operations.
   248  type portworxManager interface {
   249  	// Creates a volume
   250  	CreateVolume(provisioner *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error)
   251  	// Deletes a volume
   252  	DeleteVolume(deleter *portworxVolumeDeleter) error
   253  	// Attach a volume
   254  	AttachVolume(mounter *portworxVolumeMounter, attachOptions map[string]string) (string, error)
   255  	// Detach a volume
   256  	DetachVolume(unmounter *portworxVolumeUnmounter) error
   257  	// Mount a volume
   258  	MountVolume(mounter *portworxVolumeMounter, mountDir string) error
   259  	// Unmount a volume
   260  	UnmountVolume(unmounter *portworxVolumeUnmounter, mountDir string) error
   261  	// Resize a volume
   262  	ResizeVolume(spec *volume.Spec, newSize resource.Quantity, host volume.VolumeHost) error
   263  }
   264  
   265  // portworxVolume volumes are portworx block devices
   266  // that are attached to the kubelet's host machine and exposed to the pod.
   267  type portworxVolume struct {
   268  	volName string
   269  	podUID  types.UID
   270  	// Unique id of the PD, used to find the disk resource in the provider.
   271  	volumeID string
   272  	// Utility interface that provides API calls to the provider to attach/detach disks.
   273  	manager portworxManager
   274  	// Mounter interface that provides system calls to mount the global path to the pod local path.
   275  	mounter mount.Interface
   276  	plugin  *portworxVolumePlugin
   277  	volume.MetricsProvider
   278  }
   279  
   280  type portworxVolumeMounter struct {
   281  	*portworxVolume
   282  	// Filesystem type, optional.
   283  	fsType string
   284  	// Specifies whether the disk will be attached as read-only.
   285  	readOnly bool
   286  	// diskMounter provides the interface that is used to mount the actual block device.
   287  	diskMounter *mount.SafeFormatAndMount
   288  }
   289  
   290  var _ volume.Mounter = &portworxVolumeMounter{}
   291  
   292  func (b *portworxVolumeMounter) GetAttributes() volume.Attributes {
   293  	return volume.Attributes{
   294  		ReadOnly:       b.readOnly,
   295  		Managed:        !b.readOnly,
   296  		SELinuxRelabel: false,
   297  	}
   298  }
   299  
   300  // SetUp attaches the disk and bind mounts to the volume path.
   301  func (b *portworxVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   302  	return b.SetUpAt(b.GetPath(), mounterArgs)
   303  }
   304  
   305  // SetUpAt attaches the disk and bind mounts to the volume path.
   306  func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   307  	notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
   308  	klog.Infof("Portworx Volume set up. Dir: %s %v %v", dir, !notMnt, err)
   309  	if err != nil && !os.IsNotExist(err) {
   310  		klog.Errorf("Cannot validate mountpoint: %s", dir)
   311  		return err
   312  	}
   313  	if !notMnt {
   314  		return nil
   315  	}
   316  
   317  	attachOptions := make(map[string]string)
   318  	attachOptions[attachContextKey] = dir
   319  	attachOptions[attachHostKey] = b.plugin.host.GetHostName()
   320  	if _, err := b.manager.AttachVolume(b, attachOptions); err != nil {
   321  		return err
   322  	}
   323  
   324  	klog.V(4).Infof("Portworx Volume %s attached", b.volumeID)
   325  
   326  	if err := os.MkdirAll(dir, 0750); err != nil {
   327  		return err
   328  	}
   329  
   330  	if err := b.manager.MountVolume(b, dir); err != nil {
   331  		return err
   332  	}
   333  	if !b.readOnly {
   334  		volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil))
   335  	}
   336  	klog.Infof("Portworx Volume %s setup at %s", b.volumeID, dir)
   337  	return nil
   338  }
   339  
   340  func (pwx *portworxVolume) GetPath() string {
   341  	return getPath(pwx.podUID, pwx.volName, pwx.plugin.host)
   342  }
   343  
   344  type portworxVolumeUnmounter struct {
   345  	*portworxVolume
   346  }
   347  
   348  var _ volume.Unmounter = &portworxVolumeUnmounter{}
   349  
   350  // Unmounts the bind mount, and detaches the disk only if the PD
   351  // resource was the last reference to that disk on the kubelet.
   352  func (c *portworxVolumeUnmounter) TearDown() error {
   353  	return c.TearDownAt(c.GetPath())
   354  }
   355  
   356  // Unmounts the bind mount, and detaches the disk only if the PD
   357  // resource was the last reference to that disk on the kubelet.
   358  func (c *portworxVolumeUnmounter) TearDownAt(dir string) error {
   359  	klog.Infof("Portworx Volume TearDown of %s", dir)
   360  
   361  	if err := c.manager.UnmountVolume(c, dir); err != nil {
   362  		return err
   363  	}
   364  
   365  	// Call Portworx Detach Volume.
   366  	if err := c.manager.DetachVolume(c); err != nil {
   367  		return err
   368  	}
   369  
   370  	return nil
   371  }
   372  
   373  type portworxVolumeDeleter struct {
   374  	*portworxVolume
   375  }
   376  
   377  var _ volume.Deleter = &portworxVolumeDeleter{}
   378  
   379  func (d *portworxVolumeDeleter) GetPath() string {
   380  	return getPath(d.podUID, d.volName, d.plugin.host)
   381  }
   382  
   383  func (d *portworxVolumeDeleter) Delete() error {
   384  	return d.manager.DeleteVolume(d)
   385  }
   386  
   387  type portworxVolumeProvisioner struct {
   388  	*portworxVolume
   389  	options volume.VolumeOptions
   390  }
   391  
   392  var _ volume.Provisioner = &portworxVolumeProvisioner{}
   393  
   394  func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
   395  	if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
   396  		return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
   397  	}
   398  
   399  	if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) {
   400  		return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName())
   401  	}
   402  
   403  	volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	pv := &v1.PersistentVolume{
   409  		ObjectMeta: metav1.ObjectMeta{
   410  			Name:   c.options.PVName,
   411  			Labels: map[string]string{},
   412  			Annotations: map[string]string{
   413  				util.VolumeDynamicallyCreatedByKey: "portworx-volume-dynamic-provisioner",
   414  			},
   415  		},
   416  		Spec: v1.PersistentVolumeSpec{
   417  			PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
   418  			AccessModes:                   c.options.PVC.Spec.AccessModes,
   419  			Capacity: v1.ResourceList{
   420  				v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGiB)),
   421  			},
   422  			PersistentVolumeSource: v1.PersistentVolumeSource{
   423  				PortworxVolume: &v1.PortworxVolumeSource{
   424  					VolumeID: volumeID,
   425  				},
   426  			},
   427  		},
   428  	}
   429  
   430  	if len(labels) != 0 {
   431  		if pv.Labels == nil {
   432  			pv.Labels = make(map[string]string)
   433  		}
   434  		for k, v := range labels {
   435  			pv.Labels[k] = v
   436  		}
   437  	}
   438  
   439  	if len(c.options.PVC.Spec.AccessModes) == 0 {
   440  		pv.Spec.AccessModes = c.plugin.GetAccessModes()
   441  	}
   442  
   443  	return pv, nil
   444  }