Merge pull request #124505 from jsafrane/clean-pvlabeler
Remove PersistentVolumeLabel admission plugin
This commit is contained in:
		| @@ -56,7 +56,7 @@ function run_kube_apiserver() { | |||||||
|  |  | ||||||
|   # Admission Controllers to invoke prior to persisting objects in cluster |   # Admission Controllers to invoke prior to persisting objects in cluster | ||||||
|   ENABLE_ADMISSION_PLUGINS="LimitRanger,ResourceQuota" |   ENABLE_ADMISSION_PLUGINS="LimitRanger,ResourceQuota" | ||||||
|   DISABLE_ADMISSION_PLUGINS="ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,StorageObjectInUseProtection" |   DISABLE_ADMISSION_PLUGINS="ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,StorageObjectInUseProtection" | ||||||
|  |  | ||||||
|   # Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions |   # Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions | ||||||
|   AUTHORIZATION_MODE="RBAC,AlwaysAllow" |   AUTHORIZATION_MODE="RBAC,AlwaysAllow" | ||||||
|   | |||||||
| @@ -49,7 +49,6 @@ import ( | |||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" | 	"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" | 	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" | 	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label" |  | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize" | 	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault" | 	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageobjectinuseprotection" | 	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageobjectinuseprotection" | ||||||
| @@ -82,7 +81,6 @@ var AllOrderedPlugins = []string{ | |||||||
| 	podtolerationrestriction.PluginName,     // PodTolerationRestriction | 	podtolerationrestriction.PluginName,     // PodTolerationRestriction | ||||||
| 	eventratelimit.PluginName,               // EventRateLimit | 	eventratelimit.PluginName,               // EventRateLimit | ||||||
| 	extendedresourcetoleration.PluginName,   // ExtendedResourceToleration | 	extendedresourcetoleration.PluginName,   // ExtendedResourceToleration | ||||||
| 	label.PluginName,                        // PersistentVolumeLabel |  | ||||||
| 	setdefault.PluginName,                   // DefaultStorageClass | 	setdefault.PluginName,                   // DefaultStorageClass | ||||||
| 	storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection | 	storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection | ||||||
| 	gc.PluginName,                           // OwnerReferencesPermissionEnforcement | 	gc.PluginName,                           // OwnerReferencesPermissionEnforcement | ||||||
| @@ -126,7 +124,6 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { | |||||||
| 	exists.Register(plugins) | 	exists.Register(plugins) | ||||||
| 	noderestriction.Register(plugins) | 	noderestriction.Register(plugins) | ||||||
| 	nodetaint.Register(plugins) | 	nodetaint.Register(plugins) | ||||||
| 	label.Register(plugins) // DEPRECATED, future PVs should not rely on labels for zone topology |  | ||||||
| 	podnodeselector.Register(plugins) | 	podnodeselector.Register(plugins) | ||||||
| 	podtolerationrestriction.Register(plugins) | 	podtolerationrestriction.Register(plugins) | ||||||
| 	runtimeclass.Register(plugins) | 	runtimeclass.Register(plugins) | ||||||
|   | |||||||
| @@ -1,10 +0,0 @@ | |||||||
| # See the OWNERS docs at https://go.k8s.io/owners |  | ||||||
|  |  | ||||||
| reviewers: |  | ||||||
|   - andrewsykim |  | ||||||
|   - dims |  | ||||||
|   - msau42 |  | ||||||
| approvers: |  | ||||||
|   - andrewsykim |  | ||||||
|   - dims |  | ||||||
|   - msau42 |  | ||||||
| @@ -1,309 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 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 label |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	v1 "k8s.io/api/core/v1" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apiserver/pkg/admission" |  | ||||||
| 	cloudprovider "k8s.io/cloud-provider" |  | ||||||
| 	cloudvolume "k8s.io/cloud-provider/volume" |  | ||||||
| 	volumehelpers "k8s.io/cloud-provider/volume/helpers" |  | ||||||
| 	persistentvolume "k8s.io/component-helpers/storage/volume" |  | ||||||
| 	"k8s.io/klog/v2" |  | ||||||
| 	api "k8s.io/kubernetes/pkg/apis/core" |  | ||||||
| 	k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" |  | ||||||
| 	kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	// PluginName is the name of persistent volume label admission plugin |  | ||||||
| 	PluginName = "PersistentVolumeLabel" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Register registers a plugin |  | ||||||
| func Register(plugins *admission.Plugins) { |  | ||||||
| 	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { |  | ||||||
| 		persistentVolumeLabelAdmission := newPersistentVolumeLabel() |  | ||||||
| 		return persistentVolumeLabelAdmission, nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var _ = admission.Interface(&persistentVolumeLabel{}) |  | ||||||
|  |  | ||||||
| type persistentVolumeLabel struct { |  | ||||||
| 	*admission.Handler |  | ||||||
|  |  | ||||||
| 	mutex            sync.Mutex |  | ||||||
| 	cloudConfig      []byte |  | ||||||
| 	azurePVLabeler   cloudprovider.PVLabeler |  | ||||||
| 	vspherePVLabeler cloudprovider.PVLabeler |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var _ admission.MutationInterface = &persistentVolumeLabel{} |  | ||||||
| var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{} |  | ||||||
|  |  | ||||||
| // newPersistentVolumeLabel returns an admission.Interface implementation which adds labels to PersistentVolume CREATE requests, |  | ||||||
| // based on the labels provided by the underlying cloud provider. |  | ||||||
| // |  | ||||||
| // As a side effect, the cloud provider may block invalid or non-existent volumes. |  | ||||||
| func newPersistentVolumeLabel() *persistentVolumeLabel { |  | ||||||
| 	// DEPRECATED: in a future release, we will use mutating admission webhooks to apply PV labels. |  | ||||||
| 	// Once the mutating admission webhook is used for Azure, and GCE, |  | ||||||
| 	// this admission controller will be removed. |  | ||||||
| 	klog.Warning("PersistentVolumeLabel admission controller is deprecated. " + |  | ||||||
| 		"Please remove this controller from your configuration files and scripts.") |  | ||||||
| 	return &persistentVolumeLabel{ |  | ||||||
| 		Handler: admission.NewHandler(admission.Create), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) SetCloudConfig(cloudConfig []byte) { |  | ||||||
| 	l.cloudConfig = cloudConfig |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func nodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []api.NodeSelectorRequirement, terms []api.NodeSelectorTerm) bool { |  | ||||||
| 	for _, req := range reqs { |  | ||||||
| 		for _, term := range terms { |  | ||||||
| 			for _, r := range term.MatchExpressions { |  | ||||||
| 				if r.Key == req.Key { |  | ||||||
| 					return true |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { |  | ||||||
| 	if a.GetResource().GroupResource() != api.Resource("persistentvolumes") { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	obj := a.GetObject() |  | ||||||
| 	if obj == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	volume, ok := obj.(*api.PersistentVolume) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	volumeLabels, err := l.findVolumeLabels(volume) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return admission.NewForbidden(a, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	requirements := make([]api.NodeSelectorRequirement, 0) |  | ||||||
| 	if len(volumeLabels) != 0 { |  | ||||||
| 		if volume.Labels == nil { |  | ||||||
| 			volume.Labels = make(map[string]string) |  | ||||||
| 		} |  | ||||||
| 		for k, v := range volumeLabels { |  | ||||||
| 			// We (silently) replace labels if they are provided. |  | ||||||
| 			// This should be OK because they are in the kubernetes.io namespace |  | ||||||
| 			// i.e. we own them |  | ||||||
| 			volume.Labels[k] = v |  | ||||||
|  |  | ||||||
| 			// Set NodeSelectorRequirements based on the labels |  | ||||||
| 			var values []string |  | ||||||
| 			if k == v1.LabelTopologyZone || k == v1.LabelFailureDomainBetaZone { |  | ||||||
| 				zones, err := volumehelpers.LabelZonesToSet(v) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return admission.NewForbidden(a, fmt.Errorf("failed to convert label string for Zone: %s to a Set", v)) |  | ||||||
| 				} |  | ||||||
| 				// zone values here are sorted for better testability. |  | ||||||
| 				values = zones.List() |  | ||||||
| 			} else { |  | ||||||
| 				values = []string{v} |  | ||||||
| 			} |  | ||||||
| 			requirements = append(requirements, api.NodeSelectorRequirement{Key: k, Operator: api.NodeSelectorOpIn, Values: values}) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if volume.Spec.NodeAffinity == nil { |  | ||||||
| 			volume.Spec.NodeAffinity = new(api.VolumeNodeAffinity) |  | ||||||
| 		} |  | ||||||
| 		if volume.Spec.NodeAffinity.Required == nil { |  | ||||||
| 			volume.Spec.NodeAffinity.Required = new(api.NodeSelector) |  | ||||||
| 		} |  | ||||||
| 		if len(volume.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 { |  | ||||||
| 			// Need at least one term pre-allocated whose MatchExpressions can be appended to |  | ||||||
| 			volume.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]api.NodeSelectorTerm, 1) |  | ||||||
| 		} |  | ||||||
| 		if nodeSelectorRequirementKeysExistInNodeSelectorTerms(requirements, volume.Spec.NodeAffinity.Required.NodeSelectorTerms) { |  | ||||||
| 			klog.V(4).Infof("NodeSelectorRequirements for cloud labels %v conflict with existing NodeAffinity %v. Skipping addition of NodeSelectorRequirements for cloud labels.", |  | ||||||
| 				requirements, volume.Spec.NodeAffinity) |  | ||||||
| 		} else { |  | ||||||
| 			for _, req := range requirements { |  | ||||||
| 				for i := range volume.Spec.NodeAffinity.Required.NodeSelectorTerms { |  | ||||||
| 					volume.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions = append(volume.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions, req) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) findVolumeLabels(volume *api.PersistentVolume) (map[string]string, error) { |  | ||||||
| 	existingLabels := volume.Labels |  | ||||||
|  |  | ||||||
| 	// All cloud providers set only these two labels. |  | ||||||
| 	topologyLabelGA := true |  | ||||||
| 	domain, domainOK := existingLabels[v1.LabelTopologyZone] |  | ||||||
| 	region, regionOK := existingLabels[v1.LabelTopologyRegion] |  | ||||||
| 	// If they don't have GA labels we should check for failuredomain beta labels |  | ||||||
| 	// TODO: remove this once all the cloud provider change to GA topology labels |  | ||||||
| 	if !domainOK || !regionOK { |  | ||||||
| 		topologyLabelGA = false |  | ||||||
| 		domain, domainOK = existingLabels[v1.LabelFailureDomainBetaZone] |  | ||||||
| 		region, regionOK = existingLabels[v1.LabelFailureDomainBetaRegion] |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	isDynamicallyProvisioned := metav1.HasAnnotation(volume.ObjectMeta, persistentvolume.AnnDynamicallyProvisioned) |  | ||||||
| 	if isDynamicallyProvisioned && domainOK && regionOK { |  | ||||||
| 		// PV already has all the labels and we can trust the dynamic provisioning that it provided correct values. |  | ||||||
| 		if topologyLabelGA { |  | ||||||
| 			return map[string]string{ |  | ||||||
| 				v1.LabelTopologyZone:   domain, |  | ||||||
| 				v1.LabelTopologyRegion: region, |  | ||||||
| 			}, nil |  | ||||||
| 		} |  | ||||||
| 		return map[string]string{ |  | ||||||
| 			v1.LabelFailureDomainBetaZone:   domain, |  | ||||||
| 			v1.LabelFailureDomainBetaRegion: region, |  | ||||||
| 		}, nil |  | ||||||
|  |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Either missing labels or we don't trust the user provided correct values. |  | ||||||
| 	switch { |  | ||||||
| 	case volume.Spec.AzureDisk != nil: |  | ||||||
| 		labels, err := l.findAzureDiskLabels(volume) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error querying AzureDisk volume %s: %v", volume.Spec.AzureDisk.DiskName, err) |  | ||||||
| 		} |  | ||||||
| 		return labels, nil |  | ||||||
| 	case volume.Spec.VsphereVolume != nil: |  | ||||||
| 		labels, err := l.findVsphereVolumeLabels(volume) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error querying vSphere Volume %s: %v", volume.Spec.VsphereVolume.VolumePath, err) |  | ||||||
| 		} |  | ||||||
| 		return labels, nil |  | ||||||
| 	} |  | ||||||
| 	// Unrecognized volume, do not add any labels |  | ||||||
| 	return nil, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // getAzurePVLabeler returns the Azure implementation of PVLabeler |  | ||||||
| func (l *persistentVolumeLabel) getAzurePVLabeler() (cloudprovider.PVLabeler, error) { |  | ||||||
| 	l.mutex.Lock() |  | ||||||
| 	defer l.mutex.Unlock() |  | ||||||
|  |  | ||||||
| 	if l.azurePVLabeler == nil { |  | ||||||
| 		var cloudConfigReader io.Reader |  | ||||||
| 		if len(l.cloudConfig) > 0 { |  | ||||||
| 			cloudConfigReader = bytes.NewReader(l.cloudConfig) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		cloudProvider, err := cloudprovider.GetCloudProvider("azure", cloudConfigReader) |  | ||||||
| 		if err != nil || cloudProvider == nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		azurePVLabeler, ok := cloudProvider.(cloudprovider.PVLabeler) |  | ||||||
| 		if !ok { |  | ||||||
| 			return nil, errors.New("Azure cloud provider does not implement PV labeling") |  | ||||||
| 		} |  | ||||||
| 		l.azurePVLabeler = azurePVLabeler |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return l.azurePVLabeler, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) findAzureDiskLabels(volume *api.PersistentVolume) (map[string]string, error) { |  | ||||||
| 	// Ignore any volumes that are being provisioned |  | ||||||
| 	if volume.Spec.AzureDisk.DiskName == cloudvolume.ProvisionedVolumeName { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	pvlabler, err := l.getAzurePVLabeler() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if pvlabler == nil { |  | ||||||
| 		return nil, fmt.Errorf("unable to build Azure cloud provider for AzureDisk") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	pv := &v1.PersistentVolume{} |  | ||||||
| 	err = k8s_api_v1.Convert_core_PersistentVolume_To_v1_PersistentVolume(volume, pv, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("failed to convert PersistentVolume to core/v1: %q", err) |  | ||||||
| 	} |  | ||||||
| 	return pvlabler.GetLabelsForVolume(context.TODO(), pv) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) findVsphereVolumeLabels(volume *api.PersistentVolume) (map[string]string, error) { |  | ||||||
| 	pvlabler, err := l.getVspherePVLabeler() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if pvlabler == nil { |  | ||||||
| 		return nil, fmt.Errorf("unable to build vSphere cloud provider") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	pv := &v1.PersistentVolume{} |  | ||||||
| 	err = k8s_api_v1.Convert_core_PersistentVolume_To_v1_PersistentVolume(volume, pv, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("failed to convert PersistentVolume to core/v1: %q", err) |  | ||||||
| 	} |  | ||||||
| 	labels, err := pvlabler.GetLabelsForVolume(context.TODO(), pv) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return labels, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *persistentVolumeLabel) getVspherePVLabeler() (cloudprovider.PVLabeler, error) { |  | ||||||
| 	l.mutex.Lock() |  | ||||||
| 	defer l.mutex.Unlock() |  | ||||||
|  |  | ||||||
| 	if l.vspherePVLabeler == nil { |  | ||||||
| 		var cloudConfigReader io.Reader |  | ||||||
| 		if len(l.cloudConfig) > 0 { |  | ||||||
| 			cloudConfigReader = bytes.NewReader(l.cloudConfig) |  | ||||||
| 		} |  | ||||||
| 		cloudProvider, err := cloudprovider.GetCloudProvider("vsphere", cloudConfigReader) |  | ||||||
| 		if err != nil || cloudProvider == nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		vspherePVLabeler, ok := cloudProvider.(cloudprovider.PVLabeler) |  | ||||||
| 		if !ok { |  | ||||||
| 			// GetCloudProvider failed |  | ||||||
| 			return nil, errors.New("vSphere Cloud Provider does not implement PV labeling") |  | ||||||
| 		} |  | ||||||
| 		l.vspherePVLabeler = vspherePVLabeler |  | ||||||
| 	} |  | ||||||
| 	return l.vspherePVLabeler, nil |  | ||||||
| } |  | ||||||
| @@ -1,666 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 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 label |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"reflect" |  | ||||||
| 	"sort" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	v1 "k8s.io/api/core/v1" |  | ||||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| 	"k8s.io/apiserver/pkg/admission" |  | ||||||
| 	admissiontesting "k8s.io/apiserver/pkg/admission/testing" |  | ||||||
| 	cloudprovider "k8s.io/cloud-provider" |  | ||||||
| 	persistentvolume "k8s.io/component-helpers/storage/volume" |  | ||||||
| 	api "k8s.io/kubernetes/pkg/apis/core" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type mockVolumes struct { |  | ||||||
| 	volumeLabels      map[string]string |  | ||||||
| 	volumeLabelsError error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var _ cloudprovider.PVLabeler = &mockVolumes{} |  | ||||||
|  |  | ||||||
| func (v *mockVolumes) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) { |  | ||||||
| 	return v.volumeLabels, v.volumeLabelsError |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func mockVolumeFailure(err error) *mockVolumes { |  | ||||||
| 	return &mockVolumes{volumeLabelsError: err} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func mockVolumeLabels(labels map[string]string) *mockVolumes { |  | ||||||
| 	return &mockVolumes{volumeLabels: labels} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func Test_PVLAdmission(t *testing.T) { |  | ||||||
| 	testcases := []struct { |  | ||||||
| 		name            string |  | ||||||
| 		handler         *persistentVolumeLabel |  | ||||||
| 		pvlabeler       cloudprovider.PVLabeler |  | ||||||
| 		preAdmissionPV  *api.PersistentVolume |  | ||||||
| 		postAdmissionPV *api.PersistentVolume |  | ||||||
| 		err             error |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name:    "non-cloud PV ignored", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				"a":                  "1", |  | ||||||
| 				"b":                  "2", |  | ||||||
| 				v1.LabelTopologyZone: "1__2__3", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						HostPath: &api.HostPathVolumeSource{ |  | ||||||
| 							Path: "/", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						HostPath: &api.HostPathVolumeSource{ |  | ||||||
| 							Path: "/", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:      "cloud provider error blocks creation of volume", |  | ||||||
| 			handler:   newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeFailure(errors.New("invalid volume")), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "vSpherePV", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "vSpherePV", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: apierrors.NewForbidden(schema.ParseGroupResource("persistentvolumes"), "vSpherePV", errors.New("error querying vSphere Volume 123: invalid volume")), |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:      "cloud provider returns no labels", |  | ||||||
| 			handler:   newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:      "cloud provider returns nil, nil", |  | ||||||
| 			handler:   newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeFailure(nil), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "existing Beta labels from dynamic provisioning are not changed", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				v1.LabelFailureDomainBetaZone:   "domain1", |  | ||||||
| 				v1.LabelFailureDomainBetaRegion: "region1", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name: "awsebs", Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelFailureDomainBetaZone:   "existingDomain", |  | ||||||
| 						v1.LabelFailureDomainBetaRegion: "existingRegion", |  | ||||||
| 					}, |  | ||||||
| 					Annotations: map[string]string{ |  | ||||||
| 						persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "awsebs", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelFailureDomainBetaZone:   "existingDomain", |  | ||||||
| 						v1.LabelFailureDomainBetaRegion: "existingRegion", |  | ||||||
| 					}, |  | ||||||
| 					Annotations: map[string]string{ |  | ||||||
| 						persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelFailureDomainBetaRegion, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"existingRegion"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelFailureDomainBetaZone, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"existingDomain"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "existing GA labels from dynamic provisioning are not changed", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				v1.LabelTopologyZone:   "domain1", |  | ||||||
| 				v1.LabelTopologyRegion: "region1", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name: "awsebs", Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelTopologyZone:   "existingDomain", |  | ||||||
| 						v1.LabelTopologyRegion: "existingRegion", |  | ||||||
| 					}, |  | ||||||
| 					Annotations: map[string]string{ |  | ||||||
| 						persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "awsebs", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelTopologyZone:   "existingDomain", |  | ||||||
| 						v1.LabelTopologyRegion: "existingRegion", |  | ||||||
| 					}, |  | ||||||
| 					Annotations: map[string]string{ |  | ||||||
| 						persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ |  | ||||||
| 							VolumeID: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelTopologyRegion, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"existingRegion"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelTopologyZone, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"existingDomain"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "existing labels from user are changed", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				v1.LabelTopologyZone:   "domain1", |  | ||||||
| 				v1.LabelTopologyRegion: "region1", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name: "vSpherePV", Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelTopologyZone:   "existingDomain", |  | ||||||
| 						v1.LabelTopologyRegion: "existingRegion", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "vSpherePV", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						v1.LabelTopologyZone:   "domain1", |  | ||||||
| 						v1.LabelTopologyRegion: "region1", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelTopologyRegion, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"region1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelTopologyZone, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"domain1"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "Azure Disk PV labeled correctly", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				"a":                           "1", |  | ||||||
| 				"b":                           "2", |  | ||||||
| 				v1.LabelFailureDomainBetaZone: "1__2__3", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "azurepd", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AzureDisk: &api.AzureDiskVolumeSource{ |  | ||||||
| 							DiskName: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "azurepd", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						"a":                           "1", |  | ||||||
| 						"b":                           "2", |  | ||||||
| 						v1.LabelFailureDomainBetaZone: "1__2__3", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						AzureDisk: &api.AzureDiskVolumeSource{ |  | ||||||
| 							DiskName: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      "a", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "b", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"2"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelFailureDomainBetaZone, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1", "2", "3"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "vSphere PV non-conflicting affinity rules added", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				"d": "1", |  | ||||||
| 				"e": "2", |  | ||||||
| 				"f": "3", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "vSpherePV", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						"a": "1", |  | ||||||
| 						"b": "2", |  | ||||||
| 						"c": "3", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      "a", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "b", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"2"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "c", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"3"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "vSpherePV", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						"a": "1", |  | ||||||
| 						"b": "2", |  | ||||||
| 						"c": "3", |  | ||||||
| 						"d": "1", |  | ||||||
| 						"e": "2", |  | ||||||
| 						"f": "3", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      "a", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "b", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"2"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "c", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"3"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "d", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "e", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"2"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "f", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"3"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "vSphere PV labeled correctly", |  | ||||||
| 			handler: newPersistentVolumeLabel(), |  | ||||||
| 			pvlabeler: mockVolumeLabels(map[string]string{ |  | ||||||
| 				"a":                           "1", |  | ||||||
| 				"b":                           "2", |  | ||||||
| 				v1.LabelFailureDomainBetaZone: "1__2__3", |  | ||||||
| 			}), |  | ||||||
| 			preAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "vSpherePV", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			postAdmissionPV: &api.PersistentVolume{ |  | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 					Name:      "vSpherePV", |  | ||||||
| 					Namespace: "myns", |  | ||||||
| 					Labels: map[string]string{ |  | ||||||
| 						"a":                           "1", |  | ||||||
| 						"b":                           "2", |  | ||||||
| 						v1.LabelFailureDomainBetaZone: "1__2__3", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Spec: api.PersistentVolumeSpec{ |  | ||||||
| 					PersistentVolumeSource: api.PersistentVolumeSource{ |  | ||||||
| 						VsphereVolume: &api.VsphereVirtualDiskVolumeSource{ |  | ||||||
| 							VolumePath: "123", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					NodeAffinity: &api.VolumeNodeAffinity{ |  | ||||||
| 						Required: &api.NodeSelector{ |  | ||||||
| 							NodeSelectorTerms: []api.NodeSelectorTerm{ |  | ||||||
| 								{ |  | ||||||
| 									MatchExpressions: []api.NodeSelectorRequirement{ |  | ||||||
| 										{ |  | ||||||
| 											Key:      "a", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      "b", |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"2"}, |  | ||||||
| 										}, |  | ||||||
| 										{ |  | ||||||
| 											Key:      v1.LabelFailureDomainBetaZone, |  | ||||||
| 											Operator: api.NodeSelectorOpIn, |  | ||||||
| 											Values:   []string{"1", "2", "3"}, |  | ||||||
| 										}, |  | ||||||
| 									}, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			err: nil, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, testcase := range testcases { |  | ||||||
| 		t.Run(testcase.name, func(t *testing.T) { |  | ||||||
| 			setPVLabeler(testcase.handler, testcase.pvlabeler) |  | ||||||
| 			handler := admissiontesting.WithReinvocationTesting(t, admission.NewChainHandler(testcase.handler)) |  | ||||||
|  |  | ||||||
| 			err := handler.Admit(context.TODO(), admission.NewAttributesRecord(testcase.preAdmissionPV, nil, api.Kind("PersistentVolume").WithVersion("version"), testcase.preAdmissionPV.Namespace, testcase.preAdmissionPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) |  | ||||||
| 			if !reflect.DeepEqual(err, testcase.err) { |  | ||||||
| 				t.Logf("expected error: %q", testcase.err) |  | ||||||
| 				t.Logf("actual error: %q", err) |  | ||||||
| 				t.Error("unexpected error when admitting PV") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// sort node selector match expression by key because they are added out of order in the admission controller |  | ||||||
| 			sortMatchExpressions(testcase.preAdmissionPV) |  | ||||||
| 			if !reflect.DeepEqual(testcase.preAdmissionPV, testcase.postAdmissionPV) { |  | ||||||
| 				t.Logf("expected PV: %+v", testcase.postAdmissionPV) |  | ||||||
| 				t.Logf("actual PV: %+v", testcase.preAdmissionPV) |  | ||||||
| 				t.Error("unexpected PV") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // setPVLabler applies the given mock pvlabeler to implement PV labeling for all cloud providers. |  | ||||||
| // Given we mock out the values of the labels anyways, assigning the same mock labeler for every |  | ||||||
| // provider does not reduce test coverage but it does simplify/clean up the tests here because |  | ||||||
| // the provider is then decided based on the type of PV (EBS, GCEPD, Azure Disk, etc) |  | ||||||
| func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabeler) { |  | ||||||
| 	handler.azurePVLabeler = pvlabeler |  | ||||||
| 	handler.vspherePVLabeler = pvlabeler |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // sortMatchExpressions sorts a PV's node selector match expressions by key name if it is not nil |  | ||||||
| func sortMatchExpressions(pv *api.PersistentVolume) { |  | ||||||
| 	if pv.Spec.NodeAffinity == nil || |  | ||||||
| 		pv.Spec.NodeAffinity.Required == nil || |  | ||||||
| 		pv.Spec.NodeAffinity.Required.NodeSelectorTerms == nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	match := pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions |  | ||||||
| 	sort.Slice(match, func(i, j int) bool { |  | ||||||
| 		return match[i].Key < match[j].Key |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = match |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 label created persistent volumes with zone information |  | ||||||
| // as provided by the cloud provider |  | ||||||
| package label // import "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label" |  | ||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot