From a380b391eabd82f1fc9336be01fbb773be0bb63a Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 22 Apr 2019 23:17:49 -0700 Subject: [PATCH] Implement CSI migration logic for volume resize * Using PVC annotation for sychronication between in-tree resizer and extternal resizer * Modify enqueue condition for resizer migration * Add unit tests --- Gopkg.lock | 20 ++ pkg/controller/controller.go | 20 +- pkg/controller/controller_test.go | 2 +- pkg/csi/mock_client.go | 3 +- pkg/resizer/csi_resizer.go | 43 +++- pkg/resizer/csi_resizer_test.go | 202 +++++++++++++++++- pkg/resizer/resizer.go | 5 +- pkg/resizer/trivial_resizer.go | 9 +- pkg/util/events.go | 6 + vendor/k8s.io/cloud-provider/LICENSE | 201 +++++++++++++++++ .../k8s.io/cloud-provider/volume/constants.go | 26 +++ vendor/k8s.io/csi-translation-lib/LICENSE | 201 +++++++++++++++++ .../csi-translation-lib/plugins/aws_ebs.go | 170 +++++++++++++++ .../csi-translation-lib/plugins/gce_pd.go | 190 ++++++++++++++++ .../plugins/in_tree_volume.go | 46 ++++ .../plugins/openstack_cinder.go | 103 +++++++++ .../k8s.io/csi-translation-lib/translate.go | 149 +++++++++++++ 17 files changed, 1379 insertions(+), 17 deletions(-) create mode 100644 vendor/k8s.io/cloud-provider/LICENSE create mode 100644 vendor/k8s.io/cloud-provider/volume/constants.go create mode 100644 vendor/k8s.io/csi-translation-lib/LICENSE create mode 100644 vendor/k8s.io/csi-translation-lib/plugins/aws_ebs.go create mode 100644 vendor/k8s.io/csi-translation-lib/plugins/gce_pd.go create mode 100644 vendor/k8s.io/csi-translation-lib/plugins/in_tree_volume.go create mode 100644 vendor/k8s.io/csi-translation-lib/plugins/openstack_cinder.go create mode 100644 vendor/k8s.io/csi-translation-lib/translate.go diff --git a/Gopkg.lock b/Gopkg.lock index 9fbca139e..594177573 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -597,6 +597,25 @@ revision = "6ee68ca5fd8355d024d02f9db0b3b667e8357a0f" version = "kubernetes-1.14.0" +[[projects]] + branch = "master" + digest = "1:c3dbea23f3fdb796c9464cec72ec3d4609178f6974026b344992f5b6b7566ef5" + name = "k8s.io/cloud-provider" + packages = ["volume"] + pruneopts = "NUT" + revision = "029ecc113e6d819f75bde3705b0ff3fafb52d397" + +[[projects]] + branch = "master" + digest = "1:4f625748474cd525730ed105e6f0d344a555881207bc7aed241a4356b63804f0" + name = "k8s.io/csi-translation-lib" + packages = [ + ".", + "plugins", + ] + pruneopts = "NUT" + revision = "2a8de10f2ef801771beeea04ba3e0b5e35db8fac" + [[projects]] digest = "1:9cc257b3c9ff6a0158c9c661ab6eebda1fe8a4a4453cd5c4044dc9a2ebfb992b" name = "k8s.io/klog" @@ -664,6 +683,7 @@ "k8s.io/client-go/tools/clientcmd", "k8s.io/client-go/tools/record", "k8s.io/client-go/util/workqueue", + "k8s.io/csi-translation-lib", "k8s.io/klog", ] solver-name = "gps-cdcl" diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 3ac3f71e7..738ab2d72 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -123,11 +123,27 @@ func (ctrl *resizeController) updatePVC(oldObj, newObj interface{}) { newSize := newPVC.Spec.Resources.Requests[v1.ResourceStorage] oldSize := oldPVC.Spec.Resources.Requests[v1.ResourceStorage] + newResizerName := newPVC.Annotations[util.VolumeResizerKey] + oldResizerName := oldPVC.Annotations[util.VolumeResizerKey] + // We perform additional checks to avoid double processing of PVCs, as we will also receive Update event when: // 1. Administrator or users may introduce other changes(such as add labels, modify annotations, etc.) // unrelated to volume resize. // 2. Informer will resync and send Update event periodically without any changes. - if newSize.Cmp(oldSize) > 0 { + // + // We add the PVC into work queue when the new size is larger then the old size + // or when the resizer name changes. This is needed for CSI migration for the follow two cases: + // + // 1. First time a migrated PVC is expanded: + // It does not yet have the annotation because annotation is only added by in-tree resizer when it receives a volume + // expansion request. So first update event that will be received by external-resizer will be ignored because it won't + // know how to support resizing of a "un-annotated" in-tree PVC. When in-tree resizer does add the annotation, a second + // update even will be received and we add the pvc to workqueue. If annotation matches the registered driver name in + // csi_resizer object, we proceeds with expansion internally or we discard the PVC. + // 2. An already expanded in-tree PVC: + // An in-tree PVC is resized with in-tree resizer. And later, CSI migration is turned on and resizer name is updated from + // in-tree resizer name to CSI driver name. + if newSize.Cmp(oldSize) > 0 || newResizerName != oldResizerName { ctrl.addPVC(newObj) } } @@ -249,7 +265,7 @@ func (ctrl *resizeController) pvcNeedResize(pvc *v1.PersistentVolumeClaim) bool // pvNeedResize returns true if a pv supports and also requests resize. func (ctrl *resizeController) pvNeedResize(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) bool { - if !ctrl.resizer.CanSupport(pv) { + if !ctrl.resizer.CanSupport(pv, pvc) { klog.V(4).Infof("Resizer %q doesn't support PV %q", ctrl.name, pv.Name) return false } diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 5cb8c3e43..3b698c996 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -65,7 +65,7 @@ func TestController(t *testing.T) { NodeResize: true, }, } { - client := csi.NewMockClient(test.NodeResize, true, true) + client := csi.NewMockClient("mock", test.NodeResize, true, true) driverName, _ := client.GetDriverName(context.TODO()) initialObjects := []runtime.Object{} diff --git a/pkg/csi/mock_client.go b/pkg/csi/mock_client.go index b5b312bbf..231bed78d 100644 --- a/pkg/csi/mock_client.go +++ b/pkg/csi/mock_client.go @@ -3,11 +3,12 @@ package csi import "context" func NewMockClient( + name string, supportsNodeResize bool, supportsControllerResize bool, supportsPluginControllerService bool) *MockClient { return &MockClient{ - name: "mock", + name: name, supportsNodeResize: supportsNodeResize, supportsControllerResize: supportsControllerResize, supportsPluginControllerService: supportsPluginControllerService, diff --git a/pkg/resizer/csi_resizer.go b/pkg/resizer/csi_resizer.go index 800d633cf..89cb57ff2 100644 --- a/pkg/resizer/csi_resizer.go +++ b/pkg/resizer/csi_resizer.go @@ -23,12 +23,15 @@ import ( "time" "github.com/kubernetes-csi/external-resizer/pkg/csi" + "github.com/kubernetes-csi/external-resizer/pkg/util" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + + csitranslationlib "k8s.io/csi-translation-lib" "k8s.io/klog" ) @@ -107,7 +110,18 @@ func (r *csiResizer) Name() string { return r.name } -func (r *csiResizer) CanSupport(pv *v1.PersistentVolume) bool { +// CanSupport returns whether the PV is supported by resizer +// Resizer will resize the volume if it is CSI volume or is migration enabled in-tree volume +func (r *csiResizer) CanSupport(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) bool { + resizerName := pvc.Annotations[util.VolumeResizerKey] + // resizerName will be CSI driver name when CSI migration is enabled + // otherwise, it will be in-tree plugin name + // r.name is the CSI driver name, return true only when they match + // and the CSI driver is migrated + if csitranslationlib.IsMigratedCSIDriverByName(r.name) && resizerName == r.name { + return true + } + source := pv.Spec.CSI if source == nil { klog.V(4).Infof("PV %s is not a CSI volume, skip it", pv.Name) @@ -120,14 +134,32 @@ func (r *csiResizer) CanSupport(pv *v1.PersistentVolume) bool { return true } +// Resize resizes the persistence volume given request size +// It supports both CSI volume and migrated in-tree volume func (r *csiResizer) Resize(pv *v1.PersistentVolume, requestSize resource.Quantity) (resource.Quantity, bool, error) { oldSize := pv.Spec.Capacity[v1.ResourceStorage] - source := pv.Spec.CSI - if source == nil { - return oldSize, false, errors.New("not a CSI volume") + var volumeID string + var source *v1.CSIPersistentVolumeSource + if pv.Spec.CSI != nil { + // handle CSI volume + source = pv.Spec.CSI + volumeID = source.VolumeHandle + } else { + if csitranslationlib.IsMigratedCSIDriverByName(r.name) { + // handle migrated in-tree volume + csiPV, err := csitranslationlib.TranslateInTreePVToCSI(pv) + if err != nil { + return oldSize, false, fmt.Errorf("failed to translate persistent volume: %v", err) + } + source = csiPV.Spec.CSI + volumeID = source.VolumeHandle + } else { + // non-migrated in-tree volume + return oldSize, false, fmt.Errorf("volume %v is not migrated to CSI", pv.Name) + } } - volumeID := source.VolumeHandle + if len(volumeID) == 0 { return oldSize, false, errors.New("empty volume handle") } @@ -148,6 +180,7 @@ func (r *csiResizer) Resize(pv *v1.PersistentVolume, requestSize resource.Quanti if err != nil { return oldSize, nodeResizeRequired, err } + return *resource.NewQuantity(newSizeBytes, resource.BinarySI), nodeResizeRequired, err } diff --git a/pkg/resizer/csi_resizer_test.go b/pkg/resizer/csi_resizer_test.go index e99a82913..dc122f030 100644 --- a/pkg/resizer/csi_resizer_test.go +++ b/pkg/resizer/csi_resizer_test.go @@ -1,14 +1,16 @@ package resizer import ( + "errors" "fmt" "testing" "time" "github.com/kubernetes-csi/external-resizer/pkg/csi" + "github.com/kubernetes-csi/external-resizer/pkg/util" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" @@ -56,7 +58,7 @@ func TestNewResizer(t *testing.T) { Error: resizeNotSupportErr, }, } { - client := csi.NewMockClient(c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService) + client := csi.NewMockClient("mock", c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService) k8sClient, informerFactory := fakeK8s() resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory) if err != c.Error { @@ -89,7 +91,7 @@ func TestResizeWithSecret(t *testing.T) { }, } for _, tc := range tests { - client := csi.NewMockClient(true, true, true) + client := csi.NewMockClient("mock", true, true, true) secret := makeSecret("some-secret", "secret-namespace") k8sClient := fake.NewSimpleClientset(secret) pv := makeTestPV("test-csi", 2, "ebs-csi", "vol-abcde", tc.hasExpansionSecret) @@ -117,7 +119,7 @@ func TestResizeWithSecret(t *testing.T) { func makeSecret(name string, namespace string) *v1.Secret { return &v1.Secret{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, UID: "23456", @@ -130,9 +132,161 @@ func makeSecret(name string, namespace string) *v1.Secret { } } +func TestResizeMigratedPV(t *testing.T) { + testCases := []struct { + name string + driverName string + pv *v1.PersistentVolume + nodeResizeRequired bool + err error + }{ + { + name: "Test AWS EBS CSI Driver", + driverName: "ebs.csi.aws.com", + pv: createInTreeEBSPV(1), + nodeResizeRequired: true, + }, + { + name: "Test GCE PD Driver", + driverName: "pd.csi.storage.gke.io", + pv: createInTreeGCEPDPV(1), + nodeResizeRequired: true, + }, + { + name: "Test unknonwn driver", + driverName: "unknown", + pv: createInTreeEBSPV(1), + nodeResizeRequired: true, + err: errors.New("volume testEBSPV is not migrated to CSI"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + driverName := tc.driverName + client := csi.NewMockClient(driverName, true, true, true) + k8sClient, informerFactory := fakeK8s() + resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory) + if err != nil { + t.Fatalf("Failed to create resizer: %v", err) + } + + pv := tc.pv + expectedSize := quantityGB(2) + newSize, nodeResizeRequired, err := resizer.Resize(pv, expectedSize) + + if tc.err != nil { + if err == nil { + t.Fatalf("Got wrong error, wanted: %v, got: %v", tc.err, err) + } + } else { + if err != nil { + t.Fatalf("Failed to resize the PV: %v", err) + } + + if newSize != expectedSize { + t.Fatalf("newSize mismatches, wanted: %v, got: %v", expectedSize, newSize) + } + if nodeResizeRequired != tc.nodeResizeRequired { + t.Fatalf("nodeResizeRequired mismatches, wanted: %v, got: %v", tc.nodeResizeRequired, nodeResizeRequired) + } + } + }) + } +} + +func TestCanSupport(t *testing.T) { + testCases := []struct { + name string + driverName string + pv *v1.PersistentVolume + pvc *v1.PersistentVolumeClaim + canSupport bool + }{ + { + name: "EBS PV/PVC is supported", + driverName: "ebs.csi.aws.com", + pv: createInTreeEBSPV(1), + pvc: createPVC("ebs.csi.aws.com"), + canSupport: true, + }, + { + name: "EBS PV/PVC is not supported when migartion is disabled", + driverName: "ebs.csi.aws.com", + pv: createInTreeEBSPV(1), + pvc: createPVC("kubernetes.io/aws-ebs"), + canSupport: false, + }, + { + name: "PD PV/PVC is supported", + driverName: "pd.csi.storage.gke.io", + pv: createInTreeGCEPDPV(1), + pvc: createPVC("pd.csi.storage.gke.io"), + canSupport: true, + }, + { + name: "unknown PV/PVC is not supported", + driverName: "ebs.csi.aws.com", + pv: createInTreeEBSPV(1), + pvc: createPVC("unknown"), + canSupport: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + driverName := tc.driverName + client := csi.NewMockClient(driverName, true, true, true) + k8sClient, informerFactory := fakeK8s() + resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory) + if err != nil { + t.Fatalf("Failed to create resizer: %v", err) + } + + canSupport := resizer.CanSupport(tc.pv, tc.pvc) + if canSupport != tc.canSupport { + t.Fatalf("Wrong canSupport, wanted: %v got: %v", tc.canSupport, canSupport) + } + }) + } +} + +func quantityGB(i int) resource.Quantity { + q := resource.NewQuantity(int64(i*1024*1024), resource.BinarySI) + return *q +} + +func createPVC(resizerName string) *v1.PersistentVolumeClaim { + request := quantityGB(2) + capacity := quantityGB(1) + + return &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPVC", + Namespace: "test", + Annotations: map[string]string{ + util.VolumeResizerKey: resizerName, + }, + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: request, + }, + }, + VolumeName: "testPV", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + Capacity: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: capacity, + }, + }, + } +} + func makeTestPV(name string, sizeGig int, driverName, volID string, withSecret bool) *v1.PersistentVolume { pv := &v1.PersistentVolume{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: metav1.ObjectMeta{ Name: name, }, Spec: v1.PersistentVolumeSpec{ @@ -165,3 +319,41 @@ func fakeK8s() (kubernetes.Interface, informers.SharedInformerFactory) { informerFactory := informers.NewSharedInformerFactory(client, 0) return client, informerFactory } + +func createInTreeEBSPV(capacityGB int) *v1.PersistentVolume { + capacity := quantityGB(capacityGB) + + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testEBSPV", + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: capacity, + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "testVolumeId", + }, + }, + }, + } +} + +func createInTreeGCEPDPV(capacityGB int) *v1.PersistentVolume { + capacity := quantityGB(capacityGB) + + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPDPV", + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: capacity, + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, + }, + }, + } +} diff --git a/pkg/resizer/resizer.go b/pkg/resizer/resizer.go index 2a3f299ba..51738571e 100644 --- a/pkg/resizer/resizer.go +++ b/pkg/resizer/resizer.go @@ -25,8 +25,9 @@ import ( type Resizer interface { // Name returns the resizer's name. Name() string - // CanSupport returns true if resizer supports resize operation of this PV. - CanSupport(pv *v1.PersistentVolume) bool + // CanSupport returns true if resizer supports resize operation of this PV + // with its corresponding PVC. + CanSupport(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) bool // Resize executes the resize operation of this PV. Resize(pv *v1.PersistentVolume, requestSize resource.Quantity) (newSize resource.Quantity, fsResizeRequired bool, err error) } diff --git a/pkg/resizer/trivial_resizer.go b/pkg/resizer/trivial_resizer.go index fa6f5221e..e174a1dc3 100644 --- a/pkg/resizer/trivial_resizer.go +++ b/pkg/resizer/trivial_resizer.go @@ -17,8 +17,10 @@ limitations under the License. package resizer import ( + "github.com/kubernetes-csi/external-resizer/pkg/util" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + csitranslationlib "k8s.io/csi-translation-lib" "k8s.io/klog" ) @@ -35,7 +37,12 @@ func (r *trivialResizer) Name() string { return r.name } -func (r *trivialResizer) CanSupport(pv *v1.PersistentVolume) bool { +func (r *trivialResizer) CanSupport(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) bool { + resizerName := pvc.Annotations[util.VolumeResizerKey] + if csitranslationlib.IsMigratedCSIDriverByName(r.name) && resizerName == r.name { + return true + } + source := pv.Spec.CSI if source == nil { klog.V(4).Infof("PV %s is not a CSI volume, skip it", pv.Name) diff --git a/pkg/util/events.go b/pkg/util/events.go index fb09b7772..0467d66a3 100644 --- a/pkg/util/events.go +++ b/pkg/util/events.go @@ -23,3 +23,9 @@ const ( VolumeResizeSuccess = "VolumeResizeSuccessful" FileSystemResizeRequired = "FileSystemResizeRequired" ) + +const ( + // If CSI migration is enabled, the value will be CSI driver name + // Otherwise, it will be in-tree storage plugin name + VolumeResizerKey = "volume.kubernetes.io/storage-resizer" +) diff --git a/vendor/k8s.io/cloud-provider/LICENSE b/vendor/k8s.io/cloud-provider/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/k8s.io/cloud-provider/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/vendor/k8s.io/cloud-provider/volume/constants.go b/vendor/k8s.io/cloud-provider/volume/constants.go new file mode 100644 index 000000000..d05f64ae2 --- /dev/null +++ b/vendor/k8s.io/cloud-provider/volume/constants.go @@ -0,0 +1,26 @@ +/* +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 volume + +const ( + // ProvisionedVolumeName is the name of a volume in an external cloud + // that is being provisioned and thus should be ignored by rest of Kubernetes. + ProvisionedVolumeName = "placeholder-for-provisioning" + + // LabelMultiZoneDelimiter separates zones for volumes + LabelMultiZoneDelimiter = "__" +) diff --git a/vendor/k8s.io/csi-translation-lib/LICENSE b/vendor/k8s.io/csi-translation-lib/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/vendor/k8s.io/csi-translation-lib/plugins/aws_ebs.go b/vendor/k8s.io/csi-translation-lib/plugins/aws_ebs.go new file mode 100644 index 000000000..9cb617684 --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/plugins/aws_ebs.go @@ -0,0 +1,170 @@ +/* +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" + "net/url" + "regexp" + "strconv" + "strings" + + "k8s.io/api/core/v1" +) + +const ( + // AWSEBSDriverName is the name of the CSI driver for EBS + AWSEBSDriverName = "ebs.csi.aws.com" + // AWSEBSInTreePluginName is the name of the intree plugin for EBS + AWSEBSInTreePluginName = "kubernetes.io/aws-ebs" +) + +var _ InTreePlugin = &awsElasticBlockStoreCSITranslator{} + +// awsElasticBlockStoreTranslator handles translation of PV spec from In-tree EBS to CSI EBS and vice versa +type awsElasticBlockStoreCSITranslator struct{} + +// NewAWSElasticBlockStoreCSITranslator returns a new instance of awsElasticBlockStoreTranslator +func NewAWSElasticBlockStoreCSITranslator() InTreePlugin { + return &awsElasticBlockStoreCSITranslator{} +} + +// TranslateInTreeStorageClassParametersToCSI translates InTree EBS storage class parameters to CSI storage class +func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeStorageClassParametersToCSI(scParameters map[string]string) (map[string]string, error) { + return scParameters, 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) { + if pv == nil || pv.Spec.AWSElasticBlockStore == nil { + return nil, fmt.Errorf("pv is nil or AWS EBS not defined on pv") + } + + ebsSource := pv.Spec.AWSElasticBlockStore + + volumeHandle, err := KubernetesVolumeIDToEBSVolumeID(ebsSource.VolumeID) + if err != nil { + return nil, fmt.Errorf("failed to translate Kubernetes ID to EBS Volume ID %v", err) + } + + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: AWSEBSDriverName, + VolumeHandle: volumeHandle, + ReadOnly: ebsSource.ReadOnly, + FSType: ebsSource.FSType, + VolumeAttributes: map[string]string{ + "partition": strconv.FormatInt(int64(ebsSource.Partition), 10), + }, + } + + pv.Spec.AWSElasticBlockStore = nil + pv.Spec.CSI = csiSource + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the EBS CSI source to a AWSElasticBlockStore source. +func (t *awsElasticBlockStoreCSITranslator) 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 + + ebsSource := &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: csiSource.VolumeHandle, + FSType: csiSource.FSType, + ReadOnly: csiSource.ReadOnly, + } + + if partition, ok := csiSource.VolumeAttributes["partition"]; ok { + partValue, err := strconv.Atoi(partition) + if err != nil { + return nil, fmt.Errorf("Failed to convert partition %v to integer: %v", partition, err) + } + ebsSource.Partition = int32(partValue) + } + + pv.Spec.CSI = nil + pv.Spec.AWSElasticBlockStore = ebsSource + 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 *awsElasticBlockStoreCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.AWSElasticBlockStore != nil +} + +// GetInTreePluginName returns the name of the intree plugin driver +func (t *awsElasticBlockStoreCSITranslator) GetInTreePluginName() string { + return AWSEBSInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (t *awsElasticBlockStoreCSITranslator) GetCSIPluginName() string { + return AWSEBSDriverName +} + +// awsVolumeRegMatch represents Regex Match for AWS volume. +var awsVolumeRegMatch = regexp.MustCompile("^vol-[^/]*$") + +// KubernetesVolumeIDToEBSVolumeID translates Kubernetes volume ID to EBS volume ID +// KubernetsVolumeID forms: +// * aws:/// +// * aws:/// +// * +// EBS Volume ID form: +// * vol- +// This translation shouldn't be needed and should be fixed in long run +// See https://github.com/kubernetes/kubernetes/issues/73730 +func KubernetesVolumeIDToEBSVolumeID(kubernetesID string) (string, error) { + // name looks like aws://availability-zone/awsVolumeId + + // The original idea of the URL-style name was to put the AZ into the + // host, so we could find the AZ immediately from the name without + // querying the API. But it turns out we don't actually need it for + // multi-AZ clusters, as we put the AZ into the labels on the PV instead. + // However, if in future we want to support multi-AZ cluster + // volume-awareness without using PersistentVolumes, we likely will + // want the AZ in the host. + if !strings.HasPrefix(kubernetesID, "aws://") { + // Assume a bare aws volume id (vol-1234...) + return kubernetesID, nil + } + url, err := url.Parse(kubernetesID) + if err != nil { + // TODO: Maybe we should pass a URL into the Volume functions + return "", fmt.Errorf("Invalid disk name (%s): %v", kubernetesID, err) + } + if url.Scheme != "aws" { + return "", fmt.Errorf("Invalid scheme for AWS volume (%s)", kubernetesID) + } + + awsID := url.Path + awsID = strings.Trim(awsID, "/") + + // We sanity check the resulting volume; the two known formats are + // vol-12345678 and vol-12345678abcdef01 + if !awsVolumeRegMatch.MatchString(awsID) { + return "", fmt.Errorf("Invalid format for AWS volume (%s)", kubernetesID) + } + + return awsID, nil +} diff --git a/vendor/k8s.io/csi-translation-lib/plugins/gce_pd.go b/vendor/k8s.io/csi-translation-lib/plugins/gce_pd.go new file mode 100644 index 000000000..103a62279 --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/plugins/gce_pd.go @@ -0,0 +1,190 @@ +/* +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" + "strconv" + "strings" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + cloudvolume "k8s.io/cloud-provider/volume" +) + +const ( + // GCEPDDriverName is the name of the CSI driver for GCE PD + GCEPDDriverName = "pd.csi.storage.gke.io" + // GCEPDInTreePluginName is the name of the intree plugin for GCE PD + GCEPDInTreePluginName = "kubernetes.io/gce-pd" + + // Volume ID Expected Format + // "projects/{projectName}/zones/{zoneName}/disks/{diskName}" + volIDZonalFmt = "projects/%s/zones/%s/disks/%s" + // "projects/{projectName}/regions/{regionName}/disks/{diskName}" + volIDRegionalFmt = "projects/%s/regions/%s/disks/%s" + volIDDiskNameValue = 5 + volIDTotalElements = 6 + + // UnspecifiedValue is used for an unknown zone string + UnspecifiedValue = "UNSPECIFIED" +) + +var _ InTreePlugin = &gcePersistentDiskCSITranslator{} + +// gcePersistentDiskCSITranslator handles translation of PV spec from In-tree +// GCE PD to CSI GCE PD and vice versa +type gcePersistentDiskCSITranslator struct{} + +// NewGCEPersistentDiskCSITranslator returns a new instance of gcePersistentDiskTranslator +func NewGCEPersistentDiskCSITranslator() InTreePlugin { + return &gcePersistentDiskCSITranslator{} +} + +// TranslateInTreeStorageClassParametersToCSI translates InTree GCE storage class parameters to CSI storage class +func (g *gcePersistentDiskCSITranslator) TranslateInTreeStorageClassParametersToCSI(scParameters map[string]string) (map[string]string, error) { + return scParameters, 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) { + var volID string + + if pv == nil || pv.Spec.GCEPersistentDisk == nil { + return nil, fmt.Errorf("pv is nil or GCE Persistent Disk source not defined on pv") + } + + zonesLabel := pv.Labels[v1.LabelZoneFailureDomain] + zones := strings.Split(zonesLabel, cloudvolume.LabelMultiZoneDelimiter) + if len(zones) == 1 && len(zones[0]) != 0 { + // Zonal + volID = fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, zones[0], pv.Spec.GCEPersistentDisk.PDName) + } else if len(zones) > 1 { + // Regional + region, err := getRegionFromZones(zones) + if err != nil { + return nil, fmt.Errorf("failed to get region from zones: %v", err) + } + volID = fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, region, pv.Spec.GCEPersistentDisk.PDName) + } else { + // Unspecified + volID = fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, UnspecifiedValue, pv.Spec.GCEPersistentDisk.PDName) + } + + gceSource := pv.Spec.PersistentVolumeSource.GCEPersistentDisk + + partition := "" + if gceSource.Partition != 0 { + partition = strconv.Itoa(int(gceSource.Partition)) + } + + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: GCEPDDriverName, + VolumeHandle: volID, + ReadOnly: gceSource.ReadOnly, + FSType: gceSource.FSType, + VolumeAttributes: map[string]string{ + "partition": partition, + }, + } + + pv.Spec.PersistentVolumeSource.GCEPersistentDisk = nil + pv.Spec.PersistentVolumeSource.CSI = csiSource + + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the GCE PD CSI source to a GCEPersistentDisk source. +func (g *gcePersistentDiskCSITranslator) 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 + + pdName, err := pdNameFromVolumeID(csiSource.VolumeHandle) + if err != nil { + return nil, err + } + + gceSource := &v1.GCEPersistentDiskVolumeSource{ + PDName: pdName, + FSType: csiSource.FSType, + ReadOnly: csiSource.ReadOnly, + } + if partition, ok := csiSource.VolumeAttributes["partition"]; ok && partition != "" { + partInt, err := strconv.Atoi(partition) + if err != nil { + return nil, fmt.Errorf("Failed to convert partition %v to integer: %v", partition, err) + } + gceSource.Partition = int32(partInt) + } + + // TODO: Take the zone/regional information and stick it into the label. + + pv.Spec.CSI = nil + pv.Spec.GCEPersistentDisk = gceSource + + return pv, nil +} + +// CanSupport tests whether the plugin supports a given 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 +} + +// GetInTreePluginName returns the name of the intree plugin driver +func (g *gcePersistentDiskCSITranslator) GetInTreePluginName() string { + return GCEPDInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (g *gcePersistentDiskCSITranslator) GetCSIPluginName() string { + return GCEPDDriverName +} + +func pdNameFromVolumeID(id string) (string, error) { + splitID := strings.Split(id, "/") + if len(splitID) != volIDTotalElements { + return "", fmt.Errorf("failed to get id components. Expected projects/{project}/zones/{zone}/disks/{name}. Got: %s", id) + } + return splitID[volIDDiskNameValue], nil +} + +// TODO: Replace this with the imported one from GCE PD CSI Driver when +// the driver removes all k8s/k8s dependencies +func getRegionFromZones(zones []string) (string, error) { + regions := sets.String{} + if len(zones) < 1 { + return "", fmt.Errorf("no zones specified") + } + for _, zone := range zones { + // Zone expected format {locale}-{region}-{zone} + splitZone := strings.Split(zone, "-") + if len(splitZone) != 3 { + return "", fmt.Errorf("zone in unexpected format, expected: {locale}-{region}-{zone}, got: %v", zone) + } + regions.Insert(strings.Join(splitZone[0:2], "-")) + } + if regions.Len() != 1 { + return "", fmt.Errorf("multiple or no regions gotten from zones, got: %v", regions) + } + return regions.UnsortedList()[0], nil +} diff --git a/vendor/k8s.io/csi-translation-lib/plugins/in_tree_volume.go b/vendor/k8s.io/csi-translation-lib/plugins/in_tree_volume.go new file mode 100644 index 000000000..6d5afd9f1 --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/plugins/in_tree_volume.go @@ -0,0 +1,46 @@ +/* +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 "k8s.io/api/core/v1" + +// InTreePlugin handles translations between CSI and in-tree sources in a PV +type InTreePlugin interface { + + // TranslateInTreeStorageClassParametersToCSI takes in-tree storage class + // parameters and translates them to a set of parameters consumable by CSI plugin + TranslateInTreeStorageClassParametersToCSI(scParameters map[string]string) (map[string]string, error) + + // TranslateInTreePVToCSI takes a persistent volume and will translate + // the in-tree 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 + // it to a in-tree Persistent Volume Source for the in-tree volume + // 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 + // specification from the API. + CanSupport(pv *v1.PersistentVolume) bool + + // GetInTreePluginName returns the in-tree plugin name this migrates + GetInTreePluginName() string + + // GetCSIPluginName returns the name of the CSI plugin that supersedes the in-tree plugin + GetCSIPluginName() string +} diff --git a/vendor/k8s.io/csi-translation-lib/plugins/openstack_cinder.go b/vendor/k8s.io/csi-translation-lib/plugins/openstack_cinder.go new file mode 100644 index 000000000..abd204a55 --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/plugins/openstack_cinder.go @@ -0,0 +1,103 @@ +/* +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" + "k8s.io/api/core/v1" +) + +const ( + // CinderDriverName is the name of the CSI driver for Cinder + CinderDriverName = "cinder.csi.openstack.org" + // CinderInTreePluginName is the name of the intree plugin for Cinder + CinderInTreePluginName = "kubernetes.io/cinder" +) + +var _ InTreePlugin = (*osCinderCSITranslator)(nil) + +// osCinderCSITranslator handles translation of PV spec from In-tree Cinder to CSI Cinder and vice versa +type osCinderCSITranslator struct{} + +// NewOpenStackCinderCSITranslator returns a new instance of osCinderCSITranslator +func NewOpenStackCinderCSITranslator() InTreePlugin { + return &osCinderCSITranslator{} +} + +// TranslateInTreeStorageClassParametersToCSI translates InTree Cinder storage class parameters to CSI storage class +func (t *osCinderCSITranslator) TranslateInTreeStorageClassParametersToCSI(scParameters map[string]string) (map[string]string, error) { + return scParameters, 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) { + if pv == nil || pv.Spec.Cinder == nil { + return nil, fmt.Errorf("pv is nil or Cinder not defined on pv") + } + + cinderSource := pv.Spec.Cinder + + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: CinderDriverName, + VolumeHandle: cinderSource.VolumeID, + ReadOnly: cinderSource.ReadOnly, + FSType: cinderSource.FSType, + VolumeAttributes: map[string]string{}, + } + + pv.Spec.Cinder = nil + pv.Spec.CSI = csiSource + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the Cinder CSI source to a Cinder In-tree source. +func (t *osCinderCSITranslator) 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 + + cinderSource := &v1.CinderPersistentVolumeSource{ + VolumeID: csiSource.VolumeHandle, + FSType: csiSource.FSType, + ReadOnly: csiSource.ReadOnly, + } + + pv.Spec.CSI = nil + pv.Spec.Cinder = cinderSource + 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 *osCinderCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.Cinder != nil +} + +// GetInTreePluginName returns the name of the intree plugin driver +func (t *osCinderCSITranslator) GetInTreePluginName() string { + return CinderInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (t *osCinderCSITranslator) GetCSIPluginName() string { + return CinderDriverName +} diff --git a/vendor/k8s.io/csi-translation-lib/translate.go b/vendor/k8s.io/csi-translation-lib/translate.go new file mode 100644 index 000000000..ac1a4dd3d --- /dev/null +++ b/vendor/k8s.io/csi-translation-lib/translate.go @@ -0,0 +1,149 @@ +/* +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 csitranslation + +import ( + "errors" + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/csi-translation-lib/plugins" +) + +var ( + inTreePlugins = map[string]plugins.InTreePlugin{ + plugins.GCEPDDriverName: plugins.NewGCEPersistentDiskCSITranslator(), + plugins.AWSEBSDriverName: plugins.NewAWSElasticBlockStoreCSITranslator(), + plugins.CinderDriverName: plugins.NewOpenStackCinderCSITranslator(), + } +) + +// TranslateInTreeStorageClassParametersToCSI takes in-tree storage class +// parameters and translates them to a set of parameters consumable by CSI plugin +func TranslateInTreeStorageClassParametersToCSI(inTreePluginName string, scParameters map[string]string) (map[string]string, error) { + for _, curPlugin := range inTreePlugins { + if inTreePluginName == curPlugin.GetInTreePluginName() { + return curPlugin.TranslateInTreeStorageClassParametersToCSI(scParameters) + } + } + return nil, fmt.Errorf("could not find in-tree storage class parameter translation logic for %#v", inTreePluginName) +} + +// 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 +// be modified +func TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil { + return nil, errors.New("persistent volume was nil") + } + copiedPV := pv.DeepCopy() + for _, curPlugin := range inTreePlugins { + if curPlugin.CanSupport(copiedPV) { + return curPlugin.TranslateInTreePVToCSI(copiedPV) + } + } + return nil, fmt.Errorf("could not find in-tree plugin translation logic for %#v", copiedPV.Name) +} + +// TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate +// it to a in-tree Persistent Volume Source for the specific in-tree volume specified +// by the `Driver` field in the CSI Source. The input PV object will not be modified. +func TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.CSI == nil { + return nil, errors.New("CSI persistent volume was nil") + } + copiedPV := pv.DeepCopy() + for driverName, curPlugin := range inTreePlugins { + if copiedPV.Spec.CSI.Driver == driverName { + return curPlugin.TranslateCSIPVToInTree(copiedPV) + } + } + return nil, fmt.Errorf("could not find in-tree plugin translation logic for %s", copiedPV.Spec.CSI.Driver) +} + +// IsMigratableIntreePluginByName tests whether there is migration logic for the in-tree plugin +// whose name matches the given name +func IsMigratableIntreePluginByName(inTreePluginName string) bool { + for _, curPlugin := range inTreePlugins { + if curPlugin.GetInTreePluginName() == inTreePluginName { + return true + } + } + return false +} + +// IsMigratedCSIDriverByName tests whether there exists an in-tree plugin with logic +// to migrate to the CSI driver with given name +func IsMigratedCSIDriverByName(csiPluginName string) bool { + if _, ok := inTreePlugins[csiPluginName]; ok { + return true + } + return false +} + +// GetInTreePluginNameFromSpec returns the plugin name +func GetInTreePluginNameFromSpec(pv *v1.PersistentVolume, vol *v1.Volume) (string, error) { + if pv != nil { + for _, curPlugin := range inTreePlugins { + if curPlugin.CanSupport(pv) { + return curPlugin.GetInTreePluginName(), nil + } + } + return "", fmt.Errorf("could not find in-tree plugin name from persistent volume %v", pv) + } else if vol != nil { + // TODO(dyzz): Implement inline volume migration support + return "", errors.New("inline volume migration not yet supported") + } else { + return "", errors.New("both persistent volume and volume are nil") + } +} + +// GetCSINameFromInTreeName returns the name of a CSI driver that supersedes the +// in-tree plugin with the given name +func GetCSINameFromInTreeName(pluginName string) (string, error) { + for csiDriverName, curPlugin := range inTreePlugins { + if curPlugin.GetInTreePluginName() == pluginName { + return csiDriverName, nil + } + } + return "", fmt.Errorf("could not find CSI Driver name for plugin %v", pluginName) +} + +// GetInTreeNameFromCSIName returns the name of the in-tree plugin superseded by +// a CSI driver with the given name +func GetInTreeNameFromCSIName(pluginName string) (string, error) { + if plugin, ok := inTreePlugins[pluginName]; ok { + return plugin.GetInTreePluginName(), nil + } + return "", fmt.Errorf("Could not find In-Tree driver name for CSI plugin %v", pluginName) +} + +// IsPVMigratable tests whether there is migration logic for the given Persistent Volume +func IsPVMigratable(pv *v1.PersistentVolume) bool { + for _, curPlugin := range inTreePlugins { + if curPlugin.CanSupport(pv) { + return true + } + } + return false +} + +// IsInlineMigratable tests whether there is Migration logic for the given Inline Volume +func IsInlineMigratable(vol *v1.Volume) bool { + return false +}