github.com/openshift/installer@v1.4.17/pkg/asset/agent/manifests/nmstateconfig_test.go (about) 1 package manifests 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "testing" 10 11 "github.com/golang/mock/gomock" 12 "github.com/stretchr/testify/assert" 13 v1 "k8s.io/api/core/v1" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 16 aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" 17 "github.com/openshift/assisted-service/models" 18 "github.com/openshift/installer/pkg/asset" 19 agentconfig "github.com/openshift/installer/pkg/asset/agent" 20 "github.com/openshift/installer/pkg/asset/agent/joiner" 21 "github.com/openshift/installer/pkg/asset/agent/workflow" 22 "github.com/openshift/installer/pkg/asset/mock" 23 "github.com/openshift/installer/pkg/types/agent" 24 ) 25 26 func TestNMStateConfig_Generate(t *testing.T) { 27 cases := []struct { 28 name string 29 dependencies []asset.Asset 30 requiresNmstatectl bool 31 expectedConfig []*aiv1beta1.NMStateConfig 32 expectedError string 33 }{ 34 { 35 name: "add-nodes workflow", 36 dependencies: []asset.Asset{ 37 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeAddNodes}, 38 &joiner.ClusterInfo{}, 39 getAgentHostsNoHosts(), 40 &agentconfig.OptionalInstallConfig{}, 41 }, 42 requiresNmstatectl: false, 43 expectedConfig: nil, 44 expectedError: "", 45 }, 46 { 47 name: "add-nodes workflow - agentHosts with some hosts without networkconfig", 48 dependencies: []asset.Asset{ 49 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeAddNodes}, 50 &joiner.ClusterInfo{ 51 Namespace: "cluster0", 52 ClusterName: "ostest", 53 Nodes: &v1.NodeList{}, 54 }, 55 getAgentHostsWithSomeHostsWithoutNetworkConfig(), 56 &agentconfig.OptionalInstallConfig{}, 57 }, 58 requiresNmstatectl: true, 59 expectedConfig: []*aiv1beta1.NMStateConfig{ 60 { 61 TypeMeta: metav1.TypeMeta{ 62 Kind: "NMStateConfig", 63 APIVersion: "agent-install.openshift.io/v1beta1", 64 }, 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: "ostest-0", 67 Namespace: "cluster0", 68 Labels: getNMStateConfigLabels("ostest"), 69 }, 70 Spec: aiv1beta1.NMStateConfigSpec{ 71 Interfaces: []*aiv1beta1.Interface{ 72 { 73 Name: "enp2t0", 74 MacAddress: "98:af:65:a5:8d:02", 75 }, 76 }, 77 NetConfig: aiv1beta1.NetConfig{ 78 Raw: unmarshalJSON([]byte(rawNMStateConfigNoIP)), 79 }, 80 }, 81 }, 82 }, 83 expectedError: "", 84 }, 85 { 86 name: "add-nodes workflow - invalid ip", 87 dependencies: []asset.Asset{ 88 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeAddNodes}, 89 &joiner.ClusterInfo{ 90 Namespace: "cluster0", 91 ClusterName: "ostest", 92 Nodes: &v1.NodeList{ 93 Items: []v1.Node{ 94 { 95 ObjectMeta: metav1.ObjectMeta{ 96 Name: "master-0", 97 }, 98 Status: v1.NodeStatus{ 99 Addresses: []v1.NodeAddress{ 100 { 101 Address: "192.168.122.21", // configured by getValidAgentHostsConfig() 102 }, 103 }, 104 }, 105 }, 106 }, 107 }, 108 }, 109 getValidAgentHostsConfig(), 110 &agentconfig.OptionalInstallConfig{}, 111 }, 112 requiresNmstatectl: false, 113 expectedError: "address conflict found. The configured address 192.168.122.21 is already used by the cluster node master-0", 114 }, 115 { 116 name: "add-nodes workflow - invalid hostname", 117 dependencies: []asset.Asset{ 118 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeAddNodes}, 119 &joiner.ClusterInfo{ 120 Namespace: "cluster0", 121 ClusterName: "ostest", 122 Nodes: &v1.NodeList{ 123 Items: []v1.Node{ 124 { 125 ObjectMeta: metav1.ObjectMeta{ 126 Name: "control-0.example.org", 127 }, 128 Status: v1.NodeStatus{ 129 Addresses: []v1.NodeAddress{ 130 { 131 Address: "control-0.example.org", // configured by getValidAgentHostsConfig() 132 }, 133 }, 134 }, 135 }, 136 }, 137 }, 138 }, 139 getValidAgentHostsConfig(), 140 &agentconfig.OptionalInstallConfig{}, 141 }, 142 requiresNmstatectl: false, 143 expectedError: "hostname conflict found. The configured hostname control-0.example.org is already used in the cluster", 144 }, 145 { 146 name: "agentHosts does not contain networkConfig", 147 dependencies: []asset.Asset{ 148 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, 149 &joiner.ClusterInfo{}, 150 getAgentHostsNoHosts(), 151 getValidOptionalInstallConfig(), 152 }, 153 requiresNmstatectl: false, 154 expectedConfig: nil, 155 expectedError: "", 156 }, 157 { 158 name: "agentHosts with some hosts without networkconfig", 159 dependencies: []asset.Asset{ 160 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, 161 &joiner.ClusterInfo{}, 162 getAgentHostsWithSomeHostsWithoutNetworkConfig(), 163 getValidOptionalInstallConfig(), 164 }, 165 requiresNmstatectl: true, 166 expectedConfig: []*aiv1beta1.NMStateConfig{ 167 { 168 TypeMeta: metav1.TypeMeta{ 169 Kind: "NMStateConfig", 170 APIVersion: "agent-install.openshift.io/v1beta1", 171 }, 172 ObjectMeta: metav1.ObjectMeta{ 173 Name: fmt.Sprint(getValidOptionalInstallConfig().ClusterName(), "-0"), 174 Namespace: getValidOptionalInstallConfig().ClusterNamespace(), 175 Labels: getNMStateConfigLabels(getValidOptionalInstallConfig().ClusterName()), 176 }, 177 Spec: aiv1beta1.NMStateConfigSpec{ 178 Interfaces: []*aiv1beta1.Interface{ 179 { 180 Name: "enp2t0", 181 MacAddress: "98:af:65:a5:8d:02", 182 }, 183 }, 184 NetConfig: aiv1beta1.NetConfig{ 185 Raw: unmarshalJSON([]byte(rawNMStateConfigNoIP)), 186 }, 187 }, 188 }, 189 }, 190 expectedError: "", 191 }, 192 { 193 name: "valid config", 194 dependencies: []asset.Asset{ 195 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, 196 &joiner.ClusterInfo{}, 197 getValidAgentHostsConfig(), 198 getValidOptionalInstallConfig(), 199 }, 200 requiresNmstatectl: true, 201 expectedConfig: []*aiv1beta1.NMStateConfig{ 202 { 203 TypeMeta: metav1.TypeMeta{ 204 Kind: "NMStateConfig", 205 APIVersion: "agent-install.openshift.io/v1beta1", 206 }, 207 ObjectMeta: metav1.ObjectMeta{ 208 Name: fmt.Sprint(getValidOptionalInstallConfig().ClusterName(), "-0"), 209 Namespace: getValidOptionalInstallConfig().ClusterNamespace(), 210 Labels: getNMStateConfigLabels(getValidOptionalInstallConfig().ClusterName()), 211 }, 212 Spec: aiv1beta1.NMStateConfigSpec{ 213 Interfaces: []*aiv1beta1.Interface{ 214 { 215 Name: "enp2s0", 216 MacAddress: "98:af:65:a5:8d:01", 217 }, 218 { 219 Name: "enp3s1", 220 MacAddress: "28:d2:44:d2:b2:1a", 221 }, 222 }, 223 NetConfig: aiv1beta1.NetConfig{ 224 Raw: unmarshalJSON([]byte(rawNMStateConfig)), 225 }, 226 }, 227 }, 228 { 229 TypeMeta: metav1.TypeMeta{ 230 Kind: "NMStateConfig", 231 APIVersion: "agent-install.openshift.io/v1beta1", 232 }, 233 ObjectMeta: metav1.ObjectMeta{ 234 Name: fmt.Sprint(getValidOptionalInstallConfig().ClusterName(), "-1"), 235 Namespace: getValidOptionalInstallConfig().ClusterNamespace(), 236 Labels: getNMStateConfigLabels(getValidOptionalInstallConfig().ClusterName()), 237 }, 238 Spec: aiv1beta1.NMStateConfigSpec{ 239 Interfaces: []*aiv1beta1.Interface{ 240 { 241 Name: "enp2t0", 242 MacAddress: "98:af:65:a5:8d:02", 243 }, 244 }, 245 NetConfig: aiv1beta1.NetConfig{ 246 Raw: unmarshalJSON([]byte(rawNMStateConfig)), 247 }, 248 }, 249 }, 250 { 251 TypeMeta: metav1.TypeMeta{ 252 Kind: "NMStateConfig", 253 APIVersion: "agent-install.openshift.io/v1beta1", 254 }, 255 ObjectMeta: metav1.ObjectMeta{ 256 Name: fmt.Sprint(getValidOptionalInstallConfig().ClusterName(), "-2"), 257 Namespace: getValidOptionalInstallConfig().ClusterNamespace(), 258 Labels: getNMStateConfigLabels(getValidOptionalInstallConfig().ClusterName()), 259 }, 260 Spec: aiv1beta1.NMStateConfigSpec{ 261 Interfaces: []*aiv1beta1.Interface{ 262 { 263 Name: "enp2u0", 264 MacAddress: "98:af:65:a5:8d:03", 265 }, 266 }, 267 NetConfig: aiv1beta1.NetConfig{ 268 Raw: unmarshalJSON([]byte(rawNMStateConfig)), 269 }, 270 }, 271 }, 272 }, 273 expectedError: "", 274 }, 275 { 276 name: "invalid networkConfig", 277 dependencies: []asset.Asset{ 278 &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, 279 &joiner.ClusterInfo{}, 280 getInValidAgentHostsConfig(), 281 getValidOptionalInstallConfig(), 282 }, 283 requiresNmstatectl: true, 284 expectedConfig: nil, 285 expectedError: "failed to validate network yaml", 286 }, 287 } 288 for _, tc := range cases { 289 t.Run(tc.name, func(t *testing.T) { 290 parents := asset.Parents{} 291 parents.Add(tc.dependencies...) 292 293 asset := &NMStateConfig{} 294 err := asset.Generate(context.Background(), parents) 295 296 // Check if the test failed because nmstatectl is not available in CI 297 if tc.requiresNmstatectl { 298 _, execErr := exec.LookPath("nmstatectl") 299 if execErr != nil { 300 assert.ErrorContains(t, err, "executable file not found") 301 t.Skip("No nmstatectl binary available") 302 } 303 } 304 305 switch { 306 case tc.expectedError != "": 307 assert.ErrorContains(t, err, tc.expectedError) 308 case len(tc.expectedConfig) == 0: 309 assert.NoError(t, err) 310 assert.Equal(t, tc.expectedConfig, asset.Config) 311 default: 312 assert.NoError(t, err) 313 assert.Equal(t, tc.expectedConfig, asset.Config) 314 assert.NotEmpty(t, asset.Files()) 315 316 configFile := asset.Files()[0] 317 assert.Equal(t, "cluster-manifests/nmstateconfig.yaml", configFile.Filename) 318 319 // Split up the file into multiple YAMLs if it contains NMStateConfig for more than one node 320 yamlList, err := GetMultipleYamls[aiv1beta1.NMStateConfig](configFile.Data) 321 322 assert.NoError(t, err) 323 assert.Equal(t, len(tc.expectedConfig), len(yamlList)) 324 325 for i := range tc.expectedConfig { 326 assert.Equal(t, *tc.expectedConfig[i], yamlList[i]) 327 } 328 assert.Equal(t, len(tc.expectedConfig), len(asset.StaticNetworkConfig)) 329 } 330 }) 331 } 332 } 333 334 func TestNMStateConfig_LoadedFromDisk(t *testing.T) { 335 cases := []struct { 336 name string 337 data string 338 fetchError error 339 expectedFound bool 340 expectedError string 341 requiresNmstatectl bool 342 expectedConfig []*models.HostStaticNetworkConfig 343 }{ 344 { 345 name: "valid-config-file", 346 data: ` 347 metadata: 348 name: mynmstateconfig 349 namespace: spoke-cluster 350 labels: 351 cluster0-nmstate-label-name: cluster0-nmstate-label-value 352 spec: 353 config: 354 interfaces: 355 - name: eth0 356 type: ethernet 357 state: up 358 mac-address: 52:54:01:aa:aa:a1 359 ipv4: 360 enabled: true 361 address: 362 - ip: 192.168.122.21 363 prefix-length: 24 364 dhcp: false 365 dns-resolver: 366 config: 367 server: 368 - 192.168.122.1 369 routes: 370 config: 371 - destination: 0.0.0.0/0 372 next-hop-address: 192.168.122.1 373 next-hop-interface: eth0 374 table-id: 254 375 interfaces: 376 - name: "eth0" 377 macAddress: "52:54:01:aa:aa:a1" 378 - name: "eth1" 379 macAddress: "52:54:01:bb:bb:b1"`, 380 requiresNmstatectl: true, 381 expectedFound: true, 382 expectedConfig: []*models.HostStaticNetworkConfig{ 383 { 384 MacInterfaceMap: models.MacInterfaceMap{ 385 {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, 386 {LogicalNicName: "eth1", MacAddress: "52:54:01:bb:bb:b1"}, 387 }, 388 NetworkYaml: "dns-resolver:\n config:\n server:\n - 192.168.122.1\ninterfaces:\n- ipv4:\n address:\n - ip: 192.168.122.21\n prefix-length: 24\n dhcp: false\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\nroutes:\n config:\n - destination: 0.0.0.0/0\n next-hop-address: 192.168.122.1\n next-hop-interface: eth0\n table-id: 254\n", 389 }, 390 }, 391 }, 392 393 { 394 name: "valid-config-multiple-yamls", 395 data: ` 396 metadata: 397 name: mynmstateconfig 398 namespace: spoke-cluster 399 labels: 400 cluster0-nmstate-label-name: cluster0-nmstate-label-value 401 spec: 402 config: 403 interfaces: 404 - name: eth0 405 type: ethernet 406 state: up 407 mac-address: 52:54:01:aa:aa:a1 408 ipv4: 409 enabled: true 410 address: 411 - ip: 192.168.122.21 412 prefix-length: 24 413 interfaces: 414 - name: "eth0" 415 macAddress: "52:54:01:aa:aa:a1" 416 --- 417 metadata: 418 name: mynmstateconfig-2 419 namespace: spoke-cluster 420 labels: 421 cluster0-nmstate-label-name: cluster0-nmstate-label-value 422 spec: 423 config: 424 interfaces: 425 - name: eth0 426 type: ethernet 427 state: up 428 mac-address: 52:54:01:cc:cc:c1 429 ipv4: 430 enabled: true 431 address: 432 - ip: 192.168.122.22 433 prefix-length: 24 434 interfaces: 435 - name: "eth0" 436 macAddress: "52:54:01:cc:cc:c1"`, 437 requiresNmstatectl: true, 438 expectedFound: true, 439 expectedConfig: []*models.HostStaticNetworkConfig{ 440 { 441 MacInterfaceMap: models.MacInterfaceMap{ 442 {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, 443 }, 444 NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: 192.168.122.21\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\n", 445 }, 446 { 447 MacInterfaceMap: models.MacInterfaceMap{ 448 {LogicalNicName: "eth0", MacAddress: "52:54:01:cc:cc:c1"}, 449 }, 450 NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: 192.168.122.22\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:cc:cc:c1\n name: eth0\n state: up\n type: ethernet\n", 451 }, 452 }, 453 }, 454 455 { 456 name: "invalid-interfaces", 457 data: ` 458 metadata: 459 name: mynmstateconfig 460 namespace: spoke-cluster 461 labels: 462 cluster0-nmstate-label-name: cluster0-nmstate-label-value 463 spec: 464 interfaces: 465 - name: "eth0" 466 macAddress: "52:54:01:aa:aa:a1" 467 - name: "eth0" 468 macAddress: "52:54:01:bb:bb:b1"`, 469 requiresNmstatectl: true, 470 expectedError: "staticNetwork configuration is not valid", 471 }, 472 473 // This test case currently does not work for libnmstate 2.2.9, 474 // due a regression that will be fixed in https://github.com/nmstate/nmstate/issues/2311 475 // { 476 // name: "invalid-address-for-type", 477 // data: ` 478 // metadata: 479 // name: mynmstateconfig 480 // namespace: spoke-cluster 481 // labels: 482 // cluster0-nmstate-label-name: cluster0-nmstate-label-value 483 // spec: 484 // config: 485 // interfaces: 486 // - name: eth0 487 // type: ethernet 488 // state: up 489 // mac-address: 52:54:01:aa:aa:a1 490 // ipv6: 491 // enabled: true 492 // address: 493 // - ip: 192.168.122.21 494 // prefix-length: 24 495 // interfaces: 496 // - name: "eth0" 497 // macAddress: "52:54:01:aa:aa:a1"`, 498 // requiresNmstatectl: true, 499 // expectedError: "staticNetwork configuration is not valid", 500 // }, 501 502 { 503 name: "missing-label", 504 data: ` 505 metadata: 506 name: mynmstateconfig 507 namespace: spoke-cluster 508 spec: 509 config: 510 interfaces: 511 - name: eth0 512 type: ethernet 513 state: up 514 mac-address: 52:54:01:aa:aa:a1 515 ipv4: 516 enabled: true 517 address: 518 - ip: 192.168.122.21 519 prefix-length: 24 520 interfaces: 521 - name: "eth0" 522 macAddress: "52:54:01:aa:aa:a1"`, 523 requiresNmstatectl: true, 524 expectedError: "invalid NMStateConfig configuration: ObjectMeta.Labels: Required value: mynmstateconfig does not have any label set", 525 }, 526 527 { 528 name: "not-yaml", 529 data: `This is not a yaml file`, 530 expectedError: "could not decode YAML for cluster-manifests/nmstateconfig.yaml: Error reading multiple YAMLs: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1beta1.NMStateConfig", 531 }, 532 { 533 name: "file-not-found", 534 fetchError: &os.PathError{Err: os.ErrNotExist}, 535 }, 536 { 537 name: "error-fetching-file", 538 fetchError: errors.New("fetch failed"), 539 expectedError: "failed to load file cluster-manifests/nmstateconfig.yaml: fetch failed", 540 }, 541 } 542 for _, tc := range cases { 543 t.Run(tc.name, func(t *testing.T) { 544 // nmstate may not be installed yet in CI so skip this test if not 545 if tc.requiresNmstatectl { 546 _, execErr := exec.LookPath("nmstatectl") 547 if execErr != nil { 548 t.Skip("No nmstatectl binary available") 549 } 550 } 551 552 mockCtrl := gomock.NewController(t) 553 defer mockCtrl.Finish() 554 555 fileFetcher := mock.NewMockFileFetcher(mockCtrl) 556 fileFetcher.EXPECT().FetchByName(nmStateConfigFilename). 557 Return( 558 &asset.File{ 559 Filename: nmStateConfigFilename, 560 Data: []byte(tc.data)}, 561 tc.fetchError, 562 ) 563 564 asset := &NMStateConfig{} 565 found, err := asset.Load(fileFetcher) 566 assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") 567 if tc.expectedError != "" { 568 assert.ErrorContains(t, err, tc.expectedError) 569 } else { 570 assert.NoError(t, err) 571 } 572 if tc.expectedFound { 573 assert.Equal(t, tc.expectedConfig, asset.StaticNetworkConfig, "unexpected Config in NMStateConfig") 574 assert.Equal(t, len(tc.expectedConfig), len(asset.Config)) 575 for i := 0; i < len(tc.expectedConfig); i++ { 576 577 staticNetworkConfig := asset.StaticNetworkConfig[i] 578 nmStateConfig := asset.Config[i] 579 580 for n := 0; n < len(staticNetworkConfig.MacInterfaceMap); n++ { 581 macInterfaceMap := staticNetworkConfig.MacInterfaceMap[n] 582 iface := nmStateConfig.Spec.Interfaces[n] 583 584 assert.Equal(t, macInterfaceMap.LogicalNicName, iface.Name) 585 assert.Equal(t, macInterfaceMap.MacAddress, iface.MacAddress) 586 } 587 assert.YAMLEq(t, staticNetworkConfig.NetworkYaml, string(nmStateConfig.Spec.NetConfig.Raw)) 588 } 589 590 } 591 }) 592 } 593 } 594 595 func TestGetNodeZeroIP(t *testing.T) { 596 cases := []struct { 597 name string 598 expectedIP string 599 expectedError string 600 configs []string 601 hosts []agent.Host 602 }{ 603 { 604 name: "no interfaces", 605 expectedError: "no interface IPs set", 606 }, 607 { 608 name: "first interface", 609 expectedIP: "192.168.122.21", 610 configs: []string{ 611 ` 612 interfaces: 613 - name: eth0 614 type: ethernet 615 ipv4: 616 address: 617 - ip: 192.168.122.21 618 - name: eth1 619 type: ethernet 620 ipv4: 621 address: 622 - ip: 192.168.122.22 623 `, 624 }, 625 }, 626 { 627 name: "second interface", 628 expectedIP: "192.168.122.22", 629 configs: []string{ 630 ` 631 interfaces: 632 - name: eth0 633 type: ethernet 634 - name: eth1 635 type: ethernet 636 ipv4: 637 address: 638 - ip: 192.168.122.22 639 `, 640 }, 641 }, 642 { 643 name: "second host", 644 expectedIP: "192.168.122.22", 645 configs: []string{ 646 ` 647 interfaces: 648 - name: eth0 649 type: ethernet 650 - name: eth1 651 type: ethernet 652 `, 653 ` 654 interfaces: 655 - name: eth0 656 type: ethernet 657 - name: eth1 658 type: ethernet 659 ipv4: 660 address: 661 - ip: 192.168.122.22 662 `, 663 }, 664 }, 665 { 666 name: "ipv4 first", 667 expectedIP: "192.168.122.22", 668 configs: []string{ 669 ` 670 interfaces: 671 - name: eth0 672 type: ethernet 673 ipv6: 674 address: 675 - ip: "2001:0db8::0001" 676 ipv4: 677 address: 678 - ip: 192.168.122.22 679 `, 680 }, 681 }, 682 { 683 name: "ipv6 host first", 684 expectedIP: "2001:0db8::0001", 685 configs: []string{ 686 ` 687 interfaces: 688 - name: eth0 689 type: ethernet 690 ipv6: 691 address: 692 - ip: "2001:0db8::0001" 693 `, 694 ` 695 interfaces: 696 - name: eth0 697 type: ethernet 698 ipv4: 699 address: 700 - ip: 192.168.122.31 701 `, 702 }, 703 }, 704 { 705 name: "ipv6 first", 706 expectedIP: "2001:0db8::0001", 707 configs: []string{ 708 ` 709 interfaces: 710 - name: eth0 711 type: ethernet 712 ipv6: 713 address: 714 - ip: "2001:0db8::0001" 715 - name: eth1 716 type: ethernet 717 ipv4: 718 address: 719 - ip: 192.168.122.22 720 `, 721 }, 722 }, 723 { 724 name: "ipv6", 725 expectedIP: "2001:0db8::0001", 726 configs: []string{ 727 ` 728 interfaces: 729 - name: eth0 730 type: ethernet 731 ipv6: 732 address: 733 - ip: "2001:0db8::0001" 734 `, 735 }, 736 }, 737 { 738 name: "skip workers/nodes without role", 739 expectedIP: "192.168.122.22", 740 hosts: []agent.Host{ 741 { 742 Role: "worker", 743 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 744 interfaces: 745 - name: eth0 746 type: ethernet 747 ipv4: 748 address: 749 - ip: 192.168.122.31`)}, 750 }, 751 { 752 Role: "", 753 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 754 interfaces: 755 - name: eth0 756 type: ethernet 757 ipv4: 758 address: 759 - ip: 192.168.122.32`)}, 760 }, 761 { 762 Role: "master", 763 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 764 interfaces: 765 - name: eth0 766 type: ethernet 767 ipv4: 768 address: 769 - ip: 192.168.122.22`)}, 770 }, 771 }, 772 }, 773 { 774 name: "fail if only workers", 775 expectedError: "invalid NMState configurations provided, no interface IPs set", 776 hosts: []agent.Host{ 777 { 778 Role: "worker", 779 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 780 interfaces: 781 - name: eth0 782 type: ethernet 783 ipv4: 784 address: 785 - ip: 192.168.122.31`)}, 786 }, 787 }, 788 }, 789 { 790 name: "fail if only master without static configuration", 791 expectedError: "invalid NMState configurations provided, no interface IPs set", 792 hosts: []agent.Host{ 793 { 794 Role: "master", 795 }, 796 }, 797 }, 798 { 799 name: "fallback on configs if missing host definition", 800 expectedIP: "192.168.122.22", 801 hosts: []agent.Host{ 802 { 803 Role: "master", 804 }, 805 }, 806 configs: []string{` 807 interfaces: 808 - name: eth0 809 type: ethernet 810 ipv4: 811 address: 812 - ip: 192.168.122.22`, 813 }, 814 }, 815 { 816 name: "implicit masters", 817 expectedIP: "192.168.122.32", 818 hosts: []agent.Host{ 819 { 820 Role: "worker", 821 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 822 interfaces: 823 - name: eth0 824 type: ethernet 825 ipv4: 826 address: 827 - ip: 192.168.122.31`)}, 828 }, 829 { 830 Role: "", 831 NetworkConfig: aiv1beta1.NetConfig{Raw: []byte(` 832 interfaces: 833 - name: eth0 834 type: ethernet 835 ipv4: 836 address: 837 - ip: 192.168.122.32`)}, 838 }, 839 }, 840 }, 841 } 842 for _, tc := range cases { 843 t.Run(tc.name, func(t *testing.T) { 844 var configs []*aiv1beta1.NMStateConfig 845 for _, hostRaw := range tc.configs { 846 configs = append(configs, &aiv1beta1.NMStateConfig{ 847 Spec: aiv1beta1.NMStateConfigSpec{ 848 NetConfig: aiv1beta1.NetConfig{ 849 Raw: aiv1beta1.RawNetConfig(hostRaw), 850 }, 851 }, 852 }) 853 } 854 855 ip, err := GetNodeZeroIP(tc.hosts, configs) 856 if tc.expectedError == "" { 857 assert.NoError(t, err) 858 assert.Equal(t, tc.expectedIP, ip) 859 } else { 860 assert.ErrorContains(t, err, tc.expectedError) 861 } 862 }) 863 } 864 }