node_tree_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /*
  2. Copyright 2018 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 cache
  14. import (
  15. "reflect"
  16. "testing"
  17. "k8s.io/api/core/v1"
  18. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. )
  20. var allNodes = []*v1.Node{
  21. // Node 0: a node without any region-zone label
  22. {
  23. ObjectMeta: metav1.ObjectMeta{
  24. Name: "node-0",
  25. },
  26. },
  27. // Node 1: a node with region label only
  28. {
  29. ObjectMeta: metav1.ObjectMeta{
  30. Name: "node-1",
  31. Labels: map[string]string{
  32. v1.LabelZoneRegion: "region-1",
  33. },
  34. },
  35. },
  36. // Node 2: a node with zone label only
  37. {
  38. ObjectMeta: metav1.ObjectMeta{
  39. Name: "node-2",
  40. Labels: map[string]string{
  41. v1.LabelZoneFailureDomain: "zone-2",
  42. },
  43. },
  44. },
  45. // Node 3: a node with proper region and zone labels
  46. {
  47. ObjectMeta: metav1.ObjectMeta{
  48. Name: "node-3",
  49. Labels: map[string]string{
  50. v1.LabelZoneRegion: "region-1",
  51. v1.LabelZoneFailureDomain: "zone-2",
  52. },
  53. },
  54. },
  55. // Node 4: a node with proper region and zone labels
  56. {
  57. ObjectMeta: metav1.ObjectMeta{
  58. Name: "node-4",
  59. Labels: map[string]string{
  60. v1.LabelZoneRegion: "region-1",
  61. v1.LabelZoneFailureDomain: "zone-2",
  62. },
  63. },
  64. },
  65. // Node 5: a node with proper region and zone labels in a different zone, same region as above
  66. {
  67. ObjectMeta: metav1.ObjectMeta{
  68. Name: "node-5",
  69. Labels: map[string]string{
  70. v1.LabelZoneRegion: "region-1",
  71. v1.LabelZoneFailureDomain: "zone-3",
  72. },
  73. },
  74. },
  75. // Node 6: a node with proper region and zone labels in a new region and zone
  76. {
  77. ObjectMeta: metav1.ObjectMeta{
  78. Name: "node-6",
  79. Labels: map[string]string{
  80. v1.LabelZoneRegion: "region-2",
  81. v1.LabelZoneFailureDomain: "zone-2",
  82. },
  83. },
  84. },
  85. // Node 7: a node with proper region and zone labels in a region and zone as node-6
  86. {
  87. ObjectMeta: metav1.ObjectMeta{
  88. Name: "node-7",
  89. Labels: map[string]string{
  90. v1.LabelZoneRegion: "region-2",
  91. v1.LabelZoneFailureDomain: "zone-2",
  92. },
  93. },
  94. },
  95. // Node 8: a node with proper region and zone labels in a region and zone as node-6
  96. {
  97. ObjectMeta: metav1.ObjectMeta{
  98. Name: "node-8",
  99. Labels: map[string]string{
  100. v1.LabelZoneRegion: "region-2",
  101. v1.LabelZoneFailureDomain: "zone-2",
  102. },
  103. },
  104. },
  105. // Node 9: a node with zone + region label and the deprecated zone + region label
  106. {
  107. ObjectMeta: metav1.ObjectMeta{
  108. Name: "node-9",
  109. Labels: map[string]string{
  110. v1.LabelZoneRegionStable: "region-2",
  111. v1.LabelZoneFailureDomainStable: "zone-2",
  112. v1.LabelZoneRegion: "region-2",
  113. v1.LabelZoneFailureDomain: "zone-2",
  114. },
  115. },
  116. },
  117. // Node 10: a node with only the deprecated zone + region labels
  118. {
  119. ObjectMeta: metav1.ObjectMeta{
  120. Name: "node-10",
  121. Labels: map[string]string{
  122. v1.LabelZoneRegion: "region-2",
  123. v1.LabelZoneFailureDomain: "zone-3",
  124. },
  125. },
  126. },
  127. }
  128. func verifyNodeTree(t *testing.T, nt *nodeTree, expectedTree map[string]*nodeArray) {
  129. expectedNumNodes := int(0)
  130. for _, na := range expectedTree {
  131. expectedNumNodes += len(na.nodes)
  132. }
  133. if numNodes := nt.numNodes; numNodes != expectedNumNodes {
  134. t.Errorf("unexpected nodeTree.numNodes. Expected: %v, Got: %v", expectedNumNodes, numNodes)
  135. }
  136. if !reflect.DeepEqual(nt.tree, expectedTree) {
  137. t.Errorf("The node tree is not the same as expected. Expected: %v, Got: %v", expectedTree, nt.tree)
  138. }
  139. if len(nt.zones) != len(expectedTree) {
  140. t.Errorf("Number of zones in nodeTree.zones is not expected. Expected: %v, Got: %v", len(expectedTree), len(nt.zones))
  141. }
  142. for _, z := range nt.zones {
  143. if _, ok := expectedTree[z]; !ok {
  144. t.Errorf("zone %v is not expected to exist in nodeTree.zones", z)
  145. }
  146. }
  147. }
  148. func TestNodeTree_AddNode(t *testing.T) {
  149. tests := []struct {
  150. name string
  151. nodesToAdd []*v1.Node
  152. expectedTree map[string]*nodeArray
  153. }{
  154. {
  155. name: "single node no labels",
  156. nodesToAdd: allNodes[:1],
  157. expectedTree: map[string]*nodeArray{"": {[]string{"node-0"}, 0}},
  158. },
  159. {
  160. name: "mix of nodes with and without proper labels",
  161. nodesToAdd: allNodes[:4],
  162. expectedTree: map[string]*nodeArray{
  163. "": {[]string{"node-0"}, 0},
  164. "region-1:\x00:": {[]string{"node-1"}, 0},
  165. ":\x00:zone-2": {[]string{"node-2"}, 0},
  166. "region-1:\x00:zone-2": {[]string{"node-3"}, 0},
  167. },
  168. },
  169. {
  170. name: "mix of nodes with and without proper labels and some zones with multiple nodes",
  171. nodesToAdd: allNodes[:7],
  172. expectedTree: map[string]*nodeArray{
  173. "": {[]string{"node-0"}, 0},
  174. "region-1:\x00:": {[]string{"node-1"}, 0},
  175. ":\x00:zone-2": {[]string{"node-2"}, 0},
  176. "region-1:\x00:zone-2": {[]string{"node-3", "node-4"}, 0},
  177. "region-1:\x00:zone-3": {[]string{"node-5"}, 0},
  178. "region-2:\x00:zone-2": {[]string{"node-6"}, 0},
  179. },
  180. },
  181. {
  182. name: "nodes also using deprecated zone/region label",
  183. nodesToAdd: allNodes[9:],
  184. expectedTree: map[string]*nodeArray{
  185. "region-2:\x00:zone-2": {[]string{"node-9"}, 0},
  186. "region-2:\x00:zone-3": {[]string{"node-10"}, 0},
  187. },
  188. },
  189. }
  190. for _, test := range tests {
  191. t.Run(test.name, func(t *testing.T) {
  192. nt := newNodeTree(nil)
  193. for _, n := range test.nodesToAdd {
  194. nt.addNode(n)
  195. }
  196. verifyNodeTree(t, nt, test.expectedTree)
  197. })
  198. }
  199. }
  200. func TestNodeTree_RemoveNode(t *testing.T) {
  201. tests := []struct {
  202. name string
  203. existingNodes []*v1.Node
  204. nodesToRemove []*v1.Node
  205. expectedTree map[string]*nodeArray
  206. expectError bool
  207. }{
  208. {
  209. name: "remove a single node with no labels",
  210. existingNodes: allNodes[:7],
  211. nodesToRemove: allNodes[:1],
  212. expectedTree: map[string]*nodeArray{
  213. "region-1:\x00:": {[]string{"node-1"}, 0},
  214. ":\x00:zone-2": {[]string{"node-2"}, 0},
  215. "region-1:\x00:zone-2": {[]string{"node-3", "node-4"}, 0},
  216. "region-1:\x00:zone-3": {[]string{"node-5"}, 0},
  217. "region-2:\x00:zone-2": {[]string{"node-6"}, 0},
  218. },
  219. },
  220. {
  221. name: "remove a few nodes including one from a zone with multiple nodes",
  222. existingNodes: allNodes[:7],
  223. nodesToRemove: allNodes[1:4],
  224. expectedTree: map[string]*nodeArray{
  225. "": {[]string{"node-0"}, 0},
  226. "region-1:\x00:zone-2": {[]string{"node-4"}, 0},
  227. "region-1:\x00:zone-3": {[]string{"node-5"}, 0},
  228. "region-2:\x00:zone-2": {[]string{"node-6"}, 0},
  229. },
  230. },
  231. {
  232. name: "remove all nodes",
  233. existingNodes: allNodes[:7],
  234. nodesToRemove: allNodes[:7],
  235. expectedTree: map[string]*nodeArray{},
  236. },
  237. {
  238. name: "remove non-existing node",
  239. existingNodes: nil,
  240. nodesToRemove: allNodes[:5],
  241. expectedTree: map[string]*nodeArray{},
  242. expectError: true,
  243. },
  244. }
  245. for _, test := range tests {
  246. t.Run(test.name, func(t *testing.T) {
  247. nt := newNodeTree(test.existingNodes)
  248. for _, n := range test.nodesToRemove {
  249. err := nt.removeNode(n)
  250. if test.expectError == (err == nil) {
  251. t.Errorf("unexpected returned error value: %v", err)
  252. }
  253. }
  254. verifyNodeTree(t, nt, test.expectedTree)
  255. })
  256. }
  257. }
  258. func TestNodeTree_UpdateNode(t *testing.T) {
  259. tests := []struct {
  260. name string
  261. existingNodes []*v1.Node
  262. nodeToUpdate *v1.Node
  263. expectedTree map[string]*nodeArray
  264. }{
  265. {
  266. name: "update a node without label",
  267. existingNodes: allNodes[:7],
  268. nodeToUpdate: &v1.Node{
  269. ObjectMeta: metav1.ObjectMeta{
  270. Name: "node-0",
  271. Labels: map[string]string{
  272. v1.LabelZoneRegion: "region-1",
  273. v1.LabelZoneFailureDomain: "zone-2",
  274. },
  275. },
  276. },
  277. expectedTree: map[string]*nodeArray{
  278. "region-1:\x00:": {[]string{"node-1"}, 0},
  279. ":\x00:zone-2": {[]string{"node-2"}, 0},
  280. "region-1:\x00:zone-2": {[]string{"node-3", "node-4", "node-0"}, 0},
  281. "region-1:\x00:zone-3": {[]string{"node-5"}, 0},
  282. "region-2:\x00:zone-2": {[]string{"node-6"}, 0},
  283. },
  284. },
  285. {
  286. name: "update the only existing node",
  287. existingNodes: allNodes[:1],
  288. nodeToUpdate: &v1.Node{
  289. ObjectMeta: metav1.ObjectMeta{
  290. Name: "node-0",
  291. Labels: map[string]string{
  292. v1.LabelZoneRegion: "region-1",
  293. v1.LabelZoneFailureDomain: "zone-2",
  294. },
  295. },
  296. },
  297. expectedTree: map[string]*nodeArray{
  298. "region-1:\x00:zone-2": {[]string{"node-0"}, 0},
  299. },
  300. },
  301. {
  302. name: "update non-existing node",
  303. existingNodes: allNodes[:1],
  304. nodeToUpdate: &v1.Node{
  305. ObjectMeta: metav1.ObjectMeta{
  306. Name: "node-new",
  307. Labels: map[string]string{
  308. v1.LabelZoneRegion: "region-1",
  309. v1.LabelZoneFailureDomain: "zone-2",
  310. },
  311. },
  312. },
  313. expectedTree: map[string]*nodeArray{
  314. "": {[]string{"node-0"}, 0},
  315. "region-1:\x00:zone-2": {[]string{"node-new"}, 0},
  316. },
  317. },
  318. }
  319. for _, test := range tests {
  320. t.Run(test.name, func(t *testing.T) {
  321. nt := newNodeTree(test.existingNodes)
  322. var oldNode *v1.Node
  323. for _, n := range allNodes {
  324. if n.Name == test.nodeToUpdate.Name {
  325. oldNode = n
  326. break
  327. }
  328. }
  329. if oldNode == nil {
  330. oldNode = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "nonexisting-node"}}
  331. }
  332. nt.updateNode(oldNode, test.nodeToUpdate)
  333. verifyNodeTree(t, nt, test.expectedTree)
  334. })
  335. }
  336. }
  337. func TestNodeTree_Next(t *testing.T) {
  338. tests := []struct {
  339. name string
  340. nodesToAdd []*v1.Node
  341. numRuns int // number of times to run Next()
  342. expectedOutput []string
  343. }{
  344. {
  345. name: "empty tree",
  346. nodesToAdd: nil,
  347. numRuns: 2,
  348. expectedOutput: []string{"", ""},
  349. },
  350. {
  351. name: "should go back to the first node after finishing a round",
  352. nodesToAdd: allNodes[:1],
  353. numRuns: 2,
  354. expectedOutput: []string{"node-0", "node-0"},
  355. },
  356. {
  357. name: "should go back to the first node after going over all nodes",
  358. nodesToAdd: allNodes[:4],
  359. numRuns: 5,
  360. expectedOutput: []string{"node-0", "node-1", "node-2", "node-3", "node-0"},
  361. },
  362. {
  363. name: "should go to all zones before going to the second nodes in the same zone",
  364. nodesToAdd: allNodes[:9],
  365. numRuns: 11,
  366. expectedOutput: []string{"node-0", "node-1", "node-2", "node-3", "node-5", "node-6", "node-4", "node-7", "node-8", "node-0", "node-1"},
  367. },
  368. }
  369. for _, test := range tests {
  370. t.Run(test.name, func(t *testing.T) {
  371. nt := newNodeTree(test.nodesToAdd)
  372. var output []string
  373. for i := 0; i < test.numRuns; i++ {
  374. output = append(output, nt.next())
  375. }
  376. if !reflect.DeepEqual(output, test.expectedOutput) {
  377. t.Errorf("unexpected output. Expected: %v, Got: %v", test.expectedOutput, output)
  378. }
  379. })
  380. }
  381. }
  382. func TestNodeTreeMultiOperations(t *testing.T) {
  383. tests := []struct {
  384. name string
  385. nodesToAdd []*v1.Node
  386. nodesToRemove []*v1.Node
  387. operations []string
  388. expectedOutput []string
  389. }{
  390. {
  391. name: "add and remove all nodes between two Next operations",
  392. nodesToAdd: allNodes[2:9],
  393. nodesToRemove: allNodes[2:9],
  394. operations: []string{"add", "add", "next", "add", "remove", "remove", "remove", "next"},
  395. expectedOutput: []string{"node-2", ""},
  396. },
  397. {
  398. name: "add and remove some nodes between two Next operations",
  399. nodesToAdd: allNodes[2:9],
  400. nodesToRemove: allNodes[2:9],
  401. operations: []string{"add", "add", "next", "add", "remove", "remove", "next"},
  402. expectedOutput: []string{"node-2", "node-4"},
  403. },
  404. {
  405. name: "remove nodes already iterated on and add new nodes",
  406. nodesToAdd: allNodes[2:9],
  407. nodesToRemove: allNodes[2:9],
  408. operations: []string{"add", "add", "next", "next", "add", "remove", "remove", "next"},
  409. expectedOutput: []string{"node-2", "node-3", "node-4"},
  410. },
  411. {
  412. name: "add more nodes to an exhausted zone",
  413. nodesToAdd: append(allNodes[4:9], allNodes[3]),
  414. nodesToRemove: nil,
  415. operations: []string{"add", "add", "add", "add", "add", "next", "next", "next", "next", "add", "next", "next", "next"},
  416. expectedOutput: []string{"node-4", "node-6", "node-7", "node-8", "node-3", "node-4", "node-6"},
  417. },
  418. {
  419. name: "remove zone and add new to ensure exhausted is reset correctly",
  420. nodesToAdd: append(allNodes[3:5], allNodes[6:8]...),
  421. nodesToRemove: allNodes[3:5],
  422. operations: []string{"add", "add", "next", "next", "remove", "add", "add", "next", "next", "remove", "next", "next"},
  423. expectedOutput: []string{"node-3", "node-4", "node-6", "node-7", "node-6", "node-7"},
  424. },
  425. }
  426. for _, test := range tests {
  427. t.Run(test.name, func(t *testing.T) {
  428. nt := newNodeTree(nil)
  429. addIndex := 0
  430. removeIndex := 0
  431. var output []string
  432. for _, op := range test.operations {
  433. switch op {
  434. case "add":
  435. if addIndex >= len(test.nodesToAdd) {
  436. t.Error("more add operations than nodesToAdd")
  437. } else {
  438. nt.addNode(test.nodesToAdd[addIndex])
  439. addIndex++
  440. }
  441. case "remove":
  442. if removeIndex >= len(test.nodesToRemove) {
  443. t.Error("more remove operations than nodesToRemove")
  444. } else {
  445. nt.removeNode(test.nodesToRemove[removeIndex])
  446. removeIndex++
  447. }
  448. case "next":
  449. output = append(output, nt.next())
  450. default:
  451. t.Errorf("unknow operation: %v", op)
  452. }
  453. }
  454. if !reflect.DeepEqual(output, test.expectedOutput) {
  455. t.Errorf("unexpected output. Expected: %v, Got: %v", test.expectedOutput, output)
  456. }
  457. })
  458. }
  459. }