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:
Kubernetes Submit Queue
2018-07-09 12:16:02 -07:00
committed by GitHub
8 changed files with 749 additions and 50 deletions

View File

@@ -283,6 +283,20 @@ func NodeSelectorRequirementsAsFieldSelector(nsm []v1.NodeSelectorRequirement) (
return fields.AndSelectors(selectors...), nil
}
// NodeSelectorRequirementKeysExistInNodeSelectorTerms checks if a NodeSelectorTerm with key is already specified in terms
func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorRequirement, terms []v1.NodeSelectorTerm) bool {
for _, req := range reqs {
for _, term := range terms {
for _, r := range term.MatchExpressions {
if r.Key == req.Key {
return true
}
}
}
}
return false
}
// MatchNodeSelectorTerms checks whether the node labels and fields match node selector terms in ORed;
// nil or empty term matches no objects.
func MatchNodeSelectorTerms(

View File

@@ -968,3 +968,193 @@ func TestMatchTopologySelectorTerms(t *testing.T) {
})
}
}
func TestNodeSelectorRequirementKeyExistsInNodeSelectorTerms(t *testing.T) {
tests := []struct {
name string
reqs []v1.NodeSelectorRequirement
terms []v1.NodeSelectorTerm
exists bool
}{
{
name: "empty set of keys in empty set of terms",
reqs: []v1.NodeSelectorRequirement{},
terms: []v1.NodeSelectorTerm{},
exists: false,
},
{
name: "key existence in terms with all keys specified",
reqs: []v1.NodeSelectorRequirement{
{
Key: "key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value1"},
},
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
},
},
terms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
},
{
Key: "key3",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value3"},
},
},
},
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value11, test-value12"},
},
{
Key: "key4",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value41, test-value42"},
},
},
},
},
exists: true,
},
{
name: "key existence in terms with one of the keys specfied",
reqs: []v1.NodeSelectorRequirement{
{
Key: "key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value1"},
},
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
},
{
Key: "key3",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value3"},
},
{
Key: "key6",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value6"},
},
},
terms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
}, {
Key: "key4",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value4"},
},
},
},
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key5",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value5"},
},
},
},
},
exists: true,
},
{
name: "key existence in terms without any of the keys specified",
reqs: []v1.NodeSelectorRequirement{
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
},
{
Key: "key3",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value3"},
},
},
terms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key4",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value"},
},
{
Key: "key5",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value"},
},
},
},
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key6",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value"},
},
},
},
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "key7",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value"},
},
{
Key: "key8",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value"},
},
},
},
},
exists: false,
},
{
name: "key existence in empty set of terms",
reqs: []v1.NodeSelectorRequirement{
{
Key: "key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value2"},
},
{
Key: "key3",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value3"},
},
},
terms: []v1.NodeSelectorTerm{},
exists: false,
},
}
for _, test := range tests {
keyExists := NodeSelectorRequirementKeysExistInNodeSelectorTerms(test.reqs, test.terms)
if test.exists != keyExists {
t.Errorf("test %s failed. Expected %v but got %v", test.name, test.exists, keyExists)
}
}
}