nodeinfomanager_test.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  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 nodeinfomanager
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "k8s.io/apimachinery/pkg/runtime"
  18. "reflect"
  19. "testing"
  20. "github.com/stretchr/testify/assert"
  21. "k8s.io/api/core/v1"
  22. storage "k8s.io/api/storage/v1beta1"
  23. "k8s.io/apimachinery/pkg/api/errors"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. "k8s.io/apimachinery/pkg/util/strategicpatch"
  28. utilfeature "k8s.io/apiserver/pkg/util/feature"
  29. "k8s.io/client-go/kubernetes/fake"
  30. clienttesting "k8s.io/client-go/testing"
  31. utiltesting "k8s.io/client-go/util/testing"
  32. featuregatetesting "k8s.io/component-base/featuregate/testing"
  33. "k8s.io/kubernetes/pkg/apis/core/helper"
  34. "k8s.io/kubernetes/pkg/features"
  35. volumetest "k8s.io/kubernetes/pkg/volume/testing"
  36. "k8s.io/kubernetes/pkg/volume/util"
  37. )
  38. type testcase struct {
  39. name string
  40. driverName string
  41. existingNode *v1.Node
  42. existingCSINode *storage.CSINode
  43. inputNodeID string
  44. inputTopology map[string]string
  45. inputVolumeLimit int64
  46. expectedNode *v1.Node
  47. expectedCSINode *storage.CSINode
  48. expectFail bool
  49. hasModified bool
  50. }
  51. type nodeIDMap map[string]string
  52. type topologyKeyMap map[string][]string
  53. type labelMap map[string]string
  54. // TestInstallCSIDriver tests InstallCSIDriver with various existing Node and/or CSINode objects.
  55. // The node IDs in all test cases below are the same between the Node annotation and CSINode.
  56. func TestInstallCSIDriver(t *testing.T) {
  57. testcases := []testcase{
  58. {
  59. name: "empty node",
  60. driverName: "com.example.csi.driver1",
  61. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  62. inputNodeID: "com.example.csi/csi-node1",
  63. inputTopology: map[string]string{
  64. "com.example.csi/zone": "zoneA",
  65. },
  66. expectedNode: &v1.Node{
  67. ObjectMeta: metav1.ObjectMeta{
  68. Name: "node1",
  69. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  70. Labels: labelMap{"com.example.csi/zone": "zoneA"},
  71. },
  72. },
  73. expectedCSINode: &storage.CSINode{
  74. ObjectMeta: getCSINodeObjectMeta(),
  75. Spec: storage.CSINodeSpec{
  76. Drivers: []storage.CSINodeDriver{
  77. {
  78. Name: "com.example.csi.driver1",
  79. NodeID: "com.example.csi/csi-node1",
  80. TopologyKeys: []string{"com.example.csi/zone"},
  81. },
  82. },
  83. },
  84. },
  85. },
  86. {
  87. name: "pre-existing node info from the same driver",
  88. driverName: "com.example.csi.driver1",
  89. existingNode: generateNode(
  90. nodeIDMap{
  91. "com.example.csi.driver1": "com.example.csi/csi-node1",
  92. },
  93. labelMap{
  94. "com.example.csi/zone": "zoneA",
  95. },
  96. nil /*capacity*/),
  97. existingCSINode: generateCSINode(
  98. nodeIDMap{
  99. "com.example.csi.driver1": "com.example.csi/csi-node1",
  100. },
  101. topologyKeyMap{
  102. "com.example.csi.driver1": {"com.example.csi/zone"},
  103. },
  104. ),
  105. inputNodeID: "com.example.csi/csi-node1",
  106. inputTopology: map[string]string{
  107. "com.example.csi/zone": "zoneA",
  108. },
  109. expectedNode: &v1.Node{
  110. ObjectMeta: metav1.ObjectMeta{
  111. Name: "node1",
  112. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  113. Labels: labelMap{"com.example.csi/zone": "zoneA"},
  114. },
  115. },
  116. expectedCSINode: &storage.CSINode{
  117. ObjectMeta: getCSINodeObjectMeta(),
  118. Spec: storage.CSINodeSpec{
  119. Drivers: []storage.CSINodeDriver{
  120. {
  121. Name: "com.example.csi.driver1",
  122. NodeID: "com.example.csi/csi-node1",
  123. TopologyKeys: []string{"com.example.csi/zone"},
  124. },
  125. },
  126. },
  127. },
  128. },
  129. {
  130. name: "pre-existing node info from the same driver, but without topology info",
  131. driverName: "com.example.csi.driver1",
  132. existingNode: generateNode(
  133. nodeIDMap{
  134. "com.example.csi.driver1": "com.example.csi/csi-node1",
  135. },
  136. nil /* labels */, nil /*capacity*/),
  137. existingCSINode: generateCSINode(
  138. nodeIDMap{
  139. "com.example.csi.driver1": "com.example.csi/csi-node1",
  140. },
  141. nil, /* topologyKeys */
  142. ),
  143. inputNodeID: "com.example.csi/csi-node1",
  144. inputTopology: map[string]string{
  145. "com.example.csi/zone": "zoneA",
  146. },
  147. expectedNode: &v1.Node{
  148. ObjectMeta: metav1.ObjectMeta{
  149. Name: "node1",
  150. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  151. Labels: labelMap{"com.example.csi/zone": "zoneA"},
  152. },
  153. },
  154. expectedCSINode: &storage.CSINode{
  155. ObjectMeta: getCSINodeObjectMeta(),
  156. Spec: storage.CSINodeSpec{
  157. Drivers: []storage.CSINodeDriver{
  158. {
  159. Name: "com.example.csi.driver1",
  160. NodeID: "com.example.csi/csi-node1",
  161. TopologyKeys: []string{"com.example.csi/zone"},
  162. },
  163. },
  164. },
  165. },
  166. },
  167. {
  168. name: "pre-existing node info from different driver",
  169. driverName: "com.example.csi.driver1",
  170. existingNode: generateNode(
  171. nodeIDMap{
  172. "net.example.storage.other-driver": "net.example.storage/test-node",
  173. },
  174. labelMap{
  175. "net.example.storage/rack": "rack1",
  176. }, nil /*capacity*/),
  177. existingCSINode: generateCSINode(
  178. nodeIDMap{
  179. "net.example.storage.other-driver": "net.example.storage/test-node",
  180. },
  181. topologyKeyMap{
  182. "net.example.storage.other-driver": {"net.example.storage/rack"},
  183. },
  184. ),
  185. inputNodeID: "com.example.csi/csi-node1",
  186. inputTopology: map[string]string{
  187. "com.example.csi/zone": "zoneA",
  188. },
  189. expectedNode: &v1.Node{
  190. ObjectMeta: metav1.ObjectMeta{
  191. Name: "node1",
  192. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
  193. "com.example.csi.driver1": "com.example.csi/csi-node1",
  194. "net.example.storage.other-driver": "net.example.storage/test-node",
  195. })},
  196. Labels: labelMap{
  197. "com.example.csi/zone": "zoneA",
  198. "net.example.storage/rack": "rack1",
  199. },
  200. },
  201. },
  202. expectedCSINode: &storage.CSINode{
  203. ObjectMeta: getCSINodeObjectMeta(),
  204. Spec: storage.CSINodeSpec{
  205. Drivers: []storage.CSINodeDriver{
  206. {
  207. Name: "net.example.storage.other-driver",
  208. NodeID: "net.example.storage/test-node",
  209. TopologyKeys: []string{"net.example.storage/rack"},
  210. },
  211. {
  212. Name: "com.example.csi.driver1",
  213. NodeID: "com.example.csi/csi-node1",
  214. TopologyKeys: []string{"com.example.csi/zone"},
  215. },
  216. },
  217. },
  218. },
  219. },
  220. {
  221. name: "pre-existing node info from the same driver, but different node ID and topology values; labels should conflict",
  222. driverName: "com.example.csi.driver1",
  223. existingNode: generateNode(
  224. nodeIDMap{
  225. "com.example.csi.driver1": "com.example.csi/csi-node1",
  226. },
  227. labelMap{
  228. "com.example.csi/zone": "zoneA",
  229. }, nil /*capacity*/),
  230. existingCSINode: generateCSINode(
  231. nodeIDMap{
  232. "com.example.csi.driver1": "com.example.csi/csi-node1",
  233. },
  234. topologyKeyMap{
  235. "com.example.csi.driver1": {"com.example.csi/zone"},
  236. },
  237. ),
  238. inputNodeID: "com.example.csi/csi-node1",
  239. inputTopology: map[string]string{
  240. "com.example.csi/zone": "other-zone",
  241. },
  242. expectFail: true,
  243. },
  244. {
  245. name: "pre-existing node info from the same driver, but different node ID and topology keys; new labels should be added",
  246. driverName: "com.example.csi.driver1",
  247. existingNode: generateNode(
  248. nodeIDMap{
  249. "com.example.csi.driver1": "com.example.csi/csi-node1",
  250. },
  251. labelMap{
  252. "com.example.csi/zone": "zoneA",
  253. }, nil /*capacity*/),
  254. existingCSINode: generateCSINode(
  255. nodeIDMap{
  256. "com.example.csi.driver1": "com.example.csi/csi-node1",
  257. },
  258. topologyKeyMap{
  259. "com.example.csi.driver1": {"com.example.csi/zone"},
  260. },
  261. ),
  262. inputNodeID: "com.example.csi/other-node",
  263. inputTopology: map[string]string{
  264. "com.example.csi/rack": "rack1",
  265. },
  266. expectedNode: &v1.Node{
  267. ObjectMeta: metav1.ObjectMeta{
  268. Name: "node1",
  269. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/other-node"})},
  270. Labels: labelMap{
  271. "com.example.csi/zone": "zoneA",
  272. "com.example.csi/rack": "rack1",
  273. },
  274. },
  275. },
  276. expectedCSINode: &storage.CSINode{
  277. ObjectMeta: getCSINodeObjectMeta(),
  278. Spec: storage.CSINodeSpec{
  279. Drivers: []storage.CSINodeDriver{
  280. {
  281. Name: "com.example.csi.driver1",
  282. NodeID: "com.example.csi/other-node",
  283. TopologyKeys: []string{"com.example.csi/rack"},
  284. },
  285. },
  286. },
  287. },
  288. },
  289. {
  290. name: "nil topology, empty node",
  291. driverName: "com.example.csi.driver1",
  292. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  293. inputNodeID: "com.example.csi/csi-node1",
  294. inputTopology: nil,
  295. expectedNode: &v1.Node{
  296. ObjectMeta: metav1.ObjectMeta{
  297. Name: "node1",
  298. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  299. },
  300. },
  301. expectedCSINode: &storage.CSINode{
  302. ObjectMeta: getCSINodeObjectMeta(),
  303. Spec: storage.CSINodeSpec{
  304. Drivers: []storage.CSINodeDriver{
  305. {
  306. Name: "com.example.csi.driver1",
  307. NodeID: "com.example.csi/csi-node1",
  308. TopologyKeys: nil,
  309. },
  310. },
  311. },
  312. },
  313. },
  314. {
  315. name: "nil topology, pre-existing node info from the same driver",
  316. driverName: "com.example.csi.driver1",
  317. existingNode: generateNode(
  318. nodeIDMap{
  319. "com.example.csi.driver1": "com.example.csi/csi-node1",
  320. },
  321. labelMap{
  322. "com.example.csi/zone": "zoneA",
  323. }, nil /*capacity*/),
  324. existingCSINode: generateCSINode(
  325. nodeIDMap{
  326. "com.example.csi.driver1": "com.example.csi/csi-node1",
  327. },
  328. topologyKeyMap{
  329. "com.example.csi.driver1": {"com.example.csi/zone"},
  330. },
  331. ),
  332. inputNodeID: "com.example.csi/csi-node1",
  333. inputTopology: nil,
  334. expectedNode: &v1.Node{
  335. ObjectMeta: metav1.ObjectMeta{
  336. Name: "node1",
  337. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  338. Labels: labelMap{
  339. "com.example.csi/zone": "zoneA",
  340. },
  341. },
  342. },
  343. expectedCSINode: &storage.CSINode{
  344. ObjectMeta: getCSINodeObjectMeta(),
  345. Spec: storage.CSINodeSpec{
  346. Drivers: []storage.CSINodeDriver{
  347. {
  348. Name: "com.example.csi.driver1",
  349. NodeID: "com.example.csi/csi-node1",
  350. TopologyKeys: nil,
  351. },
  352. },
  353. },
  354. },
  355. },
  356. {
  357. name: "nil topology, pre-existing node info from different driver",
  358. driverName: "com.example.csi.driver1",
  359. existingNode: generateNode(
  360. nodeIDMap{
  361. "net.example.storage.other-driver": "net.example.storage/test-node",
  362. },
  363. labelMap{
  364. "net.example.storage/rack": "rack1",
  365. }, nil /*capacity*/),
  366. existingCSINode: generateCSINode(
  367. nodeIDMap{
  368. "net.example.storage.other-driver": "net.example.storage/test-node",
  369. },
  370. topologyKeyMap{
  371. "net.example.storage.other-driver": {"net.example.storage/rack"},
  372. },
  373. ),
  374. inputNodeID: "com.example.csi/csi-node1",
  375. inputTopology: nil,
  376. expectedNode: &v1.Node{
  377. ObjectMeta: metav1.ObjectMeta{
  378. Name: "node1",
  379. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
  380. "com.example.csi.driver1": "com.example.csi/csi-node1",
  381. "net.example.storage.other-driver": "net.example.storage/test-node",
  382. })},
  383. Labels: labelMap{
  384. "net.example.storage/rack": "rack1",
  385. },
  386. },
  387. },
  388. expectedCSINode: &storage.CSINode{
  389. ObjectMeta: getCSINodeObjectMeta(),
  390. Spec: storage.CSINodeSpec{
  391. Drivers: []storage.CSINodeDriver{
  392. {
  393. Name: "net.example.storage.other-driver",
  394. NodeID: "net.example.storage/test-node",
  395. TopologyKeys: []string{"net.example.storage/rack"},
  396. },
  397. {
  398. Name: "com.example.csi.driver1",
  399. NodeID: "com.example.csi/csi-node1",
  400. TopologyKeys: nil,
  401. },
  402. },
  403. },
  404. },
  405. },
  406. {
  407. name: "empty node ID",
  408. driverName: "com.example.csi.driver1",
  409. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  410. inputNodeID: "",
  411. expectFail: true,
  412. },
  413. {
  414. name: "new node with valid max limit",
  415. driverName: "com.example.csi.driver1",
  416. existingNode: generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
  417. inputVolumeLimit: 10,
  418. inputTopology: nil,
  419. inputNodeID: "com.example.csi/csi-node1",
  420. expectedNode: &v1.Node{
  421. ObjectMeta: metav1.ObjectMeta{
  422. Name: "node1",
  423. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  424. },
  425. Status: v1.NodeStatus{
  426. Capacity: v1.ResourceList{
  427. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi.driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  428. },
  429. Allocatable: v1.ResourceList{
  430. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi.driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  431. },
  432. },
  433. },
  434. expectedCSINode: &storage.CSINode{
  435. ObjectMeta: getCSINodeObjectMeta(),
  436. Spec: storage.CSINodeSpec{
  437. Drivers: []storage.CSINodeDriver{
  438. {
  439. Name: "com.example.csi.driver1",
  440. NodeID: "com.example.csi/csi-node1",
  441. TopologyKeys: nil,
  442. },
  443. },
  444. },
  445. },
  446. },
  447. {
  448. name: "node with existing valid max limit",
  449. driverName: "com.example.csi.driver1",
  450. existingNode: generateNode(
  451. nil, /*nodeIDs*/
  452. nil, /*labels*/
  453. map[v1.ResourceName]resource.Quantity{
  454. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  455. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  456. }),
  457. inputVolumeLimit: 20,
  458. inputTopology: nil,
  459. inputNodeID: "com.example.csi/csi-node1",
  460. expectedNode: &v1.Node{
  461. ObjectMeta: metav1.ObjectMeta{
  462. Name: "node1",
  463. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  464. },
  465. Status: v1.NodeStatus{
  466. Capacity: v1.ResourceList{
  467. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi.driver1")): *resource.NewQuantity(20, resource.DecimalSI),
  468. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  469. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  470. },
  471. Allocatable: v1.ResourceList{
  472. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi.driver1")): *resource.NewQuantity(20, resource.DecimalSI),
  473. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  474. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  475. },
  476. },
  477. },
  478. expectedCSINode: &storage.CSINode{
  479. ObjectMeta: getCSINodeObjectMeta(),
  480. Spec: storage.CSINodeSpec{
  481. Drivers: []storage.CSINodeDriver{
  482. {
  483. Name: "com.example.csi.driver1",
  484. NodeID: "com.example.csi/csi-node1",
  485. TopologyKeys: nil,
  486. },
  487. },
  488. },
  489. },
  490. },
  491. }
  492. test(t, true /* addNodeInfo */, true /* csiNodeInfoEnabled */, testcases)
  493. }
  494. // TestInstallCSIDriver_CSINodeInfoDisabled tests InstallCSIDriver with various existing Node annotations
  495. // and CSINodeInfo feature gate disabled.
  496. func TestInstallCSIDriverCSINodeInfoDisabled(t *testing.T) {
  497. testcases := []testcase{
  498. {
  499. name: "empty node",
  500. driverName: "com.example.csi.driver1",
  501. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  502. inputNodeID: "com.example.csi/csi-node1",
  503. expectedNode: &v1.Node{
  504. ObjectMeta: metav1.ObjectMeta{
  505. Name: "node1",
  506. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  507. },
  508. },
  509. },
  510. {
  511. name: "pre-existing node info from the same driver",
  512. driverName: "com.example.csi.driver1",
  513. existingNode: generateNode(
  514. nodeIDMap{
  515. "com.example.csi.driver1": "com.example.csi/csi-node1",
  516. },
  517. nil /* labels */, nil /*capacity*/),
  518. inputNodeID: "com.example.csi/csi-node1",
  519. expectedNode: &v1.Node{
  520. ObjectMeta: metav1.ObjectMeta{
  521. Name: "node1",
  522. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
  523. },
  524. },
  525. },
  526. {
  527. name: "pre-existing node info from different driver",
  528. driverName: "com.example.csi.driver1",
  529. existingNode: generateNode(
  530. nodeIDMap{
  531. "net.example.storage.other-driver": "net.example.storage/test-node",
  532. },
  533. nil /* labels */, nil /*capacity*/),
  534. inputNodeID: "com.example.csi/csi-node1",
  535. expectedNode: &v1.Node{
  536. ObjectMeta: metav1.ObjectMeta{
  537. Name: "node1",
  538. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
  539. "com.example.csi.driver1": "com.example.csi/csi-node1",
  540. "net.example.storage.other-driver": "net.example.storage/test-node",
  541. })},
  542. },
  543. },
  544. },
  545. }
  546. test(t, true /* addNodeInfo */, false /* csiNodeInfoEnabled */, testcases)
  547. }
  548. // TestUninstallCSIDriver tests UninstallCSIDriver with various existing Node and/or CSINode objects.
  549. func TestUninstallCSIDriver(t *testing.T) {
  550. testcases := []testcase{
  551. {
  552. name: "empty node and empty CSINode",
  553. driverName: "com.example.csi.driver1",
  554. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  555. expectedNode: &v1.Node{
  556. ObjectMeta: metav1.ObjectMeta{
  557. Name: "node1",
  558. },
  559. },
  560. expectedCSINode: &storage.CSINode{
  561. ObjectMeta: getCSINodeObjectMeta(),
  562. Spec: storage.CSINodeSpec{},
  563. },
  564. },
  565. {
  566. name: "pre-existing node info from the same driver",
  567. driverName: "com.example.csi.driver1",
  568. existingNode: generateNode(
  569. nodeIDMap{
  570. "com.example.csi.driver1": "com.example.csi/csi-node1",
  571. },
  572. labelMap{
  573. "com.example.csi/zone": "zoneA",
  574. }, nil /*capacity*/),
  575. existingCSINode: generateCSINode(
  576. nodeIDMap{
  577. "com.example.csi.driver1": "com.example.csi/csi-node1",
  578. },
  579. topologyKeyMap{
  580. "com.example.csi.driver1": {"com.example.csi/zone"},
  581. },
  582. ),
  583. expectedNode: &v1.Node{
  584. ObjectMeta: metav1.ObjectMeta{
  585. Name: "node1",
  586. Labels: labelMap{"com.example.csi/zone": "zoneA"},
  587. },
  588. },
  589. expectedCSINode: &storage.CSINode{
  590. ObjectMeta: getCSINodeObjectMeta(),
  591. Spec: storage.CSINodeSpec{},
  592. },
  593. hasModified: true,
  594. },
  595. {
  596. name: "pre-existing node info from different driver",
  597. driverName: "com.example.csi.driver1",
  598. existingNode: generateNode(
  599. nodeIDMap{
  600. "net.example.storage.other-driver": "net.example.storage/csi-node1",
  601. },
  602. labelMap{
  603. "net.example.storage/zone": "zoneA",
  604. }, nil /*capacity*/),
  605. existingCSINode: generateCSINode(
  606. nodeIDMap{
  607. "net.example.storage.other-driver": "net.example.storage/csi-node1",
  608. },
  609. topologyKeyMap{
  610. "net.example.storage.other-driver": {"net.example.storage/zone"},
  611. },
  612. ),
  613. expectedNode: &v1.Node{
  614. ObjectMeta: metav1.ObjectMeta{
  615. Name: "node1",
  616. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
  617. Labels: labelMap{"net.example.storage/zone": "zoneA"},
  618. },
  619. },
  620. expectedCSINode: &storage.CSINode{
  621. ObjectMeta: getCSINodeObjectMeta(),
  622. Spec: storage.CSINodeSpec{
  623. Drivers: []storage.CSINodeDriver{
  624. {
  625. Name: "net.example.storage.other-driver",
  626. NodeID: "net.example.storage/csi-node1",
  627. TopologyKeys: []string{"net.example.storage/zone"},
  628. },
  629. },
  630. },
  631. },
  632. hasModified: false,
  633. },
  634. {
  635. name: "pre-existing info about the same driver in node, but empty CSINode",
  636. driverName: "com.example.csi.driver1",
  637. existingNode: generateNode(
  638. nodeIDMap{
  639. "com.example.csi.driver1": "com.example.csi/csi-node1",
  640. },
  641. nil /* labels */, nil /*capacity*/),
  642. expectedNode: &v1.Node{
  643. ObjectMeta: metav1.ObjectMeta{
  644. Name: "node1",
  645. },
  646. },
  647. expectedCSINode: &storage.CSINode{
  648. ObjectMeta: getCSINodeObjectMeta(),
  649. Spec: storage.CSINodeSpec{},
  650. },
  651. },
  652. {
  653. name: "pre-existing info about a different driver in node, but empty CSINode",
  654. existingNode: generateNode(
  655. nodeIDMap{
  656. "net.example.storage.other-driver": "net.example.storage/csi-node1",
  657. },
  658. nil /* labels */, nil /*capacity*/),
  659. expectedNode: &v1.Node{
  660. ObjectMeta: metav1.ObjectMeta{
  661. Name: "node1",
  662. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
  663. },
  664. },
  665. expectedCSINode: &storage.CSINode{
  666. ObjectMeta: getCSINodeObjectMeta(),
  667. Spec: storage.CSINodeSpec{},
  668. },
  669. },
  670. {
  671. name: "new node with valid max limit",
  672. driverName: "com.example.csi.driver1",
  673. existingNode: generateNode(
  674. nil, /*nodeIDs*/
  675. nil, /*labels*/
  676. map[v1.ResourceName]resource.Quantity{
  677. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  678. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  679. },
  680. ),
  681. expectedNode: &v1.Node{
  682. ObjectMeta: metav1.ObjectMeta{
  683. Name: "node1",
  684. },
  685. Status: v1.NodeStatus{
  686. Capacity: v1.ResourceList{
  687. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  688. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  689. },
  690. Allocatable: v1.ResourceList{
  691. v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
  692. v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
  693. },
  694. },
  695. },
  696. expectedCSINode: &storage.CSINode{
  697. ObjectMeta: getCSINodeObjectMeta(),
  698. Spec: storage.CSINodeSpec{},
  699. },
  700. inputTopology: nil,
  701. inputNodeID: "com.example.csi/csi-node1",
  702. },
  703. }
  704. test(t, false /* addNodeInfo */, true /* csiNodeInfoEnabled */, testcases)
  705. }
  706. // TestUninstallCSIDriver tests UninstallCSIDriver with various existing Node objects and CSINode
  707. // feature disabled.
  708. func TestUninstallCSIDriverCSINodeInfoDisabled(t *testing.T) {
  709. testcases := []testcase{
  710. {
  711. name: "empty node",
  712. driverName: "com.example.csi/driver1",
  713. existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
  714. expectedNode: &v1.Node{
  715. ObjectMeta: metav1.ObjectMeta{
  716. Name: "node1",
  717. },
  718. },
  719. },
  720. {
  721. name: "pre-existing node info from the same driver",
  722. driverName: "com.example.csi/driver1",
  723. existingNode: generateNode(
  724. nodeIDMap{
  725. "com.example.csi/driver1": "com.example.csi/csi-node1",
  726. },
  727. nil /* labels */, nil /*capacity*/),
  728. expectedNode: &v1.Node{
  729. ObjectMeta: metav1.ObjectMeta{
  730. Name: "node1",
  731. },
  732. },
  733. },
  734. {
  735. name: "pre-existing node info from different driver",
  736. driverName: "com.example.csi/driver1",
  737. existingNode: generateNode(
  738. nodeIDMap{
  739. "net.example.storage/other-driver": "net.example.storage/csi-node1",
  740. },
  741. nil /* labels */, nil /*capacity*/),
  742. expectedNode: &v1.Node{
  743. ObjectMeta: metav1.ObjectMeta{
  744. Name: "node1",
  745. Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage/other-driver": "net.example.storage/csi-node1"})},
  746. },
  747. },
  748. },
  749. }
  750. test(t, false /* addNodeInfo */, false /* csiNodeInfoEnabled */, testcases)
  751. }
  752. func TestSetMigrationAnnotation(t *testing.T) {
  753. testcases := []struct {
  754. name string
  755. migratedPlugins map[string](func() bool)
  756. existingNode *storage.CSINode
  757. expectedNode *storage.CSINode
  758. expectModified bool
  759. }{
  760. {
  761. name: "nil migrated plugins",
  762. existingNode: &storage.CSINode{
  763. ObjectMeta: metav1.ObjectMeta{
  764. Name: "node1",
  765. },
  766. },
  767. expectedNode: &storage.CSINode{
  768. ObjectMeta: metav1.ObjectMeta{
  769. Name: "node1",
  770. },
  771. },
  772. },
  773. {
  774. name: "one modified plugin",
  775. migratedPlugins: map[string](func() bool){
  776. "test": func() bool { return true },
  777. },
  778. existingNode: &storage.CSINode{
  779. ObjectMeta: metav1.ObjectMeta{
  780. Name: "node1",
  781. },
  782. },
  783. expectedNode: &storage.CSINode{
  784. ObjectMeta: metav1.ObjectMeta{
  785. Name: "node1",
  786. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
  787. },
  788. },
  789. expectModified: true,
  790. },
  791. {
  792. name: "existing plugin",
  793. migratedPlugins: map[string](func() bool){
  794. "test": func() bool { return true },
  795. },
  796. existingNode: &storage.CSINode{
  797. ObjectMeta: metav1.ObjectMeta{
  798. Name: "node1",
  799. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
  800. },
  801. },
  802. expectedNode: &storage.CSINode{
  803. ObjectMeta: metav1.ObjectMeta{
  804. Name: "node1",
  805. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
  806. },
  807. },
  808. expectModified: false,
  809. },
  810. {
  811. name: "remove plugin",
  812. migratedPlugins: map[string](func() bool){},
  813. existingNode: &storage.CSINode{
  814. ObjectMeta: metav1.ObjectMeta{
  815. Name: "node1",
  816. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
  817. },
  818. },
  819. expectedNode: &storage.CSINode{
  820. ObjectMeta: metav1.ObjectMeta{
  821. Name: "node1",
  822. Annotations: map[string]string{},
  823. },
  824. },
  825. expectModified: true,
  826. },
  827. {
  828. name: "one modified plugin, other annotations stable",
  829. migratedPlugins: map[string](func() bool){
  830. "test": func() bool { return true },
  831. },
  832. existingNode: &storage.CSINode{
  833. ObjectMeta: metav1.ObjectMeta{
  834. Name: "node1",
  835. Annotations: map[string]string{"other": "annotation"},
  836. },
  837. },
  838. expectedNode: &storage.CSINode{
  839. ObjectMeta: metav1.ObjectMeta{
  840. Name: "node1",
  841. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
  842. },
  843. },
  844. expectModified: true,
  845. },
  846. {
  847. name: "multiple plugins modified, other annotations stable",
  848. migratedPlugins: map[string](func() bool){
  849. "test": func() bool { return true },
  850. "foo": func() bool { return false },
  851. },
  852. existingNode: &storage.CSINode{
  853. ObjectMeta: metav1.ObjectMeta{
  854. Name: "node1",
  855. Annotations: map[string]string{"other": "annotation", v1.MigratedPluginsAnnotationKey: "foo"},
  856. },
  857. },
  858. expectedNode: &storage.CSINode{
  859. ObjectMeta: metav1.ObjectMeta{
  860. Name: "node1",
  861. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
  862. },
  863. },
  864. expectModified: true,
  865. },
  866. {
  867. name: "multiple plugins added, other annotations stable",
  868. migratedPlugins: map[string](func() bool){
  869. "test": func() bool { return true },
  870. "foo": func() bool { return true },
  871. },
  872. existingNode: &storage.CSINode{
  873. ObjectMeta: metav1.ObjectMeta{
  874. Name: "node1",
  875. Annotations: map[string]string{"other": "annotation"},
  876. },
  877. },
  878. expectedNode: &storage.CSINode{
  879. ObjectMeta: metav1.ObjectMeta{
  880. Name: "node1",
  881. Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "foo,test", "other": "annotation"},
  882. },
  883. },
  884. expectModified: true,
  885. },
  886. }
  887. for _, tc := range testcases {
  888. t.Logf("test case: %s", tc.name)
  889. modified := setMigrationAnnotation(tc.migratedPlugins, tc.existingNode)
  890. if modified != tc.expectModified {
  891. t.Errorf("Expected modified to be %v but got %v instead", tc.expectModified, modified)
  892. }
  893. if !reflect.DeepEqual(tc.expectedNode, tc.existingNode) {
  894. t.Errorf("Expected CSINode: %v, but got: %v", tc.expectedNode, tc.existingNode)
  895. }
  896. }
  897. }
  898. func TestInstallCSIDriverExistingAnnotation(t *testing.T) {
  899. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeInfo, true)()
  900. driverName := "com.example.csi/driver1"
  901. nodeID := "com.example.csi/some-node"
  902. testcases := []struct {
  903. name string
  904. existingNode *v1.Node
  905. }{
  906. {
  907. name: "pre-existing info about the same driver in node, but empty CSINode",
  908. existingNode: generateNode(
  909. nodeIDMap{
  910. "com.example.csi/driver1": "com.example.csi/csi-node1",
  911. },
  912. nil /* labels */, nil /*capacity*/),
  913. },
  914. {
  915. name: "pre-existing info about a different driver in node, but empty CSINode",
  916. existingNode: generateNode(
  917. nodeIDMap{
  918. "net.example.storage/other-driver": "net.example.storage/test-node",
  919. },
  920. nil /* labels */, nil /*capacity*/),
  921. },
  922. }
  923. for _, tc := range testcases {
  924. t.Logf("test case: %q", tc.name)
  925. // Arrange
  926. nodeName := tc.existingNode.Name
  927. client := fake.NewSimpleClientset(tc.existingNode)
  928. tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
  929. if err != nil {
  930. t.Fatalf("can't create temp dir: %v", err)
  931. }
  932. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  933. tmpDir,
  934. client,
  935. nil,
  936. nodeName,
  937. nil,
  938. )
  939. nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
  940. // Act
  941. _, err = nim.CreateCSINode()
  942. if err != nil {
  943. t.Errorf("expected no error from creating CSINodeinfo but got: %v", err)
  944. continue
  945. }
  946. err = nim.InstallCSIDriver(driverName, nodeID, 0 /* maxVolumeLimit */, nil) // TODO test maxVolumeLimit
  947. if err != nil {
  948. t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
  949. continue
  950. }
  951. // Assert
  952. nodeInfo, err := client.StorageV1beta1().CSINodes().Get(nodeName, metav1.GetOptions{})
  953. if err != nil {
  954. t.Errorf("error getting CSINode: %v", err)
  955. continue
  956. }
  957. driver := nodeInfo.Spec.Drivers[0]
  958. if driver.Name != driverName || driver.NodeID != nodeID {
  959. t.Errorf("expected Driver to be %q and NodeID to be %q, but got: %q:%q", driverName, nodeID, driver.Name, driver.NodeID)
  960. }
  961. }
  962. }
  963. func getClientSet(existingNode *v1.Node, existingCSINode *storage.CSINode) *fake.Clientset {
  964. objects := []runtime.Object{}
  965. if existingNode != nil {
  966. objects = append(objects, existingNode)
  967. }
  968. if existingCSINode != nil {
  969. objects = append(objects, existingCSINode)
  970. }
  971. return fake.NewSimpleClientset(objects...)
  972. }
  973. func test(t *testing.T, addNodeInfo bool, csiNodeInfoEnabled bool, testcases []testcase) {
  974. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeInfo, csiNodeInfoEnabled)()
  975. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AttachVolumeLimit, true)()
  976. for _, tc := range testcases {
  977. t.Logf("test case: %q", tc.name)
  978. //// Arrange
  979. nodeName := tc.existingNode.Name
  980. client := getClientSet(tc.existingNode, tc.existingCSINode)
  981. tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
  982. if err != nil {
  983. t.Fatalf("can't create temp dir: %v", err)
  984. }
  985. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  986. tmpDir,
  987. client,
  988. nil,
  989. nodeName,
  990. nil,
  991. )
  992. nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
  993. //// Act
  994. nim.CreateCSINode()
  995. if addNodeInfo {
  996. err = nim.InstallCSIDriver(tc.driverName, tc.inputNodeID, tc.inputVolumeLimit, tc.inputTopology)
  997. } else {
  998. err = nim.UninstallCSIDriver(tc.driverName)
  999. }
  1000. //// Assert
  1001. if tc.expectFail {
  1002. if err == nil {
  1003. t.Errorf("expected an error from InstallCSIDriver call but got none")
  1004. }
  1005. continue
  1006. } else if err != nil {
  1007. t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
  1008. continue
  1009. }
  1010. actions := client.Actions()
  1011. var node *v1.Node
  1012. if action := hasPatchAction(actions); action != nil {
  1013. node, err = applyNodeStatusPatch(tc.existingNode, action.(clienttesting.PatchActionImpl).GetPatch())
  1014. assert.NoError(t, err)
  1015. } else {
  1016. node, err = client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
  1017. assert.NoError(t, err)
  1018. }
  1019. if node == nil {
  1020. t.Errorf("error getting node: %v", err)
  1021. continue
  1022. }
  1023. if !helper.Semantic.DeepEqual(node, tc.expectedNode) {
  1024. t.Errorf("expected Node %v; got: %v", tc.expectedNode, node)
  1025. }
  1026. if csiNodeInfoEnabled {
  1027. // CSINode validation
  1028. nodeInfo, err := client.StorageV1beta1().CSINodes().Get(nodeName, metav1.GetOptions{})
  1029. if err != nil {
  1030. if !errors.IsNotFound(err) {
  1031. t.Errorf("error getting CSINode: %v", err)
  1032. }
  1033. continue
  1034. }
  1035. if !helper.Semantic.DeepEqual(nodeInfo, tc.expectedCSINode) {
  1036. t.Errorf("expected CSINode %v; got: %v", tc.expectedCSINode, nodeInfo)
  1037. }
  1038. if !addNodeInfo && tc.existingCSINode != nil && tc.existingNode != nil {
  1039. if tc.hasModified && helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
  1040. t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
  1041. }
  1042. if !tc.hasModified && !helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
  1043. t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
  1044. }
  1045. }
  1046. }
  1047. }
  1048. }
  1049. func generateNode(nodeIDs, labels map[string]string, capacity map[v1.ResourceName]resource.Quantity) *v1.Node {
  1050. var annotations map[string]string
  1051. if len(nodeIDs) > 0 {
  1052. b, _ := json.Marshal(nodeIDs)
  1053. annotations = map[string]string{annotationKeyNodeID: string(b)}
  1054. }
  1055. node := &v1.Node{
  1056. ObjectMeta: metav1.ObjectMeta{
  1057. Name: "node1",
  1058. Annotations: annotations,
  1059. Labels: labels,
  1060. },
  1061. }
  1062. if len(capacity) > 0 {
  1063. node.Status.Capacity = v1.ResourceList(capacity)
  1064. node.Status.Allocatable = v1.ResourceList(capacity)
  1065. }
  1066. return node
  1067. }
  1068. func marshall(nodeIDs nodeIDMap) string {
  1069. b, _ := json.Marshal(nodeIDs)
  1070. return string(b)
  1071. }
  1072. func generateCSINode(nodeIDs nodeIDMap, topologyKeys topologyKeyMap) *storage.CSINode {
  1073. nodeDrivers := []storage.CSINodeDriver{}
  1074. for k, nodeID := range nodeIDs {
  1075. dspec := storage.CSINodeDriver{
  1076. Name: k,
  1077. NodeID: nodeID,
  1078. }
  1079. if top, exists := topologyKeys[k]; exists {
  1080. dspec.TopologyKeys = top
  1081. }
  1082. nodeDrivers = append(nodeDrivers, dspec)
  1083. }
  1084. return &storage.CSINode{
  1085. ObjectMeta: getCSINodeObjectMeta(),
  1086. Spec: storage.CSINodeSpec{
  1087. Drivers: nodeDrivers,
  1088. },
  1089. }
  1090. }
  1091. func getCSINodeObjectMeta() metav1.ObjectMeta {
  1092. return metav1.ObjectMeta{
  1093. Name: "node1",
  1094. OwnerReferences: []metav1.OwnerReference{
  1095. {
  1096. APIVersion: nodeKind.Version,
  1097. Kind: nodeKind.Kind,
  1098. Name: "node1",
  1099. },
  1100. },
  1101. }
  1102. }
  1103. func applyNodeStatusPatch(originalNode *v1.Node, patch []byte) (*v1.Node, error) {
  1104. original, err := json.Marshal(originalNode)
  1105. if err != nil {
  1106. return nil, fmt.Errorf("failed to marshal original node %#v: %v", originalNode, err)
  1107. }
  1108. updated, err := strategicpatch.StrategicMergePatch(original, patch, v1.Node{})
  1109. if err != nil {
  1110. return nil, fmt.Errorf("failed to apply strategic merge patch %q on node %#v: %v",
  1111. patch, originalNode, err)
  1112. }
  1113. updatedNode := &v1.Node{}
  1114. if err := json.Unmarshal(updated, updatedNode); err != nil {
  1115. return nil, fmt.Errorf("failed to unmarshal updated node %q: %v", updated, err)
  1116. }
  1117. return updatedNode, nil
  1118. }
  1119. func hasPatchAction(actions []clienttesting.Action) clienttesting.Action {
  1120. for _, action := range actions {
  1121. if action.GetVerb() == "patch" {
  1122. return action
  1123. }
  1124. }
  1125. return nil
  1126. }