Merge pull request #4010 from whitewindmills/deprecated-zone-rs
adopt zones in resource selector
This commit is contained in:
commit
90235bb72b
|
@ -1,9 +1,12 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"k8s.io/utils/strings/slices"
|
||||||
|
|
||||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||||
|
@ -105,14 +108,31 @@ func ClusterMatches(cluster *clusterv1alpha1.Cluster, affinity policyv1alpha1.Cl
|
||||||
}
|
}
|
||||||
|
|
||||||
if affinity.FieldSelector != nil {
|
if affinity.FieldSelector != nil {
|
||||||
var matchFields labels.Selector
|
var clusterFieldsMatchExpressions []corev1.NodeSelectorRequirement
|
||||||
var errs []error
|
for i := range affinity.FieldSelector.MatchExpressions {
|
||||||
if matchFields, errs = lifted.NodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); errs != nil {
|
matchExpression := &affinity.FieldSelector.MatchExpressions[i]
|
||||||
return false
|
if matchExpression.Key != ZoneField {
|
||||||
|
clusterFieldsMatchExpressions = append(clusterFieldsMatchExpressions, *matchExpression)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, match zones field.
|
||||||
|
if !matchZones(matchExpression, cluster.Spec.Zones) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clusterFields := extractClusterFields(cluster)
|
|
||||||
if matchFields != nil && !matchFields.Matches(clusterFields) {
|
if len(clusterFieldsMatchExpressions) > 0 {
|
||||||
return false
|
// Second, match other fields.
|
||||||
|
var matchFields labels.Selector
|
||||||
|
var errs []error
|
||||||
|
if matchFields, errs = lifted.NodeSelectorRequirementsAsSelector(clusterFieldsMatchExpressions); errs != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
clusterFields := extractClusterFields(cluster)
|
||||||
|
if matchFields != nil && !matchFields.Matches(clusterFields) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,9 +185,41 @@ func extractClusterFields(cluster *clusterv1alpha1.Cluster) labels.Set {
|
||||||
clusterFieldsMap[RegionField] = cluster.Spec.Region
|
clusterFieldsMap[RegionField] = cluster.Spec.Region
|
||||||
}
|
}
|
||||||
|
|
||||||
if cluster.Spec.Zone != "" {
|
|
||||||
clusterFieldsMap[ZoneField] = cluster.Spec.Zone
|
|
||||||
}
|
|
||||||
|
|
||||||
return clusterFieldsMap
|
return clusterFieldsMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchZones checks if zoneMatchExpression can match zones and returns true if it matches.
|
||||||
|
// For unknown operators, matchZones always returns false.
|
||||||
|
// The matching rules are as follows:
|
||||||
|
// 1. When the operator is "In", zoneMatchExpression must contain all zones, otherwise it doesn't match.
|
||||||
|
// 2. When the operator is "NotIn", zoneMatchExpression mustn't contain any one of zones, otherwise it doesn't match.
|
||||||
|
// 3. When the operator is "Exists", zones mustn't be empty, otherwise it doesn't match.
|
||||||
|
// 4. When the operator is "DoesNotExist", zones must be empty, otherwise it doesn't match.
|
||||||
|
func matchZones(zoneMatchExpression *corev1.NodeSelectorRequirement, zones []string) bool {
|
||||||
|
switch zoneMatchExpression.Operator {
|
||||||
|
case corev1.NodeSelectorOpIn:
|
||||||
|
if len(zones) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, zone := range zones {
|
||||||
|
if !slices.Contains(zoneMatchExpression.Values, zone) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case corev1.NodeSelectorOpNotIn:
|
||||||
|
for _, zone := range zones {
|
||||||
|
if slices.Contains(zoneMatchExpression.Values, zone) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case corev1.NodeSelectorOpExists:
|
||||||
|
return len(zones) > 0
|
||||||
|
case corev1.NodeSelectorOpDoesNotExist:
|
||||||
|
return len(zones) == 0
|
||||||
|
default:
|
||||||
|
klog.V(5).Infof("Unsupported %q operator for zones requirement", zoneMatchExpression.Operator)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: clusterv1alpha1.ClusterSpec{
|
Spec: clusterv1alpha1.ClusterSpec{
|
||||||
Zone: "zone1",
|
Zones: []string{"zone1", "zone2", "zone3"},
|
||||||
Region: "region1",
|
Region: "region1",
|
||||||
Provider: "provider1",
|
Provider: "provider1",
|
||||||
},
|
},
|
||||||
|
@ -332,7 +332,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
ExcludeClusters: []string{cluster.Name},
|
ExcludeClusters: []string{cluster.Name},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -373,12 +373,13 @@ func TestClusterMatches(t *testing.T) {
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test cluster names and field selector(zone)",
|
name: "test cluster names and field selector(zone & region)",
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
ClusterNames: []string{cluster.Name},
|
ClusterNames: []string{cluster.Name},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
|
{Key: RegionField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Region}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -414,7 +415,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
ClusterNames: []string{cluster.Name},
|
ClusterNames: []string{cluster.Name},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -452,7 +453,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
},
|
},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -494,7 +495,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
},
|
},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -576,7 +577,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -588,7 +589,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
ClusterNames: []string{cluster.Name},
|
ClusterNames: []string{cluster.Name},
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -690,7 +691,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LabelSelector: &metav1.LabelSelector{
|
LabelSelector: &metav1.LabelSelector{
|
||||||
|
@ -705,7 +706,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LabelSelector: &metav1.LabelSelector{
|
LabelSelector: &metav1.LabelSelector{
|
||||||
|
@ -720,7 +721,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpNotIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LabelSelector: &metav1.LabelSelector{
|
LabelSelector: &metav1.LabelSelector{
|
||||||
|
@ -735,7 +736,7 @@ func TestClusterMatches(t *testing.T) {
|
||||||
affinity: policyv1alpha1.ClusterAffinity{
|
affinity: policyv1alpha1.ClusterAffinity{
|
||||||
FieldSelector: &policyv1alpha1.FieldSelector{
|
FieldSelector: &policyv1alpha1.FieldSelector{
|
||||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||||
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: []string{cluster.Spec.Zone}},
|
{Key: ZoneField, Operator: corev1.NodeSelectorOpIn, Values: cluster.Spec.Zones},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LabelSelector: &metav1.LabelSelector{
|
LabelSelector: &metav1.LabelSelector{
|
||||||
|
@ -949,19 +950,6 @@ func Test_extractClusterFields(t *testing.T) {
|
||||||
RegionField: "foo",
|
RegionField: "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "zone is set",
|
|
||||||
args: args{
|
|
||||||
cluster: &clusterv1alpha1.Cluster{
|
|
||||||
Spec: clusterv1alpha1.ClusterSpec{
|
|
||||||
Zone: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: labels.Set{
|
|
||||||
ZoneField: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "all are set",
|
name: "all are set",
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -969,14 +957,12 @@ func Test_extractClusterFields(t *testing.T) {
|
||||||
Spec: clusterv1alpha1.ClusterSpec{
|
Spec: clusterv1alpha1.ClusterSpec{
|
||||||
Provider: "foo",
|
Provider: "foo",
|
||||||
Region: "bar",
|
Region: "bar",
|
||||||
Zone: "baz",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: labels.Set{
|
want: labels.Set{
|
||||||
ProviderField: "foo",
|
ProviderField: "foo",
|
||||||
RegionField: "bar",
|
RegionField: "bar",
|
||||||
ZoneField: "baz",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -988,3 +974,100 @@ func Test_extractClusterFields(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_matchZones(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
zoneMatchExpression *corev1.NodeSelectorRequirement
|
||||||
|
zones []string
|
||||||
|
matched bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty zones for In operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpIn,
|
||||||
|
Values: []string{"foo"},
|
||||||
|
},
|
||||||
|
zones: nil,
|
||||||
|
matched: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial zones for In operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpIn,
|
||||||
|
Values: []string{"foo"},
|
||||||
|
},
|
||||||
|
zones: []string{"foo", "bar"},
|
||||||
|
matched: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all zones for In operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpIn,
|
||||||
|
Values: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
zones: []string{"foo", "bar"},
|
||||||
|
matched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty zones for NotIn operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpNotIn,
|
||||||
|
Values: []string{"foo"},
|
||||||
|
},
|
||||||
|
zones: nil,
|
||||||
|
matched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial zones for NotIn operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpNotIn,
|
||||||
|
Values: []string{"foo"},
|
||||||
|
},
|
||||||
|
zones: []string{"foo", "bar"},
|
||||||
|
matched: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty zones for Exists operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpExists,
|
||||||
|
Values: nil,
|
||||||
|
},
|
||||||
|
zones: nil,
|
||||||
|
matched: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty zones for DoesNotExist operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpDoesNotExist,
|
||||||
|
Values: nil,
|
||||||
|
},
|
||||||
|
zones: nil,
|
||||||
|
matched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown operator",
|
||||||
|
zoneMatchExpression: &corev1.NodeSelectorRequirement{
|
||||||
|
Key: ZoneField,
|
||||||
|
Operator: corev1.NodeSelectorOpGt,
|
||||||
|
Values: nil,
|
||||||
|
},
|
||||||
|
zones: []string{"foo"},
|
||||||
|
matched: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := matchZones(tt.zoneMatchExpression, tt.zones); got != tt.matched {
|
||||||
|
t.Errorf("matchZones() got %v, but expected %v", got, tt.matched)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue