From de7be9d6133bb985d9f1d0fa029e03166f6cd0f4 Mon Sep 17 00:00:00 2001 From: Deep Debroy Date: Thu, 30 May 2019 09:35:22 +0000 Subject: [PATCH] Populate InlineVolumeSpec in CSI attacher and translation library Signed-off-by: Deep Debroy --- pkg/volume/csi/csi_attacher.go | 24 +++++++++-- pkg/volume/plugins.go | 7 ++-- .../operationexecutor/operation_generator.go | 21 +++++++--- .../csi-translation-lib/plugins/aws_ebs.go | 35 +++++++++++++++- .../csi-translation-lib/plugins/azure_disk.go | 40 ++++++++++++++++++ .../csi-translation-lib/plugins/azure_file.go | 35 ++++++++++++++++ .../csi-translation-lib/plugins/gce_pd.go | 42 ++++++++++++++++++- .../plugins/in_tree_volume.go | 13 +++++- .../plugins/openstack_cinder.go | 34 ++++++++++++++- .../k8s.io/csi-translation-lib/translate.go | 20 +++++++++ 10 files changed, 253 insertions(+), 18 deletions(-) diff --git a/pkg/volume/csi/csi_attacher.go b/pkg/volume/csi/csi_attacher.go index 57551a38154..9b1bd2a58fc 100644 --- a/pkg/volume/csi/csi_attacher.go +++ b/pkg/volume/csi/csi_attacher.go @@ -71,9 +71,27 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string } node := string(nodeName) - pvName := spec.PersistentVolume.GetName() attachID := getAttachmentName(pvSrc.VolumeHandle, pvSrc.Driver, node) + var vaSrc storage.VolumeAttachmentSource + if spec.InlineVolumeSpecForCSIMigration { + // inline PV scenario - use PV spec to populate VA source. + // The volume spec will be populated by CSI translation API + // for inline volumes. This allows fields required by the CSI + // attacher such as AccessMode and MountOptions (in addition to + // fields in the CSI persistent volume source) to be populated + // as part of CSI translation for inline volumes. + vaSrc = storage.VolumeAttachmentSource{ + InlineVolumeSpec: &spec.PersistentVolume.Spec, + } + } else { + // regular PV scenario - use PV name to populate VA source + pvName := spec.PersistentVolume.GetName() + vaSrc = storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + } + } + attachment := &storage.VolumeAttachment{ ObjectMeta: meta.ObjectMeta{ Name: attachID, @@ -81,9 +99,7 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string Spec: storage.VolumeAttachmentSpec{ NodeName: node, Attacher: pvSrc.Driver, - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &pvName, - }, + Source: vaSrc, }, } diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index d64a11f17aa..e41fc4e52c3 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -452,9 +452,10 @@ type VolumePluginMgr struct { // Spec is an internal representation of a volume. All API volume types translate to Spec. type Spec struct { - Volume *v1.Volume - PersistentVolume *v1.PersistentVolume - ReadOnly bool + Volume *v1.Volume + PersistentVolume *v1.PersistentVolume + ReadOnly bool + InlineVolumeSpecForCSIMigration bool } // Name returns the name of either Volume or PersistentVolume, one of which must not be nil. diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 5704be54258..ecdc088d6f4 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -1855,19 +1855,28 @@ func nodeUsingCSIPlugin(og *operationGenerator, spec *volume.Spec, nodeName type } func translateSpec(spec *volume.Spec) (*volume.Spec, error) { + var csiPV *v1.PersistentVolume + var err error + inlineVolume := false if spec.PersistentVolume != nil { // TranslateInTreePVToCSI will create a new PV - csiPV, err := csilib.TranslateInTreePVToCSI(spec.PersistentVolume) + csiPV, err = csilib.TranslateInTreePVToCSI(spec.PersistentVolume) if err != nil { return nil, fmt.Errorf("failed to translate in tree pv to CSI: %v", err) } - return &volume.Spec{ - PersistentVolume: csiPV, - ReadOnly: spec.ReadOnly, - }, nil } else if spec.Volume != nil { - return &volume.Spec{}, goerrors.New("translation is not supported for in-line volumes yet") + // TranslateInTreeInlineVolumeToCSI will create a new PV + csiPV, err = csilib.TranslateInTreeInlineVolumeToCSI(spec.Volume) + if err != nil { + return nil, fmt.Errorf("failed to translate in tree inline volume to CSI: %v", err) + } + inlineVolume = true } else { return &volume.Spec{}, goerrors.New("not a valid volume spec") } + return &volume.Spec{ + PersistentVolume: csiPV, + ReadOnly: spec.ReadOnly, + InlineVolumeSpecForCSIMigration: inlineVolume, + }, nil } diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go b/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go index 6a9a8f6a6c8..97c264f965e 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go @@ -49,6 +49,32 @@ func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeStorageClassToCSI(sc return sc, nil } +// TranslateInTreeInlineVolumeToCSI takes a Volume with AWSElasticBlockStore set from in-tree +// and converts the AWSElasticBlockStore source to a CSIPersistentVolumeSource +func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.AWSElasticBlockStore == nil { + return nil, fmt.Errorf("volume is nil or AWS EBS not defined on volume") + } + ebsSource := volume.AWSElasticBlockStore + pv := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: AWSEBSDriverName, + VolumeHandle: ebsSource.VolumeID, + ReadOnly: ebsSource.ReadOnly, + FSType: ebsSource.FSType, + VolumeAttributes: map[string]string{ + "partition": strconv.FormatInt(int64(ebsSource.Partition), 10), + }, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + return pv, nil +} + // TranslateInTreePVToCSI takes a PV with AWSElasticBlockStore set from in-tree // and converts the AWSElasticBlockStore source to a CSIPersistentVolumeSource func (t *awsElasticBlockStoreCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { @@ -106,13 +132,20 @@ func (t *awsElasticBlockStoreCSITranslator) TranslateCSIPVToInTree(pv *v1.Persis return pv, nil } -// CanSupport tests whether the plugin supports a given volume +// CanSupport tests whether the plugin supports a given persistent volume // specification from the API. The spec pointer should be considered // const. func (t *awsElasticBlockStoreCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { return pv != nil && pv.Spec.AWSElasticBlockStore != nil } +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (t *awsElasticBlockStoreCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.AWSElasticBlockStore != nil +} + // GetInTreePluginName returns the name of the intree plugin driver func (t *awsElasticBlockStoreCSITranslator) GetInTreePluginName() string { return AWSEBSInTreePluginName diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/azure_disk.go b/staging/src/k8s.io/csi-translation-lib/plugins/azure_disk.go index d5f0d605e7c..7b44c658b55 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/azure_disk.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/azure_disk.go @@ -58,6 +58,39 @@ func (t *azureDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.St return sc, nil } +// TranslateInTreeInlineVolumeToCSI takes a Volume with AzureDisk set from in-tree +// and converts the AzureDisk source to a CSIPersistentVolumeSource +func (t *azureDiskCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.AzureDisk == nil { + return nil, fmt.Errorf("volume is nil or Azure Disk not defined on volume") + } + + azureSource := volume.AzureDisk + pv := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: AzureDiskDriverName, + VolumeHandle: azureSource.DataDiskURI, + ReadOnly: *azureSource.ReadOnly, + FSType: *azureSource.FSType, + VolumeAttributes: map[string]string{}, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + + if *azureSource.CachingMode != "" { + pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes[azureDiskCachingMode] = string(*azureSource.CachingMode) + } + if *azureSource.FSType != "" { + pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes[azureDiskFSType] = *azureSource.FSType + } + + return pv, nil +} + // TranslateInTreePVToCSI takes a PV with AzureDisk set from in-tree // and converts the AzureDisk source to a CSIPersistentVolumeSource func (t *azureDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { @@ -137,6 +170,13 @@ func (t *azureDiskCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { return pv != nil && pv.Spec.AzureDisk != nil } +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (t *azureDiskCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.AzureDisk != nil +} + // GetInTreePluginName returns the name of the intree plugin driver func (t *azureDiskCSITranslator) GetInTreePluginName() string { return AzureDiskInTreePluginName diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/azure_file.go b/staging/src/k8s.io/csi-translation-lib/plugins/azure_file.go index b0ab0cd662a..2b58dbda765 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/azure_file.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/azure_file.go @@ -53,6 +53,34 @@ func (t *azureFileCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.St return sc, nil } +// TranslateInTreeInlineVolumeToCSI takes a Volume with AzureFile set from in-tree +// and converts the AzureFile source to a CSIPersistentVolumeSource +func (t *azureFileCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.AzureFile == nil { + return nil, fmt.Errorf("volume is nil or AWS EBS not defined on volume") + } + + azureSource := volume.AzureFile + + pv := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeHandle: fmt.Sprintf(volumeIDTemplate, "", azureSource.SecretName, azureSource.ShareName), + ReadOnly: azureSource.ReadOnly, + VolumeAttributes: map[string]string{azureFileShareName: azureSource.ShareName}, + NodePublishSecretRef: &v1.SecretReference{ + Name: azureSource.ShareName, + Namespace: "default", + }, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, + }, + } + return pv, nil +} + // TranslateInTreePVToCSI takes a PV with AzureFile set from in-tree // and converts the AzureFile source to a CSIPersistentVolumeSource func (t *azureFileCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { @@ -126,6 +154,13 @@ func (t *azureFileCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { return pv != nil && pv.Spec.AzureFile != nil } +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (t *azureFileCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.AzureFile != nil +} + // GetInTreePluginName returns the name of the intree plugin driver func (t *azureFileCSITranslator) GetInTreePluginName() string { return AzureFileInTreePluginName diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go index 95ada8d227d..1a9b11a13ab 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go @@ -157,6 +157,39 @@ func backwardCompatibleAccessModes(ams []v1.PersistentVolumeAccessMode) []v1.Per return newAM } +// TranslateInTreeInlineVolumeToCSI takes a Volume with GCEPersistentDisk set from in-tree +// and converts the GCEPersistentDisk source to a CSIPersistentVolumeSource +func (g *gcePersistentDiskCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.GCEPersistentDisk == nil { + return nil, fmt.Errorf("volume is nil or GCE PD not defined on volume") + } + + pdSource := volume.GCEPersistentDisk + + partition := "" + if pdSource.Partition != 0 { + partition = strconv.Itoa(int(pdSource.Partition)) + } + + pv := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: GCEPDDriverName, + VolumeHandle: fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, UnspecifiedValue, pdSource.PDName), + ReadOnly: pdSource.ReadOnly, + FSType: pdSource.FSType, + VolumeAttributes: map[string]string{ + "partition": partition, + }, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + return pv, nil +} + // TranslateInTreePVToCSI takes a PV with GCEPersistentDisk set from in-tree // and converts the GCEPersistentDisk source to a CSIPersistentVolumeSource func (g *gcePersistentDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { @@ -241,13 +274,20 @@ func (g *gcePersistentDiskCSITranslator) TranslateCSIPVToInTree(pv *v1.Persisten return pv, nil } -// CanSupport tests whether the plugin supports a given volume +// CanSupport tests whether the plugin supports a given persistent volume // specification from the API. The spec pointer should be considered // const. func (g *gcePersistentDiskCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { return pv != nil && pv.Spec.GCEPersistentDisk != nil } +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (g *gcePersistentDiskCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.GCEPersistentDisk != nil +} + // GetInTreePluginName returns the name of the intree plugin driver func (g *gcePersistentDiskCSITranslator) GetInTreePluginName() string { return GCEPDInTreePluginName diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go index d50316743c9..2095084d82b 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go @@ -28,8 +28,13 @@ type InTreePlugin interface { // and translates them to a volume options consumable by CSI plugin TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) + // TranslateInTreeInlineVolumeToCSI takes a inline volume and will translate + // the in-tree inline volume source to a CSIPersistentVolumeSource + // A PV object containing the CSIPersistentVolumeSource in it's spec is returned + TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) + // TranslateInTreePVToCSI takes a persistent volume and will translate - // the in-tree source to a CSI Source. The input persistent volume can be modified + // the in-tree pv source to a CSI Source. The input persistent volume can be modified TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) // TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate @@ -37,10 +42,14 @@ type InTreePlugin interface { // by the `Driver` field in the CSI Source. The input PV object can be modified TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) - // CanSupport tests whether the plugin supports a given volume + // CanSupport tests whether the plugin supports a given persistent volume // specification from the API. CanSupport(pv *v1.PersistentVolume) bool + // CanSupportInline tests whether the plugin supports a given inline volume + // specification from the API. + CanSupportInline(vol *v1.Volume) bool + // GetInTreePluginName returns the in-tree plugin name this migrates GetInTreePluginName() string diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go index de283453a4b..0573ebc0526 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go @@ -45,6 +45,31 @@ func (t *osCinderCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.Sto return sc, nil } +// TranslateInTreeInlineVolumeToCSI takes a Volume with Cinder set from in-tree +// and converts the Cinder source to a CSIPersistentVolumeSource +func (t *osCinderCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.Cinder == nil { + return nil, fmt.Errorf("volume is nil or Cinder not defined on volume") + } + + cinderSource := volume.Cinder + pv := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: CinderDriverName, + VolumeHandle: cinderSource.VolumeID, + ReadOnly: cinderSource.ReadOnly, + FSType: cinderSource.FSType, + VolumeAttributes: map[string]string{}, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + return pv, nil +} + // TranslateInTreePVToCSI takes a PV with Cinder set from in-tree // and converts the Cinder source to a CSIPersistentVolumeSource func (t *osCinderCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { @@ -87,13 +112,20 @@ func (t *osCinderCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) return pv, nil } -// CanSupport tests whether the plugin supports a given volume +// CanSupport tests whether the plugin supports a given persistent volume // specification from the API. The spec pointer should be considered // const. func (t *osCinderCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { return pv != nil && pv.Spec.Cinder != nil } +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (t *osCinderCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.Cinder != nil +} + // GetInTreePluginName returns the name of the intree plugin driver func (t *osCinderCSITranslator) GetInTreePluginName() string { return CinderInTreePluginName diff --git a/staging/src/k8s.io/csi-translation-lib/translate.go b/staging/src/k8s.io/csi-translation-lib/translate.go index c9864f301c3..46ae1cbbfd3 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate.go +++ b/staging/src/k8s.io/csi-translation-lib/translate.go @@ -47,6 +47,21 @@ func TranslateInTreeStorageClassToCSI(inTreePluginName string, sc *storage.Stora return nil, fmt.Errorf("could not find in-tree storage class parameter translation logic for %#v", inTreePluginName) } +// TranslateInTreeInlineVolumeToCSI takes a inline volume and will translate +// the in-tree volume source to a CSIPersistentVolumeSource (wrapped in a PV) +// if the translation logic has been implemented. +func TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil { + return nil, fmt.Errorf("persistent volume was nil") + } + for _, curPlugin := range inTreePlugins { + if curPlugin.CanSupportInline(volume) { + return curPlugin.TranslateInTreeInlineVolumeToCSI(volume) + } + } + return nil, fmt.Errorf("could not find in-tree plugin translation logic for %#v", volume.Name) +} + // TranslateInTreePVToCSI takes a persistent volume and will translate // the in-tree source to a CSI Source if the translation logic // has been implemented. The input persistent volume will not @@ -149,5 +164,10 @@ func IsPVMigratable(pv *v1.PersistentVolume) bool { // IsInlineMigratable tests whether there is Migration logic for the given Inline Volume func IsInlineMigratable(vol *v1.Volume) bool { + for _, curPlugin := range inTreePlugins { + if curPlugin.CanSupportInline(vol) { + return true + } + } return false }