Graduate PodAffinityNamespaceSelector to GA

This commit is contained in:
Abdullah Gharaibeh 2022-02-15 13:08:48 -05:00
parent 2355747e7c
commit 8a1c70b48c
30 changed files with 138 additions and 591 deletions

View File

@ -8296,10 +8296,10 @@
}, },
"namespaceSelector": { "namespaceSelector": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled." "description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces."
}, },
"namespaces": { "namespaces": {
"description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"items": { "items": {
"type": "string" "type": "string"
}, },
@ -9671,7 +9671,7 @@
"type": "string" "type": "string"
}, },
"scopeName": { "scopeName": {
"description": "The name of the scope that the selector applies to.\n\nPossible enum values:\n - `\"BestEffort\"` Match all pod objects that have best effort quality of service\n - `\"CrossNamespacePodAffinity\"` Match all pod objects that have cross-namespace pod (anti)affinity mentioned. This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag.\n - `\"NotBestEffort\"` Match all pod objects that do not have best effort quality of service\n - `\"NotTerminating\"` Match all pod objects where spec.activeDeadlineSeconds is nil\n - `\"PriorityClass\"` Match all pod objects that have priority class mentioned\n - `\"Terminating\"` Match all pod objects where spec.activeDeadlineSeconds >=0", "description": "The name of the scope that the selector applies to.\n\nPossible enum values:\n - `\"BestEffort\"` Match all pod objects that have best effort quality of service\n - `\"CrossNamespacePodAffinity\"` Match all pod objects that have cross-namespace pod (anti)affinity mentioned.\n - `\"NotBestEffort\"` Match all pod objects that do not have best effort quality of service\n - `\"NotTerminating\"` Match all pod objects where spec.activeDeadlineSeconds is nil\n - `\"PriorityClass\"` Match all pod objects that have priority class mentioned\n - `\"Terminating\"` Match all pod objects where spec.activeDeadlineSeconds >=0",
"enum": [ "enum": [
"BestEffort", "BestEffort",
"CrossNamespacePodAffinity", "CrossNamespacePodAffinity",

View File

@ -3914,10 +3914,10 @@
}, },
"namespaceSelector": { "namespaceSelector": {
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled." "description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces."
}, },
"namespaces": { "namespaces": {
"description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"items": { "items": {
"default": "", "default": "",
"type": "string" "type": "string"
@ -5371,7 +5371,7 @@
}, },
"scopeName": { "scopeName": {
"default": "", "default": "",
"description": "The name of the scope that the selector applies to.\n\nPossible enum values:\n - `\"BestEffort\"` Match all pod objects that have best effort quality of service\n - `\"CrossNamespacePodAffinity\"` Match all pod objects that have cross-namespace pod (anti)affinity mentioned. This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag.\n - `\"NotBestEffort\"` Match all pod objects that do not have best effort quality of service\n - `\"NotTerminating\"` Match all pod objects where spec.activeDeadlineSeconds is nil\n - `\"PriorityClass\"` Match all pod objects that have priority class mentioned\n - `\"Terminating\"` Match all pod objects where spec.activeDeadlineSeconds >=0", "description": "The name of the scope that the selector applies to.\n\nPossible enum values:\n - `\"BestEffort\"` Match all pod objects that have best effort quality of service\n - `\"CrossNamespacePodAffinity\"` Match all pod objects that have cross-namespace pod (anti)affinity mentioned.\n - `\"NotBestEffort\"` Match all pod objects that do not have best effort quality of service\n - `\"NotTerminating\"` Match all pod objects where spec.activeDeadlineSeconds is nil\n - `\"PriorityClass\"` Match all pod objects that have priority class mentioned\n - `\"Terminating\"` Match all pod objects where spec.activeDeadlineSeconds >=0",
"enum": [ "enum": [
"BestEffort", "BestEffort",
"CrossNamespacePodAffinity", "CrossNamespacePodAffinity",

View File

@ -2695,10 +2695,10 @@
}, },
"namespaceSelector": { "namespaceSelector": {
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled." "description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces."
}, },
"namespaces": { "namespaces": {
"description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"items": { "items": {
"default": "", "default": "",
"type": "string" "type": "string"

View File

@ -1921,10 +1921,10 @@
}, },
"namespaceSelector": { "namespaceSelector": {
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled." "description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces."
}, },
"namespaces": { "namespaces": {
"description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"items": { "items": {
"default": "", "default": "",
"type": "string" "type": "string"

View File

@ -1723,10 +1723,10 @@
}, },
"namespaceSelector": { "namespaceSelector": {
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled." "description": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces."
}, },
"namespaces": { "namespaces": {
"description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"items": { "items": {
"default": "", "default": "",
"type": "string" "type": "string"

View File

@ -583,8 +583,6 @@ func dropDisabledFields(
if !utilfeature.DefaultFeatureGate.Enabled(features.IdentifyPodOS) && !podOSInUse(oldPodSpec) { if !utilfeature.DefaultFeatureGate.Enabled(features.IdentifyPodOS) && !podOSInUse(oldPodSpec) {
podSpec.OS = nil podSpec.OS = nil
} }
dropDisabledPodAffinityTermFields(podSpec, oldPodSpec)
} }
// podOSInUse returns true if the pod spec is non-nil and has OS field set // podOSInUse returns true if the pod spec is non-nil and has OS field set
@ -625,66 +623,6 @@ func dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
} }
} }
func dropPodAffinityTermNamespaceSelector(terms []api.PodAffinityTerm) {
for i := range terms {
terms[i].NamespaceSelector = nil
}
}
func dropWeightedPodAffinityTermNamespaceSelector(terms []api.WeightedPodAffinityTerm) {
for i := range terms {
terms[i].PodAffinityTerm.NamespaceSelector = nil
}
}
// dropDisabledPodAffinityTermFields removes disabled fields from PodSpec related
// to PodAffinityTerm only if it is not already used by the old spec
func dropDisabledPodAffinityTermFields(podSpec, oldPodSpec *api.PodSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector) &&
podSpec != nil && podSpec.Affinity != nil &&
!podAffinityNamespaceSelectorInUse(oldPodSpec) {
if podSpec.Affinity.PodAffinity != nil {
dropPodAffinityTermNamespaceSelector(podSpec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
dropWeightedPodAffinityTermNamespaceSelector(podSpec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution)
}
if podSpec.Affinity.PodAntiAffinity != nil {
dropPodAffinityTermNamespaceSelector(podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
dropWeightedPodAffinityTermNamespaceSelector(podSpec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution)
}
}
}
func podAffinityNamespaceSelectorInUse(podSpec *api.PodSpec) bool {
if podSpec == nil || podSpec.Affinity == nil {
return false
}
if podSpec.Affinity.PodAffinity != nil {
for _, t := range podSpec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if t.NamespaceSelector != nil {
return true
}
}
for _, t := range podSpec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if t.PodAffinityTerm.NamespaceSelector != nil {
return true
}
}
}
if podSpec.Affinity.PodAntiAffinity != nil {
for _, t := range podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if t.NamespaceSelector != nil {
return true
}
}
for _, t := range podSpec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if t.PodAffinityTerm.NamespaceSelector == nil {
return true
}
}
}
return false
}
func ephemeralContainersInUse(podSpec *api.PodSpec) bool { func ephemeralContainersInUse(podSpec *api.PodSpec) bool {
if podSpec == nil { if podSpec == nil {
return false return false

View File

@ -1519,163 +1519,6 @@ func TestHaveSameExpandedDNSConfig(t *testing.T) {
} }
} }
func TestDropDisabledPodAffinityTermFields(t *testing.T) {
testCases := []struct {
name string
enabled bool
podSpec *api.PodSpec
oldPodSpec *api.PodSpec
wantPodSpec *api.PodSpec
}{
{
name: "nil affinity",
podSpec: &api.PodSpec{},
wantPodSpec: &api.PodSpec{},
},
{
name: "empty affinity",
podSpec: &api.PodSpec{Affinity: &api.Affinity{}},
wantPodSpec: &api.PodSpec{Affinity: &api.Affinity{}},
},
{
name: "NamespaceSelector cleared",
podSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
}},
oldPodSpec: &api.PodSpec{Affinity: &api.Affinity{}},
wantPodSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1"},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2"}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3"},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4"}},
},
},
}},
},
{
name: "NamespaceSelector not cleared since old spec already sets it",
podSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
}},
oldPodSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
},
}},
wantPodSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
}},
},
{
name: "NamespaceSelector not cleared since feature is enabled",
enabled: true,
podSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
}},
wantPodSpec: &api.PodSpec{Affinity: &api.Affinity{
PodAffinity: &api.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, TopologyKey: "region1", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, TopologyKey: "region2", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}, TopologyKey: "region3", NamespaceSelector: &metav1.LabelSelector{}},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
{PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns4"}, TopologyKey: "region4", NamespaceSelector: &metav1.LabelSelector{}}},
},
},
}},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, tc.enabled)()
dropDisabledPodAffinityTermFields(tc.podSpec, tc.oldPodSpec)
if diff := cmp.Diff(tc.wantPodSpec, tc.podSpec); diff != "" {
t.Errorf("unexpected pod spec (-want, +got):\n%s", diff)
}
})
}
}
func TestDropOSField(t *testing.T) { func TestDropOSField(t *testing.T) {
podWithOSField := func() *api.Pod { podWithOSField := func() *api.Pod {
osField := api.PodOS{Name: "linux"} osField := api.PodOS{Name: "linux"}

View File

@ -123,9 +123,8 @@ var standardResourceQuotaScopes = sets.NewString(
) )
// IsStandardResourceQuotaScope returns true if the scope is a standard value // IsStandardResourceQuotaScope returns true if the scope is a standard value
func IsStandardResourceQuotaScope(str string, allowNamespaceAffinityScope bool) bool { func IsStandardResourceQuotaScope(str string) bool {
return standardResourceQuotaScopes.Has(str) || return standardResourceQuotaScopes.Has(str) || str == string(core.ResourceQuotaScopeCrossNamespacePodAffinity)
(allowNamespaceAffinityScope && str == string(core.ResourceQuotaScopeCrossNamespacePodAffinity))
} }
var podObjectCountQuotaResources = sets.NewString( var podObjectCountQuotaResources = sets.NewString(

View File

@ -2662,7 +2662,7 @@ type PodAffinityTerm struct {
// namespaces specifies a static list of namespace names that the term applies to. // namespaces specifies a static list of namespace names that the term applies to.
// The term is applied to the union of the namespaces listed in this field // The term is applied to the union of the namespaces listed in this field
// and the ones selected by namespaceSelector. // and the ones selected by namespaceSelector.
// null or empty namespaces list and null namespaceSelector means "this pod's namespace" // null or empty namespaces list and null namespaceSelector means "this pod's namespace".
// +optional // +optional
Namespaces []string Namespaces []string
// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@ -2676,7 +2676,6 @@ type PodAffinityTerm struct {
// and the ones listed in the namespaces field. // and the ones listed in the namespaces field.
// null selector and null or empty namespaces list means "this pod's namespace". // null selector and null or empty namespaces list means "this pod's namespace".
// An empty selector ({}) matches all namespaces. // An empty selector ({}) matches all namespaces.
// This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.
// +optional // +optional
NamespaceSelector *metav1.LabelSelector NamespaceSelector *metav1.LabelSelector
} }
@ -5065,7 +5064,6 @@ const (
// Match all pod objects that have priority class mentioned // Match all pod objects that have priority class mentioned
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass" ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
// Match all pod objects that have cross-namespace pod (anti)affinity mentioned // Match all pod objects that have cross-namespace pod (anti)affinity mentioned
// This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag.
ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity" ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity"
) )

View File

@ -5764,7 +5764,7 @@ func validateResourceQuantityHugePageValue(name core.ResourceName, quantity reso
} }
// validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes // validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes
func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts ResourceQuotaValidationOptions, fld *field.Path) field.ErrorList { func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if len(resourceQuotaSpec.Scopes) == 0 { if len(resourceQuotaSpec.Scopes) == 0 {
return allErrs return allErrs
@ -5776,7 +5776,7 @@ func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts
fldPath := fld.Child("scopes") fldPath := fld.Child("scopes")
scopeSet := sets.NewString() scopeSet := sets.NewString()
for _, scope := range resourceQuotaSpec.Scopes { for _, scope := range resourceQuotaSpec.Scopes {
if !helper.IsStandardResourceQuotaScope(string(scope), opts.AllowPodAffinityNamespaceSelector) { if !helper.IsStandardResourceQuotaScope(string(scope)) {
allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope")) allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope"))
} }
for _, k := range hardLimits.List() { for _, k := range hardLimits.List() {
@ -5799,7 +5799,7 @@ func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts
} }
// validateScopedResourceSelectorRequirement tests that the match expressions has valid data // validateScopedResourceSelectorRequirement tests that the match expressions has valid data
func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQuotaSpec, opts ResourceQuotaValidationOptions, fld *field.Path) field.ErrorList { func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
hardLimits := sets.NewString() hardLimits := sets.NewString()
for k := range resourceQuotaSpec.Hard { for k := range resourceQuotaSpec.Hard {
@ -5808,7 +5808,7 @@ func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQ
fldPath := fld.Child("matchExpressions") fldPath := fld.Child("matchExpressions")
scopeSet := sets.NewString() scopeSet := sets.NewString()
for _, req := range resourceQuotaSpec.ScopeSelector.MatchExpressions { for _, req := range resourceQuotaSpec.ScopeSelector.MatchExpressions {
if !helper.IsStandardResourceQuotaScope(string(req.ScopeName), opts.AllowPodAffinityNamespaceSelector) { if !helper.IsStandardResourceQuotaScope(string(req.ScopeName)) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("scopeName"), req.ScopeName, "unsupported scope")) allErrs = append(allErrs, field.Invalid(fldPath.Child("scopeName"), req.ScopeName, "unsupported scope"))
} }
for _, k := range hardLimits.List() { for _, k := range hardLimits.List() {
@ -5854,26 +5854,20 @@ func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQ
} }
// validateScopeSelector tests that the specified scope selector has valid data // validateScopeSelector tests that the specified scope selector has valid data
func validateScopeSelector(resourceQuotaSpec *core.ResourceQuotaSpec, opts ResourceQuotaValidationOptions, fld *field.Path) field.ErrorList { func validateScopeSelector(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if resourceQuotaSpec.ScopeSelector == nil { if resourceQuotaSpec.ScopeSelector == nil {
return allErrs return allErrs
} }
allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, opts, fld.Child("scopeSelector"))...) allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, fld.Child("scopeSelector"))...)
return allErrs return allErrs
} }
// ResourceQuotaValidationOptions contains the different settings for ResourceQuota validation
type ResourceQuotaValidationOptions struct {
// Allow pod-affinity namespace selector validation.
AllowPodAffinityNamespaceSelector bool
}
// ValidateResourceQuota tests if required fields in the ResourceQuota are set. // ValidateResourceQuota tests if required fields in the ResourceQuota are set.
func ValidateResourceQuota(resourceQuota *core.ResourceQuota, opts ResourceQuotaValidationOptions) field.ErrorList { func ValidateResourceQuota(resourceQuota *core.ResourceQuota) field.ErrorList {
allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata")) allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, opts, field.NewPath("spec"))...) allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...) allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...)
return allErrs return allErrs
@ -5898,7 +5892,7 @@ func ValidateResourceQuotaStatus(status *core.ResourceQuotaStatus, fld *field.Pa
return allErrs return allErrs
} }
func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, opts ResourceQuotaValidationOptions, fld *field.Path) field.ErrorList { func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
fldPath := fld.Child("hard") fldPath := fld.Child("hard")
@ -5908,8 +5902,8 @@ func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, opts R
allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...)
} }
allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, opts, fld)...) allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...)
allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, opts, fld)...) allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, fld)...)
return allErrs return allErrs
} }
@ -5927,9 +5921,9 @@ func ValidateResourceQuantityValue(resource string, value resource.Quantity, fld
} }
// ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make. // ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make.
func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota, opts ResourceQuotaValidationOptions) field.ErrorList { func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList {
allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, opts, field.NewPath("spec"))...) allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...)
// ensure scopes cannot change, and that resources are still valid for scope // ensure scopes cannot change, and that resources are still valid for scope
fldPath := field.NewPath("spec", "scopes") fldPath := field.NewPath("spec", "scopes")

