github.com/openshift/installer@v1.4.17/pkg/asset/machines/vsphere/machines_test.go (about) 1 package vsphere 2 3 import ( 4 "net/netip" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/pkg/errors" 9 "github.com/stretchr/testify/assert" 10 "sigs.k8s.io/yaml" 11 12 machineapi "github.com/openshift/api/machine/v1beta1" 13 "github.com/openshift/installer/pkg/types" 14 "github.com/openshift/installer/pkg/types/conversion" 15 "github.com/openshift/installer/pkg/types/vsphere" 16 ) 17 18 const testClusterID = "test" 19 20 var installConfigSample = ` 21 apiVersion: v1 22 baseDomain: example.com 23 controlPlane: 24 name: master 25 platform: 26 vsphere: 27 zones: 28 - deployzone-us-east-1a 29 - deployzone-us-east-2a 30 - deployzone-us-east-3a 31 cpus: 8 32 coresPerSocket: 2 33 memoryMB: 24576 34 osDisk: 35 diskSizeGB: 512 36 replicas: 3 37 compute: 38 - name: worker 39 platform: 40 vsphere: 41 zones: 42 - deployzone-us-east-1a 43 - deployzone-us-east-2a 44 - deployzone-us-east-3a 45 cpus: 8 46 coresPerSocket: 2 47 memoryMB: 24576 48 osDisk: 49 diskSizeGB: 512 50 replicas: 3 51 metadata: 52 name: test-cluster 53 platform: 54 vSphere: 55 vcenters: 56 - server: your.vcenter.example.com 57 username: username 58 password: password 59 datacenters: 60 - dc1 61 - dc2 62 - dc3 63 - dc4 64 failureDomains: 65 - name: deployzone-us-east-1a 66 server: your.vcenter.example.com 67 region: us-east 68 zone: us-east-1a 69 topology: 70 resourcePool: /dc1/host/c1/Resources/rp1 71 folder: /dc1/vm/folder1 72 datacenter: dc1 73 computeCluster: /dc1/host/c1 74 networks: 75 - network1 76 datastore: /dc1/datastore/datastore1 77 - name: deployzone-us-east-2a 78 server: your.vcenter.example.com 79 region: us-east 80 zone: us-east-2a 81 topology: 82 resourcePool: /dc2/host/c2/Resources/rp2 83 folder: /dc2/vm/folder2 84 datacenter: dc2 85 computeCluster: /dc2/host/c2 86 networks: 87 - network1 88 datastore: /dc2/datastore/datastore2 89 - name: deployzone-us-east-3a 90 server: your.vcenter.example.com 91 region: us-east 92 zone: us-east-3a 93 topology: 94 resourcePool: /dc3/host/c3/Resources/rp3 95 folder: /dc3/vm/folder3 96 datacenter: dc3 97 computeCluster: /dc3/host/c3 98 networks: 99 - network1 100 datastore: /dc3/datastore/datastore3 101 - name: deployzone-us-east-4a 102 server: your.vcenter.example.com 103 region: us-east 104 zone: us-east-4a 105 topology: 106 resourcePool: /dc4/host/c4/Resources/rp4 107 folder: /dc4/vm/folder4 108 datacenter: dc4 109 computeCluster: /dc4/host/c4 110 networks: 111 - network1 112 datastore: /dc4/datastore/datastore4 113 hosts: 114 - role: bootstrap 115 networkDevice: 116 ipaddrs: 117 - 192.168.101.240/24 118 gateway: 192.168.101.1 119 nameservers: 120 - 192.168.101.2 121 - role: control-plane 122 failureDomain: deployzone-us-east-1a 123 networkDevice: 124 ipAddrs: 125 - 192.168.101.241/24 126 gateway: 192.168.101.1 127 nameservers: 128 - 192.168.101.2 129 - role: control-plane 130 failureDomain: deployzone-us-east-2a 131 networkDevice: 132 ipAddrs: 133 - 192.168.101.242/24 134 gateway: 192.168.101.1 135 nameservers: 136 - 192.168.101.2 137 - role: control-plane 138 failureDomain: deployzone-us-east-3a 139 networkDevice: 140 ipAddrs: 141 - 192.168.101.243/24 142 gateway: 192.168.101.1 143 nameservers: 144 - 192.168.101.2 145 - role: compute 146 failureDomain: deployzone-us-east-1a 147 networkDevice: 148 ipAddrs: 149 - 192.168.101.244/24 150 gateway: 192.168.101.1 151 nameservers: 152 - 192.168.101.2 153 - role: compute 154 failureDomain: deployzone-us-east-2a 155 networkDevice: 156 ipAddrs: 157 - 192.168.101.245/24 158 gateway: 192.168.101.1 159 nameservers: 160 - 192.168.101.2 161 - role: compute 162 failureDomain: deployzone-us-east-3a 163 networkDevice: 164 ipAddrs: 165 - 192.168.101.246/24 166 gateway: 192.168.101.1 167 nameservers: 168 - 192.168.101.2 169 pullSecret: 170 sshKey:` 171 172 var machinePoolReplicas = int64(3) 173 var machinePoolMinReplicas = int64(2) 174 175 var machinePoolValidZones = types.MachinePool{ 176 Name: "master", 177 Replicas: &machinePoolReplicas, 178 Platform: types.MachinePoolPlatform{ 179 VSphere: &vsphere.MachinePool{ 180 NumCPUs: 4, 181 NumCoresPerSocket: 2, 182 MemoryMiB: 16384, 183 OSDisk: vsphere.OSDisk{ 184 DiskSizeGB: 60, 185 }, 186 Zones: []string{ 187 "deployzone-us-east-1a", 188 "deployzone-us-east-2a", 189 "deployzone-us-east-3a", 190 }, 191 }, 192 }, 193 Hyperthreading: "true", 194 Architecture: types.ArchitectureAMD64, 195 } 196 197 var machinePoolReducedZones = types.MachinePool{ 198 Name: "master", 199 Replicas: &machinePoolReplicas, 200 Platform: types.MachinePoolPlatform{ 201 VSphere: &vsphere.MachinePool{ 202 NumCPUs: 4, 203 NumCoresPerSocket: 2, 204 MemoryMiB: 16384, 205 OSDisk: vsphere.OSDisk{ 206 DiskSizeGB: 60, 207 }, 208 Zones: []string{ 209 "deployzone-us-east-1a", 210 "deployzone-us-east-2a", 211 }, 212 }, 213 }, 214 Hyperthreading: "true", 215 Architecture: types.ArchitectureAMD64, 216 } 217 218 var machinePoolSingleZones = types.MachinePool{ 219 Name: "master", 220 Replicas: &machinePoolReplicas, 221 Platform: types.MachinePoolPlatform{ 222 VSphere: &vsphere.MachinePool{ 223 NumCPUs: 4, 224 NumCoresPerSocket: 2, 225 MemoryMiB: 16384, 226 OSDisk: vsphere.OSDisk{ 227 DiskSizeGB: 60, 228 }, 229 Zones: []string{ 230 "deployzone-us-east-1a", 231 }, 232 }, 233 }, 234 Hyperthreading: "true", 235 Architecture: types.ArchitectureAMD64, 236 } 237 238 var machinePoolUndefinedZones = types.MachinePool{ 239 Name: "master", 240 Replicas: &machinePoolReplicas, 241 Platform: types.MachinePoolPlatform{ 242 VSphere: &vsphere.MachinePool{ 243 NumCPUs: 4, 244 NumCoresPerSocket: 2, 245 MemoryMiB: 16384, 246 OSDisk: vsphere.OSDisk{ 247 DiskSizeGB: 60, 248 }, 249 Zones: []string{ 250 "region-dc1-zone-undefined", 251 }, 252 }, 253 }, 254 Hyperthreading: "true", 255 Architecture: types.ArchitectureAMD64, 256 } 257 258 func parseInstallConfig() (*types.InstallConfig, error) { 259 config := &types.InstallConfig{} 260 if err := yaml.Unmarshal([]byte(installConfigSample), config); err != nil { 261 return nil, errors.Wrapf(err, "failed to unmarshal sample install config") 262 } 263 264 // Upconvert any deprecated fields 265 if err := conversion.ConvertInstallConfig(config); err != nil { 266 return nil, errors.Wrap(err, "failed to upconvert install config") 267 } 268 return config, nil 269 } 270 271 func assertOnUnexpectedErrorState(t *testing.T, expectedError string, err error) { 272 t.Helper() 273 if expectedError == "" && err != nil { 274 t.Errorf("unexpected error encountered: %s", err.Error()) 275 } else if expectedError != "" { 276 if err != nil { 277 if expectedError != err.Error() { 278 t.Errorf("expected error does not match intended error. should be: %s was: %s", expectedError, err.Error()) 279 } 280 } else { 281 t.Errorf("expected error but error did not occur") 282 } 283 } 284 } 285 286 func TestConfigMasters(t *testing.T) { 287 clusterID := testClusterID 288 installConfig, err := parseInstallConfig() 289 if err != nil { 290 assert.Errorf(t, err, "unable to parse sample install config") 291 return 292 } 293 defaultClusterResourcePool, err := parseInstallConfig() 294 if err != nil { 295 t.Error(err) 296 return 297 } 298 defaultClusterResourcePool.VSphere.FailureDomains[0].Topology.ResourcePool = "" 299 300 testCases := []struct { 301 testCase string 302 expectedError string 303 machinePool *types.MachinePool 304 workspaces []machineapi.Workspace 305 installConfig *types.InstallConfig 306 maxAllowedWorkspaceMatches int 307 minAllowedWorkspaceMatches int 308 }{ 309 { 310 testCase: "zones distributed among control plane machines(zone count matches machine count)", 311 machinePool: &machinePoolValidZones, 312 maxAllowedWorkspaceMatches: 1, 313 installConfig: installConfig, 314 workspaces: []machineapi.Workspace{ 315 { 316 Server: "your.vcenter.example.com", 317 Datacenter: "dc1", 318 Folder: "/dc1/vm/folder1", 319 Datastore: "/dc1/datastore/datastore1", 320 ResourcePool: "/dc1/host/c1/Resources/rp1", 321 }, 322 { 323 Server: "your.vcenter.example.com", 324 Datacenter: "dc2", 325 Folder: "/dc2/vm/folder2", 326 Datastore: "/dc2/datastore/datastore2", 327 ResourcePool: "/dc2/host/c2/Resources/rp2", 328 }, 329 { 330 Server: "your.vcenter.example.com", 331 Datacenter: "dc3", 332 Folder: "/dc3/vm/folder3", 333 Datastore: "/dc3/datastore/datastore3", 334 ResourcePool: "/dc3/host/c3/Resources/rp3", 335 }, 336 }, 337 }, 338 { 339 testCase: "undefined zone in machinepool results in error", 340 machinePool: &machinePoolUndefinedZones, 341 installConfig: installConfig, 342 expectedError: "zone [region-dc1-zone-undefined] specified by machinepool is not defined", 343 }, 344 { 345 testCase: "zones distributed among control plane machines(zone count is less than machine count)", 346 machinePool: &machinePoolReducedZones, 347 maxAllowedWorkspaceMatches: 2, 348 minAllowedWorkspaceMatches: 1, 349 installConfig: installConfig, 350 workspaces: []machineapi.Workspace{ 351 { 352 Server: "your.vcenter.example.com", 353 Datacenter: "dc1", 354 Folder: "/dc1/vm/folder1", 355 Datastore: "/dc1/datastore/datastore1", 356 ResourcePool: "/dc1/host/c1/Resources/rp1", 357 }, 358 { 359 Server: "your.vcenter.example.com", 360 Datacenter: "dc2", 361 Folder: "/dc2/vm/folder2", 362 Datastore: "/dc2/datastore/datastore2", 363 ResourcePool: "/dc2/host/c2/Resources/rp2", 364 }, 365 }, 366 }, 367 { 368 testCase: "all masters in single zone / pool", 369 machinePool: &machinePoolSingleZones, 370 maxAllowedWorkspaceMatches: 3, 371 installConfig: installConfig, 372 workspaces: []machineapi.Workspace{ 373 { 374 Server: "your.vcenter.example.com", 375 Datacenter: "dc1", 376 Folder: "/dc1/vm/folder1", 377 Datastore: "/dc1/datastore/datastore1", 378 ResourcePool: "/dc1/host/c1/Resources/rp1", 379 }, 380 }, 381 }, 382 { 383 testCase: "full path to cluster resource pool when no pool provided via placement constraint", 384 machinePool: &machinePoolValidZones, 385 maxAllowedWorkspaceMatches: 1, 386 minAllowedWorkspaceMatches: 1, 387 installConfig: defaultClusterResourcePool, 388 workspaces: []machineapi.Workspace{ 389 { 390 Server: "your.vcenter.example.com", 391 Datacenter: "dc1", 392 Folder: "/dc1/vm/folder1", 393 Datastore: "/dc1/datastore/datastore1", 394 ResourcePool: "/dc1/host/c1/Resources", 395 }, 396 { 397 Server: "your.vcenter.example.com", 398 Datacenter: "dc2", 399 Folder: "/dc2/vm/folder2", 400 Datastore: "/dc2/datastore/datastore2", 401 ResourcePool: "/dc2/host/c2/Resources/rp2", 402 }, 403 { 404 Server: "your.vcenter.example.com", 405 Datacenter: "dc3", 406 Folder: "/dc3/vm/folder3", 407 Datastore: "/dc3/datastore/datastore3", 408 ResourcePool: "/dc3/host/c3/Resources/rp3", 409 }, 410 }, 411 }, 412 } 413 414 for _, tc := range testCases { 415 t.Run(tc.testCase, func(t *testing.T) { 416 data, err := Machines(clusterID, tc.installConfig, tc.machinePool, "", "", "") 417 assertOnUnexpectedErrorState(t, tc.expectedError, err) 418 419 if len(tc.workspaces) > 0 { 420 var matchCountByIndex []int 421 for range tc.workspaces { 422 matchCountByIndex = append(matchCountByIndex, 0) 423 } 424 425 for _, machine := range data.Machines { 426 // check if expected workspaces are returned 427 machineWorkspace := machine.Spec.ProviderSpec.Value.Object.(*machineapi.VSphereMachineProviderSpec).Workspace 428 for idx, workspace := range tc.workspaces { 429 if cmp.Equal(workspace, *machineWorkspace) { 430 matchCountByIndex[idx]++ 431 } 432 } 433 } 434 for _, count := range matchCountByIndex { 435 if count > tc.maxAllowedWorkspaceMatches { 436 t.Errorf("machine workspace was enountered too many times[max: %d]", tc.maxAllowedWorkspaceMatches) 437 } 438 if count < tc.minAllowedWorkspaceMatches { 439 t.Errorf("machine workspace was enountered too few times[min: %d]", tc.minAllowedWorkspaceMatches) 440 } 441 } 442 443 if data.ControlPlaneMachineSet != nil { 444 // Make sure FDs equal same quantity as config 445 fds := data.ControlPlaneMachineSet.Spec.Template.OpenShiftMachineV1Beta1Machine.FailureDomains 446 if len(fds.VSphere) != len(tc.workspaces) { 447 t.Errorf("machine workspace count %d does not equal number of failure domains [count: %d] in CPMS", len(tc.workspaces), len(fds.VSphere)) 448 } 449 } 450 } 451 }) 452 } 453 } 454 455 func TestHostsToMachines(t *testing.T) { 456 clusterID := testClusterID 457 installConfig, err := parseInstallConfig() 458 if err != nil { 459 assert.Errorf(t, err, "unable to parse sample install config") 460 return 461 } 462 defaultClusterResourcePool, err := parseInstallConfig() 463 if err != nil { 464 t.Error(err) 465 return 466 } 467 defaultClusterResourcePool.VSphere.FailureDomains[0].Topology.ResourcePool = "" 468 469 testCases := []struct { 470 testCase string 471 expectedError string 472 machinePool *types.MachinePool 473 installConfig *types.InstallConfig 474 role string 475 machineCount int 476 }{ 477 { 478 testCase: "Static IP - ControlPlane", 479 machinePool: &machinePoolValidZones, 480 installConfig: installConfig, 481 role: "master", 482 machineCount: 3, 483 }, 484 { 485 testCase: "Static IP - Compute", 486 machinePool: &machinePoolValidZones, 487 installConfig: installConfig, 488 role: "worker", 489 machineCount: 3, 490 }, 491 } 492 493 for _, tc := range testCases { 494 t.Run(tc.testCase, func(t *testing.T) { 495 data, err := Machines(clusterID, tc.installConfig, tc.machinePool, "", tc.role, "") 496 assertOnUnexpectedErrorState(t, tc.expectedError, err) 497 498 // Check machine counts 499 if len(data.Machines) != tc.machineCount { 500 t.Errorf("machine count (%v) did not match expected (%v).", len(data.Machines), tc.machineCount) 501 } 502 503 // Check Claim counts 504 if len(data.IPClaims) != tc.machineCount { 505 t.Errorf("ip address claim count (%v) did not match expected (%v).", len(data.IPClaims), tc.machineCount) 506 } 507 508 // Check ip address counts 509 if len(data.IPAddresses) != tc.machineCount { 510 t.Errorf("ip address count (%v) did not match expected (%v).", len(data.IPAddresses), tc.machineCount) 511 } 512 513 // Verify static IP was set on all machines 514 for index, machine := range data.Machines { 515 provider, success := machine.Spec.ProviderSpec.Value.Object.(*machineapi.VSphereMachineProviderSpec) 516 if !success { 517 t.Errorf("Unable to convert vshere machine provider spec.") 518 } 519 520 if len(provider.Network.Devices) == 1 { 521 // Check IP 522 if provider.Network.Devices[0].AddressesFromPools == nil { 523 t.Errorf("AddressesFromPools is not set: %v", machine) 524 } 525 526 // Check nameserver 527 if provider.Network.Devices[0].Nameservers == nil || provider.Network.Devices[0].Nameservers[0] == "" { 528 t.Errorf("Nameserver is not set: %v", machine) 529 } 530 531 gateway := data.IPAddresses[index].Spec.Gateway 532 ip, err := netip.ParseAddr(gateway) 533 if err != nil { 534 t.Error(err) 535 } 536 targetGateway := "192.168.101.1" 537 if ip.Is6() { 538 targetGateway = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 539 } 540 541 // Check gateway 542 if gateway != targetGateway { 543 t.Errorf("Gateway is incorrect: %v", gateway) 544 } 545 } else { 546 t.Errorf("Devices not set for machine: %v", machine) 547 } 548 } 549 }) 550 } 551 }