service_affinity_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package serviceaffinity
  14. import (
  15. "context"
  16. "reflect"
  17. "sort"
  18. "testing"
  19. v1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
  22. "k8s.io/kubernetes/pkg/scheduler/internal/cache"
  23. fakelisters "k8s.io/kubernetes/pkg/scheduler/listers/fake"
  24. "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
  25. )
  26. func TestServiceAffinity(t *testing.T) {
  27. selector := map[string]string{"foo": "bar"}
  28. labels1 := map[string]string{
  29. "region": "r1",
  30. "zone": "z11",
  31. }
  32. labels2 := map[string]string{
  33. "region": "r1",
  34. "zone": "z12",
  35. }
  36. labels3 := map[string]string{
  37. "region": "r2",
  38. "zone": "z21",
  39. }
  40. labels4 := map[string]string{
  41. "region": "r2",
  42. "zone": "z22",
  43. }
  44. node1 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labels1}}
  45. node2 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labels2}}
  46. node3 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labels3}}
  47. node4 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine4", Labels: labels4}}
  48. node5 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: labels4}}
  49. tests := []struct {
  50. name string
  51. pod *v1.Pod
  52. pods []*v1.Pod
  53. services []*v1.Service
  54. node *v1.Node
  55. labels []string
  56. res framework.Code
  57. }{
  58. {
  59. name: "nothing scheduled",
  60. pod: new(v1.Pod),
  61. node: &node1,
  62. labels: []string{"region"},
  63. res: framework.Success,
  64. },
  65. {
  66. name: "pod with region label match",
  67. pod: &v1.Pod{Spec: v1.PodSpec{NodeSelector: map[string]string{"region": "r1"}}},
  68. node: &node1,
  69. labels: []string{"region"},
  70. res: framework.Success,
  71. },
  72. {
  73. name: "pod with region label mismatch",
  74. pod: &v1.Pod{Spec: v1.PodSpec{NodeSelector: map[string]string{"region": "r2"}}},
  75. node: &node1,
  76. labels: []string{"region"},
  77. res: framework.Unschedulable,
  78. },
  79. {
  80. name: "service pod on same node",
  81. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector}},
  82. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: selector}}},
  83. node: &node1,
  84. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}}},
  85. labels: []string{"region"},
  86. res: framework.Success,
  87. },
  88. {
  89. name: "service pod on different node, region match",
  90. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector}},
  91. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: selector}}},
  92. node: &node1,
  93. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}}},
  94. labels: []string{"region"},
  95. res: framework.Success,
  96. },
  97. {
  98. name: "service pod on different node, region mismatch",
  99. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector}},
  100. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: selector}}},
  101. node: &node1,
  102. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}}},
  103. labels: []string{"region"},
  104. res: framework.Unschedulable,
  105. },
  106. {
  107. name: "service in different namespace, region mismatch",
  108. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns1"}},
  109. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns1"}}},
  110. node: &node1,
  111. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}, ObjectMeta: metav1.ObjectMeta{Namespace: "ns2"}}},
  112. labels: []string{"region"},
  113. res: framework.Success,
  114. },
  115. {
  116. name: "pod in different namespace, region mismatch",
  117. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns1"}},
  118. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns2"}}},
  119. node: &node1,
  120. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}, ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"}}},
  121. labels: []string{"region"},
  122. res: framework.Success,
  123. },
  124. {
  125. name: "service and pod in same namespace, region mismatch",
  126. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns1"}},
  127. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: metav1.ObjectMeta{Labels: selector, Namespace: "ns1"}}},
  128. node: &node1,
  129. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}, ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"}}},
  130. labels: []string{"region"},
  131. res: framework.Unschedulable,
  132. },
  133. {
  134. name: "service pod on different node, multiple labels, not all match",
  135. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector}},
  136. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: selector}}},
  137. node: &node1,
  138. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}}},
  139. labels: []string{"region", "zone"},
  140. res: framework.Unschedulable,
  141. },
  142. {
  143. name: "service pod on different node, multiple labels, all match",
  144. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: selector}},
  145. pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine5"}, ObjectMeta: metav1.ObjectMeta{Labels: selector}}},
  146. node: &node4,
  147. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector}}},
  148. labels: []string{"region", "zone"},
  149. res: framework.Success,
  150. },
  151. }
  152. for _, test := range tests {
  153. t.Run(test.name, func(t *testing.T) {
  154. nodes := []*v1.Node{&node1, &node2, &node3, &node4, &node5}
  155. snapshot := cache.NewSnapshot(test.pods, nodes)
  156. p := &ServiceAffinity{
  157. sharedLister: snapshot,
  158. serviceLister: fakelisters.ServiceLister(test.services),
  159. args: Args{
  160. AffinityLabels: test.labels,
  161. },
  162. }
  163. state := framework.NewCycleState()
  164. if s := p.PreFilter(context.Background(), state, test.pod); !s.IsSuccess() {
  165. t.Errorf("PreFilter failed: %v", s.Message())
  166. }
  167. nodeInfo := mustGetNodeInfo(t, snapshot, test.node.Name)
  168. status := p.Filter(context.Background(), state, test.pod, nodeInfo)
  169. if status.Code() != test.res {
  170. t.Errorf("Status mismatch. got: %v, want: %v", status.Code(), test.res)
  171. }
  172. })
  173. }
  174. }
  175. func TestServiceAffinityScore(t *testing.T) {
  176. labels1 := map[string]string{
  177. "foo": "bar",
  178. "baz": "blah",
  179. }
  180. labels2 := map[string]string{
  181. "bar": "foo",
  182. "baz": "blah",
  183. }
  184. zone1 := map[string]string{
  185. "zone": "zone1",
  186. }
  187. zone1Rack1 := map[string]string{
  188. "zone": "zone1",
  189. "rack": "rack1",
  190. }
  191. zone1Rack2 := map[string]string{
  192. "zone": "zone1",
  193. "rack": "rack2",
  194. }
  195. zone2 := map[string]string{
  196. "zone": "zone2",
  197. }
  198. zone2Rack1 := map[string]string{
  199. "zone": "zone2",
  200. "rack": "rack1",
  201. }
  202. nozone := map[string]string{
  203. "name": "value",
  204. }
  205. zone0Spec := v1.PodSpec{
  206. NodeName: "machine01",
  207. }
  208. zone1Spec := v1.PodSpec{
  209. NodeName: "machine11",
  210. }
  211. zone2Spec := v1.PodSpec{
  212. NodeName: "machine21",
  213. }
  214. labeledNodes := map[string]map[string]string{
  215. "machine01": nozone, "machine02": nozone,
  216. "machine11": zone1, "machine12": zone1,
  217. "machine21": zone2, "machine22": zone2,
  218. }
  219. nodesWithZoneAndRackLabels := map[string]map[string]string{
  220. "machine01": nozone, "machine02": nozone,
  221. "machine11": zone1Rack1, "machine12": zone1Rack2,
  222. "machine21": zone2Rack1, "machine22": zone2Rack1,
  223. }
  224. tests := []struct {
  225. pod *v1.Pod
  226. pods []*v1.Pod
  227. nodes map[string]map[string]string
  228. services []*v1.Service
  229. labels []string
  230. expectedList framework.NodeScoreList
  231. name string
  232. }{
  233. {
  234. pod: new(v1.Pod),
  235. nodes: labeledNodes,
  236. labels: []string{"zone"},
  237. expectedList: []framework.NodeScore{{Name: "machine11", Score: framework.MaxNodeScore}, {Name: "machine12", Score: framework.MaxNodeScore},
  238. {Name: "machine21", Score: framework.MaxNodeScore}, {Name: "machine22", Score: framework.MaxNodeScore},
  239. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  240. name: "nothing scheduled",
  241. },
  242. {
  243. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  244. pods: []*v1.Pod{{Spec: zone1Spec}},
  245. nodes: labeledNodes,
  246. labels: []string{"zone"},
  247. expectedList: []framework.NodeScore{{Name: "machine11", Score: framework.MaxNodeScore}, {Name: "machine12", Score: framework.MaxNodeScore},
  248. {Name: "machine21", Score: framework.MaxNodeScore}, {Name: "machine22", Score: framework.MaxNodeScore},
  249. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  250. name: "no services",
  251. },
  252. {
  253. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  254. pods: []*v1.Pod{{Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}}},
  255. nodes: labeledNodes,
  256. labels: []string{"zone"},
  257. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: map[string]string{"key": "value"}}}},
  258. expectedList: []framework.NodeScore{{Name: "machine11", Score: framework.MaxNodeScore}, {Name: "machine12", Score: framework.MaxNodeScore},
  259. {Name: "machine21", Score: framework.MaxNodeScore}, {Name: "machine22", Score: framework.MaxNodeScore},
  260. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  261. name: "different services",
  262. },
  263. {
  264. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  265. pods: []*v1.Pod{
  266. {Spec: zone0Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  267. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  268. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  269. },
  270. nodes: labeledNodes,
  271. labels: []string{"zone"},
  272. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}},
  273. expectedList: []framework.NodeScore{{Name: "machine11", Score: framework.MaxNodeScore}, {Name: "machine12", Score: framework.MaxNodeScore},
  274. {Name: "machine21", Score: 0}, {Name: "machine22", Score: 0},
  275. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  276. name: "three pods, one service pod",
  277. },
  278. {
  279. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  280. pods: []*v1.Pod{
  281. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  282. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  283. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  284. },
  285. nodes: labeledNodes,
  286. labels: []string{"zone"},
  287. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}},
  288. expectedList: []framework.NodeScore{{Name: "machine11", Score: 50}, {Name: "machine12", Score: 50},
  289. {Name: "machine21", Score: 50}, {Name: "machine22", Score: 50},
  290. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  291. name: "three pods, two service pods on different machines",
  292. },
  293. {
  294. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1, Namespace: metav1.NamespaceDefault}},
  295. pods: []*v1.Pod{
  296. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  297. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1, Namespace: metav1.NamespaceDefault}},
  298. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  299. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1, Namespace: "ns1"}},
  300. },
  301. nodes: labeledNodes,
  302. labels: []string{"zone"},
  303. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}, ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}}},
  304. expectedList: []framework.NodeScore{{Name: "machine11", Score: 0}, {Name: "machine12", Score: 0},
  305. {Name: "machine21", Score: framework.MaxNodeScore}, {Name: "machine22", Score: framework.MaxNodeScore},
  306. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  307. name: "three service label match pods in different namespaces",
  308. },
  309. {
  310. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  311. pods: []*v1.Pod{
  312. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  313. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  314. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  315. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  316. },
  317. nodes: labeledNodes,
  318. labels: []string{"zone"},
  319. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}},
  320. expectedList: []framework.NodeScore{{Name: "machine11", Score: 66}, {Name: "machine12", Score: 66},
  321. {Name: "machine21", Score: 33}, {Name: "machine22", Score: 33},
  322. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  323. name: "four pods, three service pods",
  324. },
  325. {
  326. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  327. pods: []*v1.Pod{
  328. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  329. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  330. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  331. },
  332. nodes: labeledNodes,
  333. labels: []string{"zone"},
  334. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: map[string]string{"baz": "blah"}}}},
  335. expectedList: []framework.NodeScore{{Name: "machine11", Score: 33}, {Name: "machine12", Score: 33},
  336. {Name: "machine21", Score: 66}, {Name: "machine22", Score: 66},
  337. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  338. name: "service with partial pod label matches",
  339. },
  340. {
  341. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  342. pods: []*v1.Pod{
  343. {Spec: zone0Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  344. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  345. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  346. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  347. },
  348. nodes: labeledNodes,
  349. labels: []string{"zone"},
  350. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}},
  351. expectedList: []framework.NodeScore{{Name: "machine11", Score: 75}, {Name: "machine12", Score: 75},
  352. {Name: "machine21", Score: 50}, {Name: "machine22", Score: 50},
  353. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  354. name: "service pod on non-zoned node",
  355. },
  356. {
  357. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  358. pods: []*v1.Pod{
  359. {Spec: zone0Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels2}},
  360. {Spec: zone1Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  361. {Spec: zone2Spec, ObjectMeta: metav1.ObjectMeta{Labels: labels1}},
  362. },
  363. nodes: nodesWithZoneAndRackLabels,
  364. labels: []string{"zone", "rack"},
  365. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}},
  366. expectedList: []framework.NodeScore{{Name: "machine11", Score: 25}, {Name: "machine12", Score: 75},
  367. {Name: "machine21", Score: 25}, {Name: "machine22", Score: 25},
  368. {Name: "machine01", Score: 0}, {Name: "machine02", Score: 0}},
  369. name: "three pods, two service pods, with rack label",
  370. },
  371. }
  372. for _, test := range tests {
  373. t.Run(test.name, func(t *testing.T) {
  374. nodes := makeLabeledNodeList(test.nodes)
  375. snapshot := cache.NewSnapshot(test.pods, nodes)
  376. serviceLister := fakelisters.ServiceLister(test.services)
  377. p := &ServiceAffinity{
  378. sharedLister: snapshot,
  379. serviceLister: serviceLister,
  380. args: Args{
  381. AntiAffinityLabelsPreference: test.labels,
  382. },
  383. }
  384. state := framework.NewCycleState()
  385. var gotList framework.NodeScoreList
  386. for _, n := range makeLabeledNodeList(test.nodes) {
  387. score, status := p.Score(context.Background(), state, test.pod, n.Name)
  388. if !status.IsSuccess() {
  389. t.Errorf("unexpected error: %v", status)
  390. }
  391. gotList = append(gotList, framework.NodeScore{Name: n.Name, Score: score})
  392. }
  393. status := p.ScoreExtensions().NormalizeScore(context.Background(), state, test.pod, gotList)
  394. if !status.IsSuccess() {
  395. t.Errorf("unexpected error: %v", status)
  396. }
  397. // sort the two lists to avoid failures on account of different ordering
  398. sortNodeScoreList(test.expectedList)
  399. sortNodeScoreList(gotList)
  400. if !reflect.DeepEqual(test.expectedList, gotList) {
  401. t.Errorf("expected %#v, got %#v", test.expectedList, gotList)
  402. }
  403. })
  404. }
  405. }
  406. func TestPreFilterStateAddRemovePod(t *testing.T) {
  407. var label1 = map[string]string{
  408. "region": "r1",
  409. "zone": "z11",
  410. }
  411. var label2 = map[string]string{
  412. "region": "r1",
  413. "zone": "z12",
  414. }
  415. var label3 = map[string]string{
  416. "region": "r2",
  417. "zone": "z21",
  418. }
  419. selector1 := map[string]string{"foo": "bar"}
  420. tests := []struct {
  421. name string
  422. pendingPod *v1.Pod
  423. addedPod *v1.Pod
  424. existingPods []*v1.Pod
  425. nodes []*v1.Node
  426. services []*v1.Service
  427. }{
  428. {
  429. name: "no anti-affinity or service affinity exist",
  430. pendingPod: &v1.Pod{
  431. ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1},
  432. },
  433. existingPods: []*v1.Pod{
  434. {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1},
  435. Spec: v1.PodSpec{NodeName: "nodeA"},
  436. },
  437. {ObjectMeta: metav1.ObjectMeta{Name: "p2"},
  438. Spec: v1.PodSpec{NodeName: "nodeC"},
  439. },
  440. },
  441. addedPod: &v1.Pod{
  442. ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1},
  443. Spec: v1.PodSpec{NodeName: "nodeB"},
  444. },
  445. nodes: []*v1.Node{
  446. {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}},
  447. {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}},
  448. {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}},
  449. },
  450. },
  451. {
  452. name: "metadata service-affinity data are updated correctly after adding and removing a pod",
  453. pendingPod: &v1.Pod{
  454. ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1},
  455. },
  456. existingPods: []*v1.Pod{
  457. {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1},
  458. Spec: v1.PodSpec{NodeName: "nodeA"},
  459. },
  460. {ObjectMeta: metav1.ObjectMeta{Name: "p2"},
  461. Spec: v1.PodSpec{NodeName: "nodeC"},
  462. },
  463. },
  464. addedPod: &v1.Pod{
  465. ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1},
  466. Spec: v1.PodSpec{NodeName: "nodeB"},
  467. },
  468. services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}},
  469. nodes: []*v1.Node{
  470. {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}},
  471. {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}},
  472. {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}},
  473. },
  474. },
  475. }
  476. for _, test := range tests {
  477. t.Run(test.name, func(t *testing.T) {
  478. // getMeta creates predicate meta data given the list of pods.
  479. getState := func(pods []*v1.Pod) (*ServiceAffinity, *framework.CycleState, *preFilterState, *cache.Snapshot) {
  480. snapshot := cache.NewSnapshot(pods, test.nodes)
  481. p := &ServiceAffinity{
  482. sharedLister: snapshot,
  483. serviceLister: fakelisters.ServiceLister(test.services),
  484. }
  485. cycleState := framework.NewCycleState()
  486. preFilterStatus := p.PreFilter(context.Background(), cycleState, test.pendingPod)
  487. if !preFilterStatus.IsSuccess() {
  488. t.Errorf("prefilter failed with status: %v", preFilterStatus)
  489. }
  490. plState, err := getPreFilterState(cycleState)
  491. if err != nil {
  492. t.Errorf("failed to get metadata from cycleState: %v", err)
  493. }
  494. return p, cycleState, plState, snapshot
  495. }
  496. sortState := func(plState *preFilterState) *preFilterState {
  497. sort.SliceStable(plState.matchingPodList, func(i, j int) bool {
  498. return plState.matchingPodList[i].Name < plState.matchingPodList[j].Name
  499. })
  500. sort.SliceStable(plState.matchingPodServices, func(i, j int) bool {
  501. return plState.matchingPodServices[i].Name < plState.matchingPodServices[j].Name
  502. })
  503. return plState
  504. }
  505. // allPodsState is the state produced when all pods, including test.addedPod are given to prefilter.
  506. _, _, plStateAllPods, _ := getState(append(test.existingPods, test.addedPod))
  507. // state is produced for test.existingPods (without test.addedPod).
  508. ipa, state, plState, snapshot := getState(test.existingPods)
  509. // clone the state so that we can compare it later when performing Remove.
  510. plStateOriginal, _ := plState.Clone().(*preFilterState)
  511. // Add test.addedPod to state1 and verify it is equal to allPodsState.
  512. nodeInfo := mustGetNodeInfo(t, snapshot, test.addedPod.Spec.NodeName)
  513. if err := ipa.AddPod(context.Background(), state, test.pendingPod, test.addedPod, nodeInfo); err != nil {
  514. t.Errorf("error adding pod to preFilterState: %v", err)
  515. }
  516. if !reflect.DeepEqual(sortState(plStateAllPods), sortState(plState)) {
  517. t.Errorf("State is not equal, got: %v, want: %v", plState, plStateAllPods)
  518. }
  519. // Remove the added pod pod and make sure it is equal to the original state.
  520. if err := ipa.RemovePod(context.Background(), state, test.pendingPod, test.addedPod, nodeInfo); err != nil {
  521. t.Errorf("error removing pod from preFilterState: %v", err)
  522. }
  523. if !reflect.DeepEqual(sortState(plStateOriginal), sortState(plState)) {
  524. t.Errorf("State is not equal, got: %v, want: %v", plState, plStateOriginal)
  525. }
  526. })
  527. }
  528. }
  529. func TestPreFilterStateClone(t *testing.T) {
  530. source := &preFilterState{
  531. matchingPodList: []*v1.Pod{
  532. {ObjectMeta: metav1.ObjectMeta{Name: "pod1"}},
  533. {ObjectMeta: metav1.ObjectMeta{Name: "pod2"}},
  534. },
  535. matchingPodServices: []*v1.Service{
  536. {ObjectMeta: metav1.ObjectMeta{Name: "service1"}},
  537. },
  538. }
  539. clone := source.Clone()
  540. if clone == source {
  541. t.Errorf("Clone returned the exact same object!")
  542. }
  543. if !reflect.DeepEqual(clone, source) {
  544. t.Errorf("Copy is not equal to source!")
  545. }
  546. }
  547. func makeLabeledNodeList(nodeMap map[string]map[string]string) []*v1.Node {
  548. nodes := make([]*v1.Node, 0, len(nodeMap))
  549. for nodeName, labels := range nodeMap {
  550. nodes = append(nodes, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName, Labels: labels}})
  551. }
  552. return nodes
  553. }
  554. func sortNodeScoreList(out framework.NodeScoreList) {
  555. sort.Slice(out, func(i, j int) bool {
  556. if out[i].Score == out[j].Score {
  557. return out[i].Name < out[j].Name
  558. }
  559. return out[i].Score < out[j].Score
  560. })
  561. }
  562. func mustGetNodeInfo(t *testing.T, snapshot *cache.Snapshot, name string) *nodeinfo.NodeInfo {
  563. t.Helper()
  564. nodeInfo, err := snapshot.NodeInfos().Get(name)
  565. if err != nil {
  566. t.Fatal(err)
  567. }
  568. return nodeInfo
  569. }