mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +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": {
|
"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",
|
||||||
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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"}
|
||||||
|
@ -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(
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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},
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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},
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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{
|
||||||
|
@ -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 ©
|
return ©
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user