mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #41617 from timothysc/affinity_annotations_flaggate
Automatic merge from submit-queue (batch tested with PRs 39373, 41585, 41617, 41707, 39958) Feature-Gate affinity in annotations **What this PR does / why we need it**: Adds back basic flaggated support for alpha Affinity annotations **Special notes for your reviewer**: Reconcile function is placed in the lowest common denominator, which in this case is schedulercache, because you can't place flag-gated functions in apimachinery. **Release note**: ``` NONE ``` /cc @davidopp
This commit is contained in:
		| @@ -277,6 +277,11 @@ const ( | |||||||
| 	// an object (e.g. secret, config map) before fetching it again from apiserver. | 	// an object (e.g. secret, config map) before fetching it again from apiserver. | ||||||
| 	// This annotation can be attached to node. | 	// This annotation can be attached to node. | ||||||
| 	ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" | 	ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" | ||||||
|  |  | ||||||
|  | 	// AffinityAnnotationKey represents the key of affinity data (json serialized) | ||||||
|  | 	// in the Annotations of a Pod. | ||||||
|  | 	// TODO: remove when alpha support for affinity is removed | ||||||
|  | 	AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations | // GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations | ||||||
| @@ -646,3 +651,18 @@ func RemoveTaint(node *Node, taint *Taint) (*Node, bool, error) { | |||||||
| 	} | 	} | ||||||
| 	return newNode, true, nil | 	return newNode, true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetAffinityFromPodAnnotations gets the json serialized affinity data from Pod.Annotations | ||||||
|  | // and converts it to the Affinity type in api. | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func GetAffinityFromPodAnnotations(annotations map[string]string) (*Affinity, error) { | ||||||
|  | 	if len(annotations) > 0 && annotations[AffinityAnnotationKey] != "" { | ||||||
|  | 		var affinity Affinity | ||||||
|  | 		err := json.Unmarshal([]byte(annotations[AffinityAnnotationKey]), &affinity) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &affinity, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -644,3 +644,60 @@ func TestSysctlsFromPodAnnotation(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func TestGetAffinityFromPodAnnotations(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		pod       *Pod | ||||||
|  | 		expectErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			pod:       &Pod{}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: map[string]string{ | ||||||
|  | 						AffinityAnnotationKey: ` | ||||||
|  | 						{"nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { | ||||||
|  | 							"nodeSelectorTerms": [{ | ||||||
|  | 								"matchExpressions": [{ | ||||||
|  | 									"key": "foo", | ||||||
|  | 									"operator": "In", | ||||||
|  | 									"values": ["value1", "value2"] | ||||||
|  | 								}] | ||||||
|  | 							}] | ||||||
|  | 						}}}`, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: map[string]string{ | ||||||
|  | 						AffinityAnnotationKey: ` | ||||||
|  | 						{"nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { | ||||||
|  | 							"nodeSelectorTerms": [{ | ||||||
|  | 								"matchExpressions": [{ | ||||||
|  | 									"key": "foo", | ||||||
|  | 						`, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, tc := range testCases { | ||||||
|  | 		_, err := GetAffinityFromPodAnnotations(tc.pod.Annotations) | ||||||
|  | 		if err == nil && tc.expectErr { | ||||||
|  | 			t.Errorf("[%v]expected error but got none.", i) | ||||||
|  | 		} | ||||||
|  | 		if err != nil && !tc.expectErr { | ||||||
|  | 			t.Errorf("[%v]did not expect error but got: %v", i, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -67,7 +67,11 @@ const ( | |||||||
| 	// Note: This feature is not supported for `BestEffort` pods. | 	// Note: This feature is not supported for `BestEffort` pods. | ||||||
| 	ExperimentalCriticalPodAnnotation utilfeature.Feature = "ExperimentalCriticalPodAnnotation" | 	ExperimentalCriticalPodAnnotation utilfeature.Feature = "ExperimentalCriticalPodAnnotation" | ||||||
|  |  | ||||||
|  | 	// owner: @davidopp | ||||||
|  | 	// alpha: v1.6 | ||||||
|  | 	// | ||||||
| 	// Determines if affinity defined in annotations should be processed | 	// Determines if affinity defined in annotations should be processed | ||||||
|  | 	// TODO: remove when alpha support for affinity is removed | ||||||
| 	AffinityInAnnotations utilfeature.Feature = "AffinityInAnnotations" | 	AffinityInAnnotations utilfeature.Feature = "AffinityInAnnotations" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -85,6 +89,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS | |||||||
| 	DynamicVolumeProvisioning:                   {Default: true, PreRelease: utilfeature.Alpha}, | 	DynamicVolumeProvisioning:                   {Default: true, PreRelease: utilfeature.Alpha}, | ||||||
| 	ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: utilfeature.Beta}, | 	ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: utilfeature.Beta}, | ||||||
| 	ExperimentalCriticalPodAnnotation:           {Default: false, PreRelease: utilfeature.Alpha}, | 	ExperimentalCriticalPodAnnotation:           {Default: false, PreRelease: utilfeature.Alpha}, | ||||||
|  | 	AffinityInAnnotations:                       {Default: false, PreRelease: utilfeature.Alpha}, | ||||||
|  |  | ||||||
| 	// inherited features from generic apiserver, relisted here to get a conflict if it is changed | 	// inherited features from generic apiserver, relisted here to get a conflict if it is changed | ||||||
| 	// unintentionally on either side: | 	// unintentionally on either side: | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ go_test( | |||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/resource", |         "//vendor:k8s.io/apimachinery/pkg/api/resource", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", |         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", |         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||||
|  |         "//vendor:k8s.io/apiserver/pkg/util/feature", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -585,7 +585,7 @@ func podMatchesNodeLabels(pod *v1.Pod, node *v1.Node) bool { | |||||||
| 	// 5. zero-length non-nil []NodeSelectorRequirement matches no nodes also, just for simplicity | 	// 5. zero-length non-nil []NodeSelectorRequirement matches no nodes also, just for simplicity | ||||||
| 	// 6. non-nil empty NodeSelectorRequirement is not allowed | 	// 6. non-nil empty NodeSelectorRequirement is not allowed | ||||||
| 	nodeAffinityMatches := true | 	nodeAffinityMatches := true | ||||||
| 	affinity := pod.Spec.Affinity | 	affinity := schedulercache.ReconcileAffinity(pod) | ||||||
| 	if affinity != nil && affinity.NodeAffinity != nil { | 	if affinity != nil && affinity.NodeAffinity != nil { | ||||||
| 		nodeAffinity := affinity.NodeAffinity | 		nodeAffinity := affinity.NodeAffinity | ||||||
| 		// if no required NodeAffinity requirements, will do no-op, means select all nodes. | 		// if no required NodeAffinity requirements, will do no-op, means select all nodes. | ||||||
| @@ -897,7 +897,7 @@ func (c *PodAffinityChecker) InterPodAffinityMatches(pod *v1.Pod, meta interface | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Now check if <pod> requirements will be satisfied on this node. | 	// Now check if <pod> requirements will be satisfied on this node. | ||||||
| 	affinity := pod.Spec.Affinity | 	affinity := schedulercache.ReconcileAffinity(pod) | ||||||
| 	if affinity == nil || (affinity.PodAffinity == nil && affinity.PodAntiAffinity == nil) { | 	if affinity == nil || (affinity.PodAffinity == nil && affinity.PodAntiAffinity == nil) { | ||||||
| 		return true, nil, nil | 		return true, nil, nil | ||||||
| 	} | 	} | ||||||
| @@ -1001,7 +1001,7 @@ func getMatchingAntiAffinityTerms(pod *v1.Pod, nodeInfoMap map[string]*scheduler | |||||||
| 		} | 		} | ||||||
| 		var nodeResult []matchingPodAntiAffinityTerm | 		var nodeResult []matchingPodAntiAffinityTerm | ||||||
| 		for _, existingPod := range nodeInfo.PodsWithAffinity() { | 		for _, existingPod := range nodeInfo.PodsWithAffinity() { | ||||||
| 			affinity := existingPod.Spec.Affinity | 			affinity := schedulercache.ReconcileAffinity(existingPod) | ||||||
| 			if affinity == nil { | 			if affinity == nil { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @@ -1029,7 +1029,7 @@ func getMatchingAntiAffinityTerms(pod *v1.Pod, nodeInfoMap map[string]*scheduler | |||||||
| func (c *PodAffinityChecker) getMatchingAntiAffinityTerms(pod *v1.Pod, allPods []*v1.Pod) ([]matchingPodAntiAffinityTerm, error) { | func (c *PodAffinityChecker) getMatchingAntiAffinityTerms(pod *v1.Pod, allPods []*v1.Pod) ([]matchingPodAntiAffinityTerm, error) { | ||||||
| 	var result []matchingPodAntiAffinityTerm | 	var result []matchingPodAntiAffinityTerm | ||||||
| 	for _, existingPod := range allPods { | 	for _, existingPod := range allPods { | ||||||
| 		affinity := existingPod.Spec.Affinity | 		affinity := schedulercache.ReconcileAffinity(existingPod) | ||||||
| 		if affinity != nil && affinity.PodAntiAffinity != nil { | 		if affinity != nil && affinity.PodAntiAffinity != nil { | ||||||
| 			existingPodNode, err := c.info.GetNodeInfo(existingPod.Spec.NodeName) | 			existingPodNode, err := c.info.GetNodeInfo(existingPod.Spec.NodeName) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -67,6 +67,7 @@ go_test( | |||||||
|         "//plugin/pkg/scheduler/schedulercache:go_default_library", |         "//plugin/pkg/scheduler/schedulercache:go_default_library", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/resource", |         "//vendor:k8s.io/apimachinery/pkg/api/resource", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", |         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||||
|  |         "//vendor:k8s.io/apiserver/pkg/util/feature", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -116,7 +116,7 @@ func (p *podAffinityPriorityMap) processTerms(terms []v1.WeightedPodAffinityTerm | |||||||
| // Symmetry need to be considered for preferredDuringSchedulingIgnoredDuringExecution from podAffinity & podAntiAffinity, | // Symmetry need to be considered for preferredDuringSchedulingIgnoredDuringExecution from podAffinity & podAntiAffinity, | ||||||
| // symmetry need to be considered for hard requirements from podAffinity | // symmetry need to be considered for hard requirements from podAffinity | ||||||
| func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) { | func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) { | ||||||
| 	affinity := pod.Spec.Affinity | 	affinity := schedulercache.ReconcileAffinity(pod) | ||||||
| 	hasAffinityConstraints := affinity != nil && affinity.PodAffinity != nil | 	hasAffinityConstraints := affinity != nil && affinity.PodAffinity != nil | ||||||
| 	hasAntiAffinityConstraints := affinity != nil && affinity.PodAntiAffinity != nil | 	hasAntiAffinityConstraints := affinity != nil && affinity.PodAntiAffinity != nil | ||||||
|  |  | ||||||
| @@ -137,7 +137,7 @@ func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, node | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		existingPodAffinity := existingPod.Spec.Affinity | 		existingPodAffinity := schedulercache.ReconcileAffinity(existingPod) | ||||||
| 		existingHasAffinityConstraints := existingPodAffinity != nil && existingPodAffinity.PodAffinity != nil | 		existingHasAffinityConstraints := existingPodAffinity != nil && existingPodAffinity.PodAffinity != nil | ||||||
| 		existingHasAntiAffinityConstraints := existingPodAffinity != nil && existingPodAffinity.PodAntiAffinity != nil | 		existingHasAntiAffinityConstraints := existingPodAffinity != nil && existingPodAffinity.PodAntiAffinity != nil | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
| 	"k8s.io/kubernetes/pkg/api/v1" | 	"k8s.io/kubernetes/pkg/api/v1" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" | 	"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" | ||||||
| 	schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" | 	schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" | ||||||
| @@ -613,3 +614,567 @@ func TestHardPodAffinitySymmetricWeight(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func TestInterPodAffinityAnnotationsPriority(t *testing.T) { | ||||||
|  | 	utilfeature.DefaultFeatureGate.Set("AffinityInAnnotations=true") | ||||||
|  | 	labelRgChina := map[string]string{ | ||||||
|  | 		"region": "China", | ||||||
|  | 	} | ||||||
|  | 	labelRgIndia := map[string]string{ | ||||||
|  | 		"region": "India", | ||||||
|  | 	} | ||||||
|  | 	labelAzAz1 := map[string]string{ | ||||||
|  | 		"az": "az1", | ||||||
|  | 	} | ||||||
|  | 	labelAzAz2 := map[string]string{ | ||||||
|  | 		"az": "az2", | ||||||
|  | 	} | ||||||
|  | 	labelRgChinaAzAz1 := map[string]string{ | ||||||
|  | 		"region": "China", | ||||||
|  | 		"az":     "az1", | ||||||
|  | 	} | ||||||
|  | 	podLabelSecurityS1 := map[string]string{ | ||||||
|  | 		"security": "S1", | ||||||
|  | 	} | ||||||
|  | 	podLabelSecurityS2 := map[string]string{ | ||||||
|  | 		"security": "S2", | ||||||
|  | 	} | ||||||
|  | 	// considered only preferredDuringSchedulingIgnoredDuringExecution in pod affinity | ||||||
|  | 	stayWithS1InRegion := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 5, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S1"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	stayWithS2InRegion := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 6, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S2"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	affinity3 := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 				{ | ||||||
|  | 					"weight": 8, | ||||||
|  | 					"podAffinityTerm": { | ||||||
|  | 						"labelSelector": { | ||||||
|  | 							"matchExpressions": [{ | ||||||
|  | 								"key": "security", | ||||||
|  | 								"operator": "NotIn", | ||||||
|  | 								"values":["S1"] | ||||||
|  | 							}, { | ||||||
|  | 								"key": "security", | ||||||
|  | 								"operator": "In", | ||||||
|  | 								"values":["S2"] | ||||||
|  | 							}] | ||||||
|  | 						}, | ||||||
|  | 						"namespaces": [], | ||||||
|  | 						"topologyKey": "region" | ||||||
|  | 					} | ||||||
|  | 				}, { | ||||||
|  | 					"weight": 2, | ||||||
|  | 					"podAffinityTerm": { | ||||||
|  | 						"labelSelector": { | ||||||
|  | 							"matchExpressions": [{ | ||||||
|  | 								"key": "security", | ||||||
|  | 								"operator": "Exists" | ||||||
|  | 							}, { | ||||||
|  | 								"key": "wrongkey", | ||||||
|  | 								"operator": "DoesNotExist" | ||||||
|  | 							}] | ||||||
|  | 						}, | ||||||
|  | 						"namespaces": [], | ||||||
|  | 						"topologyKey": "region" | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	hardAffinity := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"requiredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 				{ | ||||||
|  | 					"labelSelector":{ | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values": ["S1", "value2"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				}, { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "Exists" | ||||||
|  | 						}, { | ||||||
|  | 							"key": "wrongkey", | ||||||
|  | 							"operator": "DoesNotExist" | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	awayFromS1InAz := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAntiAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 5, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S1"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "az" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	// to stay away from security S2 in any az. | ||||||
|  | 	awayFromS2InAz := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAntiAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 5, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S2"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "az" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	// to stay with security S1 in same region, stay away from security S2 in any az. | ||||||
|  | 	stayWithS1InRegionAwayFromS2InAz := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 8, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S1"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}, | ||||||
|  | 		"podAntiAffinity": { | ||||||
|  | 			"preferredDuringSchedulingIgnoredDuringExecution": [{ | ||||||
|  | 				"weight": 5, | ||||||
|  | 				"podAffinityTerm": { | ||||||
|  | 					"labelSelector": { | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "security", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values":["S2"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "az" | ||||||
|  | 				} | ||||||
|  | 			}] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		pod          *v1.Pod | ||||||
|  | 		pods         []*v1.Pod | ||||||
|  | 		nodes        []*v1.Node | ||||||
|  | 		expectedList schedulerapi.HostPriorityList | ||||||
|  | 		test         string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: map[string]string{}}}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "all machines are same priority as Affinity is nil", | ||||||
|  | 		}, | ||||||
|  | 		// the node(machine1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score | ||||||
|  | 		// the node(machine3) that don't have the label {"region": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get low score | ||||||
|  | 		// the node(machine2) that have the label {"region": "China"} (match the topology key) but that have existing pods that mismatch the labelSelector get low score | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegion}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test: "Affinity: pod that matches topology key & pods in nodes will get high score comparing to others" + | ||||||
|  | 				"which doesn't match either pods in nodes or in topology key", | ||||||
|  | 		}, | ||||||
|  | 		// the node1(machine1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score | ||||||
|  | 		// the node2(machine2) that have the label {"region": "China"}, match the topology key and have the same label value with node1, get the same high score with node1 | ||||||
|  | 		// the node3(machine3) that have the label {"region": "India"}, match the topology key but have a different label value, don't have existing pods that match the labelSelector, | ||||||
|  | 		// get a low score. | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Annotations: stayWithS1InRegion}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgChinaAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "All the nodes that have the same topology key & label value with one of them has an existing pod that match the affinity rules, have the same score", | ||||||
|  | 		}, | ||||||
|  | 		// there are 2 regions, say regionChina(machine1,machine3,machine4) and regionIndia(machine2,machine5), both regions have nodes that match the preference. | ||||||
|  | 		// But there are more nodes(actually more existing pods) in regionChina that match the preference than regionIndia. | ||||||
|  | 		// Then, nodes in regionChina get higher score than nodes in regionIndia, and all the nodes in regionChina should get a same score(high score), | ||||||
|  | 		// while all the nodes in regionIndia should get another same score(low score). | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS2InRegion}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine5"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine4", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: labelRgIndia}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 5}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 10}, {Host: "machine5", Score: 5}}, | ||||||
|  | 			test:         "Affinity: nodes in one region has more matching pods comparing to other reqion, so the region which has more macthes will get high score", | ||||||
|  | 		}, | ||||||
|  | 		// Test with the different operators and values for pod affinity scheduling preference, including some match failures. | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: affinity3}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 2}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "Affinity: different Label operators and values for pod affinity scheduling preference, including some match failures ", | ||||||
|  | 		}, | ||||||
|  | 		// Test the symmetry cases for affinity, the difference between affinity and symmetry is not the pod wants to run together with some existing pods, | ||||||
|  | 		// but the existing pods have the inter pod affinity preference while the pod to schedule satisfy the preference. | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegion}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: stayWithS2InRegion}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "Affinity symmetry: considred only the preferredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: hardAffinity}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: hardAffinity}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "Affinity symmetry: considred RequiredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		// The pod to schedule prefer to stay away from some existing pods at node level using the pod anti affinity. | ||||||
|  | 		// the nodes that have the label {"node": "bar"} (match the topology key) and that have existing pods that match the labelSelector get low score | ||||||
|  | 		// the nodes that don't have the label {"node": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get high score | ||||||
|  | 		// the nodes that have the label {"node": "bar"} (match the topology key) but that have existing pods that mismatch the labelSelector get high score | ||||||
|  | 		// there are 2 nodes, say node1 and node2, both nodes have pods that match the labelSelector and have topology-key in node.Labels. | ||||||
|  | 		// But there are more pods on node1 that match the preference than node2. Then, node1 get a lower score than node2. | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgChina}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, | ||||||
|  | 			test:         "Anti Affinity: pod that doesnot match existing pods in node will get high score ", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgChina}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, | ||||||
|  | 			test:         "Anti Affinity: pod that does not matches topology key & matches the pods in nodes will get higher score comparing to others ", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, | ||||||
|  | 			test:         "Anti Affinity: one node has more matching pods comparing to other node, so the node which has more unmacthes will get high score", | ||||||
|  | 		}, | ||||||
|  | 		// Test the symmetry cases for anti affinity | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS2InAz}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: awayFromS1InAz}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelAzAz2}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, | ||||||
|  | 			test:         "Anti Affinity symmetry: the existing pods in node which has anti affinity match will get high score", | ||||||
|  | 		}, | ||||||
|  | 		// Test both  affinity and anti-affinity | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}}, | ||||||
|  | 			test:         "Affinity and Anti Affinity: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity", | ||||||
|  | 		}, | ||||||
|  | 		// Combined cases considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels (they are in the same RC/service), | ||||||
|  | 		// the pod prefer to run together with its brother pods in the same region, but wants to stay away from them at node level, | ||||||
|  | 		// so that all the pods of a RC/service can stay in a same region but trying to separate with each other | ||||||
|  | 		// machine-1,machine-3,machine-4 are in ChinaRegion others machin-2,machine-5 are in IndiaRegion | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine5"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChinaAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine4", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: labelRgIndia}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 4}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 10}, {Host: "machine5", Score: 4}}, | ||||||
|  | 			test:         "Affinity and Anti Affinity: considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels", | ||||||
|  | 		}, | ||||||
|  | 		// Consider Affinity, Anti Affinity and symmetry together. | ||||||
|  | 		// for Affinity, the weights are:                8,  0,  0,  0 | ||||||
|  | 		// for Anti Affinity, the weights are:           0, -5,  0,  0 | ||||||
|  | 		// for Affinity symmetry, the weights are:       0,  0,  8,  0 | ||||||
|  | 		// for Anti Affinity symmetry, the weights are:  0,  0,  0, -5 | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Annotations: stayWithS1InRegionAwayFromS2InAz}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: metav1.ObjectMeta{Annotations: awayFromS1InAz}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelAzAz1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine4", Labels: labelAzAz2}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 0}}, | ||||||
|  | 			test:         "Affinity and Anti Affinity and symmetry: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity & symmetry", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, test := range tests { | ||||||
|  | 		nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) | ||||||
|  | 		interPodAffinity := InterPodAffinity{ | ||||||
|  | 			info:                  FakeNodeListInfo(test.nodes), | ||||||
|  | 			nodeLister:            algorithm.FakeNodeLister(test.nodes), | ||||||
|  | 			podLister:             algorithm.FakePodLister(test.pods), | ||||||
|  | 			hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, | ||||||
|  | 		} | ||||||
|  | 		list, err := interPodAffinity.CalculateInterPodAffinityPriority(test.pod, nodeNameToInfo, test.nodes) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("unexpected error: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if !reflect.DeepEqual(test.expectedList, list) { | ||||||
|  | 			t.Errorf("%s: \nexpected \n\t%#v, \ngot \n\t%#v\n", test.test, test.expectedList, list) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func TestHardPodAffinityAnnotationsSymmetricWeight(t *testing.T) { | ||||||
|  | 	utilfeature.DefaultFeatureGate.Set("AffinityInAnnotations=true") | ||||||
|  | 	podLabelServiceS1 := map[string]string{ | ||||||
|  | 		"service": "S1", | ||||||
|  | 	} | ||||||
|  | 	labelRgChina := map[string]string{ | ||||||
|  | 		"region": "China", | ||||||
|  | 	} | ||||||
|  | 	labelRgIndia := map[string]string{ | ||||||
|  | 		"region": "India", | ||||||
|  | 	} | ||||||
|  | 	labelAzAz1 := map[string]string{ | ||||||
|  | 		"az": "az1", | ||||||
|  | 	} | ||||||
|  | 	hardPodAffinity := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"podAffinity": { | ||||||
|  | 			"requiredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 				{ | ||||||
|  | 					"labelSelector":{ | ||||||
|  | 						"matchExpressions": [{ | ||||||
|  | 							"key": "service", | ||||||
|  | 							"operator": "In", | ||||||
|  | 							"values": ["S1"] | ||||||
|  | 						}] | ||||||
|  | 					}, | ||||||
|  | 					"namespaces": [], | ||||||
|  | 					"topologyKey": "region" | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
|  | 		}}`, | ||||||
|  | 	} | ||||||
|  | 	tests := []struct { | ||||||
|  | 		pod                   *v1.Pod | ||||||
|  | 		pods                  []*v1.Pod | ||||||
|  | 		nodes                 []*v1.Node | ||||||
|  | 		hardPodAffinityWeight int | ||||||
|  | 		expectedList          schedulerapi.HostPriorityList | ||||||
|  | 		test                  string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelServiceS1}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Annotations: hardPodAffinity}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Annotations: hardPodAffinity}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, | ||||||
|  | 			expectedList:          []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:                  "Hard Pod Affinity symmetry: hard pod affinity symmetry weights 1 by default, then nodes that match the hard pod affinity symmetry rules, get a high score", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelServiceS1}}, | ||||||
|  | 			pods: []*v1.Pod{ | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Annotations: hardPodAffinity}}, | ||||||
|  | 				{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Annotations: hardPodAffinity}}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, | ||||||
|  | 			}, | ||||||
|  | 			hardPodAffinityWeight: 0, | ||||||
|  | 			expectedList:          []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:                  "Hard Pod Affinity symmetry: hard pod affinity symmetry is closed(weights 0), then nodes that match the hard pod affinity symmetry rules, get same score with those not match", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, test := range tests { | ||||||
|  | 		nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) | ||||||
|  | 		ipa := InterPodAffinity{ | ||||||
|  | 			info:                  FakeNodeListInfo(test.nodes), | ||||||
|  | 			nodeLister:            algorithm.FakeNodeLister(test.nodes), | ||||||
|  | 			podLister:             algorithm.FakePodLister(test.pods), | ||||||
|  | 			hardPodAffinityWeight: test.hardPodAffinityWeight, | ||||||
|  | 		} | ||||||
|  | 		list, err := ipa.CalculateInterPodAffinityPriority(test.pod, nodeNameToInfo, test.nodes) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("unexpected error: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if !reflect.DeepEqual(test.expectedList, list) { | ||||||
|  | 			t.Errorf("%s: \nexpected \n\t%#v, \ngot \n\t%#v\n", test.test, test.expectedList, list) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -41,6 +41,6 @@ func PriorityMetadata(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.Nod | |||||||
| 	return &priorityMetadata{ | 	return &priorityMetadata{ | ||||||
| 		nonZeroRequest: getNonZeroRequests(pod), | 		nonZeroRequest: getNonZeroRequests(pod), | ||||||
| 		podTolerations: tolerations, | 		podTolerations: tolerations, | ||||||
| 		affinity:       pod.Spec.Affinity, | 		affinity:       schedulercache.ReconcileAffinity(pod), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ func CalculateNodeAffinityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *s | |||||||
| 		affinity = priorityMeta.affinity | 		affinity = priorityMeta.affinity | ||||||
| 	} else { | 	} else { | ||||||
| 		// We couldn't parse metadata - fallback to the podspec. | 		// We couldn't parse metadata - fallback to the podspec. | ||||||
| 		affinity = pod.Spec.Affinity | 		affinity = schedulercache.ReconcileAffinity(pod) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var count int32 | 	var count int32 | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
| 	"k8s.io/kubernetes/pkg/api/v1" | 	"k8s.io/kubernetes/pkg/api/v1" | ||||||
| 	schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" | 	schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" | 	"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" | ||||||
| @@ -177,3 +178,147 @@ func TestNodeAffinityPriority(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func TestNodeAffinityAnnotationsPriority(t *testing.T) { | ||||||
|  | 	utilfeature.DefaultFeatureGate.Set("AffinityInAnnotations=true") | ||||||
|  | 	label1 := map[string]string{"foo": "bar"} | ||||||
|  | 	label2 := map[string]string{"key": "value"} | ||||||
|  | 	label3 := map[string]string{"az": "az1"} | ||||||
|  | 	label4 := map[string]string{"abc": "az11", "def": "az22"} | ||||||
|  | 	label5 := map[string]string{"foo": "bar", "key": "value", "az": "az1"} | ||||||
|  |  | ||||||
|  | 	affinity1 := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"nodeAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 			{ | ||||||
|  | 				"weight": 2, | ||||||
|  | 				"preference": { | ||||||
|  | 					"matchExpressions": [ | ||||||
|  | 						{ | ||||||
|  | 							"key": "foo", | ||||||
|  | 							"operator": "In", "values": ["bar"] | ||||||
|  | 						} | ||||||
|  | 					] | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		]}}`, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	affinity2 := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"nodeAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 			{ | ||||||
|  | 				"weight": 2, | ||||||
|  | 				"preference": {"matchExpressions": [ | ||||||
|  | 					{ | ||||||
|  | 						"key": "foo", | ||||||
|  | 						"operator": "In", "values": ["bar"] | ||||||
|  | 					} | ||||||
|  | 				]} | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				"weight": 4, | ||||||
|  | 				"preference": {"matchExpressions": [ | ||||||
|  | 					{ | ||||||
|  | 						"key": "key", | ||||||
|  | 						"operator": "In", "values": ["value"] | ||||||
|  | 					} | ||||||
|  | 				]} | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				"weight": 5, | ||||||
|  | 				"preference": {"matchExpressions": [ | ||||||
|  | 					{ | ||||||
|  | 						"key": "foo", | ||||||
|  | 						"operator": "In", "values": ["bar"] | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						"key": "key", | ||||||
|  | 						"operator": "In", "values": ["value"] | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						"key": "az", | ||||||
|  | 						"operator": "In", "values": ["az1"] | ||||||
|  | 					} | ||||||
|  | 				]} | ||||||
|  | 			} | ||||||
|  | 		]}}`, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		pod          *v1.Pod | ||||||
|  | 		nodes        []*v1.Node | ||||||
|  | 		expectedList schedulerapi.HostPriorityList | ||||||
|  | 		test         string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: map[string]string{}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "all machines are same priority as NodeAffinity is nil", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: affinity1, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label4}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "no machine macthes preferred scheduling requirements in NodeAffinity of pod so all machines' priority is zero", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: affinity1, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, | ||||||
|  | 			test:         "only machine1 matches the preferred scheduling requirements of pod", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: affinity2, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			nodes: []*v1.Node{ | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: label5}}, | ||||||
|  | 				{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 1}, {Host: "machine5", Score: 10}, {Host: "machine2", Score: 3}}, | ||||||
|  | 			test:         "all machines matches the preferred scheduling requirements of pod but with different priorities ", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, test := range tests { | ||||||
|  | 		nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) | ||||||
|  | 		nap := priorityFunction(CalculateNodeAffinityPriorityMap, CalculateNodeAffinityPriorityReduce) | ||||||
|  | 		list, err := nap(test.pod, nodeNameToInfo, test.nodes) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("unexpected error: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if !reflect.DeepEqual(test.expectedList, list) { | ||||||
|  | 			t.Errorf("%s: \nexpected %#v, \ngot      %#v", test.test, test.expectedList, list) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -14,23 +14,29 @@ go_library( | |||||||
|         "cache.go", |         "cache.go", | ||||||
|         "interface.go", |         "interface.go", | ||||||
|         "node_info.go", |         "node_info.go", | ||||||
|  |         "reconcile_affinity.go", | ||||||
|         "util.go", |         "util.go", | ||||||
|     ], |     ], | ||||||
|     tags = ["automanaged"], |     tags = ["automanaged"], | ||||||
|     deps = [ |     deps = [ | ||||||
|         "//pkg/api/v1:go_default_library", |         "//pkg/api/v1:go_default_library", | ||||||
|  |         "//pkg/features:go_default_library", | ||||||
|         "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", |         "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", | ||||||
|         "//vendor:github.com/golang/glog", |         "//vendor:github.com/golang/glog", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/resource", |         "//vendor:k8s.io/apimachinery/pkg/api/resource", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", |         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", |         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||||
|  |         "//vendor:k8s.io/apiserver/pkg/util/feature", | ||||||
|         "//vendor:k8s.io/client-go/tools/cache", |         "//vendor:k8s.io/client-go/tools/cache", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
| go_test( | go_test( | ||||||
|     name = "go_default_test", |     name = "go_default_test", | ||||||
|     srcs = ["cache_test.go"], |     srcs = [ | ||||||
|  |         "cache_test.go", | ||||||
|  |         "reconcile_affinity_test.go", | ||||||
|  |     ], | ||||||
|     library = ":go_default_library", |     library = ":go_default_library", | ||||||
|     tags = ["automanaged"], |     tags = ["automanaged"], | ||||||
|     deps = [ |     deps = [ | ||||||
| @@ -39,6 +45,7 @@ go_test( | |||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/resource", |         "//vendor:k8s.io/apimachinery/pkg/api/resource", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", |         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", |         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||||
|  |         "//vendor:k8s.io/apiserver/pkg/util/feature", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -217,7 +217,7 @@ func (n *NodeInfo) String() string { | |||||||
| } | } | ||||||
|  |  | ||||||
| func hasPodAffinityConstraints(pod *v1.Pod) bool { | func hasPodAffinityConstraints(pod *v1.Pod) bool { | ||||||
| 	affinity := pod.Spec.Affinity | 	affinity := ReconcileAffinity(pod) | ||||||
| 	return affinity != nil && (affinity.PodAffinity != nil || affinity.PodAntiAffinity != nil) | 	return affinity != nil && (affinity.PodAffinity != nil || affinity.PodAntiAffinity != nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								plugin/pkg/scheduler/schedulercache/reconcile_affinity.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								plugin/pkg/scheduler/schedulercache/reconcile_affinity.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2017 The Kubernetes Authors. | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  |  | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | package schedulercache | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
|  | 	"k8s.io/kubernetes/pkg/api/v1" | ||||||
|  | 	"k8s.io/kubernetes/pkg/features" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Reconcile api and annotation affinity definitions. | ||||||
|  | // When alpha affinity feature is not enabled, always take affinity | ||||||
|  | // from PodSpec.When alpha affinity feature is enabled, if affinity | ||||||
|  | // is not set in PodSpec, take affinity from annotation. | ||||||
|  | // When alpha affinity feature is enabled, if affinity is set in PodSpec, | ||||||
|  | // take node affinity, pod affinity, and pod anti-affinity individually | ||||||
|  | // using the following rule: take affinity from PodSpec if it is defined, | ||||||
|  | // otherwise take from annotation if it is defined. | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func ReconcileAffinity(pod *v1.Pod) *v1.Affinity { | ||||||
|  | 	affinity := pod.Spec.Affinity | ||||||
|  | 	if utilfeature.DefaultFeatureGate.Enabled(features.AffinityInAnnotations) { | ||||||
|  | 		annotationsAffinity, _ := v1.GetAffinityFromPodAnnotations(pod.Annotations) | ||||||
|  | 		if affinity == nil && annotationsAffinity != nil { | ||||||
|  | 			affinity = annotationsAffinity | ||||||
|  | 		} else if annotationsAffinity != nil { | ||||||
|  | 			if affinity != nil && affinity.NodeAffinity == nil && annotationsAffinity.NodeAffinity != nil { | ||||||
|  | 				affinity.NodeAffinity = annotationsAffinity.NodeAffinity | ||||||
|  | 			} | ||||||
|  | 			if affinity != nil && affinity.PodAffinity == nil && annotationsAffinity.PodAffinity != nil { | ||||||
|  | 				affinity.PodAffinity = annotationsAffinity.PodAffinity | ||||||
|  | 			} | ||||||
|  | 			if affinity != nil && affinity.PodAntiAffinity == nil && annotationsAffinity.PodAntiAffinity != nil { | ||||||
|  | 				affinity.PodAntiAffinity = annotationsAffinity.PodAntiAffinity | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return affinity | ||||||
|  | } | ||||||
							
								
								
									
										151
									
								
								plugin/pkg/scheduler/schedulercache/reconcile_affinity_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								plugin/pkg/scheduler/schedulercache/reconcile_affinity_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2017 The Kubernetes Authors. | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  |  | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | package schedulercache | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
|  | 	"k8s.io/kubernetes/pkg/api/v1" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TODO: remove when alpha support for affinity is removed | ||||||
|  | func TestReconcileAffinity(t *testing.T) { | ||||||
|  | 	baseAffinity := &v1.Affinity{ | ||||||
|  | 		NodeAffinity: &v1.NodeAffinity{ | ||||||
|  | 			RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ | ||||||
|  | 				NodeSelectorTerms: []v1.NodeSelectorTerm{ | ||||||
|  | 					{ | ||||||
|  | 						MatchExpressions: []v1.NodeSelectorRequirement{ | ||||||
|  | 							{ | ||||||
|  | 								Key:      "foo", | ||||||
|  | 								Operator: v1.NodeSelectorOpIn, | ||||||
|  | 								Values:   []string{"bar", "value2"}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		PodAffinity: &v1.PodAffinity{ | ||||||
|  | 			RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ | ||||||
|  | 				{ | ||||||
|  | 					LabelSelector: &metav1.LabelSelector{ | ||||||
|  | 						MatchExpressions: []metav1.LabelSelectorRequirement{ | ||||||
|  | 							{ | ||||||
|  | 								Key:      "security", | ||||||
|  | 								Operator: metav1.LabelSelectorOpDoesNotExist, | ||||||
|  | 								Values:   []string{"securityscan"}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					TopologyKey: "topologyKey1", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		PodAntiAffinity: &v1.PodAntiAffinity{ | ||||||
|  | 			RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ | ||||||
|  | 				{ | ||||||
|  | 					LabelSelector: &metav1.LabelSelector{ | ||||||
|  | 						MatchExpressions: []metav1.LabelSelectorRequirement{ | ||||||
|  | 							{ | ||||||
|  | 								Key:      "service", | ||||||
|  | 								Operator: metav1.LabelSelectorOpIn, | ||||||
|  | 								Values:   []string{"S1", "value2"}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					TopologyKey: "topologyKey2", | ||||||
|  | 					Namespaces:  []string{"ns1"}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nodeAffinityAnnotation := map[string]string{ | ||||||
|  | 		v1.AffinityAnnotationKey: ` | ||||||
|  | 		{"nodeAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [ | ||||||
|  | 			{ | ||||||
|  | 				"weight": 2, | ||||||
|  | 				"preference": {"matchExpressions": [ | ||||||
|  | 					{ | ||||||
|  | 						"key": "foo", | ||||||
|  | 						"operator": "In", "values": ["bar"] | ||||||
|  | 					} | ||||||
|  | 				]} | ||||||
|  | 			} | ||||||
|  | 		]}}`, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		pod                *v1.Pod | ||||||
|  | 		expected           *v1.Affinity | ||||||
|  | 		annotationsEnabled bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			// affinity is set in both PodSpec and annotations; take from PodSpec. | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: nodeAffinityAnnotation, | ||||||
|  | 				}, | ||||||
|  | 				Spec: v1.PodSpec{ | ||||||
|  | 					Affinity: baseAffinity, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected:           baseAffinity, | ||||||
|  | 			annotationsEnabled: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			// affinity is only set in annotation; take from annotation. | ||||||
|  | 			pod: &v1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Annotations: nodeAffinityAnnotation, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: &v1.Affinity{ | ||||||
|  | 				NodeAffinity: &v1.NodeAffinity{ | ||||||
|  | 					PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{ | ||||||
|  | 						{ | ||||||
|  | 							Weight: 2, | ||||||
|  | 							Preference: v1.NodeSelectorTerm{ | ||||||
|  | 								MatchExpressions: []v1.NodeSelectorRequirement{ | ||||||
|  | 									{ | ||||||
|  | 										Key:      "foo", | ||||||
|  | 										Operator: v1.NodeSelectorOpIn, | ||||||
|  | 										Values:   []string{"bar"}, | ||||||
|  | 									}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			annotationsEnabled: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, tc := range testCases { | ||||||
|  | 		utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("AffinityInAnnotations=%t", tc.annotationsEnabled)) | ||||||
|  | 		affinity := ReconcileAffinity(tc.pod) | ||||||
|  | 		if !reflect.DeepEqual(affinity, tc.expected) { | ||||||
|  | 			t.Errorf("[%v] Did not get expected affinity:\n\n%v\n\n. got:\n\n %v", i, tc.expected, affinity) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -107,6 +107,11 @@ type FeatureGate interface { | |||||||
| 	// owner: @pweil- | 	// owner: @pweil- | ||||||
| 	// alpha: v1.5 | 	// alpha: v1.5 | ||||||
| 	ExperimentalHostUserNamespaceDefaulting() bool | 	ExperimentalHostUserNamespaceDefaulting() bool | ||||||
|  |  | ||||||
|  | 	// owner: @davidopp | ||||||
|  | 	// alpha: v1.6 | ||||||
|  | 	// TODO: remove when alpha support for affinity is removed | ||||||
|  | 	AffinityInAnnotations() bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // featureGate implements FeatureGate as well as pflag.Value for flag parsing. | // featureGate implements FeatureGate as well as pflag.Value for flag parsing. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Submit Queue
					Kubernetes Submit Queue