diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index dbab8a172573d..0dcb5708928fd 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -236,6 +236,20 @@ var standardFinalizers = sets.NewString( FinalizerOrphan, ) +// HasAnnotation returns a bool if passed in annotation exists +func HasAnnotation(obj ObjectMeta, ann string) bool { + _, found := obj.Annotations[ann] + return found +} + +// SetMetaDataAnnotation sets the annotation and value +func SetMetaDataAnnotation(obj *ObjectMeta, ann string, value string) { + if obj.Annotations == nil { + obj.Annotations = make(map[string]string) + } + obj.Annotations[ann] = value +} + func IsStandardFinalizerName(str string) bool { return standardFinalizers.Has(str) } diff --git a/pkg/apis/storage/util/helpers.go b/pkg/apis/storage/util/helpers.go new file mode 100644 index 0000000000000..49eada9934aea --- /dev/null +++ b/pkg/apis/storage/util/helpers.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 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 util + +import ( + "k8s.io/kubernetes/pkg/api" +) + +// IsDefaultStorageClassAnnotation represents a StorageClass annotation that +// marks a class as the default StorageClass +//TODO: Update IsDefaultStorageClassannotation and remove Beta when no longer used +const IsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class" +const BetaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class" + +// AlphaStorageClassAnnotation represents the previous alpha storage class +// annotation. it's no longer used and held here for posterity. +const AlphaStorageClassAnnotation = "volume.alpha.kubernetes.io/storage-class" + +// BetaStorageClassAnnotation represents the beta/previous StorageClass annotation. +// It's currently still used and will be held for backwards compatibility +const BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class" + +// StorageClassAnnotation represents the storage class associated with a resource. +// It currently matches the Beta value and can change when official is set. +// - in PersistentVolumeClaim it represents required class to match. +// Only PersistentVolumes with the same class (i.e. annotation with the same +// value) can be bound to the claim. In case no such volume exists, the +// controller will provision a new one using StorageClass instance with +// the same name as the annotation value. +// - in PersistentVolume it represents storage class to which the persistent +// volume belongs. +//TODO: Update this to final annotation value as it matches BetaStorageClassAnnotation for now +const StorageClassAnnotation = "volume.beta.kubernetes.io/storage-class" + +// GetVolumeStorageClass returns value of StorageClassAnnotation or empty string in case +// the annotation does not exist. +// TODO: change to PersistentVolume.Spec.Class value when this attribute is +// introduced. +func GetVolumeStorageClass(volume *api.PersistentVolume) string { + if class, found := volume.Annotations[StorageClassAnnotation]; found { + return class + } + + // 'nil' is interpreted as "", i.e. the volume does not belong to any class. + return "" +} + +// GetClaimStorageClass returns name of class that is requested by given claim. +// Request for `nil` class is interpreted as request for class "", +// i.e. for a classless PV. +// TODO: change to PersistentVolumeClaim.Spec.Class value when this +// attribute is introduced. +func GetClaimStorageClass(claim *api.PersistentVolumeClaim) string { + if class, found := claim.Annotations[StorageClassAnnotation]; found { + return class + } + + return "" +} + +// GetStorageClassAnnotation returns the StorageClass value +// if the annotation is set, empty string if not +// TODO: remove Alpha and Beta when no longer used or needed +func GetStorageClassAnnotation(obj api.ObjectMeta) string { + if class, ok := obj.Annotations[StorageClassAnnotation]; ok { + return class + } + if class, ok := obj.Annotations[BetaStorageClassAnnotation]; ok { + return class + } + if class, ok := obj.Annotations[AlphaStorageClassAnnotation]; ok { + return class + } + + return "" +} + +// HasStorageClassAnnotation returns a boolean +// if the annotation is set +// TODO: remove Alpha and Beta when no longer used or needed +func HasStorageClassAnnotation(obj api.ObjectMeta) bool { + if _, found := obj.Annotations[StorageClassAnnotation]; found { + return found + } + if _, found := obj.Annotations[BetaStorageClassAnnotation]; found { + return found + } + if _, found := obj.Annotations[AlphaStorageClassAnnotation]; found { + return found + } + + return false + +} + +// IsDefaultAnnotationText returns a pretty Yes/No String if +// the annotation is set +// TODO: remove Beta when no longer needed +func IsDefaultAnnotationText(obj api.ObjectMeta) string { + if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" { + return "Yes" + } + if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" { + return "Yes" + } + + return "No" +} + +// IsDefaultAnnotation returns a boolean if +// the annotation is set +// TODO: remove Beta when no longer needed +func IsDefaultAnnotation(obj api.ObjectMeta) bool { + if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" { + return true + } + if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" { + return true + } + + return false +} diff --git a/pkg/controller/volume/persistentvolume/binder_test.go b/pkg/controller/volume/persistentvolume/binder_test.go index 854e4b1d042b1..72b27c00684bf 100644 --- a/pkg/controller/volume/persistentvolume/binder_test.go +++ b/pkg/controller/volume/persistentvolume/binder_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" ) // Test single call to syncClaim and syncVolume methods. @@ -430,14 +431,14 @@ func TestSync(t *testing.T) { "13-1 - binding to class", []*api.PersistentVolume{ newVolume("volume13-1-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - newVolume("volume13-1-2", "10Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), + newVolume("volume13-1-2", "10Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation), }, []*api.PersistentVolume{ newVolume("volume13-1-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - newVolume("volume13-1-2", "10Gi", "uid13-1", "claim13-1", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass), + newVolume("volume13-1-2", "10Gi", "uid13-1", "claim13-1", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation), }, - newClaimArray("claim13-1", "uid13-1", "1Gi", "", api.ClaimPending, annClass), - withExpectedCapacity("10Gi", newClaimArray("claim13-1", "uid13-1", "1Gi", "volume13-1-2", api.ClaimBound, annBoundByController, annClass, annBindCompleted)), + newClaimArray("claim13-1", "uid13-1", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + withExpectedCapacity("10Gi", newClaimArray("claim13-1", "uid13-1", "1Gi", "volume13-1-2", api.ClaimBound, annBoundByController, storageutil.StorageClassAnnotation, annBindCompleted)), noevents, noerrors, testSyncClaim, }, { @@ -445,11 +446,11 @@ func TestSync(t *testing.T) { // smaller PV with a class available "13-2 - binding without a class", []*api.PersistentVolume{ - newVolume("volume13-2-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), + newVolume("volume13-2-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation), newVolume("volume13-2-2", "10Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), }, []*api.PersistentVolume{ - newVolume("volume13-2-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), + newVolume("volume13-2-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation), newVolume("volume13-2-2", "10Gi", "uid13-2", "claim13-2", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController), }, newClaimArray("claim13-2", "uid13-2", "1Gi", "", api.ClaimPending), @@ -462,14 +463,14 @@ func TestSync(t *testing.T) { "13-3 - binding to specific a class", volumeWithClass("silver", []*api.PersistentVolume{ newVolume("volume13-3-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - newVolume("volume13-3-2", "10Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), + newVolume("volume13-3-2", "10Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation), }), volumeWithClass("silver", []*api.PersistentVolume{ newVolume("volume13-3-1", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - newVolume("volume13-3-2", "10Gi", "uid13-3", "claim13-3", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass), + newVolume("volume13-3-2", "10Gi", "uid13-3", "claim13-3", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation), }), - newClaimArray("claim13-3", "uid13-3", "1Gi", "", api.ClaimPending, annClass), - withExpectedCapacity("10Gi", newClaimArray("claim13-3", "uid13-3", "1Gi", "volume13-3-2", api.ClaimBound, annBoundByController, annBindCompleted, annClass)), + newClaimArray("claim13-3", "uid13-3", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + withExpectedCapacity("10Gi", newClaimArray("claim13-3", "uid13-3", "1Gi", "volume13-3-2", api.ClaimBound, annBoundByController, annBindCompleted, storageutil.StorageClassAnnotation)), noevents, noerrors, testSyncClaim, }, { diff --git a/pkg/controller/volume/persistentvolume/framework_test.go b/pkg/controller/volume/persistentvolume/framework_test.go index 9fe0657eaed40..971347d264c61 100644 --- a/pkg/controller/volume/persistentvolume/framework_test.go +++ b/pkg/controller/volume/persistentvolume/framework_test.go @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" @@ -650,7 +651,7 @@ func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase a switch a { case annDynamicallyProvisioned: volume.Annotations[a] = mockPluginName - case annClass: + case storageutil.StorageClassAnnotation: volume.Annotations[a] = "gold" default: volume.Annotations[a] = "yes" @@ -699,13 +700,13 @@ func withMessage(message string, volumes []*api.PersistentVolume) []*api.Persist return volumes } -// volumeWithClass saves given class into annClass annotation. +// volumeWithClass saves given class into storage.StorageClassAnnotation annotation. // Meant to be used to compose claims specified inline in a test. func volumeWithClass(className string, volumes []*api.PersistentVolume) []*api.PersistentVolume { if volumes[0].Annotations == nil { - volumes[0].Annotations = map[string]string{annClass: className} + volumes[0].Annotations = map[string]string{storageutil.StorageClassAnnotation: className} } else { - volumes[0].Annotations[annClass] = className + volumes[0].Annotations[storageutil.StorageClassAnnotation] = className } return volumes } @@ -747,7 +748,7 @@ func newClaim(name, claimUID, capacity, boundToVolume string, phase api.Persiste claim.Annotations = make(map[string]string) for _, a := range annotations { switch a { - case annClass: + case storageutil.StorageClassAnnotation: claim.Annotations[a] = "gold" default: claim.Annotations[a] = "yes" @@ -774,13 +775,13 @@ func newClaimArray(name, claimUID, capacity, boundToVolume string, phase api.Per } } -// claimWithClass saves given class into annClass annotation. +// claimWithClass saves given class into storage.StorageClassAnnotation annotation. // Meant to be used to compose claims specified inline in a test. func claimWithClass(className string, claims []*api.PersistentVolumeClaim) []*api.PersistentVolumeClaim { if claims[0].Annotations == nil { - claims[0].Annotations = map[string]string{annClass: className} + claims[0].Annotations = map[string]string{storageutil.StorageClassAnnotation: className} } else { - claims[0].Annotations[annClass] = className + claims[0].Annotations[storageutil.StorageClassAnnotation] = className } return claims } diff --git a/pkg/controller/volume/persistentvolume/index.go b/pkg/controller/volume/persistentvolume/index.go index e0f9f1159ea70..176c4b228fe12 100644 --- a/pkg/controller/volume/persistentvolume/index.go +++ b/pkg/controller/volume/persistentvolume/index.go @@ -22,6 +22,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/labels" ) @@ -92,7 +93,7 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo var smallestVolumeSize int64 requestedQty := claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)] requestedSize := requestedQty.Value() - requestedClass := getClaimClass(claim) + requestedClass := storageutil.GetClaimStorageClass(claim) var selector labels.Selector if claim.Spec.Selector != nil { @@ -133,7 +134,7 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo // with existing PVs, findByClaim must find only PVs that are // pre-bound to the claim (by dynamic provisioning). TODO: remove in // 1.5 - if hasAnnotation(claim.ObjectMeta, annAlphaClass) { + if api.HasAnnotation(claim.ObjectMeta, storageutil.AlphaStorageClassAnnotation) { continue } @@ -146,7 +147,7 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo } else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) { continue } - if getVolumeClass(volume) != requestedClass { + if storageutil.GetVolumeStorageClass(volume) != requestedClass { continue } diff --git a/pkg/controller/volume/persistentvolume/index_test.go b/pkg/controller/volume/persistentvolume/index_test.go index a1844c667e423..ddb084e4304fe 100644 --- a/pkg/controller/volume/persistentvolume/index_test.go +++ b/pkg/controller/volume/persistentvolume/index_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" ) func TestMatchVolume(t *testing.T) { @@ -177,7 +178,7 @@ func TestMatchVolume(t *testing.T) { Name: "claim01", Namespace: "myns", Annotations: map[string]string{ - annClass: "silver", + storageutil.StorageClassAnnotation: "silver", }, }, Spec: api.PersistentVolumeClaimSpec{ @@ -202,7 +203,7 @@ func TestMatchVolume(t *testing.T) { Name: "claim01", Namespace: "myns", Annotations: map[string]string{ - annClass: "silver", + storageutil.StorageClassAnnotation: "silver", }, }, Spec: api.PersistentVolumeClaimSpec{ @@ -626,7 +627,7 @@ func createTestVolumes() []*api.PersistentVolume { "should-exist": "true", }, Annotations: map[string]string{ - annClass: "silver", + storageutil.StorageClassAnnotation: "silver", }, }, Spec: api.PersistentVolumeSpec{ @@ -646,7 +647,7 @@ func createTestVolumes() []*api.PersistentVolume { UID: "gce-pd-silver2", Name: "gce0024", Annotations: map[string]string{ - annClass: "silver", + storageutil.StorageClassAnnotation: "silver", }, }, Spec: api.PersistentVolumeSpec{ @@ -666,7 +667,7 @@ func createTestVolumes() []*api.PersistentVolume { UID: "gce-pd-gold", Name: "gce0025", Annotations: map[string]string{ - annClass: "gold", + storageutil.StorageClassAnnotation: "gold", }, }, Spec: api.PersistentVolumeSpec{ diff --git a/pkg/controller/volume/persistentvolume/provision_test.go b/pkg/controller/volume/persistentvolume/provision_test.go index aeb7dbb22a882..870fd2de9c883 100644 --- a/pkg/controller/volume/persistentvolume/provision_test.go +++ b/pkg/controller/volume/persistentvolume/provision_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" ) var class1Parameters = map[string]string{ @@ -108,10 +109,10 @@ func TestProvisionSync(t *testing.T) { // Provision a volume (with a default class) "11-1 - successful provision with storage class 1", novolumes, - newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), - newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass), + newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation), + newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), // Binding will be completed in the next syncClaim - newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, { @@ -119,8 +120,8 @@ func TestProvisionSync(t *testing.T) { "11-2 - plugin not found", novolumes, novolumes, - newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed"}, noerrors, testSyncClaim, }, @@ -129,8 +130,8 @@ func TestProvisionSync(t *testing.T) { "11-3 - newProvisioner failure", novolumes, novolumes, - newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), }, @@ -139,18 +140,18 @@ func TestProvisionSync(t *testing.T) { "11-4 - provision failure", novolumes, novolumes, - newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim), }, { // No provisioning if there is a matching volume available "11-6 - provisioning when there is a volume available", - newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), - newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass), - newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", api.ClaimBound, annClass, annBoundByController, annBindCompleted), + newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation), + newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation), + newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", api.ClaimBound, storageutil.StorageClassAnnotation, annBoundByController, annBindCompleted), noevents, noerrors, // No provisioning plugin confingure - makes the test fail when // the controller errorneously tries to provision something @@ -161,16 +162,16 @@ func TestProvisionSync(t *testing.T) { // a volume. "11-7 - claim is bound before provisioning", novolumes, - newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), - newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass), + newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation), + newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), // The claim would be bound in next syncClaim - newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), noevents, noerrors, wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Create a volume before provisionClaimOperation starts. // This similates a parallel controller provisioning the volume. reactor.lock.Lock() - volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass) + volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation) reactor.volumes[volume.Name] = volume reactor.lock.Unlock() }), @@ -180,10 +181,10 @@ func TestProvisionSync(t *testing.T) { // second retry succeeds "11-8 - cannot save provisioned volume", novolumes, - newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), - newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass), + newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation), + newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), // Binding will be completed in the next syncClaim - newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), noevents, []reactorError{ // Inject error to the first @@ -199,8 +200,8 @@ func TestProvisionSync(t *testing.T) { "11-9 - cannot save provisioned volume, delete succeeds", novolumes, novolumes, - newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed"}, []reactorError{ // Inject error to five kubeclient.PersistentVolumes.Create() @@ -224,8 +225,8 @@ func TestProvisionSync(t *testing.T) { "11-10 - cannot save provisioned volume, no delete plugin found", novolumes, novolumes, - newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"}, []reactorError{ // Inject error to five kubeclient.PersistentVolumes.Create() @@ -245,8 +246,8 @@ func TestProvisionSync(t *testing.T) { "11-11 - cannot save provisioned volume, deleter fails", novolumes, novolumes, - newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"}, []reactorError{ // Inject error to five kubeclient.PersistentVolumes.Create() @@ -275,8 +276,8 @@ func TestProvisionSync(t *testing.T) { "11-12 - cannot save provisioned volume, delete succeeds 2nd time", novolumes, novolumes, - newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, annClass), - newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), + newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), []string{"Warning ProvisioningFailed"}, []reactorError{ // Inject error to five kubeclient.PersistentVolumes.Create() @@ -365,9 +366,9 @@ func TestAlphaProvisionSync(t *testing.T) { "14-1 - successful alpha provisioning", novolumes, newVolumeArray("pvc-uid14-1", "1Gi", "uid14-1", "claim14-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), - newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, annAlphaClass), + newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, storageutil.AlphaStorageClassAnnotation), // Binding will be completed in the next syncClaim - newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, annAlphaClass), + newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, storageutil.AlphaStorageClassAnnotation), noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim), }, { @@ -379,9 +380,9 @@ func TestAlphaProvisionSync(t *testing.T) { newVolume("volume14-2", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), newVolume("pvc-uid14-2", "1Gi", "uid14-2", "claim14-2", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), }, - newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, annAlphaClass), + newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, storageutil.AlphaStorageClassAnnotation), // Binding will be completed in the next syncClaim - newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, annAlphaClass), + newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, storageutil.AlphaStorageClassAnnotation), noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim), }, } @@ -408,10 +409,10 @@ func TestProvisionMultiSync(t *testing.T) { // Provision a volume with binding "12-1 - successful provision", novolumes, - newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), - newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass), + newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation), + newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, storageutil.StorageClassAnnotation), // Binding will be completed in the next syncClaim - newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted), + newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, storageutil.StorageClassAnnotation, annBoundByController, annBindCompleted), noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, } diff --git a/pkg/controller/volume/persistentvolume/pv_controller.go b/pkg/controller/volume/persistentvolume/pv_controller.go index 404c7fe74cde2..72f528d3a887d 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller.go +++ b/pkg/controller/volume/persistentvolume/pv_controller.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/record" @@ -110,20 +111,6 @@ const annBindCompleted = "pv.kubernetes.io/bind-completed" // pre-bound). Value of this annotation does not matter. const annBoundByController = "pv.kubernetes.io/bound-by-controller" -// annClass annotation represents the storage class associated with a resource: -// - in PersistentVolumeClaim it represents required class to match. -// Only PersistentVolumes with the same class (i.e. annotation with the same -// value) can be bound to the claim. In case no such volume exists, the -// controller will provision a new one using StorageClass instance with -// the same name as the annotation value. -// - in PersistentVolume it represents storage class to which the persistent -// volume belongs. -const annClass = "volume.beta.kubernetes.io/storage-class" - -// alphaAnnClass annotation represents the previous alpha storage class -// annotation. it's no longer used and held here for posterity. -const annAlphaClass = "volume.alpha.kubernetes.io/storage-class" - // This annotation is added to a PV that has been dynamically provisioned by // Kubernetes. Its value is name of volume plugin that created the volume. // It serves both user (to show where a PV comes from) and Kubernetes (to @@ -199,7 +186,7 @@ type PersistentVolumeController struct { func (ctrl *PersistentVolumeController) syncClaim(claim *api.PersistentVolumeClaim) error { glog.V(4).Infof("synchronizing PersistentVolumeClaim[%s]: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim)) - if !hasAnnotation(claim.ObjectMeta, annBindCompleted) { + if !api.HasAnnotation(claim.ObjectMeta, annBindCompleted) { return ctrl.syncUnboundClaim(claim) } else { return ctrl.syncBoundClaim(claim) @@ -224,8 +211,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *api.PersistentVo glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim)) // No PV could be found // OBSERVATION: pvc is "Pending", will retry - // TODO: remove Alpha check in 1.5 - if getClaimClass(claim) != "" || hasAnnotation(claim.ObjectMeta, annAlphaClass) { + if storageutil.GetClaimStorageClass(claim) != "" || api.HasAnnotation(claim.ObjectMeta, storageutil.AlphaStorageClassAnnotation) { if err = ctrl.provisionClaim(claim); err != nil { return err } @@ -297,7 +283,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *api.PersistentVo } else { // User asked for a PV that is claimed by someone else // OBSERVATION: pvc is "Pending", pv is "Bound" - if !hasAnnotation(claim.ObjectMeta, annBoundByController) { + if !api.HasAnnotation(claim.ObjectMeta, annBoundByController) { glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound to different claim by user, will retry later", claimToClaimKey(claim)) // User asked for a specific PV, retry later if _, err = ctrl.updateClaimStatus(claim, api.ClaimPending, nil); err != nil { @@ -318,7 +304,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *api.PersistentVo // syncBoundClaim is the main controller method to decide what to do with a // bound claim. func (ctrl *PersistentVolumeController) syncBoundClaim(claim *api.PersistentVolumeClaim) error { - // hasAnnotation(pvc, annBindCompleted) + // HasAnnotation(pvc, annBindCompleted) // This PVC has previously been bound // OBSERVATION: pvc is not "Pending" // [Unit test set 3] @@ -462,7 +448,7 @@ func (ctrl *PersistentVolumeController) syncVolume(volume *api.PersistentVolume) } return nil } else if claim.Spec.VolumeName == "" { - if hasAnnotation(volume.ObjectMeta, annBoundByController) { + if api.HasAnnotation(volume.ObjectMeta, annBoundByController) { // The binding is not completed; let PVC sync handle it glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume not bound yet, waiting for syncClaim to fix it", volume.Name) } else { @@ -497,7 +483,7 @@ func (ctrl *PersistentVolumeController) syncVolume(volume *api.PersistentVolume) return nil } else { // Volume is bound to a claim, but the claim is bound elsewhere - if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == api.PersistentVolumeReclaimDelete { + if api.HasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == api.PersistentVolumeReclaimDelete { // This volume was dynamically provisioned for this claim. The // claim got bound elsewhere, and thus this volume is not // needed. Delete it. @@ -510,7 +496,7 @@ func (ctrl *PersistentVolumeController) syncVolume(volume *api.PersistentVolume) } else { // Volume is bound to a claim, but the claim is bound elsewhere // and it's not dynamically provisioned. - if hasAnnotation(volume.ObjectMeta, annBoundByController) { + if api.HasAnnotation(volume.ObjectMeta, annBoundByController) { // This is part of the normal operation of the controller; the // controller tried to use this volume for a claim but the claim // was fulfilled by another volume. We did this; fix it. @@ -734,8 +720,8 @@ func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *api.Persistent } // Set annBoundByController if it is not set yet - if shouldSetBoundByController && !hasAnnotation(volumeClone.ObjectMeta, annBoundByController) { - setAnnotation(&volumeClone.ObjectMeta, annBoundByController, "yes") + if shouldSetBoundByController && !api.HasAnnotation(volumeClone.ObjectMeta, annBoundByController) { + api.SetMetaDataAnnotation(&volumeClone.ObjectMeta, annBoundByController, "yes") dirty = true } @@ -791,14 +777,14 @@ func (ctrl *PersistentVolumeController) bindClaimToVolume(claim *api.PersistentV } // Set annBoundByController if it is not set yet - if shouldSetBoundByController && !hasAnnotation(claimClone.ObjectMeta, annBoundByController) { - setAnnotation(&claimClone.ObjectMeta, annBoundByController, "yes") + if shouldSetBoundByController && !api.HasAnnotation(claimClone.ObjectMeta, annBoundByController) { + api.SetMetaDataAnnotation(&claimClone.ObjectMeta, annBoundByController, "yes") dirty = true } // Set annBindCompleted if it is not set yet - if !hasAnnotation(claimClone.ObjectMeta, annBindCompleted) { - setAnnotation(&claimClone.ObjectMeta, annBindCompleted, "yes") + if !api.HasAnnotation(claimClone.ObjectMeta, annBindCompleted) { + api.SetMetaDataAnnotation(&claimClone.ObjectMeta, annBindCompleted, "yes") dirty = true } @@ -884,7 +870,7 @@ func (ctrl *PersistentVolumeController) unbindVolume(volume *api.PersistentVolum return fmt.Errorf("Unexpected volume cast error : %v", volumeClone) } - if hasAnnotation(volume.ObjectMeta, annBoundByController) { + if api.HasAnnotation(volume.ObjectMeta, annBoundByController) { // The volume was bound by the controller. volumeClone.Spec.ClaimRef = nil delete(volumeClone.Annotations, annBoundByController) @@ -1208,7 +1194,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa return } - claimClass := getClaimClass(claim) + claimClass := storageutil.GetClaimStorageClass(claim) glog.V(4).Infof("provisionClaimOperation [%s] started, class: %q", claimToClaimKey(claim), claimClass) // A previous doProvisionClaim may just have finished while we were waiting for @@ -1294,13 +1280,13 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa volume.Status.Phase = api.VolumeBound // Add annBoundByController (used in deleting the volume) - setAnnotation(&volume.ObjectMeta, annBoundByController, "yes") - setAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName()) - // For Alpha provisioning behavior, do not add annClass for volumes created - // by annAlphaClass - // TODO: remove this check in 1.5, annClass will be always non-empty there. + api.SetMetaDataAnnotation(&volume.ObjectMeta, annBoundByController, "yes") + api.SetMetaDataAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName()) + // For Alpha provisioning behavior, do not add storage.BetaStorageClassAnnotations for volumes created + // by storage.AlphaStorageClassAnnotation + // TODO: remove this check in 1.5, storage.StorageClassAnnotation will be always non-empty there. if claimClass != "" { - setAnnotation(&volume.ObjectMeta, annClass, claimClass) + api.SetMetaDataAnnotation(&volume.ObjectMeta, storageutil.StorageClassAnnotation, claimClass) } // Try to create the PV object several times @@ -1402,12 +1388,12 @@ func (ctrl *PersistentVolumeController) newRecyclerEventRecorder(volume *api.Per // provisioner is requested. func (ctrl *PersistentVolumeController) findProvisionablePlugin(claim *api.PersistentVolumeClaim) (vol.ProvisionableVolumePlugin, *storage.StorageClass, error) { // TODO: remove this alpha behavior in 1.5 - alpha := hasAnnotation(claim.ObjectMeta, annAlphaClass) - beta := hasAnnotation(claim.ObjectMeta, annClass) + alpha := api.HasAnnotation(claim.ObjectMeta, storageutil.AlphaStorageClassAnnotation) + beta := api.HasAnnotation(claim.ObjectMeta, storageutil.BetaStorageClassAnnotation) if alpha && beta { // Both Alpha and Beta annotations are set. Do beta. alpha = false - msg := fmt.Sprintf("both %q and %q annotations are present, using %q", annAlphaClass, annClass, annClass) + msg := fmt.Sprintf("both %q and %q annotations are present, using %q", storageutil.AlphaStorageClassAnnotation, storageutil.BetaStorageClassAnnotation, storageutil.BetaStorageClassAnnotation) ctrl.eventRecorder.Event(claim, api.EventTypeNormal, "ProvisioningIgnoreAlpha", msg) } if alpha { @@ -1417,7 +1403,7 @@ func (ctrl *PersistentVolumeController) findProvisionablePlugin(claim *api.Persi // provisionClaim() which leads here is never called with claimClass=="", we // can save some checks. - claimClass := getClaimClass(claim) + claimClass := storageutil.GetClaimStorageClass(claim) classObj, found, err := ctrl.classes.GetByKey(claimClass) if err != nil { return nil, nil, err @@ -1469,7 +1455,7 @@ func (ctrl *PersistentVolumeController) findAlphaProvisionablePlugin() (vol.Prov func (ctrl *PersistentVolumeController) findDeletablePlugin(volume *api.PersistentVolume) (vol.DeletableVolumePlugin, error) { // Find a plugin. Try to find the same plugin that provisioned the volume var plugin vol.DeletableVolumePlugin - if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) { + if api.HasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) { provisionPluginName := volume.Annotations[annDynamicallyProvisioned] if provisionPluginName != "" { plugin, err := ctrl.volumePluginMgr.FindDeletablePluginByName(provisionPluginName) diff --git a/pkg/controller/volume/persistentvolume/pv_controller_base.go b/pkg/controller/volume/persistentvolume/pv_controller_base.go index 42f6ca2328ed5..14292a39b5544 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller_base.go +++ b/pkg/controller/volume/persistentvolume/pv_controller_base.go @@ -496,27 +496,15 @@ func (ctrl *PersistentVolumeController) upgradeVolumeFrom1_2(volume *api.Persist // Stateless functions -func hasAnnotation(obj api.ObjectMeta, ann string) bool { - _, found := obj.Annotations[ann] - return found -} - -func setAnnotation(obj *api.ObjectMeta, ann string, value string) { - if obj.Annotations == nil { - obj.Annotations = make(map[string]string) - } - obj.Annotations[ann] = value -} - func getClaimStatusForLogging(claim *api.PersistentVolumeClaim) string { - bound := hasAnnotation(claim.ObjectMeta, annBindCompleted) - boundByController := hasAnnotation(claim.ObjectMeta, annBoundByController) + bound := api.HasAnnotation(claim.ObjectMeta, annBindCompleted) + boundByController := api.HasAnnotation(claim.ObjectMeta, annBoundByController) return fmt.Sprintf("phase: %s, bound to: %q, bindCompleted: %v, boundByController: %v", claim.Status.Phase, claim.Spec.VolumeName, bound, boundByController) } func getVolumeStatusForLogging(volume *api.PersistentVolume) string { - boundByController := hasAnnotation(volume.ObjectMeta, annBoundByController) + boundByController := api.HasAnnotation(volume.ObjectMeta, annBoundByController) claimName := "" if volume.Spec.ClaimRef != nil { claimName = fmt.Sprintf("%s/%s (uid: %s)", volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name, volume.Spec.ClaimRef.UID) @@ -592,29 +580,3 @@ func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bo } return true, nil } - -// getVolumeClass returns value of annClass annotation or empty string in case -// the annotation does not exist. -// TODO: change to PersistentVolume.Spec.Class value when this attribute is -// introduced. -func getVolumeClass(volume *api.PersistentVolume) string { - if class, found := volume.Annotations[annClass]; found { - return class - } - - // 'nil' is interpreted as "", i.e. the volume does not belong to any class. - return "" -} - -// getClaimClass returns name of class that is requested by given claim. -// Request for `nil` class is interpreted as request for class "", -// i.e. for a classless PV. -func getClaimClass(claim *api.PersistentVolumeClaim) string { - // TODO: change to PersistentVolumeClaim.Spec.Class value when this - // attribute is introduced. - if class, found := claim.Annotations[annClass]; found { - return class - } - - return "" -} diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index fb2f8c65567ee..a7749a5fe5499 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" client "k8s.io/kubernetes/pkg/client/unversioned" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" @@ -734,6 +735,7 @@ func (d *PersistentVolumeDescriber) Describe(namespace, name string, describerSe return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", pv.Name) printLabelsMultiline(out, "Labels", pv.Labels) + fmt.Fprintf(out, "StorageClass:\t%s\n", storageutil.GetStorageClassAnnotation(pv.ObjectMeta)) fmt.Fprintf(out, "Status:\t%s\n", pv.Status.Phase) if pv.Spec.ClaimRef != nil { fmt.Fprintf(out, "Claim:\t%s\n", pv.Spec.ClaimRef.Namespace+"/"+pv.Spec.ClaimRef.Name) @@ -803,6 +805,7 @@ func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string, descri return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", pvc.Name) fmt.Fprintf(out, "Namespace:\t%s\n", pvc.Namespace) + fmt.Fprintf(out, "StorageClass:\t%s\n", storageutil.GetStorageClassAnnotation(pvc.ObjectMeta)) fmt.Fprintf(out, "Status:\t%v\n", pvc.Status.Phase) fmt.Fprintf(out, "Volume:\t%s\n", pvc.Spec.VolumeName) printLabelsMultiline(out, "Labels", pvc.Labels) @@ -2365,6 +2368,7 @@ func (s *StorageClassDescriber) Describe(namespace, name string, describerSettin } return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", sc.Name) + fmt.Fprintf(out, "IsDefaultClass:\t%s\n", storageutil.IsDefaultAnnotationText(sc.ObjectMeta)) fmt.Fprintf(out, "Annotations:\t%s\n", labels.FormatLabels(sc.Annotations)) fmt.Fprintf(out, "Provisioner:\t%s\n", sc.Provisioner) fmt.Fprintf(out, "Parameters:\t%s\n", labels.FormatLabels(sc.Parameters)) diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index e1a6a1d383b50..1e382b409fe16 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -44,6 +44,7 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -2082,6 +2083,10 @@ func printNetworkPolicyList(list *extensions.NetworkPolicyList, w io.Writer, opt func printStorageClass(sc *storage.StorageClass, w io.Writer, options PrintOptions) error { name := sc.Name + + if storageutil.IsDefaultAnnotation(sc.ObjectMeta) { + name += " (default)" + } provtype := sc.Provisioner if _, err := fmt.Fprintf(w, "%s\t%s\t", name, provtype); err != nil { diff --git a/plugin/pkg/admission/storageclass/default/admission.go b/plugin/pkg/admission/storageclass/default/admission.go index d4a47d06a3b2e..cbd5850f0e496 100644 --- a/plugin/pkg/admission/storageclass/default/admission.go +++ b/plugin/pkg/admission/storageclass/default/admission.go @@ -26,6 +26,7 @@ import ( api "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/runtime" @@ -94,12 +95,6 @@ func (a *claimDefaulterPlugin) Stop() { } } -// This is a stand-in until we have a real field. This string should be a const somewhere. -const classAnnotation = "volume.beta.kubernetes.io/storage-class" - -// This indicates that a particular StorageClass nominates itself as the system default. -const isDefaultAnnotation = "storageclass.beta.kubernetes.io/is-default-class" - // Admit sets the default value of a PersistentVolumeClaim's storage class, in case the user did // not provide a value. // @@ -121,8 +116,7 @@ func (c *claimDefaulterPlugin) Admit(a admission.Attributes) error { return nil } - _, found := pvc.Annotations[classAnnotation] - if found { + if storageutil.HasStorageClassAnnotation(pvc.ObjectMeta) { // The user asked for a class. return nil } @@ -142,7 +136,7 @@ func (c *claimDefaulterPlugin) Admit(a admission.Attributes) error { if pvc.ObjectMeta.Annotations == nil { pvc.ObjectMeta.Annotations = map[string]string{} } - pvc.Annotations[classAnnotation] = def.Name + pvc.Annotations[storageutil.StorageClassAnnotation] = def.Name return nil } @@ -154,7 +148,7 @@ func getDefaultClass(store cache.Store) (*storage.StorageClass, error) { if !ok { return nil, errors.NewInternalError(fmt.Errorf("error converting stored object to StorageClass: %v", c)) } - if class.Annotations[isDefaultAnnotation] == "true" { + if storageutil.IsDefaultAnnotation(class.ObjectMeta) { defaultClasses = append(defaultClasses, class) glog.V(4).Infof("getDefaultClass added: %s", class.Name) } diff --git a/plugin/pkg/admission/storageclass/default/admission_test.go b/plugin/pkg/admission/storageclass/default/admission_test.go index 427c7abcf6766..40786a4c12b91 100644 --- a/plugin/pkg/admission/storageclass/default/admission_test.go +++ b/plugin/pkg/admission/storageclass/default/admission_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/conversion" ) @@ -36,7 +37,7 @@ func TestAdmission(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "default1", Annotations: map[string]string{ - isDefaultAnnotation: "true", + storageutil.IsDefaultStorageClassAnnotation: "true", }, }, Provisioner: "default1", @@ -48,7 +49,7 @@ func TestAdmission(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "default2", Annotations: map[string]string{ - isDefaultAnnotation: "true", + storageutil.IsDefaultStorageClassAnnotation: "true", }, }, Provisioner: "default2", @@ -61,7 +62,7 @@ func TestAdmission(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "nondefault1", Annotations: map[string]string{ - isDefaultAnnotation: "false", + storageutil.IsDefaultStorageClassAnnotation: "false", }, }, Provisioner: "nondefault1", @@ -84,7 +85,7 @@ func TestAdmission(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "nondefault2", Annotations: map[string]string{ - isDefaultAnnotation: "", + storageutil.IsDefaultStorageClassAnnotation: "", }, }, Provisioner: "nondefault1", @@ -98,7 +99,7 @@ func TestAdmission(t *testing.T) { Name: "claimWithClass", Namespace: "ns", Annotations: map[string]string{ - classAnnotation: "foo", + storageutil.StorageClassAnnotation: "foo", }, }, } @@ -110,7 +111,7 @@ func TestAdmission(t *testing.T) { Name: "claimWithEmptyClass", Namespace: "ns", Annotations: map[string]string{ - classAnnotation: "", + storageutil.StorageClassAnnotation: "", }, }, } @@ -218,7 +219,7 @@ func TestAdmission(t *testing.T) { class := "" if claim.Annotations != nil { - if value, ok := claim.Annotations[classAnnotation]; ok { + if value, ok := claim.Annotations[storageutil.StorageClassAnnotation]; ok { class = value } } diff --git a/test/e2e/volume_provisioning.go b/test/e2e/volume_provisioning.go index 3c37231210cfd..0d98fc7537a3c 100644 --- a/test/e2e/volume_provisioning.go +++ b/test/e2e/volume_provisioning.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/test/e2e/framework" @@ -172,11 +173,11 @@ func newClaim(ns string, alpha bool) *api.PersistentVolumeClaim { if alpha { claim.Annotations = map[string]string{ - "volume.alpha.kubernetes.io/storage-class": "", + storageutil.AlphaStorageClassAnnotation: "", } } else { claim.Annotations = map[string]string{ - "volume.beta.kubernetes.io/storage-class": "fast", + storageutil.StorageClassAnnotation: "fast", } } diff --git a/test/integration/persistentvolumes/persistent_volumes_test.go b/test/integration/persistentvolumes/persistent_volumes_test.go index 8e768602c716d..44cd0564eaad4 100644 --- a/test/integration/persistentvolumes/persistent_volumes_test.go +++ b/test/integration/persistentvolumes/persistent_volumes_test.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/restclient" fake_cloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" @@ -884,7 +885,7 @@ func TestPersistentVolumeProvisionMultiPVCs(t *testing.T) { for i := 0; i < objCount; i++ { pvc := createPVC("pvc-provision-"+strconv.Itoa(i), ns.Name, "1G", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}) pvc.Annotations = map[string]string{ - "volume.beta.kubernetes.io/storage-class": "gold", + storageutil.StorageClassAnnotation: "gold", } pvcs[i] = pvc } diff --git a/test/integration/storageclasses/storage_classes_test.go b/test/integration/storageclasses/storage_classes_test.go index 0c35039ea4c5c..85e4283aa5347 100644 --- a/test/integration/storageclasses/storage_classes_test.go +++ b/test/integration/storageclasses/storage_classes_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/test/integration/framework" @@ -72,7 +73,7 @@ func DoTestStorageClasses(t *testing.T, client *client.Client, ns *api.Namespace Name: "XXX", Namespace: ns.Name, Annotations: map[string]string{ - "volume.beta.kubernetes.io/storage-class": "gold", + storageutil.StorageClassAnnotation: "gold", }, }, Spec: api.PersistentVolumeClaimSpec{