diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 0f6d1c74cef..0ed154065cd 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -423,6 +423,12 @@ const ( // Enables the Azure Disk in-tree driver to Azure Disk Driver migration feature. CSIMigrationAzureDisk featuregate.Feature = "CSIMigrationAzureDisk" + // owner: @andyzhangx + // alpha: v1.15 + // + // Enables the Azure File in-tree driver to Azure File Driver migration feature. + CSIMigrationAzureFile featuregate.Feature = "CSIMigrationAzureFile" + // owner: @RobertKrawitz // beta: v1.15 // @@ -503,6 +509,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CSIMigrationGCE: {Default: false, PreRelease: featuregate.Alpha}, CSIMigrationAWS: {Default: false, PreRelease: featuregate.Alpha}, CSIMigrationAzureDisk: {Default: false, PreRelease: featuregate.Alpha}, + CSIMigrationAzureFile: {Default: false, PreRelease: featuregate.Alpha}, RunAsGroup: {Default: true, PreRelease: featuregate.Beta}, CSIMigrationOpenStack: {Default: false, PreRelease: featuregate.Alpha}, VolumeSubpath: {Default: true, PreRelease: featuregate.GA}, diff --git a/pkg/volume/azure_file/BUILD b/pkg/volume/azure_file/BUILD index 30bfee3d12e..697c655cfa9 100644 --- a/pkg/volume/azure_file/BUILD +++ b/pkg/volume/azure_file/BUILD @@ -16,6 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/azure_file", deps = [ + "//pkg/features:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/util:go_default_library", @@ -24,6 +25,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/azure:go_default_library", diff --git a/pkg/volume/azure_file/azure_file.go b/pkg/volume/azure_file/azure_file.go index 5db49ba2e82..e87aa913ce5 100644 --- a/pkg/volume/azure_file/azure_file.go +++ b/pkg/volume/azure_file/azure_file.go @@ -26,9 +26,11 @@ import ( "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" cloudprovider "k8s.io/cloud-provider" volumehelpers "k8s.io/cloud-provider/volume/helpers" "k8s.io/klog" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" volutil "k8s.io/kubernetes/pkg/volume/util" @@ -82,7 +84,8 @@ func (plugin *azureFilePlugin) CanSupport(spec *volume.Spec) bool { } func (plugin *azureFilePlugin) IsMigratedToCSI() bool { - return false + return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) && + utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureFile) } func (plugin *azureFilePlugin) RequiresRemount() bool { diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD index 43d2f4e141e..70eb5d54d7b 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD +++ b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD @@ -5,6 +5,7 @@ go_library( srcs = [ "aws_ebs.go", "azure_disk.go", + "azure_file.go", "gce_pd.go", "in_tree_volume.go", "openstack_cinder.go", @@ -39,6 +40,7 @@ go_test( srcs = [ "aws_ebs_test.go", "azure_disk_test.go", + "azure_file_test.go", "gce_pd_test.go", ], embed = [":go_default_library"], 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 new file mode 100644 index 00000000000..b0ab0cd662a --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/azure_file.go @@ -0,0 +1,148 @@ +/* +Copyright 2019 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" + + "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" +) + +const ( + // AzureFileDriverName is the name of the CSI driver for Azure File + AzureFileDriverName = "file.csi.azure.com" + // AzureFileInTreePluginName is the name of the intree plugin for Azure file + AzureFileInTreePluginName = "kubernetes.io/azure-file" + + separator = "#" + volumeIDTemplate = "%s#%s#%s" + // Parameter names defined in azure file CSI driver, refer to + // https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md + azureFileShareName = "shareName" +) + +var _ InTreePlugin = &azureFileCSITranslator{} + +// azureFileCSITranslator handles translation of PV spec from In-tree +// Azure File to CSI Azure File and vice versa +type azureFileCSITranslator struct{} + +// NewAzureFileCSITranslator returns a new instance of azureFileTranslator +func NewAzureFileCSITranslator() InTreePlugin { + return &azureFileCSITranslator{} +} + +// TranslateInTreeStorageClassParametersToCSI translates InTree Azure File storage class parameters to CSI storage class +func (t *azureFileCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) { + return sc, 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) { + if pv == nil || pv.Spec.AzureFile == nil { + return nil, fmt.Errorf("pv is nil or Azure File source not defined on pv") + } + + azureSource := pv.Spec.PersistentVolumeSource.AzureFile + + volumeID := fmt.Sprintf(volumeIDTemplate, "", azureSource.SecretName, azureSource.ShareName) + // refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md + csiSource := &v1.CSIPersistentVolumeSource{ + VolumeHandle: volumeID, + ReadOnly: azureSource.ReadOnly, + VolumeAttributes: map[string]string{azureFileShareName: azureSource.ShareName}, + } + + csiSource.NodePublishSecretRef = &v1.SecretReference{ + Name: azureSource.ShareName, + Namespace: *azureSource.SecretNamespace, + } + + pv.Spec.PersistentVolumeSource.AzureFile = nil + pv.Spec.PersistentVolumeSource.CSI = csiSource + pv.Spec.AccessModes = backwardCompatibleAccessModes(pv.Spec.AccessModes) + + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the Azure File CSI source to a AzureFile source. +func (t *azureFileCSITranslator) 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 + + // refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md + azureSource := &v1.AzureFilePersistentVolumeSource{ + ReadOnly: csiSource.ReadOnly, + } + + if csiSource.NodePublishSecretRef != nil && csiSource.NodePublishSecretRef.Name != "" { + azureSource.SecretName = csiSource.NodePublishSecretRef.Name + azureSource.SecretNamespace = &csiSource.NodePublishSecretRef.Namespace + if csiSource.VolumeAttributes != nil { + if shareName, ok := csiSource.VolumeAttributes[azureFileShareName]; ok { + azureSource.ShareName = shareName + } + } + } else { + _, _, fileShareName, err := getFileShareInfo(csiSource.VolumeHandle) + if err != nil { + return nil, err + } + azureSource.ShareName = fileShareName + // to-do: for dynamic provision scenario in CSI, it uses cluster's identity to get storage account key + // secret for the file share is not created, we may create a serect here + } + + pv.Spec.CSI = nil + pv.Spec.AzureFile = azureSource + + return pv, nil +} + +// CanSupport tests whether the plugin supports a given volume +// specification from the API. The spec pointer should be considered +// const. +func (t *azureFileCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.AzureFile != nil +} + +// GetInTreePluginName returns the name of the intree plugin driver +func (t *azureFileCSITranslator) GetInTreePluginName() string { + return AzureFileInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (t *azureFileCSITranslator) GetCSIPluginName() string { + return AzureFileDriverName +} + +// get file share info according to volume id, e.g. +// input: "rg#f5713de20cde511e8ba4900#pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41" +// output: rg, f5713de20cde511e8ba4900, pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41 +func getFileShareInfo(id string) (string, string, string, error) { + segments := strings.Split(id, separator) + if len(segments) < 3 { + return "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id) + } + return segments[0], segments[1], segments[2], nil +} diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/azure_file_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/azure_file_test.go new file mode 100644 index 00000000000..9bd35429961 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/azure_file_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2019 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" +) + +func TestGetFileShareInfo(t *testing.T) { + tests := []struct { + options string + expected1 string + expected2 string + expected3 string + expected4 error + }{ + { + options: "rg#f5713de20cde511e8ba4900#pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41", + expected1: "rg", + expected2: "f5713de20cde511e8ba4900", + expected3: "pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41", + expected4: nil, + }, + { + options: "rg#f5713de20cde511e8ba4900", + expected1: "", + expected2: "", + expected3: "", + expected4: fmt.Errorf("error parsing volume id: \"rg#f5713de20cde511e8ba4900\", should at least contain two #"), + }, + { + options: "rg", + expected1: "", + expected2: "", + expected3: "", + expected4: fmt.Errorf("error parsing volume id: \"rg\", should at least contain two #"), + }, + { + options: "", + expected1: "", + expected2: "", + expected3: "", + expected4: fmt.Errorf("error parsing volume id: \"\", should at least contain two #"), + }, + } + + for _, test := range tests { + result1, result2, result3, result4 := getFileShareInfo(test.options) + if !reflect.DeepEqual(result1, test.expected1) || !reflect.DeepEqual(result2, test.expected2) || + !reflect.DeepEqual(result3, test.expected3) || !reflect.DeepEqual(result4, test.expected4) { + t.Errorf("input: %q, getFileShareInfo result1: %q, expected1: %q, result2: %q, expected2: %q, result3: %q, expected3: %q, result4: %q, expected4: %q", test.options, result1, test.expected1, result2, test.expected2, + result3, test.expected3, result4, test.expected4) + } + } +} diff --git a/staging/src/k8s.io/csi-translation-lib/translate.go b/staging/src/k8s.io/csi-translation-lib/translate.go index 67296f3163d..c9864f301c3 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate.go +++ b/staging/src/k8s.io/csi-translation-lib/translate.go @@ -31,6 +31,7 @@ var ( plugins.AWSEBSDriverName: plugins.NewAWSElasticBlockStoreCSITranslator(), plugins.CinderDriverName: plugins.NewOpenStackCinderCSITranslator(), plugins.AzureDiskDriverName: plugins.NewAzureDiskCSITranslator(), + plugins.AzureFileDriverName: plugins.NewAzureFileCSITranslator(), } )