mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 18:02:01 +00:00
Graduate PodAffinityNamespaceSelector to GA
This commit is contained in:
parent
2355747e7c
commit
8a1c70b48c
6
api/openapi-spec/swagger.json
generated
6
api/openapi-spec/swagger.json
generated
@ -8296,10 +8296,10 @@
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"$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": {
|
||||
"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": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -9671,7 +9671,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"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": [
|
||||
"BestEffort",
|
||||
"CrossNamespacePodAffinity",
|
||||
|
@ -3914,10 +3914,10 @@
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"$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": {
|
||||
"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": {
|
||||
"default": "",
|
||||
"type": "string"
|
||||
@ -5371,7 +5371,7 @@
|
||||
},
|
||||
"scopeName": {
|
||||
"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": [
|
||||
"BestEffort",
|
||||
"CrossNamespacePodAffinity",
|
||||
|
@ -2695,10 +2695,10 @@
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"$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": {
|
||||
"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": {
|
||||
"default": "",
|
||||
"type": "string"
|
||||
|
@ -1921,10 +1921,10 @@
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"$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": {
|
||||
"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": {
|
||||
"default": "",
|
||||
"type": "string"
|
||||
|
@ -1723,10 +1723,10 @@
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"$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": {
|
||||
"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": {
|
||||
"default": "",
|
||||
"type": "string"
|
||||
|
@ -583,8 +583,6 @@ func dropDisabledFields(
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.IdentifyPodOS) && !podOSInUse(oldPodSpec) {
|
||||
podSpec.OS = nil
|
||||
}
|
||||
|
||||
dropDisabledPodAffinityTermFields(podSpec, oldPodSpec)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if podSpec == nil {
|
||||
return false
|
||||
|
@ -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) {
|
||||
podWithOSField := func() *api.Pod {
|
||||
osField := api.PodOS{Name: "linux"}
|
||||
|
@ -123,9 +123,8 @@ var standardResourceQuotaScopes = sets.NewString(
|
||||
)
|
||||
|
||||
// IsStandardResourceQuotaScope returns true if the scope is a standard value
|
||||
func IsStandardResourceQuotaScope(str string, allowNamespaceAffinityScope bool) bool {
|
||||
return standardResourceQuotaScopes.Has(str) ||
|
||||
(allowNamespaceAffinityScope && str == string(core.ResourceQuotaScopeCrossNamespacePodAffinity))
|
||||
func IsStandardResourceQuotaScope(str string) bool {
|
||||
return standardResourceQuotaScopes.Has(str) || str == string(core.ResourceQuotaScopeCrossNamespacePodAffinity)
|
||||
}
|
||||
|
||||
var podObjectCountQuotaResources = sets.NewString(
|
||||
|
@ -2662,7 +2662,7 @@ type PodAffinityTerm struct {
|
||||
// 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"
|
||||
// null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
// +optional
|
||||
Namespaces []string
|
||||
// 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.
|
||||
// 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.
|
||||
// +optional
|
||||
NamespaceSelector *metav1.LabelSelector
|
||||
}
|
||||
@ -5065,7 +5064,6 @@ const (
|
||||
// Match all pod objects that have priority class mentioned
|
||||
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
|
||||
// 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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
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{}
|
||||
if len(resourceQuotaSpec.Scopes) == 0 {
|
||||
return allErrs
|
||||
@ -5776,7 +5776,7 @@ func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts
|
||||
fldPath := fld.Child("scopes")
|
||||
scopeSet := sets.NewString()
|
||||
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"))
|
||||
}
|
||||
for _, k := range hardLimits.List() {
|
||||
@ -5799,7 +5799,7 @@ func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts
|
||||
}
|
||||
|
||||
// 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{}
|
||||
hardLimits := sets.NewString()
|
||||
for k := range resourceQuotaSpec.Hard {
|
||||
@ -5808,7 +5808,7 @@ func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQ
|
||||
fldPath := fld.Child("matchExpressions")
|
||||
scopeSet := sets.NewString()
|
||||
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"))
|
||||
}
|
||||
for _, k := range hardLimits.List() {
|
||||
@ -5854,26 +5854,20 @@ func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQ
|
||||
}
|
||||
|
||||
// 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{}
|
||||
if resourceQuotaSpec.ScopeSelector == nil {
|
||||
return allErrs
|
||||
}
|
||||
allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, opts, fld.Child("scopeSelector"))...)
|
||||
allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, fld.Child("scopeSelector"))...)
|
||||
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.
|
||||
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 = 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"))...)
|
||||
|
||||
return allErrs
|
||||
@ -5898,7 +5892,7 @@ func ValidateResourceQuotaStatus(status *core.ResourceQuotaStatus, fld *field.Pa
|
||||
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{}
|
||||
|
||||
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, validateResourceQuotaScopes(resourceQuotaSpec, opts, fld)...)
|
||||
allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, opts, fld)...)
|
||||
allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...)
|
||||
allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, fld)...)
|
||||
|
||||
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.
|
||||
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 = 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
|
||||
fldPath := field.NewPath("spec", "scopes")
|
||||
|
@ -4933,7 +4933,7 @@ func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
if errs := ValidateResourceQuota(resourceQuota, ResourceQuotaValidationOptions{}); len(errs) != 0 {
|
||||
if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
@ -16212,10 +16212,9 @@ func TestValidateResourceQuota(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
rq core.ResourceQuota
|
||||
errDetail string
|
||||
errField string
|
||||
disableNamespaceSelector bool
|
||||
rq core.ResourceQuota
|
||||
errDetail string
|
||||
errField string
|
||||
}{
|
||||
"no-scope": {
|
||||
rq: core.ResourceQuota{
|
||||
@ -16333,17 +16332,10 @@ func TestValidateResourceQuota(t *testing.T) {
|
||||
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",
|
||||
},
|
||||
"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 {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
errs := ValidateResourceQuota(&tc.rq, ResourceQuotaValidationOptions{
|
||||
AllowPodAffinityNamespaceSelector: !tc.disableNamespaceSelector,
|
||||
})
|
||||
errs := ValidateResourceQuota(&tc.rq)
|
||||
if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
} else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {
|
||||
|
@ -614,6 +614,7 @@ const (
|
||||
// owner: @ahg-g
|
||||
// alpha: v1.21
|
||||
// beta: v1.22
|
||||
// GA: v1.24
|
||||
//
|
||||
// Allow specifying NamespaceSelector in PodAffinityTerm.
|
||||
PodAffinityNamespaceSelector featuregate.Feature = "PodAffinityNamespaceSelector"
|
||||
@ -906,7 +907,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},
|
||||
StatefulSetAutoDeletePVC: {Default: false, PreRelease: featuregate.Alpha},
|
||||
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},
|
||||
IngressClassNamespacedParams: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24
|
||||
ServiceInternalTrafficPolicy: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
@ -426,9 +426,6 @@ func crossNamespaceWeightedPodAffinityTerms(terms []corev1.WeightedPodAffinityTe
|
||||
}
|
||||
|
||||
func usesCrossNamespacePodAffinity(pod *corev1.Pod) bool {
|
||||
if !feature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector) {
|
||||
return false
|
||||
}
|
||||
if pod == nil || pod.Spec.Affinity == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -576,10 +576,9 @@ func TestPodEvaluatorMatchingScopes(t *testing.T) {
|
||||
evaluator := NewPodEvaluator(nil, fakeClock)
|
||||
activeDeadlineSeconds := int64(30)
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
selectors []corev1.ScopedResourceSelectorRequirement
|
||||
wantSelectors []corev1.ScopedResourceSelectorRequirement
|
||||
disableNamespaceSelector bool
|
||||
pod *api.Pod
|
||||
selectors []corev1.ScopedResourceSelectorRequirement
|
||||
wantSelectors []corev1.ScopedResourceSelectorRequirement
|
||||
}{
|
||||
"EmptyPod": {
|
||||
pod: &api.Pod{},
|
||||
@ -762,29 +761,9 @@ func TestPodEvaluatorMatchingScopes(t *testing.T) {
|
||||
{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 {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, !testCase.disableNamespaceSelector)()
|
||||
if testCase.selectors == nil {
|
||||
testCase.selectors = []corev1.ScopedResourceSelectorRequirement{
|
||||
{ScopeName: corev1.ResourceQuotaScopeTerminating},
|
||||
|
@ -22,11 +22,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"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.
|
||||
func (resourcequotaStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
resourcequota := obj.(*api.ResourceQuota)
|
||||
opts := getValidationOptionsFromResourceQuota(resourcequota, nil)
|
||||
return validation.ValidateResourceQuota(resourcequota, opts)
|
||||
return validation.ValidateResourceQuota(resourcequota)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (resourcequotaStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
newObj, oldObj := obj.(*api.ResourceQuota), old.(*api.ResourceQuota)
|
||||
opts := getValidationOptionsFromResourceQuota(newObj, oldObj)
|
||||
return validation.ValidateResourceQuotaUpdate(newObj, oldObj, opts)
|
||||
return validation.ValidateResourceQuotaUpdate(newObj, oldObj)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
@ -19,16 +19,10 @@ package resourcequota
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
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"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
func TestResourceQuotaStrategy(t *testing.T) {
|
||||
@ -64,65 +58,3 @@ func TestResourceQuotaStrategy(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -602,7 +602,7 @@ func TestDryRunPreemption(t *testing.T) {
|
||||
name: "pod with anti-affinity is preempted",
|
||||
registerPlugins: []st.RegisterPluginFunc{
|
||||
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"},
|
||||
testPods: []*v1.Pod{
|
||||
|
@ -51,8 +51,7 @@ type preFilterState struct {
|
||||
// podInfo of the incoming pod.
|
||||
podInfo *framework.PodInfo
|
||||
// A copy of the incoming pod's namespace labels.
|
||||
namespaceLabels labels.Set
|
||||
enableNamespaceSelector bool
|
||||
namespaceLabels labels.Set
|
||||
}
|
||||
|
||||
// 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.
|
||||
copy.podInfo = s.podInfo
|
||||
copy.namespaceLabels = s.namespaceLabels
|
||||
copy.enableNamespaceSelector = s.enableNamespaceSelector
|
||||
return ©
|
||||
}
|
||||
|
||||
@ -78,11 +76,11 @@ func (s *preFilterState) updateWithPod(pInfo *framework.PodInfo, node *v1.Node,
|
||||
return
|
||||
}
|
||||
|
||||
s.existingAntiAffinityCounts.updateWithAntiAffinityTerms(pInfo.RequiredAntiAffinityTerms, s.podInfo.Pod, s.namespaceLabels, node, multiplier, s.enableNamespaceSelector)
|
||||
s.affinityCounts.updateWithAffinityTerms(s.podInfo.RequiredAffinityTerms, pInfo.Pod, 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)
|
||||
// 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.
|
||||
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 {
|
||||
@ -117,8 +115,8 @@ func (m topologyToMatchedTermCount) update(node *v1.Node, tk string, value int64
|
||||
// updates the topologyToMatchedTermCount map with the specified value
|
||||
// for each affinity term if "targetPod" matches ALL terms.
|
||||
func (m topologyToMatchedTermCount) updateWithAffinityTerms(
|
||||
terms []framework.AffinityTerm, pod *v1.Pod, node *v1.Node, value int64, enableNamespaceSelector bool) {
|
||||
if podMatchesAllAffinityTerms(terms, pod, enableNamespaceSelector) {
|
||||
terms []framework.AffinityTerm, pod *v1.Pod, node *v1.Node, value int64) {
|
||||
if podMatchesAllAffinityTerms(terms, pod) {
|
||||
for _, t := range terms {
|
||||
m.update(node, t.TopologyKey, value)
|
||||
}
|
||||
@ -127,24 +125,24 @@ func (m topologyToMatchedTermCount) updateWithAffinityTerms(
|
||||
|
||||
// updates the topologyToMatchedTermCount map with the specified value
|
||||
// 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.
|
||||
for _, t := range terms {
|
||||
if t.Matches(pod, nsLabels, enableNamespaceSelector) {
|
||||
if t.Matches(pod, nsLabels) {
|
||||
m.update(node, t.TopologyKey, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false
|
||||
}
|
||||
for _, t := range terms {
|
||||
// The incoming pod NamespaceSelector was merged into the Namespaces set, and so
|
||||
// we are not explicitly passing in namespace labels.
|
||||
if !t.Matches(pod, nil, enableNamespaceSelector) {
|
||||
if !t.Matches(pod, nil) {
|
||||
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:
|
||||
// (1) Whether it has PodAntiAffinity
|
||||
// (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))
|
||||
index := int32(-1)
|
||||
processNode := func(i int) {
|
||||
@ -166,7 +164,7 @@ func (pl *InterPodAffinity) getExistingAntiAffinityCounts(pod *v1.Pod, nsLabels
|
||||
}
|
||||
topoMap := make(topologyToMatchedTermCount)
|
||||
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 {
|
||||
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
|
||||
// predicate. With this topologyToMatchedTermCount available, the affinity predicate does not
|
||||
// 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)
|
||||
antiAffinityCounts := make(topologyToMatchedTermCount)
|
||||
if len(podInfo.RequiredAffinityTerms) == 0 && len(podInfo.RequiredAntiAffinityTerms) == 0 {
|
||||
@ -206,10 +204,10 @@ func (pl *InterPodAffinity) getIncomingAffinityAntiAffinityCounts(podInfo *frame
|
||||
affinity := make(topologyToMatchedTermCount)
|
||||
antiAffinity := make(topologyToMatchedTermCount)
|
||||
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
|
||||
// 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 {
|
||||
@ -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))
|
||||
}
|
||||
|
||||
s := &preFilterState{
|
||||
enableNamespaceSelector: pl.enableNamespaceSelector,
|
||||
}
|
||||
s := &preFilterState{}
|
||||
|
||||
s.podInfo = framework.NewPodInfo(pod)
|
||||
if s.podInfo.ParseError != nil {
|
||||
return framework.NewStatus(framework.UnschedulableAndUnresolvable, fmt.Sprintf("parsing pod: %+v", s.podInfo.ParseError))
|
||||
}
|
||||
|
||||
if pl.enableNamespaceSelector {
|
||||
for i := range s.podInfo.RequiredAffinityTerms {
|
||||
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAffinityTerms[i]); err != nil {
|
||||
return framework.AsStatus(err)
|
||||
}
|
||||
for i := range s.podInfo.RequiredAffinityTerms {
|
||||
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&s.podInfo.RequiredAffinityTerms[i]); err != nil {
|
||||
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.affinityCounts, s.antiAffinityCounts = pl.getIncomingAffinityAntiAffinityCounts(s.podInfo, allNodes, pl.enableNamespaceSelector)
|
||||
s.existingAntiAffinityCounts = pl.getExistingAntiAffinityCounts(pod, s.namespaceLabels, nodesWithRequiredAntiAffinityPods)
|
||||
s.affinityCounts, s.antiAffinityCounts = pl.getIncomingAffinityAntiAffinityCounts(s.podInfo, allNodes)
|
||||
|
||||
cycleState.Write(preFilterStateKey, s)
|
||||
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
|
||||
// its own terms, and the node has all the requested topologies, then we allow the pod
|
||||
// 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 false
|
||||
|
@ -28,9 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
|
||||
frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||
"k8s.io/kubernetes/pkg/scheduler/internal/cache"
|
||||
)
|
||||
|
||||
@ -68,12 +66,11 @@ func TestRequiredAffinitySingleNode(t *testing.T) {
|
||||
podLabel2 := map[string]string{"security": "S1"}
|
||||
node1 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labels1}}
|
||||
tests := []struct {
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
node *v1.Node
|
||||
name string
|
||||
wantStatus *framework.Status
|
||||
disableNSSelector bool
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
node *v1.Node
|
||||
name string
|
||||
wantStatus *framework.Status
|
||||
}{
|
||||
{
|
||||
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())
|
||||
defer cancel()
|
||||
snapshot := cache.NewSnapshot(test.pods, []*v1.Node{test.node})
|
||||
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector}
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{}, snapshot, namespaces)
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, namespaces)
|
||||
state := framework.NewCycleState()
|
||||
preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod)
|
||||
if !preFilterStatus.IsSuccess() {
|
||||
@ -1864,7 +1860,7 @@ func TestRequiredAffinityMultipleNodes(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
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{
|
||||
&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "NS1"}},
|
||||
})
|
||||
@ -1889,7 +1885,9 @@ func TestPreFilterDisabled(t *testing.T) {
|
||||
nodeInfo := framework.NewNodeInfo()
|
||||
node := v1.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()
|
||||
gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), cycleState, pod, nodeInfo)
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// getMeta creates predicate meta data given the list of pods.
|
||||
getState := func(pods []*v1.Pod) (*InterPodAffinity, *framework.CycleState, *preFilterState, *cache.Snapshot) {
|
||||
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()
|
||||
preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pendingPod)
|
||||
if !preFilterStatus.IsSuccess() {
|
||||
@ -2172,6 +2171,7 @@ func TestPreFilterStateAddRemovePod(t *testing.T) {
|
||||
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, _ := getState(append(test.existingPods, test.addedPod))
|
||||
|
||||
@ -2440,8 +2440,10 @@ func TestGetTPMapMatchingIncomingAffinityAntiAffinity(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
snapshot := cache.NewSnapshot(tt.existingPods, tt.nodes)
|
||||
l, _ := snapshot.NodeInfos().List()
|
||||
p := plugintesting.SetupPlugin(t, frameworkruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot)
|
||||
gotAffinityPodsMap, gotAntiAffinityPodsMap := p.(*InterPodAffinity).getIncomingAffinityAntiAffinityCounts(framework.NewPodInfo(tt.pod), l, true)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
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) {
|
||||
t.Errorf("getTPMapMatchingIncomingAffinityAntiAffinity() gotAffinityPodsMap = %#v, want %#v", gotAffinityPodsMap, tt.wantAffinityPodsMap)
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/parallelize"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
"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
|
||||
type InterPodAffinity struct {
|
||||
parallelizer parallelize.Parallelizer
|
||||
args config.InterPodAffinityArgs
|
||||
sharedLister framework.SharedLister
|
||||
nsLister listersv1.NamespaceLister
|
||||
enableNamespaceSelector bool
|
||||
parallelizer parallelize.Parallelizer
|
||||
args config.InterPodAffinityArgs
|
||||
sharedLister framework.SharedLister
|
||||
nsLister listersv1.NamespaceLister
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
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
|
||||
}
|
||||
pl := &InterPodAffinity{
|
||||
parallelizer: h.Parallelizer(),
|
||||
args: args,
|
||||
sharedLister: h.SnapshotSharedLister(),
|
||||
enableNamespaceSelector: fts.EnablePodAffinityNamespaceSelector,
|
||||
parallelizer: h.Parallelizer(),
|
||||
args: args,
|
||||
sharedLister: h.SnapshotSharedLister(),
|
||||
nsLister: h.SharedInformerFactory().Core().V1().Namespaces().Lister(),
|
||||
}
|
||||
|
||||
if pl.enableNamespaceSelector {
|
||||
pl.nsLister = h.SharedInformerFactory().Core().V1().Namespaces().Lister()
|
||||
}
|
||||
return pl, nil
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,8 @@ func (s *preScoreState) Clone() framework.StateData {
|
||||
return s
|
||||
}
|
||||
|
||||
func (m scoreMap) processTerm(term *framework.AffinityTerm, weight int32, pod *v1.Pod, nsLabels labels.Set, node *v1.Node, multiplier int32, enableNamespaceSelector bool) {
|
||||
if term.Matches(pod, nsLabels, enableNamespaceSelector) {
|
||||
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) {
|
||||
if tpValue, tpValueExist := node.Labels[term.TopologyKey]; tpValueExist {
|
||||
if m[term.TopologyKey] == nil {
|
||||
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 {
|
||||
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.
|
||||
// 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.
|
||||
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,
|
||||
// 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.
|
||||
// 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.
|
||||
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,
|
||||
// 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>
|
||||
if pl.args.HardPodAffinityWeight > 0 && len(existingPodNode.Labels) != 0 {
|
||||
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,
|
||||
// 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.
|
||||
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,
|
||||
// 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.
|
||||
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.
|
||||
@ -168,19 +168,17 @@ func (pl *InterPodAffinity) PreScore(
|
||||
return framework.AsStatus(fmt.Errorf("failed to parse pod: %w", state.podInfo.ParseError))
|
||||
}
|
||||
|
||||
if pl.enableNamespaceSelector {
|
||||
for i := range state.podInfo.PreferredAffinityTerms {
|
||||
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAffinityTerms[i].AffinityTerm); err != nil {
|
||||
return framework.AsStatus(fmt.Errorf("updating PreferredAffinityTerms: %w", err))
|
||||
}
|
||||
for i := range state.podInfo.PreferredAffinityTerms {
|
||||
if err := pl.mergeAffinityTermNamespacesIfNotEmpty(&state.podInfo.PreferredAffinityTerms[i].AffinityTerm); err != nil {
|
||||
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))
|
||||
index := int32(-1)
|
||||
|
@ -27,9 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
|
||||
frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||
"k8s.io/kubernetes/pkg/scheduler/internal/cache"
|
||||
)
|
||||
|
||||
@ -371,13 +369,12 @@ func TestPreferredAffinity(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
expectedList framework.NodeScoreList
|
||||
name string
|
||||
wantStatus *framework.Status
|
||||
disableNSSelector bool
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
expectedList framework.NodeScoreList
|
||||
name string
|
||||
wantStatus *framework.Status
|
||||
}{
|
||||
{
|
||||
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())
|
||||
defer cancel()
|
||||
state := framework.NewCycleState()
|
||||
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector}
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{HardPodAffinityWeight: 1}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: 1}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
|
||||
status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes)
|
||||
if !status.IsSuccess() {
|
||||
if !strings.Contains(status.Message(), test.wantStatus.Message()) {
|
||||
@ -825,7 +821,6 @@ func TestPreferredAffinityWithHardPodAffinitySymmetricWeight(t *testing.T) {
|
||||
hardPodAffinityWeight int32
|
||||
expectedList framework.NodeScoreList
|
||||
name string
|
||||
disableNSSelector bool
|
||||
}{
|
||||
{
|
||||
name: "with default weight",
|
||||
@ -908,8 +903,7 @@ func TestPreferredAffinityWithHardPodAffinitySymmetricWeight(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
state := framework.NewCycleState()
|
||||
fts := feature.Features{EnablePodAffinityNamespaceSelector: !test.disableNSSelector}
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, frameworkruntime.FactoryAdapter(fts, New), &config.InterPodAffinityArgs{HardPodAffinityWeight: test.hardPodAffinityWeight}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
|
||||
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: test.hardPodAffinityWeight}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
|
||||
status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes)
|
||||
if !status.IsSuccess() {
|
||||
t.Errorf("unexpected error: %v", status)
|
||||
|
@ -45,12 +45,11 @@ import (
|
||||
// through the WithFrameworkOutOfTreeRegistry option.
|
||||
func NewInTreeRegistry() runtime.Registry {
|
||||
fts := plfeature.Features{
|
||||
EnablePodAffinityNamespaceSelector: feature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector),
|
||||
EnablePodDisruptionBudget: feature.DefaultFeatureGate.Enabled(features.PodDisruptionBudget),
|
||||
EnablePodOverhead: feature.DefaultFeatureGate.Enabled(features.PodOverhead),
|
||||
EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
|
||||
EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity),
|
||||
EnablePodDisruptionBudget: feature.DefaultFeatureGate.Enabled(features.PodDisruptionBudget),
|
||||
EnablePodOverhead: feature.DefaultFeatureGate.Enabled(features.PodOverhead),
|
||||
EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
|
||||
EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity),
|
||||
}
|
||||
|
||||
return runtime.Registry{
|
||||
@ -72,7 +71,7 @@ func NewInTreeRegistry() runtime.Registry {
|
||||
nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD),
|
||||
nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk),
|
||||
nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder),
|
||||
interpodaffinity.Name: runtime.FactoryAdapter(fts, interpodaffinity.New),
|
||||
interpodaffinity.Name: interpodaffinity.New,
|
||||
queuesort.Name: queuesort.New,
|
||||
defaultbinder.Name: defaultbinder.New,
|
||||
defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New),
|
||||
|
@ -198,8 +198,8 @@ type AffinityTerm struct {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if at.Namespaces.Has(pod.Namespace) || (nsSelectorEnabled && at.NamespaceSelector.Matches(nsLabels)) {
|
||||
func (at *AffinityTerm) Matches(pod *v1.Pod, nsLabels labels.Set) bool {
|
||||
if at.Namespaces.Has(pod.Namespace) || at.NamespaceSelector.Matches(nsLabels) {
|
||||
return at.Selector.Matches(labels.Set(pod.Labels))
|
||||
}
|
||||
return false
|
||||
|
@ -34,12 +34,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/informers"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
|
||||
"k8s.io/kubernetes/pkg/scheduler/internal/heap"
|
||||
@ -280,9 +278,7 @@ func NewPriorityQueue(
|
||||
}
|
||||
pq.cond.L = &pq.lock
|
||||
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
|
||||
}
|
||||
@ -656,15 +652,13 @@ func (p *PriorityQueue) movePodsToActiveOrBackoffQueue(podInfoList []*framework.
|
||||
// any affinity term that matches "pod".
|
||||
// NOTE: this function assumes lock has been acquired in caller.
|
||||
func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod) []*framework.QueuedPodInfo {
|
||||
nsSelectorEnabled := p.nsLister != nil
|
||||
var nsLabels labels.Set
|
||||
if nsSelectorEnabled {
|
||||
nsLabels = interpodaffinity.GetNamespaceLabelsSnapshot(pod.Namespace, p.nsLister)
|
||||
}
|
||||
nsLabels = interpodaffinity.GetNamespaceLabelsSnapshot(pod.Namespace, p.nsLister)
|
||||
|
||||
var podsToMove []*framework.QueuedPodInfo
|
||||
for _, pInfo := range p.unschedulableQ.podInfoMap {
|
||||
for _, term := range pInfo.RequiredAffinityTerms {
|
||||
if term.Matches(pod, nsLabels, nsSelectorEnabled) {
|
||||
if term.Matches(pod, nsLabels) {
|
||||
podsToMove = append(podsToMove, pInfo)
|
||||
break
|
||||
}
|
||||
|
@ -3060,7 +3060,7 @@ message PodAffinityTerm {
|
||||
// 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"
|
||||
// null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
// +optional
|
||||
repeated string namespaces = 2;
|
||||
|
||||
@ -3076,7 +3076,6 @@ message PodAffinityTerm {
|
||||
// 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.
|
||||
// +optional
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 4;
|
||||
}
|
||||
|
@ -2904,7 +2904,7 @@ type PodAffinityTerm struct {
|
||||
// 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"
|
||||
// null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
// +optional
|
||||
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
|
||||
@ -2918,7 +2918,6 @@ type PodAffinityTerm struct {
|
||||
// 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.
|
||||
// +optional
|
||||
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
|
||||
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
|
||||
// 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"
|
||||
)
|
||||
|
||||
|
@ -1453,9 +1453,9 @@ func (PodAffinity) SwaggerDoc() 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",
|
||||
"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.",
|
||||
"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 {
|
||||
|
@ -26,10 +26,7 @@ import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
st "k8s.io/kubernetes/pkg/scheduler/testing"
|
||||
testutils "k8s.io/kubernetes/test/integration/util"
|
||||
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.
|
||||
// TODO(https://github.com/kubernetes/enhancements/issues/2249): merge with TestInterPodAffinity once NamespaceSelector
|
||||
// graduates to GA.
|
||||
func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
|
||||
podLabel := map[string]string{"service": "securityscan"}
|
||||
tests := []struct {
|
||||
@ -865,7 +860,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
|
||||
existingPod *v1.Pod
|
||||
fits bool
|
||||
errorType string
|
||||
disabled bool
|
||||
}{
|
||||
{
|
||||
name: "MatchingNamespaces",
|
||||
@ -916,56 +910,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
|
||||
},
|
||||
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",
|
||||
pod: &v1.Pod{
|
||||
@ -1019,7 +963,6 @@ func TestInterPodAffinityWithNamespaceSelector(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, !test.disabled)()
|
||||
testCtx := initTest(t, "")
|
||||
defer testutils.CleanupTest(t, testCtx)
|
||||
|
||||
|
@ -28,10 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"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/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/scheduler"
|
||||
configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/imagelocality"
|
||||
@ -298,7 +295,6 @@ func TestPodAffinityScoring(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodAffinityNamespaceSelector, true)()
|
||||
testCtx := initTestSchedulerForPriorityTest(t, interpodaffinity.Name)
|
||||
defer testutils.CleanupTest(t, testCtx)
|
||||
// Add a few nodes.
|
||||
|
Loading…
Reference in New Issue
Block a user