View File

@ -4933,7 +4933,7 @@ func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.
Spec: spec, Spec: spec,
} }
if errs := ValidateResourceQuota(resourceQuota, ResourceQuotaValidationOptions{}); len(errs) != 0 { if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -16212,10 +16212,9 @@ func TestValidateResourceQuota(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
rq core.ResourceQuota rq core.ResourceQuota
errDetail string errDetail string
errField string errField string
disableNamespaceSelector bool
}{ }{
"no-scope": { "no-scope": {
rq: core.ResourceQuota{ rq: core.ResourceQuota{
@ -16333,17 +16332,10 @@ func TestValidateResourceQuota(t *testing.T) {
rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec}, rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec},
errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity", errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity",
}, },
"cross-namespace-affinity-disabled": {
rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: crossNamespaceAffinitySpec},
errDetail: "unsupported scope",
disableNamespaceSelector: true,
},
} }
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
errs := ValidateResourceQuota(&tc.rq, ResourceQuotaValidationOptions{ errs := ValidateResourceQuota(&tc.rq)
AllowPodAffinityNamespaceSelector: !tc.disableNamespaceSelector,
})
if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 { if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 { } else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {

View File

@ -614,6 +614,7 @@ const (
// owner: @ahg-g // owner: @ahg-g
// alpha: v1.21 // alpha: v1.21
// beta: v1.22 // beta: v1.22
// GA: v1.24
// //
// Allow specifying NamespaceSelector in PodAffinityTerm. // Allow specifying NamespaceSelector in PodAffinityTerm.
PodAffinityNamespaceSelector featuregate.Feature = "PodAffinityNamespaceSelector" PodAffinityNamespaceSelector featuregate.Feature = "PodAffinityNamespaceSelector"
@ -906,7 +907,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta}, PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},
StatefulSetAutoDeletePVC: {Default: false, PreRelease: featuregate.Alpha}, StatefulSetAutoDeletePVC: {Default: false, PreRelease: featuregate.Alpha},
TopologyAwareHints: {Default: false, PreRelease: featuregate.Beta}, TopologyAwareHints: {Default: false, PreRelease: featuregate.Beta},
PodAffinityNamespaceSelector: {Default: true, PreRelease: featuregate.Beta}, PodAffinityNamespaceSelector: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26
ServiceLoadBalancerClass: {Default: true, PreRelease: featuregate.Beta}, ServiceLoadBalancerClass: {Default: true, PreRelease: featuregate.Beta},
IngressClassNamespacedParams: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24 IngressClassNamespacedParams: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24
ServiceInternalTrafficPolicy: {Default: true, PreRelease: featuregate.Beta}, ServiceInternalTrafficPolicy: {Default: true, PreRelease: featuregate.Beta},

View File

@ -426,9 +426,6 @@ func crossNamespaceWeightedPodAffinityTerms(terms []corev1.WeightedPodAffinityTe
} }
func usesCrossNamespacePodAffinity(pod *corev1.Pod) bool { func usesCrossNamespacePodAffinity(pod *corev1.Pod) bool {
if !feature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector) {
return false
}
if pod == nil || pod.Spec.Affinity == nil { if pod == nil || pod.Spec.Affinity == nil {
return false return false
} }

View File

@ -576,10 +576,9 @@ func TestPodEvaluatorMatchingScopes(t *testing.T) {
evaluator := NewPodEvaluator(nil, fakeClock) evaluator := NewPodEvaluator(nil, fakeClock)
activeDeadlineSeconds := int64(30) activeDeadlineSeconds := int64(30)
testCases := map[string]struct { testCases := map[string]struct {
pod *api.Pod pod *api.Pod
selectors []corev1.ScopedResourceSelectorRequirement selectors []corev1.ScopedResourceSelectorRequirement
wantSelectors []corev1.ScopedResourceSelectorRequirement wantSelectors []corev1.ScopedResourceSelectorRequirement
disableNamespaceSelector bool
}{ }{
"EmptyPod": { "EmptyPod": {
pod: &api.Pod{}, pod: &api.Pod{},
@ -762,29 +761,9 @@ func TestPodEvaluatorMatchingScopes(t *testing.T) {
{ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity},
}, },
}, },
"NamespaceSelectorFeatureDisabled": {
pod: &api.Pod{
Spec: api.PodSpec{
ActiveDeadlineSeconds: &activeDeadlineSeconds,
Affinity: &api.Affinity{
PodAntiAffinity: &api.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}},
},
},
},
},
},
wantSelectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeTerminating},
{ScopeName: corev1.ResourceQuotaScopeBestEffort},
},
disableNamespaceSelector: true,
},
} }
for testName, testCase := range testCases { for testName, testCase := range testCases {
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, !testCase.disableNamespaceSelector)()
if testCase.selectors == nil { if testCase.selectors == nil {
testCase.selectors = []corev1.ScopedResourceSelectorRequirement{ testCase.selectors = []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeTerminating}, {ScopeName: corev1.ResourceQuotaScopeTerminating},

View File

@ -22,11 +22,9 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
@ -73,8 +71,7 @@ func (resourcequotaStrategy) PrepareForUpdate(ctx context.Context, obj, old runt
// Validate validates a new resourcequota. // Validate validates a new resourcequota.
func (resourcequotaStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (resourcequotaStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
resourcequota := obj.(*api.ResourceQuota) resourcequota := obj.(*api.ResourceQuota)
opts := getValidationOptionsFromResourceQuota(resourcequota, nil) return validation.ValidateResourceQuota(resourcequota)
return validation.ValidateResourceQuota(resourcequota, opts)
} }
// WarningsOnCreate returns warnings for the creation of the given object. // WarningsOnCreate returns warnings for the creation of the given object.
@ -94,8 +91,7 @@ func (resourcequotaStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (resourcequotaStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (resourcequotaStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newObj, oldObj := obj.(*api.ResourceQuota), old.(*api.ResourceQuota) newObj, oldObj := obj.(*api.ResourceQuota), old.(*api.ResourceQuota)
opts := getValidationOptionsFromResourceQuota(newObj, oldObj) return validation.ValidateResourceQuotaUpdate(newObj, oldObj)
return validation.ValidateResourceQuotaUpdate(newObj, oldObj, opts)
} }
// WarningsOnUpdate returns warnings for the given update. // WarningsOnUpdate returns warnings for the given update.
@ -140,37 +136,3 @@ func (resourcequotaStatusStrategy) ValidateUpdate(ctx context.Context, obj, old
func (resourcequotaStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { func (resourcequotaStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil return nil
} }
func getValidationOptionsFromResourceQuota(newObj *api.ResourceQuota, oldObj *api.ResourceQuota) validation.ResourceQuotaValidationOptions {
opts := validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: utilfeature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector),
}
if oldObj == nil {
return opts
}
opts.AllowPodAffinityNamespaceSelector = opts.AllowPodAffinityNamespaceSelector || hasCrossNamespacePodAffinityScope(&oldObj.Spec)
return opts
}
func hasCrossNamespacePodAffinityScope(spec *api.ResourceQuotaSpec) bool {
if spec == nil {
return false
}
for _, scope := range spec.Scopes {
if scope == api.ResourceQuotaScopeCrossNamespacePodAffinity {
return true
}
}
if spec.ScopeSelector == nil {
return false
}
for _, req := range spec.ScopeSelector.MatchExpressions {
if req.ScopeName == api.ResourceQuotaScopeCrossNamespacePodAffinity {
return true
}
}
return false
}

View File

@ -19,16 +19,10 @@ package resourcequota
import ( import (
"testing" "testing"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
) )
func TestResourceQuotaStrategy(t *testing.T) { func TestResourceQuotaStrategy(t *testing.T) {
@ -64,65 +58,3 @@ func TestResourceQuotaStrategy(t *testing.T) {
t.Errorf("ResourceQuota does not allow setting status on create") t.Errorf("ResourceQuota does not allow setting status on create")
} }
} }
func TestGetValidationOptionsFromResourceQuota(t *testing.T) {
crossNamespaceAffinity := api.ResourceQuota{Spec: api.ResourceQuotaSpec{
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeCrossNamespacePodAffinity},
},
}
for name, tc := range map[string]struct {
old *api.ResourceQuota
namespaceSelectorFeatureEnabled bool
wantOpts validation.ResourceQuotaValidationOptions
}{
"create-feature-enabled": {
namespaceSelectorFeatureEnabled: true,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: true,
},
},
"create-feature-disabled": {
namespaceSelectorFeatureEnabled: false,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: false,
},
},
"update-old-doesn't-include-scope-feature-enabled": {
old: &api.ResourceQuota{},
namespaceSelectorFeatureEnabled: true,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: true,
},
},
"update-old-doesn't-include-scope-feature-disabled": {
old: &api.ResourceQuota{},
namespaceSelectorFeatureEnabled: false,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: false,
},
},
"update-old-includes-scope-feature-disabled": {
old: &crossNamespaceAffinity,
namespaceSelectorFeatureEnabled: false,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: true,
},
},
"update-old-includes-scope-feature-enabled": {
old: &crossNamespaceAffinity,
namespaceSelectorFeatureEnabled: true,
wantOpts: validation.ResourceQuotaValidationOptions{
AllowPodAffinityNamespaceSelector: true,
},
},
} {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, tc.namespaceSelectorFeatureEnabled)()
gotOpts := getValidationOptionsFromResourceQuota(nil, tc.old)
if diff := cmp.Diff(tc.wantOpts, gotOpts); diff != "" {
t.Errorf("unexpected opts (-want, +got):\n%s", diff)
}
})
}
}

