diff --git a/cmd/kube-controller-manager/app/plugins.go b/cmd/kube-controller-manager/app/plugins.go index 758c6b9d555a5..69daea0bb5cc9 100644 --- a/cmd/kube-controller-manager/app/plugins.go +++ b/cmd/kube-controller-manager/app/plugins.go @@ -40,7 +40,6 @@ import ( "k8s.io/kubernetes/pkg/volume/iscsi" "k8s.io/kubernetes/pkg/volume/local" "k8s.io/kubernetes/pkg/volume/nfs" - "k8s.io/kubernetes/pkg/volume/portworx" "k8s.io/kubernetes/pkg/volume/quobyte" "k8s.io/kubernetes/pkg/volume/storageos" volumeutil "k8s.io/kubernetes/pkg/volume/util" @@ -62,7 +61,6 @@ func ProbeAttachableVolumePlugins() ([]volume.VolumePlugin, error) { if err != nil { return allPlugins, err } - allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...) @@ -85,7 +83,6 @@ func ProbeExpandableVolumePlugins(config persistentvolumeconfig.VolumeConfigurat if err != nil { return allPlugins, err } - allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...) allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) @@ -137,7 +134,6 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config persiste } allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...) allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) diff --git a/cmd/kube-controller-manager/app/plugins_providers.go b/cmd/kube-controller-manager/app/plugins_providers.go index e194bdb86d26f..99b1586d2031a 100644 --- a/cmd/kube-controller-manager/app/plugins_providers.go +++ b/cmd/kube-controller-manager/app/plugins_providers.go @@ -31,6 +31,7 @@ import ( "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" + "k8s.io/kubernetes/pkg/volume/portworx" "k8s.io/kubernetes/pkg/volume/rbd" "k8s.io/kubernetes/pkg/volume/vsphere_volume" ) @@ -68,6 +69,7 @@ func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, fea pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginUnregisterFeature: features.InTreePluginOpenStackUnregister, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} + pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} pluginMigrationStatus[plugins.RBDVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationRBD, pluginUnregisterFeature: features.InTreePluginRBDUnregister, pluginProbeFunction: rbd.ProbeVolumePlugins} var err error for pluginName, pluginInfo := range pluginMigrationStatus { diff --git a/cmd/kubelet/app/plugins.go b/cmd/kubelet/app/plugins.go index 509b421a4d6b3..e13d1bc71731c 100644 --- a/cmd/kubelet/app/plugins.go +++ b/cmd/kubelet/app/plugins.go @@ -37,7 +37,6 @@ import ( "k8s.io/kubernetes/pkg/volume/iscsi" "k8s.io/kubernetes/pkg/volume/local" "k8s.io/kubernetes/pkg/volume/nfs" - "k8s.io/kubernetes/pkg/volume/portworx" "k8s.io/kubernetes/pkg/volume/projected" "k8s.io/kubernetes/pkg/volume/quobyte" "k8s.io/kubernetes/pkg/volume/secret" @@ -76,7 +75,6 @@ func ProbeVolumePlugins(featureGate featuregate.FeatureGate) ([]volume.VolumePlu allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...) allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...) allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...) allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) diff --git a/cmd/kubelet/app/plugins_providers.go b/cmd/kubelet/app/plugins_providers.go index b5a2860945e59..9b4a6391f76cb 100644 --- a/cmd/kubelet/app/plugins_providers.go +++ b/cmd/kubelet/app/plugins_providers.go @@ -36,6 +36,7 @@ import ( "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" + "k8s.io/kubernetes/pkg/volume/portworx" "k8s.io/kubernetes/pkg/volume/rbd" "k8s.io/kubernetes/pkg/volume/vsphere_volume" ) @@ -75,6 +76,7 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginUnregisterFeature: features.InTreePluginAzureFileUnregister, pluginProbeFunction: azure_file.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} + pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} pluginMigrationStatus[plugins.RBDVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationRBD, pluginUnregisterFeature: features.InTreePluginRBDUnregister, pluginProbeFunction: rbd.ProbeVolumePlugins} var err error for pluginName, pluginInfo := range pluginMigrationStatus { diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 2c6f6571532e9..435106efc223a 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -330,6 +330,18 @@ const ( // Disables the OpenStack Cinder in-tree driver. InTreePluginOpenStackUnregister featuregate.Feature = "InTreePluginOpenStackUnregister" + // owner: @trierra + // alpha: v1.23 + // + // Enables the Portworx in-tree driver to Portworx migration feature. + CSIMigrationPortworx featuregate.Feature = "CSIMigrationPortworx" + + // owner: @trierra + // alpha: v1.23 + // + // Disables the Portworx in-tree driver. + InTreePluginPortworxUnregister featuregate.Feature = "InTreePluginPortworxUnregister" + // owner: @humblec // alpha: v1.23 // @@ -858,6 +870,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CSIMigrationRBD: {Default: false, PreRelease: featuregate.Alpha}, // Off by default (requires RBD CSI driver) InTreePluginRBDUnregister: {Default: false, PreRelease: featuregate.Alpha}, ConfigurableFSGroupPolicy: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 + CSIMigrationPortworx: {Default: false, PreRelease: featuregate.Alpha}, // Off by default (requires Portworx CSI driver) + InTreePluginPortworxUnregister: {Default: false, PreRelease: featuregate.Alpha}, CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta}, CSIStorageCapacity: {Default: true, PreRelease: featuregate.Beta}, CSIServiceAccountToken: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23 @@ -880,7 +894,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta}, ProxyTerminatingEndpoints: {Default: false, PreRelease: featuregate.Alpha}, - EndpointSliceNodeName: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, //remove in 1.25 + EndpointSliceNodeName: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 WindowsEndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 PodDisruptionBudget: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 DaemonSetUpdateSurge: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22 @@ -889,7 +903,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS DownwardAPIHugePages: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22 AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha}, DefaultPodTopologySpread: {Default: true, PreRelease: featuregate.Beta}, - SetHostnameAsFQDN: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, //remove in 1.24 + SetHostnameAsFQDN: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24 WinOverlay: {Default: true, PreRelease: featuregate.Beta}, WinDSR: {Default: false, PreRelease: featuregate.Alpha}, DisableAcceleratorUsageMetrics: {Default: true, PreRelease: featuregate.Beta}, diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go index 7668b12e11c94..6796c2129a9d8 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go @@ -47,6 +47,10 @@ func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string) bool { if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAWS) { return false } + case csilibplugins.PortworxVolumePluginName: + if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) { + return false + } case csilibplugins.GCEPDInTreePluginName: if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationGCE) { return false diff --git a/pkg/scheduler/framework/plugins/volumebinding/binder.go b/pkg/scheduler/framework/plugins/volumebinding/binder.go index 83b380236a084..ee4989be0b722 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/binder.go +++ b/pkg/scheduler/framework/plugins/volumebinding/binder.go @@ -1014,7 +1014,7 @@ func (a byPVCSize) Less(i, j int) bool { return iSize.Cmp(jSize) == -1 } -// isCSIMigrationOnForPlugin checks if CSI migrartion is enabled for a given plugin. +// isCSIMigrationOnForPlugin checks if CSI migration is enabled for a given plugin. func isCSIMigrationOnForPlugin(pluginName string) bool { switch pluginName { case csiplugins.AWSEBSInTreePluginName: @@ -1025,6 +1025,8 @@ func isCSIMigrationOnForPlugin(pluginName string) bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDisk) case csiplugins.CinderInTreePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationOpenStack) + case csiplugins.PortworxVolumePluginName: + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) case csiplugins.RBDVolumePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationRBD) } diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go index 857e98baa4e14..0ae6d084f0e6d 100644 --- a/pkg/volume/csi/csi_plugin.go +++ b/pkg/volume/csi/csi_plugin.go @@ -234,6 +234,9 @@ func (p *csiPlugin) Init(host volume.VolumeHost) error { csitranslationplugins.VSphereInTreePluginName: func() bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationvSphere) }, + csitranslationplugins.PortworxVolumePluginName: func() bool { + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) + }, csitranslationplugins.RBDVolumePluginName: func() bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationRBD) }, diff --git a/pkg/volume/csimigration/plugin_manager.go b/pkg/volume/csimigration/plugin_manager.go index eb10565233961..ef83fbbb97c02 100644 --- a/pkg/volume/csimigration/plugin_manager.go +++ b/pkg/volume/csimigration/plugin_manager.go @@ -72,6 +72,8 @@ func (pm PluginManager) IsMigrationCompleteForPlugin(pluginName string) bool { return pm.featureGate.Enabled(features.InTreePluginOpenStackUnregister) case csilibplugins.VSphereInTreePluginName: return pm.featureGate.Enabled(features.InTreePluginvSphereUnregister) + case csilibplugins.PortworxVolumePluginName: + return pm.featureGate.Enabled(features.InTreePluginPortworxUnregister) case csilibplugins.RBDVolumePluginName: return pm.featureGate.Enabled(features.InTreePluginRBDUnregister) default: @@ -100,6 +102,8 @@ func (pm PluginManager) IsMigrationEnabledForPlugin(pluginName string) bool { return pm.featureGate.Enabled(features.CSIMigrationOpenStack) case csilibplugins.VSphereInTreePluginName: return pm.featureGate.Enabled(features.CSIMigrationvSphere) + case csilibplugins.PortworxVolumePluginName: + return pm.featureGate.Enabled(features.CSIMigrationPortworx) case csilibplugins.RBDVolumePluginName: return pm.featureGate.Enabled(features.CSIMigrationRBD) default: diff --git a/pkg/volume/portworx/portworx.go b/pkg/volume/portworx/portworx.go index 2f15b69e8a5e3..103e244e1fdf5 100644 --- a/pkg/volume/portworx/portworx.go +++ b/pkg/volume/portworx/portworx.go @@ -18,17 +18,18 @@ package portworx import ( "fmt" - "os" - - volumeclient "github.com/libopenstorage/openstorage/api/client/volume" "k8s.io/klog/v2" "k8s.io/mount-utils" utilstrings "k8s.io/utils/strings" + "os" + volumeclient "github.com/libopenstorage/openstorage/api/client/volume" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" ) @@ -62,6 +63,11 @@ func getPath(uid types.UID, volName string, host volume.VolumeHost) string { return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(portworxVolumePluginName), volName) } +func (plugin *portworxVolumePlugin) IsMigratedToCSI() bool { + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && + utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) +} + func (plugin *portworxVolumePlugin) Init(host volume.VolumeHost) error { client, err := volumeclient.NewDriverClient( fmt.Sprintf("http://%s:%d", host.GetHostName(), osdMgmtDefaultPort), diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/portworx.go b/staging/src/k8s.io/csi-translation-lib/plugins/portworx.go new file mode 100644 index 0000000000000..1dcf9a255db24 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/portworx.go @@ -0,0 +1,145 @@ +/* +Copyright 2021 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" + + "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + PortworxVolumePluginName = "kubernetes.io/portworx-volume" + PortworxDriverName = "pxd.portworx.com" +) + +var _ InTreePlugin = &portworxCSITranslator{} + +type portworxCSITranslator struct{} + +func NewPortworxCSITranslator() InTreePlugin { + return &portworxCSITranslator{} +} + +// TranslateInTreeStorageClassToCSI takes in-tree storage class used by in-tree plugin +// and translates them to a storageclass consumable by CSI plugin +func (p portworxCSITranslator) TranslateInTreeStorageClassToCSI(sc *storagev1.StorageClass) (*storagev1.StorageClass, error) { + if sc == nil { + return nil, fmt.Errorf("sc is nil") + } + sc.Provisioner = PortworxDriverName + return sc, nil +} + +// TranslateInTreeInlineVolumeToCSI takes a inline volume and will translate +// the in-tree inline volume source to a CSIPersistentVolumeSource +func (p portworxCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) { + if volume == nil || volume.PortworxVolume == nil { + return nil, fmt.Errorf("volume is nil or PortworxVolume not defined on volume") + } + + var am v1.PersistentVolumeAccessMode + if volume.PortworxVolume.ReadOnly { + am = v1.ReadOnlyMany + } else { + am = v1.ReadWriteOnce + } + + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", PortworxDriverName, volume.PortworxVolume.VolumeID), + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: PortworxDriverName, + VolumeHandle: volume.PortworxVolume.VolumeID, + FSType: volume.PortworxVolume.FSType, + VolumeAttributes: make(map[string]string), + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{am}, + }, + } + return pv, nil +} + +// TranslateInTreePVToCSI takes a Portworx persistent volume and will translate +// the in-tree pv source to a CSI Source +func (p portworxCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.PortworxVolume == nil { + return nil, fmt.Errorf("pv is nil or PortworxVolume not defined on pv") + } + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: PortworxDriverName, + VolumeHandle: pv.Spec.PortworxVolume.VolumeID, + FSType: pv.Spec.PortworxVolume.FSType, + VolumeAttributes: make(map[string]string), // copy access mode + } + pv.Spec.PortworxVolume = nil + pv.Spec.CSI = csiSource + + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate +// it to a in-tree Persistent Volume Source for the in-tree volume +func (p portworxCSITranslator) 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 + + portworxSource := &v1.PortworxVolumeSource{ + VolumeID: csiSource.VolumeHandle, + FSType: csiSource.FSType, + ReadOnly: csiSource.ReadOnly, + } + pv.Spec.CSI = nil + pv.Spec.PortworxVolume = portworxSource + + return pv, nil +} + +// CanSupport tests whether the plugin supports a given persistent volume +// specification from the API. +func (p portworxCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.PortworxVolume != nil +} + +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. +func (p portworxCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.PortworxVolume != nil +} + +// GetInTreePluginName returns the in-tree plugin name this migrates +func (p portworxCSITranslator) GetInTreePluginName() string { + return PortworxVolumePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin that supersedes the in-tree plugin +func (p portworxCSITranslator) GetCSIPluginName() string { + return PortworxDriverName +} + +// RepairVolumeHandle generates a correct volume handle based on node ID information. +func (p portworxCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) { + return volumeHandle, nil +} diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/portworx_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/portworx_test.go new file mode 100644 index 0000000000000..c0a79ea454328 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/portworx_test.go @@ -0,0 +1,348 @@ +/* +Copyright 2021 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 ( + v1 "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "reflect" + "testing" +) + +func TestTranslatePortworxInTreeStorageClassToCSI(t *testing.T) { + translator := NewPortworxCSITranslator() + testCases := []struct { + name string + inTreeSC *storage.StorageClass + csiSC *storage.StorageClass + errorExp bool + }{ + { + name: "correct", + inTreeSC: &storage.StorageClass{ + Provisioner: PortworxVolumePluginName, + Parameters: map[string]string{ + "repl": "1", + "fs": "ext4", + "shared": "true", + "priority_io": "high", + }, + }, + csiSC: &storage.StorageClass{ + Provisioner: PortworxDriverName, + Parameters: map[string]string{ + "repl": "1", + "fs": "ext4", + "shared": "true", + "priority_io": "high", + }, + }, + errorExp: false, + }, + { + name: "nil, err expected", + inTreeSC: nil, + csiSC: nil, + errorExp: true, + }, + { + name: "empty", + inTreeSC: &storage.StorageClass{}, + csiSC: &storage.StorageClass{ + Provisioner: PortworxDriverName, + }, + errorExp: false, + }, + } + for _, tc := range testCases { + t.Logf("Testing %v", tc.name) + result, err := translator.TranslateInTreeStorageClassToCSI(tc.inTreeSC) + if err != nil && !tc.errorExp { + t.Errorf("Did not expect error but got: %v", err) + } + if err == nil && tc.errorExp { + t.Errorf("Expected error, but did not get one.") + } + if !reflect.DeepEqual(result, tc.csiSC) { + t.Errorf("Got parameters: %v\n, expected :%v", result, tc.csiSC) + } + } +} + +func TestTranslatePortworxInTreeInlineVolumeToCSI(t *testing.T) { + translator := NewPortworxCSITranslator() + testCases := []struct { + name string + inLine *v1.Volume + csiVol *v1.PersistentVolume + errExpected bool + }{ + { + name: "normal", + inLine: &v1.Volume{ + Name: "PortworxVol", + VolumeSource: v1.VolumeSource{ + PortworxVolume: &v1.PortworxVolumeSource{ + VolumeID: "ID", + FSType: "type", + ReadOnly: false, + }, + }, + }, + csiVol: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + // Must be unique per disk as it is used as the unique part of the + // staging path + Name: "pxd.portworx.com-ID", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: PortworxDriverName, + VolumeHandle: "ID", + FSType: "type", + VolumeAttributes: make(map[string]string), + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + }, + }, + errExpected: false, + }, + { + name: "nil", + inLine: nil, + csiVol: nil, + errExpected: true, + }, + } + + for _, tc := range testCases { + t.Logf("Testing %v", tc.name) + result, err := translator.TranslateInTreeInlineVolumeToCSI(tc.inLine, "ns") + if err != nil && !tc.errExpected { + t.Errorf("Did not expect error but got: %v", err) + } + if err == nil && tc.errExpected { + t.Errorf("Expected error, but did not get one.") + } + if !reflect.DeepEqual(result, tc.csiVol) { + t.Errorf("Got parameters: %v\n, expected :%v", result, tc.csiVol) + } + } +} + +func TestTranslatePortworxInTreePVToCSI(t *testing.T) { + translator := NewPortworxCSITranslator() + + testCases := []struct { + name string + inTree *v1.PersistentVolume + csi *v1.PersistentVolume + errExpected bool + }{ + { + name: "no Portworx volume", + inTree: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + }, + }, + csi: nil, + errExpected: true, + }, + { + name: "normal", + inTree: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + PortworxVolume: &v1.PortworxVolumeSource{ + VolumeID: "ID1111", + FSType: "type", + ReadOnly: false, + }, + }, + }, + }, + csi: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: PortworxDriverName, + VolumeHandle: "ID1111", + FSType: "type", + VolumeAttributes: make(map[string]string), + }, + }, + }, + }, + errExpected: false, + }, + { + name: "nil PV", + inTree: nil, + csi: nil, + errExpected: true, + }, + } + + for _, tc := range testCases { + t.Logf("Testing %v", tc.name) + result, err := translator.TranslateInTreePVToCSI(tc.inTree) + if err != nil && !tc.errExpected { + t.Errorf("Did not expect error but got: %v", err) + } + if err == nil && tc.errExpected { + t.Errorf("Expected error, but did not get one.") + } + if !reflect.DeepEqual(result, tc.csi) { + t.Errorf("Got parameters: %v\n, expected :%v", result, tc.csi) + } + } +} + +func TestTranslatePortworxCSIPvToInTree(t *testing.T) { + translator := NewPortworxCSITranslator() + + testCases := []struct { + name string + csi *v1.PersistentVolume + inTree *v1.PersistentVolume + errExpected bool + }{ + { + name: "no CSI section", + csi: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + // Must be unique per disk as it is used as the unique part of the + // staging path + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + }, + }, + inTree: nil, + errExpected: true, + }, + { + name: "normal", + csi: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: PortworxDriverName, + VolumeHandle: "ID1111", + FSType: "type", + VolumeAttributes: make(map[string]string), + }, + }, + }, + }, + inTree: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pxd.portworx.com", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + ClaimRef: &v1.ObjectReference{ + Name: "test-pvc", + Namespace: "default", + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + PortworxVolume: &v1.PortworxVolumeSource{ + VolumeID: "ID1111", + FSType: "type", + ReadOnly: false, + }, + }, + }, + }, + errExpected: false, + }, + { + name: "nil PV", + inTree: nil, + csi: nil, + errExpected: true, + }, + } + + for _, tc := range testCases { + t.Logf("Testing %v", tc.name) + result, err := translator.TranslateCSIPVToInTree(tc.csi) + if err != nil && !tc.errExpected { + t.Errorf("Did not expect error but got: %v", err) + } + if err == nil && tc.errExpected { + t.Errorf("Expected error, but did not get one.") + } + if !reflect.DeepEqual(result, tc.inTree) { + t.Errorf("Got parameters: %v\n, expected :%v", result, tc.inTree) + } + } +} diff --git a/staging/src/k8s.io/csi-translation-lib/translate.go b/staging/src/k8s.io/csi-translation-lib/translate.go index 014c094d418ae..9dde216299bfe 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate.go +++ b/staging/src/k8s.io/csi-translation-lib/translate.go @@ -33,6 +33,7 @@ var ( plugins.AzureDiskDriverName: plugins.NewAzureDiskCSITranslator(), plugins.AzureFileDriverName: plugins.NewAzureFileCSITranslator(), plugins.VSphereDriverName: plugins.NewvSphereCSITranslator(), + plugins.PortworxDriverName: plugins.NewPortworxCSITranslator(), plugins.RBDDriverName: plugins.NewRBDCSITranslator(), } ) 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 f53b4050e31dd..8a08c6e6ed3cf 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate_test.go +++ b/staging/src/k8s.io/csi-translation-lib/translate_test.go @@ -439,6 +439,12 @@ func generateUniqueVolumeSource(driverName string) (v1.VolumeSource, error) { FSType: "ext4", }, }, nil + case plugins.PortworxDriverName: + return v1.VolumeSource{ + PortworxVolume: &v1.PortworxVolumeSource{ + VolumeID: string(uuid.NewUUID()), + }, + }, nil case plugins.RBDDriverName: return v1.VolumeSource{ RBD: &v1.RBDVolumeSource{