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  }