View File

@ -602,7 +602,7 @@ func TestDryRunPreemption(t *testing.T) {
name: "pod with anti-affinity is preempted", name: "pod with anti-affinity is preempted",
registerPlugins: []st.RegisterPluginFunc{ registerPlugins: []st.RegisterPluginFunc{
st.RegisterPluginAsExtensions(noderesources.Name, nodeResourcesFitFunc, "Filter", "PreFilter"), st.RegisterPluginAsExtensions(noderesources.Name, nodeResourcesFitFunc, "Filter", "PreFilter"),
st.RegisterPluginAsExtensions(interpodaffinity.Name, frameworkruntime.FactoryAdapter(feature.Features{}, interpodaffinity.New), "Filter", "PreFilter"), st.RegisterPluginAsExtensions(interpodaffinity.Name, interpodaffinity.New, "Filter", "PreFilter"),
}, },
nodeNames: []string{"node1", "node2"}, nodeNames: []string{"node1", "node2"},
testPods: []*v1.Pod{ testPods: []*v1.Pod{

View File

@ -51,8 +51,7 @@ type preFilterState struct {
// podInfo of the incoming pod. // podInfo of the incoming pod.
podInfo *framework.PodInfo podInfo *framework.PodInfo
// A copy of the incoming pod's namespace labels. // A copy of the incoming pod's namespace labels.
namespaceLabels labels.Set namespaceLabels labels.Set
enableNamespaceSelector bool
} }
// Clone the prefilter state. // Clone the prefilter state.
@ -68,7 +67,6 @@ func (s *preFilterState) Clone() framework.StateData {
// No need to deep copy the podInfo because it shouldn't change. // No need to deep copy the podInfo because it shouldn't change.
copy.podInfo = s.podInfo copy.podInfo = s.podInfo
copy.namespaceLabels = s.namespaceLabels copy.namespaceLabels = s.namespaceLabels
copy.enableNamespaceSelector = s.enableNamespaceSelector
return &copy return &copy
} }
@ -78,11 +76,11 @@ func (s *preFilterState) updateWithPod(pInfo *framework.PodInfo, node *v1.Node,
return return
} }
s.existingAntiAffinityCounts.updateWithAntiAffinityTerms(pInfo.RequiredAntiAffinityTerms, s.podInfo.Pod, s.namespaceLabels, node, multiplier, s.enableNamespaceSelector) s.existingAntiAffinityCounts.updateWithAntiAffinityTerms(pInfo.RequiredAntiAffinityTerms, s.podInfo.Pod, s.namespaceLabels, node, multiplier)
s.affinityCounts.updateWithAffinityTerms(s.podInfo.RequiredAffinityTerms, pInfo.Pod, node, multiplier, s.enableNamespaceSelector) s.affinityCounts.updateWithAffinityTerms(s.podInfo.RequiredAffinityTerms, pInfo.Pod, node, multiplier)
// The incoming pod's terms have the namespaceSelector merged into the namespaces, and so // The incoming pod's terms have the namespaceSelector merged into the namespaces, and so
// here we don't lookup the updated pod's namespace labels, hence passing nil for nsLabels. // here we don't lookup the updated pod's namespace labels, hence passing nil for nsLabels.
s.antiAffinityCounts.updateWithAntiAffinityTerms(s.podInfo.RequiredAntiAffinityTerms, pInfo.Pod, nil, node, multiplier, s.enableNamespaceSelector) s.antiAffinityCounts.updateWithAntiAffinityTerms(s.podInfo.RequiredAntiAffinityTerms, pInfo.Pod, nil, node, multiplier)
} }
type topologyPair struct { type topologyPair struct {
@ -117,8 +115,8 @@ func (m topologyToMatchedTermCount) update(node *v1.Node, tk string, value int64
// updates the topologyToMatchedTermCount map with the specified value // updates the topologyToMatchedTermCount map with the specified value
// for each affinity term if "targetPod" matches ALL terms. // for each affinity term if "targetPod" matches ALL terms.
func (m topologyToMatchedTermCount) updateWithAffinityTerms( func (m topologyToMatchedTermCount) updateWithAffinityTerms(
terms []framework.AffinityTerm, pod *v1.Pod, node *v1.Node, value int64, enableNamespaceSelector bool) { terms []framework.AffinityTerm, pod *v1.Pod, node *v1.Node, value int64) {
if podMatchesAllAffinityTerms(terms, pod, enableNamespaceSelector) { if podMatchesAllAffinityTerms(terms, pod) {
for _, t := range terms { for _, t := range terms {
m.update(node, t.TopologyKey, value) m.update(node, t.TopologyKey, value)
} }
@ -127,24 +125,24 @@ func (m topologyToMatchedTermCount) updateWithAffinityTerms(
// updates the topologyToMatchedTermCount map with the specified value // updates the topologyToMatchedTermCount map with the specified value
// for each anti-affinity term matched the target pod. // for each anti-affinity term matched the target pod.
func (m topologyToMatchedTermCount) updateWithAntiAffinityTerms(terms []framework.AffinityTerm, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, value int64, enableNamespaceSelector bool) { func (m topologyToMatchedTermCount) updateWithAntiAffinityTerms(terms []framework.AffinityTerm, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, value int64) {
// Check anti-affinity terms. // Check anti-affinity terms.
for _, t := range terms { for _, t := range terms {
if t.Matches(pod, nsLabels, enableNamespaceSelector) { if t.Matches(pod, nsLabels) {
m.update(node, t.TopologyKey, value) m.update(node, t.TopologyKey, value)
} }
} }
} }
// returns true IFF the given pod matches all the given terms. // returns true IFF the given pod matches all the given terms.
func podMatchesAllAffinityTerms(terms []framework.AffinityTerm, pod *v1.Pod, enableNamespaceSelector bool) bool { func podMatchesAllAffinityTerms(terms []framework.AffinityTerm, pod *v1.Pod) bool {
if len(terms) == 0 { if len(terms) == 0 {
return false return false
} }
for _, t := range terms { for _, t := range terms {
// The incoming pod NamespaceSelector was merged into the Namespaces set, and so // The incoming pod NamespaceSelector was merged into the Namespaces set, and so
// we are not explicitly passing in namespace labels. // we are not explicitly passing in namespace labels.
if !t.Matches(pod, nil, enableNamespaceSelector) { if !t.Matches(pod, nil) {
return false return false
} }
} }
@ -154,7 +152,7 @@ func podMatchesAllAffinityTerms(terms []framework.AffinityTerm, pod *v1.Pod, ena
// calculates the following for each existing pod on each node: // calculates the following for each existing pod on each node:
// (1) Whether it has PodAntiAffinity // (1) Whether it has PodAntiAffinity
// (2) Whether any AffinityTerm matches the incoming pod // (2) Whether any AffinityTerm matches the incoming pod
func (pl *InterPodAffinity) getExistingAntiAffinityCounts(pod *v1.Pod, nsLabels labels.Set, nodes []*framework.NodeInfo, enableNamespaceSelector bool) topologyToMatchedTermCount { func (pl *InterPodAffinity) getExistingAntiAffinityCounts(pod *v1.Pod, nsLabels labels.Set, nodes []*framework.NodeInfo) topologyToMatchedTermCount {
topoMaps := make([]topologyToMatchedTermCount, len(nodes)) topoMaps := make([]topologyToMatchedTermCount, len(nodes))
index := int32(-1) index := int32(-1)
processNode := func(i int) { processNode := func(i int) {
@ -166,7 +164,7 @@ func (pl *InterPodAffinity) getExistingAntiAffinityCounts(pod *v1.Pod, nsLabels
} }
topoMap := make(topologyToMatchedTermCount) topoMap := make(topologyToMatchedTermCount)
for _, existingPod := range nodeInfo.PodsWithRequiredAntiAffinity { for _, existingPod := range nodeInfo.PodsWithRequiredAntiAffinity {
topoMap.updateWithAntiAffinityTerms(existingPod.RequiredAntiAffinityTerms, pod, nsLabels, node, 1, enableNamespaceSelector) topoMap.updateWithAntiAffinityTerms(existingPod.RequiredAntiAffinityTerms, pod, nsLabels, node, 1)
} }
if len(topoMap) != 0 { if len(topoMap) != 0 {
topoMaps[atomic.AddInt32(&index, 1)] = topoMap topoMaps[atomic.AddInt32(&index, 1)] = topoMap
@ -186,7 +184,7 @@ func (pl *InterPodAffinity) getExistingAntiAffinityCounts(pod *v1.Pod, nsLabels
// It returns a topologyToMatchedTermCount that are checked later by the affinity // It returns a topologyToMatchedTermCount that are checked later by the affinity
// predicate. With this topologyToMatchedTermCount available, the affinity predicate does not // predicate. With this topologyToMatchedTermCount available, the affinity predicate does not
// need to check all the pods in the cluster. // need to check all the pods in the cluster.
func (pl *InterPodAffinity) getIncomingAffinityAntiAffinityCounts(podInfo *framework.PodInfo, allNodes []*framework.NodeInfo, enableNamespaceSelector bool) (topologyToMatchedTermCount, topologyToMatchedTermCount) { func (pl *InterPodAffinity) getIncomingAffinityAntiAffinityCounts(podInfo *framework.PodInfo, allNodes []*framework.NodeInfo) (topologyToMatchedTermCount, topologyToMatchedTermCount) {
affinityCounts := make(topologyToMatchedTermCount) affinityCounts := make(topologyToMatchedTermCount)
antiAffinityCounts := make(topologyToMatchedTermCount) antiAffinityCounts := make(topologyToMatchedTermCount)
if len(podInfo.RequiredAffinityTerms) == 0 && len(podInfo.RequiredAntiAffinityTerms) == 0 { if len(podInfo.RequiredAffinityTerms) == 0 && len(podInfo.RequiredAntiAffinityTerms) == 0 {
@ -206,10 +204,10 @@ func (pl *InterPodAffinity) getIncomingAffinityAntiAffinityCounts(podInfo *frame
affinity := make(topologyToMatchedTermCount) affinity := make(topologyToMatchedTermCount)
antiAffinity := make(topologyToMatchedTermCount) antiAffinity := make(topologyToMatchedTermCount)
for _, existingPod := range nodeInfo.Pods { for _, existingPod := range nodeInfo.Pods {
affinity.updateWithAffinityTerms(podInfo.RequiredAffinityTerms, existingPod.Pod, node, 1, enableNamespaceSelector) affinity.updateWithAffinityTerms(podInfo.RequiredAffinityTerms, existingPod.Pod, node, 1)
// The incoming pod's terms have the namespaceSelector merged into the namespaces, and so // The incoming pod's terms have the namespaceSelector merged into the namespaces, and so
// here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels. // here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels.
antiAffinity.updateWithAntiAffinityTerms(podInfo.RequiredAntiAffinityTerms, existingPod.Pod, nil, node, 1, enableNamespaceSelector) antiAffinity.updateWithAntiAffinityTerms(podInfo.RequiredAntiAffinityTerms, existingPod.Pod, nil, node, 1)
} }
if len(affinity) > 0 || len(antiAffinity) > 0 { if len(affinity) > 0 || len(antiAffinity) > 0 {
@ -240,31 +238,27 @@ func (pl *InterPodAffinity) PreFilter(ctx context.Context, cycleState *framework
return framework.AsStatus(fmt.Errorf("failed to list NodeInfos with pods with affinity: %w", err)) return framework.AsStatus(fmt.Errorf("failed to list NodeInfos with pods with affinity: %w", err))
} }
s := &preFilterState{ s := &preFilterState{}
enableNamespaceSelector: pl.enableNamespaceSelector,
}
s.podInfo = framework.NewPodInfo(pod) s.podInfo = framework.NewPodInfo(pod)
if s.podInfo.ParseError != nil { if s.podInfo.ParseError != nil {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, fmt.Sprintf("parsing pod: %+v", s.podInfo.ParseError)) return framework.NewStatus(framework.UnschedulableAndUnresolvable, fmt.Sprintf("parsing pod: %+v", s.podInfo.ParseError))
} }
if pl.enableNamespaceSelector { for i := range s.podInfo.RequiredAffinityTerms {
for i := range s.podInfo.RequiredAffinityTerms { if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAffinityTerms[i]); err != nil {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAffinityTerms[i]); err != nil { return framework.AsStatus(err)
return framework.AsStatus(err)
}
} }
for i := range s.podInfo.RequiredAntiAffinityTerms {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAntiAffinityTerms[i]); err != nil {
return framework.AsStatus(err)
}
}
s.namespaceLabels = GetNamespaceLabelsSnapshot(pod.Namespace, pl.nsLister)
} }
for i := range s.podInfo.RequiredAntiAffinityTerms {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAntiAffinityTerms[i]); err != nil {
return framework.AsStatus(err)
}
}
s.namespaceLabels = GetNamespaceLabelsSnapshot(pod.Namespace, pl.nsLister)
s.existingAntiAffinityCounts = pl.getExistingAntiAffinityCounts(pod, s.namespaceLabels, nodesWithRequiredAntiAffinityPods, pl.enableNamespaceSelector) s.existingAntiAffinityCounts = pl.getExistingAntiAffinityCounts(pod, s.namespaceLabels, nodesWithRequiredAntiAffinityPods)
s.affinityCounts, s.antiAffinityCounts = pl.getIncomingAffinityAntiAffinityCounts(s.podInfo, allNodes, pl.enableNamespaceSelector) s.affinityCounts, s.antiAffinityCounts = pl.getIncomingAffinityAntiAffinityCounts(s.podInfo, allNodes)
cycleState.Write(preFilterStateKey, s) cycleState.Write(preFilterStateKey, s)
return nil return nil
@ -361,7 +355,7 @@ func satisfyPodAffinity(state *preFilterState, nodeInfo *framework.NodeInfo) boo
// in the cluster matches the namespace and selector of this pod, the pod matches // in the cluster matches the namespace and selector of this pod, the pod matches
// its own terms, and the node has all the requested topologies, then we allow the pod // its own terms, and the node has all the requested topologies, then we allow the pod
// to pass the affinity check. // to pass the affinity check.
if len(state.affinityCounts) == 0 && podMatchesAllAffinityTerms(state.podInfo.RequiredAffinityTerms, state.podInfo.Pod, state.enableNamespaceSelector) { if len(state.affinityCounts) == 0 && podMatchesAllAffinityTerms(state.podInfo.RequiredAffinityTerms, state.podInfo.Pod) {
return true return true
} }
return false return false

View File

@ -28,9 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing" plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
"k8s.io/kubernetes/pkg/scheduler/internal/cache" "k8s.io/kubernetes/pkg/scheduler/internal/cache"
) )
@ -68,12 +66,11 @@ func TestRequiredAffinitySingleNode(t *testing.T) {
podLabel2 := map[string]string{"security": "S1"} podLabel2 := map[string]string{"security": "S1"}
node1 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labels1}} node1 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labels1}}
tests := []struct { tests := []struct {
pod *v1.Pod pod *v1.Pod
pods []*v1.Pod pods []*v1.Pod
node *v1.Node node *v1.Node
name string name string
wantStatus *framework.Status wantStatus *framework.Status
disableNSSelector bool
}{ }{
{ {
name: "A pod that has no required pod affinity scheduling rules can schedule onto a node with no existing pods", name: "A pod that has no required pod affinity scheduling rules can schedule onto a node with no existing pods",
@ -998,8 +995,7 @@ func TestRequiredAffinitySingleNode(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
snapshot := cache.NewSnapshot(test.pods, []*v1.Node{test.node}) snapshot := cache.NewSnapshot(test.pods, []*v1.Node{test.node})
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector} p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, namespaces)
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{}, snapshot, namespaces)
state := framework.NewCycleState() state := framework.NewCycleState()
preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod) preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod)
if !preFilterStatus.IsSuccess() { if !preFilterStatus.IsSuccess() {
@ -1864,7 +1860,7 @@ func TestRequiredAffinityMultipleNodes(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
snapshot := cache.NewSnapshot(test.pods, test.nodes) snapshot := cache.NewSnapshot(test.pods, test.nodes)
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot, p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot,
[]runtime.Object{ []runtime.Object{
&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "NS1"}}, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "NS1"}},
}) })
@ -1889,7 +1885,9 @@ func TestPreFilterDisabled(t *testing.T) {
nodeInfo := framework.NewNodeInfo() nodeInfo := framework.NewNodeInfo()
node := v1.Node{} node := v1.Node{}
nodeInfo.SetNode(&node) nodeInfo.SetNode(&node)
p := plugintesting.SetupPlugin(t, frameworkruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, cache.NewEmptySnapshot()) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, cache.NewEmptySnapshot(), nil)
cycleState := framework.NewCycleState() cycleState := framework.NewCycleState()
gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), cycleState, pod, nodeInfo) gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), cycleState, pod, nodeInfo)
wantStatus := framework.AsStatus(fmt.Errorf(`error reading "PreFilterInterPodAffinity" from cycleState: %w`, framework.ErrNotFound)) wantStatus := framework.AsStatus(fmt.Errorf(`error reading "PreFilterInterPodAffinity" from cycleState: %w`, framework.ErrNotFound))
@ -2153,11 +2151,12 @@ func TestPreFilterStateAddRemovePod(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
// getMeta creates predicate meta data given the list of pods. // getMeta creates predicate meta data given the list of pods.
getState := func(pods []*v1.Pod) (*InterPodAffinity, *framework.CycleState, *preFilterState, *cache.Snapshot) { getState := func(pods []*v1.Pod) (*InterPodAffinity, *framework.CycleState, *preFilterState, *cache.Snapshot) {
snapshot := cache.NewSnapshot(pods, test.nodes) snapshot := cache.NewSnapshot(pods, test.nodes)
p := plugintesting.SetupPlugin(t, frameworkruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, nil)
cycleState := framework.NewCycleState() cycleState := framework.NewCycleState()
preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pendingPod) preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pendingPod)
if !preFilterStatus.IsSuccess() { if !preFilterStatus.IsSuccess() {
@ -2172,6 +2171,7 @@ func TestPreFilterStateAddRemovePod(t *testing.T) {
return p.(*InterPodAffinity), cycleState, state, snapshot return p.(*InterPodAffinity), cycleState, state, snapshot
} }
ctx := context.Background()
// allPodsState is the state produced when all pods, including test.addedPod are given to prefilter. // allPodsState is the state produced when all pods, including test.addedPod are given to prefilter.
_, _, allPodsState, _ := getState(append(test.existingPods, test.addedPod)) _, _, allPodsState, _ := getState(append(test.existingPods, test.addedPod))
@ -2440,8 +2440,10 @@ func TestGetTPMapMatchingIncomingAffinityAntiAffinity(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
snapshot := cache.NewSnapshot(tt.existingPods, tt.nodes) snapshot := cache.NewSnapshot(tt.existingPods, tt.nodes)
l, _ := snapshot.NodeInfos().List() l, _ := snapshot.NodeInfos().List()
p := plugintesting.SetupPlugin(t, frameworkruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot) ctx, cancel := context.WithCancel(context.Background())
gotAffinityPodsMap, gotAntiAffinityPodsMap := p.(*InterPodAffinity).getIncomingAffinityAntiAffinityCounts(framework.NewPodInfo(tt.pod), l, true) defer cancel()
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, nil)
gotAffinityPodsMap, gotAntiAffinityPodsMap := p.(*InterPodAffinity).getIncomingAffinityAntiAffinityCounts(framework.NewPodInfo(tt.pod), l)
if !reflect.DeepEqual(gotAffinityPodsMap, tt.wantAffinityPodsMap) { if !reflect.DeepEqual(gotAffinityPodsMap, tt.wantAffinityPodsMap) {
t.Errorf("getTPMapMatchingIncomingAffinityAntiAffinity() gotAffinityPodsMap = %#v, want %#v", gotAffinityPodsMap, tt.wantAffinityPodsMap) t.Errorf("getTPMapMatchingIncomingAffinityAntiAffinity() gotAffinityPodsMap = %#v, want %#v", gotAffinityPodsMap, tt.wantAffinityPodsMap)
} }

View File

@ -27,7 +27,6 @@ import (
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation" "k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
"k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/parallelize" "k8s.io/kubernetes/pkg/scheduler/framework/parallelize"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
) )
@ -44,11 +43,10 @@ var _ framework.EnqueueExtensions = &InterPodAffinity{}
// InterPodAffinity is a plugin that checks inter pod affinity // InterPodAffinity is a plugin that checks inter pod affinity
type InterPodAffinity struct { type InterPodAffinity struct {
parallelizer parallelize.Parallelizer parallelizer parallelize.Parallelizer
args config.InterPodAffinityArgs args config.InterPodAffinityArgs
sharedLister framework.SharedLister sharedLister framework.SharedLister
nsLister listersv1.NamespaceLister nsLister listersv1.NamespaceLister
enableNamespaceSelector bool
} }
// Name returns name of the plugin. It is used in logs, etc. // Name returns name of the plugin. It is used in logs, etc.
@ -73,7 +71,7 @@ func (pl *InterPodAffinity) EventsToRegister() []framework.ClusterEvent {
} }
// New initializes a new plugin and returns it. // New initializes a new plugin and returns it.
func New(plArgs runtime.Object, h framework.Handle, fts feature.Features) (framework.Plugin, error) { func New(plArgs runtime.Object, h framework.Handle) (framework.Plugin, error) {
if h.SnapshotSharedLister() == nil { if h.SnapshotSharedLister() == nil {
return nil, fmt.Errorf("SnapshotSharedlister is nil") return nil, fmt.Errorf("SnapshotSharedlister is nil")
} }
@ -85,15 +83,12 @@ func New(plArgs runtime.Object, h framework.Handle, fts feature.Features) (frame
return nil, err return nil, err
} }
pl := &InterPodAffinity{ pl := &InterPodAffinity{
parallelizer: h.Parallelizer(), parallelizer: h.Parallelizer(),
args: args, args: args,
sharedLister: h.SnapshotSharedLister(), sharedLister: h.SnapshotSharedLister(),
enableNamespaceSelector: fts.EnablePodAffinityNamespaceSelector, nsLister: h.SharedInformerFactory().Core().V1().Namespaces().Lister(),
} }
if pl.enableNamespaceSelector {
pl.nsLister = h.SharedInformerFactory().Core().V1().Namespaces().Lister()
}
return pl, nil return pl, nil
} }

View File

@ -46,8 +46,8 @@ func (s *preScoreState) Clone() framework.StateData {
return s return s
} }
func (m scoreMap) processTerm(term *framework.AffinityTerm, weight int32, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, multiplier int32, enableNamespaceSelector bool) { func (m scoreMap) processTerm(term *framework.AffinityTerm, weight int32, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, multiplier int32) {
if term.Matches(pod, nsLabels, enableNamespaceSelector) { if term.Matches(pod, nsLabels) {
if tpValue, tpValueExist := node.Labels[term.TopologyKey]; tpValueExist { if tpValue, tpValueExist := node.Labels[term.TopologyKey]; tpValueExist {
if m[term.TopologyKey] == nil { if m[term.TopologyKey] == nil {
m[term.TopologyKey] = make(map[string]int64) m[term.TopologyKey] = make(map[string]int64)
@ -57,9 +57,9 @@ func (m scoreMap) processTerm(term *framework.AffinityTerm, weight int32, pod *v
} }
} }
func (m scoreMap) processTerms(terms []framework.WeightedAffinityTerm, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, multiplier int32, enableNamespaceSelector bool) { func (m scoreMap) processTerms(terms []framework.WeightedAffinityTerm, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, multiplier int32) {
for _, term := range terms { for _, term := range terms {
m.processTerm(&term.AffinityTerm, term.Weight, pod, nsLabels, node, multiplier, enableNamespaceSelector) m.processTerm(&term.AffinityTerm, term.Weight, pod, nsLabels, node, multiplier)
} }
} }
@ -93,33 +93,33 @@ func (pl *InterPodAffinity) processExistingPod(
// value as that of <existingPods>`s node by the term`s weight. // value as that of <existingPods>`s node by the term`s weight.
// Note that the incoming pod's terms have the namespaceSelector merged into the namespaces, and so // Note that the incoming pod's terms have the namespaceSelector merged into the namespaces, and so
// here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels. // here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels.
topoScore.processTerms(state.podInfo.PreferredAffinityTerms, existingPod.Pod, nil, existingPodNode, 1, pl.enableNamespaceSelector) topoScore.processTerms(state.podInfo.PreferredAffinityTerms, existingPod.Pod, nil, existingPodNode, 1)
// For every soft pod anti-affinity term of <pod>, if <existingPod> matches the term, // For every soft pod anti-affinity term of <pod>, if <existingPod> matches the term,
// decrement <p.counts> for every node in the cluster with the same <term.TopologyKey> // decrement <p.counts> for every node in the cluster with the same <term.TopologyKey>
// value as that of <existingPod>`s node by the term`s weight. // value as that of <existingPod>`s node by the term`s weight.
// Note that the incoming pod's terms have the namespaceSelector merged into the namespaces, and so // Note that the incoming pod's terms have the namespaceSelector merged into the namespaces, and so
// here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels. // here we don't lookup the existing pod's namespace labels, hence passing nil for nsLabels.
topoScore.processTerms(state.podInfo.PreferredAntiAffinityTerms, existingPod.Pod, nil, existingPodNode, -1, pl.enableNamespaceSelector) topoScore.processTerms(state.podInfo.PreferredAntiAffinityTerms, existingPod.Pod, nil, existingPodNode, -1)
// For every hard pod affinity term of <existingPod>, if <pod> matches the term, // For every hard pod affinity term of <existingPod>, if <pod> matches the term,
// increment <p.counts> for every node in the cluster with the same <term.TopologyKey> // increment <p.counts> for every node in the cluster with the same <term.TopologyKey>
// value as that of <existingPod>'s node by the constant <args.hardPodAffinityWeight> // value as that of <existingPod>'s node by the constant <args.hardPodAffinityWeight>
if pl.args.HardPodAffinityWeight > 0 && len(existingPodNode.Labels) != 0 { if pl.args.HardPodAffinityWeight > 0 && len(existingPodNode.Labels) != 0 {
for _, t := range existingPod.RequiredAffinityTerms { for _, t := range existingPod.RequiredAffinityTerms {
topoScore.processTerm(&t, pl.args.HardPodAffinityWeight, incomingPod, state.namespaceLabels, existingPodNode, 1, pl.enableNamespaceSelector) topoScore.processTerm(&t, pl.args.HardPodAffinityWeight, incomingPod, state.namespaceLabels, existingPodNode, 1)
} }
} }
// For every soft pod affinity term of <existingPod>, if <pod> matches the term, // For every soft pod affinity term of <existingPod>, if <pod> matches the term,
// increment <p.counts> for every node in the cluster with the same <term.TopologyKey> // increment <p.counts> for every node in the cluster with the same <term.TopologyKey>
// value as that of <existingPod>'s node by the term's weight. // value as that of <existingPod>'s node by the term's weight.
topoScore.processTerms(existingPod.PreferredAffinityTerms, incomingPod, state.namespaceLabels, existingPodNode, 1, pl.enableNamespaceSelector) topoScore.processTerms(existingPod.PreferredAffinityTerms, incomingPod, state.namespaceLabels, existingPodNode, 1)
// For every soft pod anti-affinity term of <existingPod>, if <pod> matches the term, // For every soft pod anti-affinity term of <existingPod>, if <pod> matches the term,
// decrement <pm.counts> for every node in the cluster with the same <term.TopologyKey> // decrement <pm.counts> for every node in the cluster with the same <term.TopologyKey>
// value as that of <existingPod>'s node by the term's weight. // value as that of <existingPod>'s node by the term's weight.
topoScore.processTerms(existingPod.PreferredAntiAffinityTerms, incomingPod, state.namespaceLabels, existingPodNode, -1, pl.enableNamespaceSelector) topoScore.processTerms(existingPod.PreferredAntiAffinityTerms, incomingPod, state.namespaceLabels, existingPodNode, -1)
} }
// PreScore builds and writes cycle state used by Score and NormalizeScore. // PreScore builds and writes cycle state used by Score and NormalizeScore.
@ -168,19 +168,17 @@ func (pl *InterPodAffinity) PreScore(
return framework.AsStatus(fmt.Errorf("failed to parse pod: %w", state.podInfo.ParseError)) return framework.AsStatus(fmt.Errorf("failed to parse pod: %w", state.podInfo.ParseError))
} }
if pl.enableNamespaceSelector { for i := range state.podInfo.PreferredAffinityTerms {
for i := range state.podInfo.PreferredAffinityTerms { if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAffinityTerms[i].AffinityTerm); err != nil {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAffinityTerms[i].AffinityTerm); err != nil { return framework.AsStatus(fmt.Errorf("updating PreferredAffinityTerms: %w", err))
return framework.AsStatus(fmt.Errorf("updating PreferredAffinityTerms: %w", err))
}
} }
for i := range state.podInfo.PreferredAntiAffinityTerms {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAntiAffinityTerms[i].AffinityTerm); err != nil {
return framework.AsStatus(fmt.Errorf("updating PreferredAntiAffinityTerms: %w", err))
}
}
state.namespaceLabels = GetNamespaceLabelsSnapshot(pod.Namespace, pl.nsLister)
} }
for i := range state.podInfo.PreferredAntiAffinityTerms {
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAntiAffinityTerms[i].AffinityTerm); err != nil {
return framework.AsStatus(fmt.Errorf("updating PreferredAntiAffinityTerms: %w", err))
}
}
state.namespaceLabels = GetNamespaceLabelsSnapshot(pod.Namespace, pl.nsLister)
topoScores := make([]scoreMap, len(allNodes)) topoScores := make([]scoreMap, len(allNodes))
index := int32(-1) index := int32(-1)

View File

@ -27,9 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing" plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
"k8s.io/kubernetes/pkg/scheduler/internal/cache" "k8s.io/kubernetes/pkg/scheduler/internal/cache"
) )
@ -371,13 +369,12 @@ func TestPreferredAffinity(t *testing.T) {
} }
tests := []struct { tests := []struct {
pod *v1.Pod pod *v1.Pod
pods []*v1.Pod pods []*v1.Pod
nodes []*v1.Node nodes []*v1.Node
expectedList framework.NodeScoreList expectedList framework.NodeScoreList
name string name string
wantStatus *framework.Status wantStatus *framework.Status
disableNSSelector bool
}{ }{
{ {
name: "all machines are same priority as Affinity is nil", name: "all machines are same priority as Affinity is nil",
@ -745,8 +742,7 @@ func TestPreferredAffinity(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
state := framework.NewCycleState() state := framework.NewCycleState()
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector} p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: 1}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{HardPodAffinityWeight: 1}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes) status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes)
if !status.IsSuccess() { if !status.IsSuccess() {
if !strings.Contains(status.Message(), test.wantStatus.Message()) { if !strings.Contains(status.Message(), test.wantStatus.Message()) {
@ -825,7 +821,6 @@ func TestPreferredAffinityWithHardPodAffinitySymmetricWeight(t *testing.T) {
hardPodAffinityWeight int32 hardPodAffinityWeight int32
expectedList framework.NodeScoreList expectedList framework.NodeScoreList
name string name string
disableNSSelector bool
}{ }{
{ {
name: "with default weight", name: "with default weight",
@ -908,8 +903,7 @@ func TestPreferredAffinityWithHardPodAffinitySymmetricWeight(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
state := framework.NewCycleState() state := framework.NewCycleState()
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector} p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: test.hardPodAffinityWeight}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{HardPodAffinityWeight: test.hardPodAffinityWeight}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes) status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes)
if !status.IsSuccess() { if !status.IsSuccess() {
t.Errorf("unexpected error: %v", status) t.Errorf("unexpected error: %v", status)

View File

@ -45,12 +45,11 @@ import (
// through the WithFrameworkOutOfTreeRegistry option. // through the WithFrameworkOutOfTreeRegistry option.
func NewInTreeRegistry() runtime.Registry { func NewInTreeRegistry() runtime.Registry {
fts := plfeature.Features{ fts := plfeature.Features{
EnablePodAffinityNamespaceSelector: feature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector), EnablePodDisruptionBudget: feature.DefaultFeatureGate.Enabled(features.PodDisruptionBudget),
EnablePodDisruptionBudget: feature.DefaultFeatureGate.Enabled(features.PodDisruptionBudget), EnablePodOverhead: feature.DefaultFeatureGate.Enabled(features.PodOverhead),
EnablePodOverhead: feature.DefaultFeatureGate.Enabled(features.PodOverhead), EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod), EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority), EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity),
EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity),
} }
return runtime.Registry{ return runtime.Registry{
@ -72,7 +71,7 @@ func NewInTreeRegistry() runtime.Registry {
nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD), nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD),
nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk), nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk),
nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder), nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder),
interpodaffinity.Name: runtime.FactoryAdapter(fts, interpodaffinity.New), interpodaffinity.Name: interpodaffinity.New,
queuesort.Name: queuesort.New, queuesort.Name: queuesort.New,
defaultbinder.Name: defaultbinder.New, defaultbinder.Name: defaultbinder.New,
defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New), defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New),

