mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
Merge pull request #64226 from ddebroy/ddebroy-affinity1
Automatic merge from submit-queue (batch tested with PRs 64226, 65880). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Populate NodeAffinity on top of labels for cloud based PersistentVolumes **What this PR does / why we need it**: This PR populates the NodeAffinity field (on top of the existing labels) for PVs backed by cloud providers like EC2 EBS and GCE PD. **Special notes for your reviewer**: Related to https://github.com/kubernetes/kubernetes/pull/63232 Sample `describe pv` output for EBS with node affinity field populated: ``` kubectl describe pv pv0001 Name: pv0001 Labels: failure-domain.beta.kubernetes.io/region=us-west-2 failure-domain.beta.kubernetes.io/zone=us-west-2a Annotations: <none> Finalizers: [kubernetes.io/pv-protection] StorageClass: Status: Available Claim: Reclaim Policy: Retain Access Modes: RWO Capacity: 5Gi Node Affinity: Required Terms: Term 0: failure-domain.beta.kubernetes.io/zone in [us-west-2a] failure-domain.beta.kubernetes.io/region in [us-west-2] Message: Source: Type: AWSElasticBlockStore (a Persistent Disk resource in AWS) VolumeID: vol-00cf03a068c62cbe6 FSType: ext4 Partition: 0 ReadOnly: false Events: <none> ``` /sig storage /assign @msau42 **Release note**: ```NONE```
This commit is contained in:
@@ -15,12 +15,15 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/cloud",
|
||||
deps = [
|
||||
"//pkg/api/v1/node:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/cloudprovider:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/util/node:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//pkg/util/node:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
@@ -30,6 +33,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
@@ -57,11 +61,13 @@ go_test(
|
||||
"//pkg/controller/testutil:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
|
||||
@@ -34,6 +34,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
@@ -70,7 +75,7 @@ func NewPersistentVolumeLabelController(
|
||||
kubeClient: kubeClient,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvLabels"),
|
||||
}
|
||||
pvlc.syncHandler = pvlc.addLabels
|
||||
pvlc.syncHandler = pvlc.addLabelsAndAffinity
|
||||
pvlc.pvlIndexer, pvlc.pvlController = cache.NewIndexerInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
@@ -166,7 +171,7 @@ func (pvlc *PersistentVolumeLabelController) processNextWorkItem() bool {
|
||||
|
||||
// AddLabels adds appropriate labels to persistent volumes and sets the
|
||||
// volume as available if successful.
|
||||
func (pvlc *PersistentVolumeLabelController) addLabels(key string) error {
|
||||
func (pvlc *PersistentVolumeLabelController) addLabelsAndAffinity(key string) error {
|
||||
_, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting name of volume %q to get volume from informer: %v", key, err)
|
||||
@@ -178,10 +183,10 @@ func (pvlc *PersistentVolumeLabelController) addLabels(key string) error {
|
||||
return fmt.Errorf("error getting volume %s from informer: %v", name, err)
|
||||
}
|
||||
|
||||
return pvlc.addLabelsToVolume(volume)
|
||||
return pvlc.addLabelsAndAffinityToVolume(volume)
|
||||
}
|
||||
|
||||
func (pvlc *PersistentVolumeLabelController) addLabelsToVolume(vol *v1.PersistentVolume) error {
|
||||
func (pvlc *PersistentVolumeLabelController) addLabelsAndAffinityToVolume(vol *v1.PersistentVolume) error {
|
||||
var volumeLabels map[string]string
|
||||
// Only add labels if the next pending initializer.
|
||||
if needsInitialization(vol.Initializers, initializerName) {
|
||||
@@ -202,11 +207,52 @@ func (pvlc *PersistentVolumeLabelController) addLabelsToVolume(vol *v1.Persisten
|
||||
func (pvlc *PersistentVolumeLabelController) createPatch(vol *v1.PersistentVolume, volLabels map[string]string) ([]byte, error) {
|
||||
volName := vol.Name
|
||||
newVolume := vol.DeepCopyObject().(*v1.PersistentVolume)
|
||||
populateAffinity := utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) && len(volLabels) != 0
|
||||
|
||||
if newVolume.Labels == nil {
|
||||
newVolume.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
requirements := make([]v1.NodeSelectorRequirement, 0)
|
||||
for k, v := range volLabels {
|
||||
newVolume.Labels[k] = v
|
||||
// Set NodeSelectorRequirements based on the labels
|
||||
if populateAffinity {
|
||||
var values []string
|
||||
if k == kubeletapis.LabelZoneFailureDomain {
|
||||
zones, err := volumeutil.LabelZonesToSet(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert label string for Zone: %s to a Set", v)
|
||||
}
|
||||
values = zones.List()
|
||||
} else {
|
||||
values = []string{v}
|
||||
}
|
||||
requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: values})
|
||||
}
|
||||
}
|
||||
if populateAffinity {
|
||||
if newVolume.Spec.NodeAffinity == nil {
|
||||
newVolume.Spec.NodeAffinity = new(v1.VolumeNodeAffinity)
|
||||
}
|
||||
if newVolume.Spec.NodeAffinity.Required == nil {
|
||||
newVolume.Spec.NodeAffinity.Required = new(v1.NodeSelector)
|
||||
}
|
||||
if len(newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 {
|
||||
// Need atleast one term pre-allocated whose MatchExpressions can be appended to
|
||||
newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1)
|
||||
}
|
||||
// Populate NodeAffinity with requirements if there are no conflicting keys found
|
||||
if v1helper.NodeSelectorRequirementKeysExistInNodeSelectorTerms(requirements, newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms) {
|
||||
glog.V(4).Info("NodeSelectorRequirements for cloud labels %v conflict with existing NodeAffinity %v. Skipping addition of NodeSelectorRequirements for cloud labels.",
|
||||
requirements, newVolume.Spec.NodeAffinity)
|
||||
} else {
|
||||
for _, req := range requirements {
|
||||
for i := range newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms {
|
||||
newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions = append(newVolume.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newVolume.Initializers = removeInitializer(newVolume.Initializers, initializerName)
|
||||
glog.V(4).Infof("removed initializer on PersistentVolume %s", newVolume.Name)
|
||||
|
||||
@@ -18,6 +18,7 @@ package cloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -26,8 +27,11 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
|
||||
fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
|
||||
)
|
||||
@@ -71,29 +75,292 @@ func TestCreatePatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
vol v1.PersistentVolume
|
||||
labels map[string]string
|
||||
}{
|
||||
"non-cloud PV": {
|
||||
vol: ignoredPV,
|
||||
labels: nil,
|
||||
expectedAffinitya1b2MergedWithAWSPV := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "a",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"no labels": {
|
||||
vol: awsPV,
|
||||
labels: nil,
|
||||
}
|
||||
expectedAffinityZone1MergedWithAWSPV := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cloudprovider returns nil, nil": {
|
||||
vol: awsPV,
|
||||
labels: nil,
|
||||
}
|
||||
expectedAffinityZonesMergedWithAWSPV := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cloudprovider labels": {
|
||||
vol: awsPV,
|
||||
labels: map[string]string{"a": "1", "b": "2"},
|
||||
}
|
||||
awsPVWithAffinity := v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsPV",
|
||||
Initializers: &metav1.Initializers{
|
||||
Pending: []metav1.Initializer{
|
||||
{
|
||||
Name: initializerName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
Key: "d",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "e",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val4", "val5"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedAffinitya1b2MergedWithAWSPVWithAffinity := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
Key: "d",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val3"},
|
||||
},
|
||||
{
|
||||
Key: "a",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "e",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val4", "val5"},
|
||||
},
|
||||
{
|
||||
Key: "a",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedAffinityZone1MergedWithAWSPVWithAffinity := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
Key: "d",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val3"},
|
||||
},
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "e",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val4", "val5"},
|
||||
},
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedAffinityZonesMergedWithAWSPVWithAffinity := v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
Key: "d",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val3"},
|
||||
},
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "e",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"val4", "val5"},
|
||||
},
|
||||
{
|
||||
Key: kubeletapis.LabelZoneFailureDomain,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
zones, _ := volumeutil.ZonesToSet("1,2,3")
|
||||
testCases := map[string]struct {
|
||||
vol v1.PersistentVolume
|
||||
labels map[string]string
|
||||
expectedAffinity *v1.VolumeNodeAffinity
|
||||
}{
|
||||
"non-cloud PV": {
|
||||
vol: ignoredPV,
|
||||
labels: nil,
|
||||
expectedAffinity: nil,
|
||||
},
|
||||
"no labels": {
|
||||
vol: awsPV,
|
||||
labels: nil,
|
||||
expectedAffinity: nil,
|
||||
},
|
||||
"cloudprovider returns nil, nil": {
|
||||
vol: awsPV,
|
||||
labels: nil,
|
||||
expectedAffinity: nil,
|
||||
},
|
||||
"cloudprovider labels": {
|
||||
vol: awsPV,
|
||||
labels: map[string]string{"a": "1", "b": "2"},
|
||||
expectedAffinity: &expectedAffinitya1b2MergedWithAWSPV,
|
||||
},
|
||||
"cloudprovider labels pre-existing affinity non-conflicting": {
|
||||
vol: awsPVWithAffinity,
|
||||
labels: map[string]string{"a": "1", "b": "2"},
|
||||
expectedAffinity: &expectedAffinitya1b2MergedWithAWSPVWithAffinity,
|
||||
},
|
||||
"cloudprovider labels pre-existing affinity conflicting": {
|
||||
vol: awsPVWithAffinity,
|
||||
labels: map[string]string{"a": "1", "c": "2"},
|
||||
expectedAffinity: nil,
|
||||
},
|
||||
"cloudprovider singlezone": {
|
||||
vol: awsPV,
|
||||
labels: map[string]string{kubeletapis.LabelZoneFailureDomain: "1"},
|
||||
expectedAffinity: &expectedAffinityZone1MergedWithAWSPV,
|
||||
},
|
||||
"cloudprovider singlezone pre-existing affinity non-conflicting": {
|
||||
vol: awsPVWithAffinity,
|
||||
labels: map[string]string{kubeletapis.LabelZoneFailureDomain: "1"},
|
||||
expectedAffinity: &expectedAffinityZone1MergedWithAWSPVWithAffinity,
|
||||
},
|
||||
"cloudprovider multizone": {
|
||||
vol: awsPV,
|
||||
labels: map[string]string{kubeletapis.LabelZoneFailureDomain: volumeutil.ZonesSetToLabelValue(zones)},
|
||||
expectedAffinity: &expectedAffinityZonesMergedWithAWSPV,
|
||||
},
|
||||
"cloudprovider multizone pre-existing affinity non-conflicting": {
|
||||
vol: awsPVWithAffinity,
|
||||
labels: map[string]string{kubeletapis.LabelZoneFailureDomain: volumeutil.ZonesSetToLabelValue(zones)},
|
||||
expectedAffinity: &expectedAffinityZonesMergedWithAWSPVWithAffinity,
|
||||
},
|
||||
}
|
||||
|
||||
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
for d, tc := range testCases {
|
||||
cloud := &fakecloud.FakeCloud{}
|
||||
client := fake.NewSimpleClientset()
|
||||
@@ -104,16 +371,20 @@ func TestCreatePatch(t *testing.T) {
|
||||
}
|
||||
obj := &v1.PersistentVolume{}
|
||||
json.Unmarshal(patch, obj)
|
||||
if tc.labels != nil {
|
||||
for k, v := range tc.labels {
|
||||
if obj.ObjectMeta.Labels[k] != v {
|
||||
t.Errorf("%s: label %s expected %s got %s", d, k, v, obj.ObjectMeta.Labels[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
if obj.ObjectMeta.Initializers != nil {
|
||||
t.Errorf("%s: initializer wasn't removed: %v", d, obj.ObjectMeta.Initializers)
|
||||
}
|
||||
if tc.labels == nil {
|
||||
continue
|
||||
}
|
||||
for k, v := range tc.labels {
|
||||
if obj.ObjectMeta.Labels[k] != v {
|
||||
t.Errorf("%s: label %s expected %s got %s", d, k, v, obj.ObjectMeta.Labels[k])
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.expectedAffinity, obj.Spec.NodeAffinity) {
|
||||
t.Errorf("Expected affinity %v does not match target affinity %v", tc.expectedAffinity, obj.Spec.NodeAffinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,32 +403,35 @@ func TestAddLabelsToVolume(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
vol v1.PersistentVolume
|
||||
initializers *metav1.Initializers
|
||||
shouldLabel bool
|
||||
vol v1.PersistentVolume
|
||||
initializers *metav1.Initializers
|
||||
shouldLabelAndSetAffinity bool
|
||||
}{
|
||||
"PV without initializer": {
|
||||
vol: pv,
|
||||
initializers: nil,
|
||||
shouldLabel: false,
|
||||
vol: pv,
|
||||
initializers: nil,
|
||||
shouldLabelAndSetAffinity: false,
|
||||
},
|
||||
"PV with initializer to remove": {
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: initializerName}}},
|
||||
shouldLabel: true,
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: initializerName}}},
|
||||
shouldLabelAndSetAffinity: true,
|
||||
},
|
||||
"PV with other initializers only": {
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}}},
|
||||
shouldLabel: false,
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}}},
|
||||
shouldLabelAndSetAffinity: false,
|
||||
},
|
||||
"PV with other initializers first": {
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}, {Name: initializerName}}},
|
||||
shouldLabel: false,
|
||||
vol: pv,
|
||||
initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}, {Name: initializerName}}},
|
||||
shouldLabelAndSetAffinity: false,
|
||||
},
|
||||
}
|
||||
|
||||
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
|
||||
for d, tc := range testCases {
|
||||
labeledCh := make(chan bool, 1)
|
||||
client := fake.NewSimpleClientset()
|
||||
@@ -168,6 +442,22 @@ func TestAddLabelsToVolume(t *testing.T) {
|
||||
if obj.ObjectMeta.Labels["a"] != "1" {
|
||||
return false, nil, nil
|
||||
}
|
||||
if obj.Spec.NodeAffinity == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
if obj.Spec.NodeAffinity.Required == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
if len(obj.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 {
|
||||
return false, nil, nil
|
||||
}
|
||||
reqs := obj.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions
|
||||
if len(reqs) != 1 {
|
||||
return false, nil, nil
|
||||
}
|
||||
if reqs[0].Key != "a" || reqs[0].Values[0] != "1" || reqs[0].Operator != v1.NodeSelectorOpIn {
|
||||
return false, nil, nil
|
||||
}
|
||||
labeledCh <- true
|
||||
return true, nil, nil
|
||||
})
|
||||
@@ -177,16 +467,16 @@ func TestAddLabelsToVolume(t *testing.T) {
|
||||
}
|
||||
pvlController := &PersistentVolumeLabelController{kubeClient: client, cloud: fakeCloud}
|
||||
tc.vol.ObjectMeta.Initializers = tc.initializers
|
||||
pvlController.addLabelsToVolume(&tc.vol)
|
||||
pvlController.addLabelsAndAffinityToVolume(&tc.vol)
|
||||
|
||||
select {
|
||||
case l := <-labeledCh:
|
||||
if l != tc.shouldLabel {
|
||||
t.Errorf("%s: label of pv failed. expected %t got %t", d, tc.shouldLabel, l)
|
||||
if l != tc.shouldLabelAndSetAffinity {
|
||||
t.Errorf("%s: label and affinity setting of pv failed. expected %t got %t", d, tc.shouldLabelAndSetAffinity, l)
|
||||
}
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
if tc.shouldLabel != false {
|
||||
t.Errorf("%s: timed out waiting for label notification", d)
|
||||
if tc.shouldLabelAndSetAffinity != false {
|
||||
t.Errorf("%s: timed out waiting for label and affinity setting notification", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user