From 148ef06a3d1a0442078bccc945e05826cd6ae29a Mon Sep 17 00:00:00 2001 From: Divyen Patel Date: Fri, 8 May 2020 14:01:26 -0700 Subject: [PATCH] vSphere in-tree volumes migration to vSphere CSI driver --- .../app/plugins_providers.go | 6 +- cmd/kubelet/app/plugins_providers.go | 3 +- pkg/features/kube_features.go | 15 + pkg/volume/csi/csi_plugin.go | 3 + pkg/volume/csimigration/plugin_manager.go | 4 + pkg/volume/vsphere_volume/BUILD | 2 + pkg/volume/vsphere_volume/vsphere_volume.go | 8 + .../k8s.io/csi-translation-lib/plugins/BUILD | 2 + .../plugins/vsphere_volume.go | 208 +++++++++++ .../plugins/vsphere_volume_test.go | 335 ++++++++++++++++++ .../k8s.io/csi-translation-lib/translate.go | 1 + .../csi-translation-lib/translate_test.go | 7 + 12 files changed, 588 insertions(+), 6 deletions(-) create mode 100644 staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume.go create mode 100644 staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume_test.go diff --git a/cmd/kube-controller-manager/app/plugins_providers.go b/cmd/kube-controller-manager/app/plugins_providers.go index ba0ba182ba1..d50bc2f9809 100644 --- a/cmd/kube-controller-manager/app/plugins_providers.go +++ b/cmd/kube-controller-manager/app/plugins_providers.go @@ -64,6 +64,7 @@ func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, fea pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginMigrationCompleteFeature: features.CSIMigrationGCEComplete, pluginProbeFunction: gcepd.ProbeVolumePlugins} pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginMigrationCompleteFeature: features.CSIMigrationOpenStackComplete, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginMigrationCompleteFeature: features.CSIMigrationAzureDiskComplete, pluginProbeFunction: azure_dd.ProbeVolumePlugins} + pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginMigrationCompleteFeature: features.CSIMigrationvSphereComplete, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} var err error for pluginName, pluginInfo := range pluginMigrationStatus { @@ -72,8 +73,6 @@ func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, fea return allPlugins, err } } - - allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) return allPlugins, nil } @@ -88,6 +87,7 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginMigrationCompleteFeature: features.CSIMigrationOpenStackComplete, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginMigrationCompleteFeature: features.CSIMigrationAzureDiskComplete, pluginProbeFunction: azure_dd.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginMigrationCompleteFeature: features.CSIMigrationAzureFileComplete, pluginProbeFunction: azure_file.ProbeVolumePlugins} + pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginMigrationCompleteFeature: features.CSIMigrationvSphereComplete, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} var err error for pluginName, pluginInfo := range pluginMigrationStatus { @@ -96,7 +96,5 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f return allPlugins, err } } - - allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) return allPlugins, nil } diff --git a/cmd/kubelet/app/plugins_providers.go b/cmd/kubelet/app/plugins_providers.go index 58d6341d05f..3ff04819f06 100644 --- a/cmd/kubelet/app/plugins_providers.go +++ b/cmd/kubelet/app/plugins_providers.go @@ -65,6 +65,7 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginMigrationCompleteFeature: features.CSIMigrationOpenStackComplete, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginMigrationCompleteFeature: features.CSIMigrationAzureDiskComplete, pluginProbeFunction: azure_dd.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginMigrationCompleteFeature: features.CSIMigrationAzureFileComplete, pluginProbeFunction: azure_file.ProbeVolumePlugins} + pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginMigrationCompleteFeature: features.CSIMigrationvSphereComplete, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} var err error for pluginName, pluginInfo := range pluginMigrationStatus { @@ -73,7 +74,5 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f return allPlugins, err } } - - allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) return allPlugins, nil } diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index ba4f4e3382c..77dc20a45ee 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -408,6 +408,19 @@ const ( // Expects Azure File CSI Driver to be installed and configured on all nodes. CSIMigrationAzureFileComplete featuregate.Feature = "CSIMigrationAzureFileComplete" + // owner: @divyenpatel + // alpha: v1.19 + // + // Enables the vSphere in-tree driver to vSphere CSI Driver migration feature. + CSIMigrationvSphere featuregate.Feature = "CSIMigrationvSphere" + + // owner: @divyenpatel + // alpha: v1.19 + // + // Disables the vSphere in-tree driver. + // Expects vSphere CSI Driver to be installed and configured on all nodes. + CSIMigrationvSphereComplete featuregate.Feature = "CSIMigrationvSphereComplete" + // owner: @gnufied // alpha: v1.18 // Allows user to configure volume permission change policy for fsGroups when mounting @@ -628,6 +641,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CSIMigrationAzureDiskComplete: {Default: false, PreRelease: featuregate.Alpha}, CSIMigrationAzureFile: {Default: false, PreRelease: featuregate.Alpha}, CSIMigrationAzureFileComplete: {Default: false, PreRelease: featuregate.Alpha}, + CSIMigrationvSphere: {Default: false, PreRelease: featuregate.Alpha}, + CSIMigrationvSphereComplete: {Default: false, PreRelease: featuregate.Alpha}, RunAsGroup: {Default: true, PreRelease: featuregate.Beta}, CSIMigrationOpenStack: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires OpenStack Cinder CSI driver) CSIMigrationOpenStackComplete: {Default: false, PreRelease: featuregate.Alpha}, diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go index 551ea1967bc..ea3f5b0bae7 100644 --- a/pkg/volume/csi/csi_plugin.go +++ b/pkg/volume/csi/csi_plugin.go @@ -219,6 +219,9 @@ func (p *csiPlugin) Init(host volume.VolumeHost) error { csitranslationplugins.AzureFileInTreePluginName: func() bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureFile) }, + csitranslationplugins.VSphereInTreePluginName: func() bool { + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationvSphere) + }, } // Initializing the label management channels diff --git a/pkg/volume/csimigration/plugin_manager.go b/pkg/volume/csimigration/plugin_manager.go index cab93506823..1d635166aed 100644 --- a/pkg/volume/csimigration/plugin_manager.go +++ b/pkg/volume/csimigration/plugin_manager.go @@ -68,6 +68,8 @@ func (pm PluginManager) IsMigrationCompleteForPlugin(pluginName string) bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDiskComplete) case csilibplugins.CinderInTreePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationOpenStackComplete) + case csilibplugins.VSphereInTreePluginName: + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationvSphereComplete) default: return false } @@ -92,6 +94,8 @@ func (pm PluginManager) IsMigrationEnabledForPlugin(pluginName string) bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDisk) case csilibplugins.CinderInTreePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationOpenStack) + case csilibplugins.VSphereInTreePluginName: + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationvSphere) default: return false } diff --git a/pkg/volume/vsphere_volume/BUILD b/pkg/volume/vsphere_volume/BUILD index 79ff9b6fe9a..d4828ec3580 100644 --- a/pkg/volume/vsphere_volume/BUILD +++ b/pkg/volume/vsphere_volume/BUILD @@ -20,6 +20,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/vsphere_volume", deps = [ + "//pkg/features:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/util:go_default_library", "//pkg/volume/util/volumepathhandler:go_default_library", @@ -27,6 +28,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library", "//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/vsphere:go_default_library", diff --git a/pkg/volume/vsphere_volume/vsphere_volume.go b/pkg/volume/vsphere_volume/vsphere_volume.go index 9994e27d745..f38295952f5 100644 --- a/pkg/volume/vsphere_volume/vsphere_volume.go +++ b/pkg/volume/vsphere_volume/vsphere_volume.go @@ -25,6 +25,7 @@ import ( "runtime" "strings" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" "k8s.io/utils/mount" utilstrings "k8s.io/utils/strings" @@ -34,6 +35,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" volumehelpers "k8s.io/cloud-provider/volume/helpers" + + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" ) @@ -70,6 +73,11 @@ func (plugin *vsphereVolumePlugin) GetPluginName() string { return vsphereVolumePluginName } +func (plugin *vsphereVolumePlugin) IsMigratedToCSI() bool { + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && + utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationvSphere) +} + func (plugin *vsphereVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { volumeSource, _, err := getVolumeSource(spec) if err != nil { diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD index 6ec5872c76d..667605de858 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD +++ b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD @@ -9,6 +9,7 @@ go_library( "gce_pd.go", "in_tree_volume.go", "openstack_cinder.go", + "vsphere_volume.go", ], importmap = "k8s.io/kubernetes/vendor/k8s.io/csi-translation-lib/plugins", importpath = "k8s.io/csi-translation-lib/plugins", @@ -45,6 +46,7 @@ go_test( "azure_file_test.go", "gce_pd_test.go", "in_tree_volume_test.go", + "vsphere_volume_test.go", ], embed = [":go_default_library"], deps = [ diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume.go b/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume.go new file mode 100644 index 00000000000..531aff0d983 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume.go @@ -0,0 +1,208 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugins + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" +) + +const ( + // VSphereDriverName is the name of the CSI driver for vSphere Volume + VSphereDriverName = "csi.vsphere.vmware.com" + // VSphereInTreePluginName is the name of the in-tree plugin for vSphere Volume + VSphereInTreePluginName = "kubernetes.io/vsphere-volume" + + // paramStoragePolicyName used to supply SPBM Policy name for Volume provisioning + paramStoragePolicyName = "storagepolicyname" + + // This param is used to tell Driver to return volumePath and not VolumeID + // in-tree vSphere plugin does not understand volume id, it uses volumePath + paramcsiMigration = "csimigration" + + // This param is used to supply datastore name for Volume provisioning + paramDatastore = "datastore-migrationparam" + + // This param supplies disk foramt (thin, thick, zeoredthick) for Volume provisioning + paramDiskFormat = "diskformat-migrationparam" + + // vSAN Policy Parameters + paramHostFailuresToTolerate = "hostfailurestotolerate-migrationparam" + paramForceProvisioning = "forceprovisioning-migrationparam" + paramCacheReservation = "cachereservation-migrationparam" + paramDiskstripes = "diskstripes-migrationparam" + paramObjectspacereservation = "objectspacereservation-migrationparam" + paramIopslimit = "iopslimit-migrationparam" + + // AttributeInitialVolumeFilepath represents the path of volume where volume is created + AttributeInitialVolumeFilepath = "initialvolumefilepath" +) + +var _ InTreePlugin = &vSphereCSITranslator{} + +// vSphereCSITranslator handles translation of PV spec from In-tree vSphere Volume to vSphere CSI +type vSphereCSITranslator struct{} + +// NewvSphereCSITranslator returns a new instance of vSphereCSITranslator +func NewvSphereCSITranslator() InTreePlugin { + return &vSphereCSITranslator{} +} + +// TranslateInTreeStorageClassToCSI translates InTree vSphere storage class parameters to CSI storage class +func (t *vSphereCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) { + if sc == nil { + return nil, fmt.Errorf("sc is nil") + } + var params = map[string]string{} + for k, v := range sc.Parameters { + switch strings.ToLower(k) { + case fsTypeKey: + params[csiFsTypeKey] = v + case paramStoragePolicyName: + params[paramStoragePolicyName] = v + case "datastore": + params[paramDatastore] = v + case "diskformat": + params[paramDiskFormat] = v + case "hostfailurestotolerate": + params[paramHostFailuresToTolerate] = v + case "forceprovisioning": + params[paramForceProvisioning] = v + case "cachereservation": + params[paramCacheReservation] = v + case "diskstripes": + params[paramDiskstripes] = v + case "objectspacereservation": + params[paramObjectspacereservation] = v + case "iopslimit": + params[paramIopslimit] = v + default: + klog.V(2).Infof("StorageClass parameter [name:%q, value:%q] is not supported", k, v) + } + } + + // This helps vSphere CSI driver to identify in-tree provisioner request vs CSI provisioner request + // When this is true, Driver returns initialvolumefilepath in the VolumeContext, which is + // used in TranslateCSIPVToInTree + params[paramcsiMigration] = "true" + // Note: sc.AllowedTopologies for Topology based volume provisioning will be supplied as it is. + sc.Parameters = params + return sc, nil +} + +// TranslateInTreeInlineVolumeToCSI takes a Volume with VsphereVolume set from in-tree +// and converts the VsphereVolume source to a CSIPersistentVolumeSource +func (t *vSphereCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume) (*v1.PersistentVolume, error) { + if volume == nil || volume.VsphereVolume == nil { + return nil, fmt.Errorf("volume is nil or VsphereVolume not defined on volume") + } + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + // Must be unique per disk as it is used as the unique part of the + // staging path + Name: fmt.Sprintf("%s-%s", VSphereDriverName, volume.VsphereVolume.VolumePath), + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: VSphereDriverName, + VolumeHandle: volume.VsphereVolume.VolumePath, + FSType: volume.VsphereVolume.FSType, + VolumeAttributes: make(map[string]string), + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + if volume.VsphereVolume.StoragePolicyName != "" { + pv.Spec.CSI.VolumeAttributes[paramStoragePolicyName] = pv.Spec.VsphereVolume.StoragePolicyName + } + return pv, nil +} + +// TranslateInTreePVToCSI takes a PV with VsphereVolume set from in-tree +// and converts the VsphereVolume source to a CSIPersistentVolumeSource +func (t *vSphereCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.VsphereVolume == nil { + return nil, fmt.Errorf("pv is nil or VsphereVolume not defined on pv") + } + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: VSphereDriverName, + VolumeHandle: pv.Spec.VsphereVolume.VolumePath, + FSType: pv.Spec.VsphereVolume.FSType, + VolumeAttributes: make(map[string]string), + } + if pv.Spec.VsphereVolume.StoragePolicyName != "" { + csiSource.VolumeAttributes[paramStoragePolicyName] = pv.Spec.VsphereVolume.StoragePolicyName + } + pv.Spec.VsphereVolume = nil + pv.Spec.CSI = csiSource + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the vSphere CSI source to a vSphereVolume source. +func (t *vSphereCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.CSI == nil { + return nil, fmt.Errorf("pv is nil or CSI source not defined on pv") + } + csiSource := pv.Spec.CSI + vsphereVirtualDiskVolumeSource := &v1.VsphereVirtualDiskVolumeSource{ + FSType: csiSource.FSType, + } + volumeFilePath, ok := csiSource.VolumeAttributes[AttributeInitialVolumeFilepath] + if ok { + vsphereVirtualDiskVolumeSource.VolumePath = volumeFilePath + } + pv.Spec.CSI = nil + pv.Spec.VsphereVolume = vsphereVirtualDiskVolumeSource + return pv, nil +} + +// CanSupport tests whether the plugin supports a given persistent volume +// specification from the API. +func (t *vSphereCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.VsphereVolume != nil +} + +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. +func (t *vSphereCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.VsphereVolume != nil +} + +// GetInTreePluginName returns the name of the in-tree plugin driver +func (t *vSphereCSITranslator) GetInTreePluginName() string { + return VSphereInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (t *vSphereCSITranslator) GetCSIPluginName() string { + return VSphereDriverName +} + +// RepairVolumeHandle is needed in VerifyVolumesAttached on the external attacher when we need to do strict volume +// handle matching to check VolumeAttachment attached status. +// vSphere volume does not need patch to help verify whether that volume is attached. +func (t *vSphereCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) { + return volumeHandle, nil +} diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume_test.go new file mode 100644 index 00000000000..31d2b0389fd --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume_test.go @@ -0,0 +1,335 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugins + +import ( + "fmt" + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTranslatevSphereInTreeStorageClassToCSI(t *testing.T) { + translator := NewvSphereCSITranslator() + topologySelectorTerm := v1.TopologySelectorTerm{[]v1.TopologySelectorLabelRequirement{ + { + Key: v1.LabelZoneFailureDomain, + Values: []string{"zone-a"}, + }, + { + Key: v1.LabelZoneRegion, + Values: []string{"region-a"}, + }, + }} + cases := []struct { + name string + sc *storage.StorageClass + expSc *storage.StorageClass + expErr bool + }{ + { + name: "expect error when sc is nil", + sc: nil, + expSc: nil, + expErr: true, + }, + { + name: "translate with no parameter", + sc: NewStorageClass(map[string]string{}, nil), + expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, nil), + }, + { + name: "translate with unknown parameter", + sc: NewStorageClass(map[string]string{"unknownparam": "value"}, nil), + expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, nil), + }, + { + name: "translate with storagepolicyname and datastore", + sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore": "vsanDatastore"}, nil), + expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "vsanDatastore", paramcsiMigration: "true"}, nil), + }, + { + name: "translate with fstype", + sc: NewStorageClass(map[string]string{"fstype": "ext4"}, nil), + expSc: NewStorageClass(map[string]string{"csi.storage.k8s.io/fstype": "ext4", paramcsiMigration: "true"}, nil), + }, + { + name: "translate with storagepolicyname and fstype", + sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "fstype": "ext4"}, nil), + expSc: NewStorageClass(map[string]string{"csi.storage.k8s.io/fstype": "ext4", "storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, nil), + }, + { + name: "translate with no parameter and allowedTopology", + sc: NewStorageClass(map[string]string{}, []v1.TopologySelectorTerm{topologySelectorTerm}), + expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + }, + { + name: "translate with storagepolicyname and allowedTopology", + sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + }, + { + name: "translate with raw vSAN policy parameters, datastore and diskformat", + sc: NewStorageClass(map[string]string{"hostfailurestotolerate": "2", "datastore": "vsanDatastore", "diskformat": "thin"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + expSc: NewStorageClass(map[string]string{"hostfailurestotolerate-migrationparam": "2", "datastore-migrationparam": "vsanDatastore", "diskformat-migrationparam": "thin", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + }, + { + name: "translate with all parameters", + sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore": "test-datastore-name", "fstype": "ext4", "diskformat": "thin", "hostfailurestotolerate": "1", "forceprovisioning": "yes", "cachereservation": "25", "diskstripes": "4", "objectspacereservation": "10", "iopslimit": "32"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "test-datastore-name", "csi.storage.k8s.io/fstype": "ext4", "diskformat-migrationparam": "thin", "hostfailurestotolerate-migrationparam": "1", "forceprovisioning-migrationparam": "yes", "cachereservation-migrationparam": "25", "diskstripes-migrationparam": "4", "objectspacereservation-migrationparam": "10", "iopslimit-migrationparam": "32", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}), + }, + } + for _, tc := range cases { + t.Logf("Testing %v", tc.name) + got, err := translator.TranslateInTreeStorageClassToCSI(tc.sc) + if err != nil && !tc.expErr { + t.Errorf("Did not expect error but got: %v", err) + } + + if err == nil && tc.expErr { + t.Errorf("Expected error, but did not get one.") + } + if !reflect.DeepEqual(got, tc.expSc) { + t.Errorf("Got parameters: %v, expected :%v", got, tc.expSc) + } + } +} + +func TestTranslateVSphereCSIPVToInTree(t *testing.T) { + translator := NewvSphereCSITranslator() + cases := []struct { + name string + csiPV *v1.PersistentVolume + intreePV *v1.PersistentVolume + expErr bool + }{ + { + name: "expect error when pv is nil", + csiPV: nil, + intreePV: nil, + expErr: true, + }, + { + name: "expect error when pv.Spec.CSI is nil", + csiPV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: nil, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + intreePV: nil, + expErr: true, + }, + { + name: "translate valid vSphere CSI PV to vSphere in-tree PV", + csiPV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: VSphereDriverName, + VolumeHandle: "e4073a6d-642e-4dff-8f4a-b4e3a47c4bbd", + FSType: "ext4", + VolumeAttributes: map[string]string{ + paramStoragePolicyName: "vSAN Default Storage Policy", + AttributeInitialVolumeFilepath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk", + }, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + intreePV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{ + VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk", + FSType: "ext4", + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + expErr: false, + }, + } + + for _, tc := range cases { + t.Logf("Testing %v", tc.name) + got, err := translator.TranslateCSIPVToInTree(tc.csiPV) + if err != nil && !tc.expErr { + t.Errorf("Did not expect error but got: %v", err) + } + + if err == nil && tc.expErr { + t.Errorf("Expected error, but did not get one.") + } + + if !reflect.DeepEqual(got, tc.intreePV) { + t.Errorf("Got PV: %v, expected :%v", got, tc.intreePV) + } + } +} + +func TestTranslateVSphereInTreePVToCSI(t *testing.T) { + translator := NewvSphereCSITranslator() + cases := []struct { + name string + intreePV *v1.PersistentVolume + csiPV *v1.PersistentVolume + expErr bool + }{ + { + name: "expect error when in-tree vsphere PV is nil", + intreePV: &v1.PersistentVolume{}, + csiPV: nil, + expErr: true, + }, + { + name: "translate valid vSphere in-tree PV to vSphere CSI PV", + intreePV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{ + VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk", + FSType: "ext4", + StoragePolicyName: "vSAN Default Storage Policy", + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + csiPV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: VSphereDriverName, + VolumeHandle: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk", + FSType: "ext4", + VolumeAttributes: map[string]string{ + paramStoragePolicyName: "vSAN Default Storage Policy", + }, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + expErr: false, + }, + } + + for _, tc := range cases { + t.Logf("Testing %v", tc.name) + got, err := translator.TranslateInTreePVToCSI(tc.intreePV) + if err != nil && !tc.expErr { + t.Errorf("Did not expect error but got: %v", err) + } + + if err == nil && tc.expErr { + t.Errorf("Expected error, but did not get one.") + } + + if !reflect.DeepEqual(got, tc.csiPV) { + t.Errorf("Got PV: %v, expected :%v", got, tc.csiPV) + } + } +} + +func TestTranslatevSphereInTreeInlineVolumeToCSI(t *testing.T) { + translator := NewvSphereCSITranslator() + cases := []struct { + name string + inlinevolume *v1.Volume + csiPV *v1.PersistentVolume + expErr bool + }{ + { + name: "expect error when inline vsphere volume is nil", + inlinevolume: &v1.Volume{}, + csiPV: nil, + expErr: true, + }, + { + name: "translate valid in-tree vsphere volume to vSphere CSI PV", + inlinevolume: &v1.Volume{ + Name: "inlinevolume", + VolumeSource: v1.VolumeSource{ + VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{ + VolumePath: "[vsanDatastore] volume/inlinevolume.vmdk", + FSType: "ext4", + }, + }}, + csiPV: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", VSphereDriverName, "[vsanDatastore] volume/inlinevolume.vmdk"), + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: VSphereDriverName, + VolumeHandle: "[vsanDatastore] volume/inlinevolume.vmdk", + FSType: "ext4", + VolumeAttributes: make(map[string]string), + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + }, + expErr: false, + }, + } + + for _, tc := range cases { + t.Logf("Testing %v", tc.name) + got, err := translator.TranslateInTreeInlineVolumeToCSI(tc.inlinevolume) + if err == nil && tc.expErr { + t.Errorf("Expected error, but did not get one.") + continue + } + if err != nil { + if tc.expErr { + t.Logf("expected error received") + continue + } else { + t.Errorf("Did not expect error but got: %v", err) + continue + } + } + if !reflect.DeepEqual(got, tc.csiPV) { + t.Errorf("Got PV: %v, expected :%v", got, tc.csiPV) + } + } +} diff --git a/staging/src/k8s.io/csi-translation-lib/translate.go b/staging/src/k8s.io/csi-translation-lib/translate.go index 668bc872af6..21f5c49d59a 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate.go +++ b/staging/src/k8s.io/csi-translation-lib/translate.go @@ -32,6 +32,7 @@ var ( plugins.CinderDriverName: plugins.NewOpenStackCinderCSITranslator(), plugins.AzureDiskDriverName: plugins.NewAzureDiskCSITranslator(), plugins.AzureFileDriverName: plugins.NewAzureFileCSITranslator(), + plugins.VSphereDriverName: plugins.NewvSphereCSITranslator(), } ) diff --git a/staging/src/k8s.io/csi-translation-lib/translate_test.go b/staging/src/k8s.io/csi-translation-lib/translate_test.go index 91c59d54afe..ce2d740f345 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate_test.go +++ b/staging/src/k8s.io/csi-translation-lib/translate_test.go @@ -384,6 +384,13 @@ func generateUniqueVolumeSource(driverName string) (v1.VolumeSource, error) { ShareName: string(uuid.NewUUID()), }, }, nil + case plugins.VSphereDriverName: + return v1.VolumeSource{ + VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{ + VolumePath: " [vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-" + string(uuid.NewUUID()+".vmdk"), + FSType: "ext4", + }, + }, nil default: return v1.VolumeSource{}, fmt.Errorf("couldn't find logic for driver: %v", driverName) }