View File

@ -198,8 +198,8 @@ type AffinityTerm struct {
} }
// Matches returns true if the pod matches the label selector and namespaces or namespace selector. // Matches returns true if the pod matches the label selector and namespaces or namespace selector.
func (at *AffinityTerm) Matches(pod *v1.Pod, nsLabels labels.Set, nsSelectorEnabled bool) bool { func (at *AffinityTerm) Matches(pod *v1.Pod, nsLabels labels.Set) bool {
if at.Namespaces.Has(pod.Namespace) || (nsSelectorEnabled && at.NamespaceSelector.Matches(nsLabels)) { if at.Namespaces.Has(pod.Namespace) || at.NamespaceSelector.Matches(nsLabels) {
return at.Selector.Matches(labels.Set(pod.Labels)) return at.Selector.Matches(labels.Set(pod.Labels))
} }
return false return false

View File

@ -34,12 +34,10 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
listersv1 "k8s.io/client-go/listers/core/v1" listersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
"k8s.io/kubernetes/pkg/scheduler/internal/heap" "k8s.io/kubernetes/pkg/scheduler/internal/heap"
@ -280,9 +278,7 @@ func NewPriorityQueue(
} }
pq.cond.L = &pq.lock pq.cond.L = &pq.lock
pq.podBackoffQ = heap.NewWithRecorder(podInfoKeyFunc, pq.podsCompareBackoffCompleted, metrics.NewBackoffPodsRecorder()) pq.podBackoffQ = heap.NewWithRecorder(podInfoKeyFunc, pq.podsCompareBackoffCompleted, metrics.NewBackoffPodsRecorder())
if utilfeature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector) { pq.nsLister = informerFactory.Core().V1().Namespaces().Lister()
pq.nsLister = informerFactory.Core().V1().Namespaces().Lister()
}
return pq return pq
} }
@ -656,15 +652,13 @@ func (p *PriorityQueue) movePodsToActiveOrBackoffQueue(podInfoList []*framework.
// any affinity term that matches "pod". // any affinity term that matches "pod".
// NOTE: this function assumes lock has been acquired in caller. // NOTE: this function assumes lock has been acquired in caller.
func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod) []*framework.QueuedPodInfo { func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod) []*framework.QueuedPodInfo {
nsSelectorEnabled := p.nsLister != nil
var nsLabels labels.Set var nsLabels labels.Set
if nsSelectorEnabled { nsLabels = interpodaffinity.GetNamespaceLabelsSnapshot(pod.Namespace, p.nsLister)
nsLabels = interpodaffinity.GetNamespaceLabelsSnapshot(pod.Namespace, p.nsLister)
}
var podsToMove []*framework.QueuedPodInfo var podsToMove []*framework.QueuedPodInfo
for _, pInfo := range p.unschedulableQ.podInfoMap { for _, pInfo := range p.unschedulableQ.podInfoMap {
for _, term := range pInfo.RequiredAffinityTerms { for _, term := range pInfo.RequiredAffinityTerms {
if term.Matches(pod, nsLabels, nsSelectorEnabled) { if term.Matches(pod, nsLabels) {
podsToMove = append(podsToMove, pInfo) podsToMove = append(podsToMove, pInfo)
break break
} }

View File

@ -3060,7 +3060,7 @@ message PodAffinityTerm {
// namespaces specifies a static list of namespace names that the term applies to. // namespaces specifies a static list of namespace names that the term applies to.
// The term is applied to the union of the namespaces listed in this field // The term is applied to the union of the namespaces listed in this field
// and the ones selected by namespaceSelector. // and the ones selected by namespaceSelector.
// null or empty namespaces list and null namespaceSelector means "this pod's namespace" // null or empty namespaces list and null namespaceSelector means "this pod's namespace".
// +optional // +optional
repeated string namespaces = 2; repeated string namespaces = 2;
@ -3076,7 +3076,6 @@ message PodAffinityTerm {
// and the ones listed in the namespaces field. // and the ones listed in the namespaces field.
// null selector and null or empty namespaces list means "this pod's namespace". // null selector and null or empty namespaces list means "this pod's namespace".
// An empty selector ({}) matches all namespaces. // An empty selector ({}) matches all namespaces.
// This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.
// +optional // +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 4; optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 4;
} }

View File

@ -2904,7 +2904,7 @@ type PodAffinityTerm struct {
// namespaces specifies a static list of namespace names that the term applies to. // namespaces specifies a static list of namespace names that the term applies to.
// The term is applied to the union of the namespaces listed in this field // The term is applied to the union of the namespaces listed in this field
// and the ones selected by namespaceSelector. // and the ones selected by namespaceSelector.
// null or empty namespaces list and null namespaceSelector means "this pod's namespace" // null or empty namespaces list and null namespaceSelector means "this pod's namespace".
// +optional // +optional
Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,2,rep,name=namespaces"` Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,2,rep,name=namespaces"`
// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching
@ -2918,7 +2918,6 @@ type PodAffinityTerm struct {
// and the ones listed in the namespaces field. // and the ones listed in the namespaces field.
// null selector and null or empty namespaces list means "this pod's namespace". // null selector and null or empty namespaces list means "this pod's namespace".
// An empty selector ({}) matches all namespaces. // An empty selector ({}) matches all namespaces.
// This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.
// +optional // +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,4,opt,name=namespaceSelector"` NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,4,opt,name=namespaceSelector"`
} }
@ -5885,7 +5884,6 @@ const (
// Match all pod objects that have priority class mentioned // Match all pod objects that have priority class mentioned
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass" ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
// Match all pod objects that have cross-namespace pod (anti)affinity mentioned. // Match all pod objects that have cross-namespace pod (anti)affinity mentioned.
// This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag.
ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity" ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity"
) )

View File

@ -1453,9 +1453,9 @@ func (PodAffinity) SwaggerDoc() map[string]string {
var map_PodAffinityTerm = map[string]string{ var map_PodAffinityTerm = map[string]string{
"": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running", "": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running",
"labelSelector": "A label query over a set of resources, in this case pods.", "labelSelector": "A label query over a set of resources, in this case pods.",
"namespaces": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\"", "namespaces": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".",
"topologyKey": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", "topologyKey": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.",
"namespaceSelector": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.", "namespaceSelector": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces.",
} }
func (PodAffinityTerm) SwaggerDoc() map[string]string { func (PodAffinityTerm) SwaggerDoc() map[string]string {

View File

@ -26,10 +26,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
st "k8s.io/kubernetes/pkg/scheduler/testing" st "k8s.io/kubernetes/pkg/scheduler/testing"
testutils "k8s.io/kubernetes/test/integration/util" testutils "k8s.io/kubernetes/test/integration/util"
imageutils "k8s.io/kubernetes/test/utils/image" imageutils "k8s.io/kubernetes/test/utils/image"
@ -855,8 +852,6 @@ func TestInterPodAffinity(t *testing.T) {
} }
// TestInterPodAffinityWithNamespaceSelector verifies that inter pod affinity with NamespaceSelector works as expected. // TestInterPodAffinityWithNamespaceSelector verifies that inter pod affinity with NamespaceSelector works as expected.
// TODO(https://github.com/kubernetes/enhancements/issues/2249): merge with TestInterPodAffinity once NamespaceSelector
// graduates to GA.
func TestInterPodAffinityWithNamespaceSelector(t *testing.T) { func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
podLabel := map[string]string{"service": "securityscan"} podLabel := map[string]string{"service": "securityscan"}
tests := []struct { tests := []struct {
@ -865,7 +860,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
existingPod *v1.Pod existingPod *v1.Pod
fits bool fits bool
errorType string errorType string
disabled bool
}{ }{
{ {
name: "MatchingNamespaces", name: "MatchingNamespaces",
@ -916,56 +910,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
}, },
fits: true, fits: true,
}, },
{
name: "Disabled",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-ns-selector",
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
Affinity: &v1.Affinity{
PodAffinity: &v1.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "service",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"securityscan"},
},
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "team",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"team1"},
},
},
},
TopologyKey: "region",
},
},
},
},
},
},
existingPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "fakename2",
Labels: podLabel,
Namespace: "ns2",
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
},
},
fits: false,
disabled: true,
},
{ {
name: "MismatchingNamespaces", name: "MismatchingNamespaces",
pod: &v1.Pod{ pod: &v1.Pod{
@ -1019,7 +963,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, !test.disabled)()
testCtx := initTest(t, "") testCtx := initTest(t, "")
defer testutils.CleanupTest(t, testCtx) defer testutils.CleanupTest(t, testCtx)

View File

@ -28,10 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kube-scheduler/config/v1beta3" "k8s.io/kube-scheduler/config/v1beta3"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler" "k8s.io/kubernetes/pkg/scheduler"
configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing" configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/imagelocality" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/imagelocality"
@ -298,7 +295,6 @@ func TestPodAffinityScoring(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, true)()
testCtx := initTestSchedulerForPriorityTest(t, interpodaffinity.Name) testCtx := initTestSchedulerForPriorityTest(t, interpodaffinity.Name)
defer testutils.CleanupTest(t, testCtx) defer testutils.CleanupTest(t, testCtx)
// Add a few nodes. // Add a few nodes.