diff --git a/pkg/executables/cmk.go b/pkg/executables/cmk.go index fbef14e81efbe..5bcb484dc7a25 100644 --- a/pkg/executables/cmk.go +++ b/pkg/executables/cmk.go @@ -380,6 +380,10 @@ func NewCmk(executable Executable, writer filewriter.FileWriter, config decoder. } } +func (c *Cmk) GetManagementApiEndpoint(ctx context.Context) string { + return c.config.ManagementUrl +} + // ValidateCloudStackConnection Calls `cmk sync` to ensure that the endpoint and credentials + domain are valid func (c *Cmk) ValidateCloudStackConnection(ctx context.Context) error { command := newCmkCommand("sync") diff --git a/pkg/providers/cloudstack/cloudstack.go b/pkg/providers/cloudstack/cloudstack.go index 572ee24183792..1288483856cc7 100644 --- a/pkg/providers/cloudstack/cloudstack.go +++ b/pkg/providers/cloudstack/cloudstack.go @@ -63,7 +63,7 @@ type cloudstackProvider struct { selfSigned bool templateBuilder *CloudStackTemplateBuilder skipIpCheck bool - validators map[string]*Validator + validator *Validator } func (p *cloudstackProvider) PreBootstrapSetup(ctx context.Context, cluster *types.Cluster) error { @@ -201,7 +201,7 @@ type ProviderKubectlClient interface { SetEksaControllerEnvVar(ctx context.Context, envVar, envVarVal, kubeconfig string) error } -func NewProvider(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineConfigs map[string]*v1alpha1.CloudStackMachineConfig, clusterConfig *v1alpha1.Cluster, providerKubectlClient ProviderKubectlClient, providerCmkClients map[string]ProviderCmkClient, writer filewriter.FileWriter, now types.NowFunc, skipIpCheck bool) *cloudstackProvider { +func NewProvider(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineConfigs map[string]*v1alpha1.CloudStackMachineConfig, clusterConfig *v1alpha1.Cluster, providerKubectlClient ProviderKubectlClient, providerCmkClients CmkClientMap, writer filewriter.FileWriter, now types.NowFunc, skipIpCheck bool) *cloudstackProvider { return NewProviderCustomNet( datacenterConfig, machineConfigs, @@ -214,7 +214,7 @@ func NewProvider(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineC ) } -func NewProviderCustomNet(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineConfigs map[string]*v1alpha1.CloudStackMachineConfig, clusterConfig *v1alpha1.Cluster, providerKubectlClient ProviderKubectlClient, providerCmkClients map[string]ProviderCmkClient, writer filewriter.FileWriter, now types.NowFunc, skipIpCheck bool) *cloudstackProvider { +func NewProviderCustomNet(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineConfigs map[string]*v1alpha1.CloudStackMachineConfig, clusterConfig *v1alpha1.Cluster, providerKubectlClient ProviderKubectlClient, providerCmkClients CmkClientMap, writer filewriter.FileWriter, now types.NowFunc, skipIpCheck bool) *cloudstackProvider { var controlPlaneMachineSpec, etcdMachineSpec *v1alpha1.CloudStackMachineConfigSpec workerNodeGroupMachineSpecs := make(map[string]v1alpha1.CloudStackMachineConfigSpec, len(machineConfigs)) if clusterConfig.Spec.ControlPlaneConfiguration.MachineGroupRef != nil && machineConfigs[clusterConfig.Spec.ControlPlaneConfiguration.MachineGroupRef.Name] != nil { @@ -225,10 +225,6 @@ func NewProviderCustomNet(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, etcdMachineSpec = &machineConfigs[clusterConfig.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name].Spec } } - validators := map[string]*Validator{} - for profileName, providerCmkClient := range providerCmkClients { - validators[profileName] = NewValidator(providerCmkClient) - } return &cloudstackProvider{ datacenterConfig: datacenterConfig, machineConfigs: machineConfigs, @@ -244,7 +240,7 @@ func NewProviderCustomNet(datacenterConfig *v1alpha1.CloudStackDatacenterConfig, now: now, }, skipIpCheck: skipIpCheck, - validators: validators, + validator: NewValidator(providerCmkClients), } } @@ -385,16 +381,14 @@ func (p *cloudstackProvider) validateEnv(ctx context.Context) error { } func (p *cloudstackProvider) validateClusterSpec(ctx context.Context, clusterSpec *cluster.Spec) (err error) { - for _, validator := range p.validators { - if err := validator.validateCloudStackAccess(ctx); err != nil { - return err - } - if err := validator.ValidateCloudStackDatacenterConfig(ctx, p.datacenterConfig); err != nil { - return err - } - if err := validator.ValidateClusterMachineConfigs(ctx, NewSpec(clusterSpec, p.machineConfigs, p.datacenterConfig)); err != nil { - return err - } + if err := p.validator.validateCloudStackAccess(ctx, p.datacenterConfig); err != nil { + return err + } + if err := p.validator.ValidateCloudStackDatacenterConfig(ctx, p.datacenterConfig); err != nil { + return err + } + if err := p.validator.ValidateClusterMachineConfigs(ctx, NewSpec(clusterSpec, p.machineConfigs, p.datacenterConfig)); err != nil { + return err } return nil } diff --git a/pkg/providers/cloudstack/cloudstack_test.go b/pkg/providers/cloudstack/cloudstack_test.go index 6433521cfabe8..1fcae1b678572 100644 --- a/pkg/providers/cloudstack/cloudstack_test.go +++ b/pkg/providers/cloudstack/cloudstack_test.go @@ -68,6 +68,7 @@ func givenWildcardCmk(mockCtrl *gomock.Controller) ProviderCmkClient { cmk.EXPECT().ValidateDomainPresent(gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAccountPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateNetworkPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + cmk.EXPECT().GetManagementApiEndpoint(gomock.Any()).AnyTimes().Return("http://127.16.0.1:8080/client/api") return cmk } @@ -193,8 +194,8 @@ func newProviderWithKubectl(t *testing.T, datacenterConfig *v1alpha1.CloudStackD func newProvider(t *testing.T, datacenterConfig *v1alpha1.CloudStackDatacenterConfig, machineConfigs map[string]*v1alpha1.CloudStackMachineConfig, clusterConfig *v1alpha1.Cluster, kubectl ProviderKubectlClient, cmk ProviderCmkClient) *cloudstackProvider { _, writer := test.NewWriter(t) - cmks := map[string]ProviderCmkClient{} - cmks["mockCmk"] = cmk + cmks := CmkClientMap{} + cmks["Global"] = cmk return NewProviderCustomNet( datacenterConfig, diff --git a/pkg/providers/cloudstack/decoder/decoder.go b/pkg/providers/cloudstack/decoder/decoder.go index 7a51ce981c66a..9e1170e55557b 100644 --- a/pkg/providers/cloudstack/decoder/decoder.go +++ b/pkg/providers/cloudstack/decoder/decoder.go @@ -13,6 +13,7 @@ const ( EksacloudStackCloudConfigB64SecretKey = "EKSA_CLOUDSTACK_B64ENCODED_SECRET" CloudStackCloudConfigB64SecretKey = "CLOUDSTACK_B64ENCODED_SECRET" EksaCloudStackHostPathToMount = "EKSA_CLOUDSTACK_HOST_PATHS_TO_MOUNT" + CloudStackGlobalAZ = "Global" ) // ParseCloudStackSecret parses the input b64 string into the ini object to extract out the api key, secret key, and url @@ -37,7 +38,7 @@ func ParseCloudStackSecret() (*CloudStackExecConfig, error) { if section.Name() == "DEFAULT" { continue } - if section.Name() == "Global" { + if section.Name() == CloudStackGlobalAZ { foundGlobalSection = true } diff --git a/pkg/providers/cloudstack/decoder/decoder_test.go b/pkg/providers/cloudstack/decoder/decoder_test.go index f2d540196e128..b6a1282c39fdb 100644 --- a/pkg/providers/cloudstack/decoder/decoder_test.go +++ b/pkg/providers/cloudstack/decoder/decoder_test.go @@ -42,7 +42,7 @@ func TestCloudStackConfigDecoder(t *testing.T) { wantConfig: &decoder.CloudStackExecConfig{ Profiles: []decoder.CloudStackProfileConfig{ { - Name: "Global", + Name: decoder.CloudStackGlobalAZ, ApiKey: "test-key1", SecretKey: "test-secret1", ManagementUrl: "http://127.16.0.1:8080/client/api", @@ -59,7 +59,7 @@ func TestCloudStackConfigDecoder(t *testing.T) { wantConfig: &decoder.CloudStackExecConfig{ Profiles: []decoder.CloudStackProfileConfig{ { - Name: "Global", + Name: decoder.CloudStackGlobalAZ, ApiKey: "test-key1", SecretKey: "test-secret1", ManagementUrl: "http://127.16.0.1:8080/client/api", @@ -98,7 +98,7 @@ func TestCloudStackConfigDecoder(t *testing.T) { wantConfig: &decoder.CloudStackExecConfig{ Profiles: []decoder.CloudStackProfileConfig{ { - Name: "Global", + Name: decoder.CloudStackGlobalAZ, ApiKey: "test-key1", SecretKey: "test-secret1", ManagementUrl: "http://127.16.0.1:8080/client/api", diff --git a/pkg/providers/cloudstack/mocks/client.go b/pkg/providers/cloudstack/mocks/client.go index afd957431e583..2f296b471a967 100644 --- a/pkg/providers/cloudstack/mocks/client.go +++ b/pkg/providers/cloudstack/mocks/client.go @@ -41,6 +41,20 @@ func (m *MockProviderCmkClient) EXPECT() *MockProviderCmkClientMockRecorder { return m.recorder } +// GetManagementApiEndpoint mocks base method. +func (m *MockProviderCmkClient) GetManagementApiEndpoint(arg0 context.Context) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetManagementApiEndpoint", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetManagementApiEndpoint indicates an expected call of GetManagementApiEndpoint. +func (mr *MockProviderCmkClientMockRecorder) GetManagementApiEndpoint(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManagementApiEndpoint", reflect.TypeOf((*MockProviderCmkClient)(nil).GetManagementApiEndpoint), arg0) +} + // ValidateAccountPresent mocks base method. func (m *MockProviderCmkClient) ValidateAccountPresent(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() diff --git a/pkg/providers/cloudstack/validator.go b/pkg/providers/cloudstack/validator.go index f1de3edcc079f..ec26c6b73847f 100644 --- a/pkg/providers/cloudstack/validator.go +++ b/pkg/providers/cloudstack/validator.go @@ -13,7 +13,8 @@ import ( ) type Validator struct { - cmk ProviderCmkClient + cmks CmkClientMap + availabilityZones []localAvailabilityZone } // Taken from https://github.com/shapeblue/cloudstack/blob/08bb4ad9fea7e422c3d3ac6d52f4670b1e89eed7/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -28,15 +29,21 @@ var restrictedUserCustomDetails = [...]string{ "keypairnames", "controlNodeLoginUser", } -var domainId string - -func NewValidator(cmk ProviderCmkClient) *Validator { +func NewValidator(cmks CmkClientMap) *Validator { return &Validator{ - cmk: cmk, + cmks: cmks, + availabilityZones: []localAvailabilityZone{}, } } +type localAvailabilityZone struct { + *anywherev1.CloudStackAvailabilityZone + ZoneId string + DomainId string +} + type ProviderCmkClient interface { + GetManagementApiEndpoint(ctx context.Context) string ValidateCloudStackConnection(ctx context.Context) error ValidateServiceOfferingPresent(ctx context.Context, zoneId string, serviceOffering anywherev1.CloudStackResourceIdentifier) error ValidateDiskOfferingPresent(ctx context.Context, zoneId string, diskOffering anywherev1.CloudStackResourceDiskOffering) error @@ -48,60 +55,60 @@ type ProviderCmkClient interface { ValidateAccountPresent(ctx context.Context, account string, domainId string) error } -func (v *Validator) validateCloudStackAccess(ctx context.Context) error { - if err := v.cmk.ValidateCloudStackConnection(ctx); err != nil { - return fmt.Errorf("failed validating connection to cloudstack: %v", err) - } - logger.MarkPass("Connected to server") - - return nil -} +type CmkClientMap map[string]ProviderCmkClient -func (v *Validator) ValidateCloudStackDatacenterConfig(ctx context.Context, datacenterConfig *anywherev1.CloudStackDatacenterConfig) error { - if len(datacenterConfig.Spec.Domain) <= 0 { - return fmt.Errorf("CloudStackDatacenterConfig domain is not set or is empty") - } - if datacenterConfig.Spec.ManagementApiEndpoint == "" { - return fmt.Errorf("CloudStackDatacenterConfig managementApiEndpoint is not set or is empty") - } - _, err := getHostnameFromUrl(datacenterConfig.Spec.ManagementApiEndpoint) - if err != nil { - return fmt.Errorf("checking management api endpoint: %v", err) +func (v *Validator) validateCloudStackAccess(ctx context.Context, datacenterConfig *anywherev1.CloudStackDatacenterConfig) error { + azNamesToCheck := []string{} + if len(datacenterConfig.Spec.Domain) > 0 { + azNamesToCheck = append(azNamesToCheck, decoder.CloudStackGlobalAZ) } - execConfig, err := decoder.ParseCloudStackSecret() - if err != nil { - return fmt.Errorf("parsing cloudstack secret: %v", err) + for _, az := range datacenterConfig.Spec.AvailabilityZones { + azNamesToCheck = append(azNamesToCheck, az.Name) } - found := false - for _, instance := range execConfig.Profiles { - if instance.ManagementUrl == datacenterConfig.Spec.ManagementApiEndpoint { - found = true - break + for _, azName := range azNamesToCheck { + cmk := v.cmks[azName] + if cmk == nil { + return fmt.Errorf("cannot find CloudStack profile for availability zone %s", azName) + } + if err := cmk.ValidateCloudStackConnection(ctx); err != nil { + return fmt.Errorf("failed validating connection to cloudstack %s: %v", azName, err) } } - if !found { - return fmt.Errorf("cluster spec management url (%s) is not found in the cloudstack secret", - datacenterConfig.Spec.ManagementApiEndpoint) - } + logger.MarkPass("Connected to", "servers", azNamesToCheck) + return nil +} - if err := v.validateDomainAndAccount(ctx, datacenterConfig); err != nil { +func (v *Validator) ValidateCloudStackDatacenterConfig(ctx context.Context, datacenterConfig *anywherev1.CloudStackDatacenterConfig) error { + if err := v.generateLocalAvailabilityZones(ctx, datacenterConfig); err != nil { return err } - zones, errZone := v.cmk.ValidateZonesPresent(ctx, datacenterConfig.Spec.Zones) - if errZone != nil { - return fmt.Errorf("checking zones %v", errZone) - } + for _, az := range v.availabilityZones { + _, err := getHostnameFromUrl(az.ManagementApiEndpoint) + if err != nil { + return fmt.Errorf("checking management api endpoint: %v", err) + } - for _, zone := range datacenterConfig.Spec.Zones { - if len(zone.Network.Id) == 0 && len(zone.Network.Name) == 0 { - return fmt.Errorf("zone network is not set or is empty") + cmk := v.cmks[az.Name] + if cmk.GetManagementApiEndpoint(ctx) != az.ManagementApiEndpoint { + return fmt.Errorf("cloudstack secret management url (%s) differs from cluster spec management url (%s)", + cmk.GetManagementApiEndpoint(ctx), az.ManagementApiEndpoint) } - err := v.cmk.ValidateNetworkPresent(ctx, domainId, zone, zones, datacenterConfig.Spec.Account, len(zones) > 1) + + zones, err := cmk.ValidateZonesPresent(ctx, []anywherev1.CloudStackZone{az.CloudStackAvailabilityZone.Zone}) if err != nil { - return fmt.Errorf("checking network %v", err) + return err + } + if len(zones) > 0 { + az.CloudStackAvailabilityZone.Zone.Id = zones[0].Id + } + if len(az.CloudStackAvailabilityZone.Zone.Network.Id) == 0 && len(az.CloudStackAvailabilityZone.Zone.Network.Name) == 0 { + return fmt.Errorf("zone network is not set or is empty") + } + if err := cmk.ValidateNetworkPresent(ctx, az.DomainId, az.CloudStackAvailabilityZone.Zone, zones, az.Account, true); err != nil { + return err } } @@ -109,31 +116,61 @@ func (v *Validator) ValidateCloudStackDatacenterConfig(ctx context.Context, data return nil } -func (v *Validator) validateDomainAndAccount(ctx context.Context, datacenterConfig *anywherev1.CloudStackDatacenterConfig) error { - if (datacenterConfig.Spec.Domain != "" && datacenterConfig.Spec.Account == "") || - (datacenterConfig.Spec.Domain == "" && datacenterConfig.Spec.Account != "") { - return fmt.Errorf("both domain and account must be specified or none of them must be specified") +func (v *Validator) generateLocalAvailabilityZones(ctx context.Context, datacenterConfig *anywherev1.CloudStackDatacenterConfig) error { + if datacenterConfig == nil { + return nil } - if datacenterConfig.Spec.Domain != "" && datacenterConfig.Spec.Account != "" { - domain, errDomain := v.cmk.ValidateDomainPresent(ctx, datacenterConfig.Spec.Domain) - if errDomain != nil { - return fmt.Errorf("checking domain: %v", errDomain) + if len(datacenterConfig.Spec.Domain) > 0 { + cmk := v.cmks[decoder.CloudStackGlobalAZ] + domain, err := cmk.ValidateDomainPresent(ctx, datacenterConfig.Spec.Domain) + if err != nil { + return err } - - errAccount := v.cmk.ValidateAccountPresent(ctx, datacenterConfig.Spec.Account, domain.Id) - if errAccount != nil { - return fmt.Errorf("checking account: %v", errAccount) + if err := cmk.ValidateAccountPresent(ctx, datacenterConfig.Spec.Account, domain.Id); err != nil { + return err + } + for _, zone := range datacenterConfig.Spec.Zones { + availabilityZone := localAvailabilityZone{ + CloudStackAvailabilityZone: &anywherev1.CloudStackAvailabilityZone{ + Name: decoder.CloudStackGlobalAZ, + Domain: datacenterConfig.Spec.Domain, + Account: datacenterConfig.Spec.Account, + ManagementApiEndpoint: datacenterConfig.Spec.ManagementApiEndpoint, + Zone: zone, + }, + DomainId: domain.Id, + } + v.availabilityZones = append(v.availabilityZones, availabilityZone) + } + } + for _, az := range datacenterConfig.Spec.AvailabilityZones { + cmk := v.cmks[az.Name] + domain, err := cmk.ValidateDomainPresent(ctx, az.Domain) + if err != nil { + return err + } + if err := cmk.ValidateAccountPresent(ctx, az.Account, domain.Id); err != nil { + return err } + availabilityZone := localAvailabilityZone{ + CloudStackAvailabilityZone: &az, + DomainId: domain.Id, + } + v.availabilityZones = append(v.availabilityZones, availabilityZone) + } - domainId = domain.Id + if len(v.availabilityZones) <= 0 { + return fmt.Errorf("CloudStackDatacenterConfig domain or availabilityZones is not set or is empty") } return nil } // TODO: dry out machine configs validations func (v *Validator) ValidateClusterMachineConfigs(ctx context.Context, cloudStackClusterSpec *Spec) error { - var etcdMachineConfig *anywherev1.CloudStackMachineConfig + if err := v.ValidateCloudStackDatacenterConfig(ctx, cloudStackClusterSpec.CloudStackDatacenter); err != nil { + return err + } if len(cloudStackClusterSpec.Cluster.Spec.ControlPlaneConfiguration.Endpoint.Host) <= 0 { return fmt.Errorf("cluster controlPlaneConfiguration.Endpoint.Host is not set or is empty") @@ -151,7 +188,7 @@ func (v *Validator) ValidateClusterMachineConfigs(ctx context.Context, cloudStac if cloudStackClusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef == nil { return fmt.Errorf("must specify machineGroupRef for etcd machines") } - etcdMachineConfig = cloudStackClusterSpec.etcdMachineConfig() + etcdMachineConfig := cloudStackClusterSpec.etcdMachineConfig() if etcdMachineConfig == nil { return fmt.Errorf("cannot find CloudStackMachineConfig %v for etcd machines", cloudStackClusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name) } @@ -243,29 +280,24 @@ func (v *Validator) validateMachineConfig(ctx context.Context, datacenterConfig return fmt.Errorf("restricted key %s found in custom user details", restrictedKey) } } - zones, err := v.cmk.ValidateZonesPresent(ctx, datacenterConfig.Spec.Zones) - if err != nil { - return fmt.Errorf("checking zones %v", err) - } - account := datacenterConfig.Spec.Account - for _, zone := range zones { - if err = v.cmk.ValidateTemplatePresent(ctx, domainId, zone.Id, account, machineConfig.Spec.Template); err != nil { + for _, az := range v.availabilityZones { + cmk := v.cmks[az.Name] + if err := cmk.ValidateTemplatePresent(ctx, az.DomainId, az.CloudStackAvailabilityZone.Zone.Id, az.Account, machineConfig.Spec.Template); err != nil { return fmt.Errorf("validating template: %v", err) } - if err = v.cmk.ValidateServiceOfferingPresent(ctx, zone.Id, machineConfig.Spec.ComputeOffering); err != nil { + if err := cmk.ValidateServiceOfferingPresent(ctx, az.CloudStackAvailabilityZone.Zone.Id, machineConfig.Spec.ComputeOffering); err != nil { return fmt.Errorf("validating service offering: %v", err) } if len(machineConfig.Spec.DiskOffering.Id) > 0 || len(machineConfig.Spec.DiskOffering.Name) > 0 { - if err = v.cmk.ValidateDiskOfferingPresent(ctx, zone.Id, machineConfig.Spec.DiskOffering); err != nil { + if err := cmk.ValidateDiskOfferingPresent(ctx, az.CloudStackAvailabilityZone.Zone.Id, machineConfig.Spec.DiskOffering); err != nil { return fmt.Errorf("validating disk offering: %v", err) } } - } - - if len(machineConfig.Spec.AffinityGroupIds) > 0 { - if err = v.cmk.ValidateAffinityGroupsPresent(ctx, domainId, account, machineConfig.Spec.AffinityGroupIds); err != nil { - return fmt.Errorf("validating affinity group ids: %v", err) + if len(machineConfig.Spec.AffinityGroupIds) > 0 { + if err := cmk.ValidateAffinityGroupsPresent(ctx, az.DomainId, az.Account, machineConfig.Spec.AffinityGroupIds); err != nil { + return fmt.Errorf("validating affinity group ids: %v", err) + } } } diff --git a/pkg/providers/cloudstack/validator_test.go b/pkg/providers/cloudstack/validator_test.go index 553621bc5b93e..d04864b98e166 100644 --- a/pkg/providers/cloudstack/validator_test.go +++ b/pkg/providers/cloudstack/validator_test.go @@ -12,6 +12,7 @@ import ( "github.com/aws/eks-anywhere/internal/test" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/providers/cloudstack/decoder" "github.com/aws/eks-anywhere/pkg/providers/cloudstack/mocks" ) @@ -42,18 +43,16 @@ func TestValidateCloudStackDatacenterConfig(t *testing.T) { ctx := context.Background() setupContext() cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) - cloudstackDatacenter, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) + datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") } - cmk.EXPECT().ValidateZonesPresent(ctx, cloudstackDatacenter.Spec.Zones).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) - cmk.EXPECT().ValidateDomainPresent(ctx, cloudstackDatacenter.Spec.Domain).Return(v1alpha1.CloudStackResourceIdentifier{Id: "5300cdac-74d5-11ec-8696-c81f66d3e965", Name: cloudstackDatacenter.Spec.Domain}, nil) - cmk.EXPECT().ValidateAccountPresent(ctx, gomock.Any(), gomock.Any()).Return(nil) - cmk.EXPECT().ValidateNetworkPresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), false).Return(nil) - err = validator.ValidateCloudStackDatacenterConfig(ctx, cloudstackDatacenter) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + + err = validator.ValidateCloudStackDatacenterConfig(ctx, datacenterConfig) if err != nil { t.Fatalf("failed to validate CloudStackDataCenterConfig: %v", err) } @@ -62,11 +61,14 @@ func TestValidateCloudStackDatacenterConfig(t *testing.T) { func TestValidateCloudStackConnection(t *testing.T) { ctx := context.Background() cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) + datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) + if err != nil { + t.Fatalf("unable to get datacenter config from file") + } cmk.EXPECT().ValidateCloudStackConnection(ctx).Return(nil) - err := validator.validateCloudStackAccess(ctx) - if err != nil { + if err := validator.validateCloudStackAccess(ctx, datacenterConfig); err != nil { t.Fatalf("failed to validate CloudStackDataCenterConfig: %v", err) } } @@ -74,7 +76,7 @@ func TestValidateCloudStackConnection(t *testing.T) { func TestValidateMachineConfigsNoControlPlaneEndpointIP(t *testing.T) { ctx := context.Background() cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { @@ -86,6 +88,8 @@ func TestValidateMachineConfigsNoControlPlaneEndpointIP(t *testing.T) { } clusterSpec.Cluster.Spec.ControlPlaneConfiguration.Endpoint.Host = "" + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "cluster controlPlaneConfiguration.Endpoint.Host is not set or is empty", err) @@ -105,10 +109,8 @@ func TestValidateDatacenterConfigsNoNetwork(t *testing.T) { datacenterConfig: datacenterConfig, machineConfigsLookup: nil, } - validator := NewValidator(cmk) - cmk.EXPECT().ValidateZonesPresent(ctx, gomock.Any()).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) - cmk.EXPECT().ValidateDomainPresent(ctx, gomock.Any()).Return(v1alpha1.CloudStackResourceIdentifier{Id: "5300cdac-74d5-11ec-8696-c81f66d3e965", Name: "ROOT"}, nil) - cmk.EXPECT().ValidateAccountPresent(ctx, gomock.Any(), gomock.Any()).Return(nil) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) datacenterConfig.Spec.Zones[0].Network.Id = "" datacenterConfig.Spec.Zones[0].Network.Name = "" @@ -130,7 +132,8 @@ func TestValidateDatacenterBadManagementEndpoint(t *testing.T) { datacenterConfig: datacenterConfig, machineConfigsLookup: nil, } - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) datacenterConfig.Spec.ManagementApiEndpoint = ":1234.5234" err = validator.ValidateCloudStackDatacenterConfig(ctx, cloudStackClusterSpec.datacenterConfig) @@ -152,12 +155,13 @@ func TestValidateDatacenterInconsistentManagementEndpoints(t *testing.T) { datacenterConfig: datacenterConfig, machineConfigsLookup: nil, } - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) datacenterConfig.Spec.ManagementApiEndpoint = "abcefg.com" err = validator.ValidateCloudStackDatacenterConfig(ctx, cloudStackClusterSpec.datacenterConfig) - thenErrorExpected(t, "cluster spec management url (abcefg.com) is not found in the cloudstack secret", err) + thenErrorExpected(t, "cloudstack secret management url (http://127.16.0.1:8080/client/api) differs from cluster spec management url (abcefg.com)", err) } func TestSetupAndValidateDiskOfferingEmpty(t *testing.T) { @@ -168,7 +172,7 @@ func TestSetupAndValidateDiskOfferingEmpty(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -185,7 +189,8 @@ func TestSetupAndValidateDiskOfferingEmpty(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Times(3).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) @@ -205,7 +210,7 @@ func TestSetupAndValidateValidDiskOffering(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -230,7 +235,8 @@ func TestSetupAndValidateValidDiskOffering(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Times(3).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(1) @@ -250,7 +256,7 @@ func TestSetupAndValidateInvalidDiskOfferingNotPresent(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -275,7 +281,8 @@ func TestSetupAndValidateInvalidDiskOfferingNotPresent(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(errors.New("match me")) @@ -294,7 +301,7 @@ func TestSetupAndValidateInValidDiskOfferingBadMountPath(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -319,7 +326,8 @@ func TestSetupAndValidateInValidDiskOfferingBadMountPath(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAffinityGroupsPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() @@ -337,7 +345,7 @@ func TestSetupAndValidateInValidDiskOfferingEmptyDevice(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -362,7 +370,8 @@ func TestSetupAndValidateInValidDiskOfferingEmptyDevice(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAffinityGroupsPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() @@ -380,7 +389,7 @@ func TestSetupAndValidateInValidDiskOfferingEmptyFilesystem(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -405,7 +414,8 @@ func TestSetupAndValidateInValidDiskOfferingEmptyFilesystem(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAffinityGroupsPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() @@ -423,7 +433,7 @@ func TestSetupAndValidateInValidDiskOfferingEmptyLabel(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -448,7 +458,8 @@ func TestSetupAndValidateInValidDiskOfferingEmptyLabel(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.DiskOffering = v1alpha1.CloudStackResourceDiskOffering{} - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAffinityGroupsPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() @@ -466,7 +477,7 @@ func TestSetupAndValidateUsersNil(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -483,7 +494,8 @@ func TestSetupAndValidateUsersNil(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.Users = nil - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Times(3).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) @@ -503,7 +515,7 @@ func TestSetupAndValidateRestrictedUserDetails(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -520,6 +532,8 @@ func TestSetupAndValidateRestrictedUserDetails(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.UserCustomDetails = map[string]string{"keyboard": "test"} + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) if err == nil { t.Fatalf("expected error like 'validation failed: restricted key keyboard found in custom user details' but no error was thrown") @@ -534,7 +548,7 @@ func TestSetupAndValidateSshAuthorizedKeysNil(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -551,7 +565,8 @@ func TestSetupAndValidateSshAuthorizedKeysNil(t *testing.T) { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[etcdMachineConfigName].Spec.Users[0].SshAuthorizedKeys = nil - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Times(3).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateServiceOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Times(3) @@ -562,6 +577,14 @@ func TestSetupAndValidateSshAuthorizedKeysNil(t *testing.T) { } } +func setupMockForDatacenterConfigValidation(cmk *mocks.MockProviderCmkClient, ctx context.Context, datacenterConfig *v1alpha1.CloudStackDatacenterConfig) { + cmk.EXPECT().ValidateZonesPresent(ctx, datacenterConfig.Spec.Zones).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + cmk.EXPECT().ValidateDomainPresent(ctx, datacenterConfig.Spec.Domain).AnyTimes().Return(v1alpha1.CloudStackResourceIdentifier{Id: "5300cdac-74d5-11ec-8696-c81f66d3e965", Name: datacenterConfig.Spec.Domain}, nil) + cmk.EXPECT().ValidateAccountPresent(ctx, gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + cmk.EXPECT().ValidateNetworkPresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + cmk.EXPECT().GetManagementApiEndpoint(ctx).AnyTimes().Return("http://127.16.0.1:8080/client/api") +} + func TestSetupAndValidateCreateClusterCPMachineGroupRefNil(t *testing.T) { ctx := context.Background() cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) @@ -570,7 +593,7 @@ func TestSetupAndValidateCreateClusterCPMachineGroupRefNil(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -582,6 +605,8 @@ func TestSetupAndValidateCreateClusterCPMachineGroupRefNil(t *testing.T) { } clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef = nil + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "must specify machineGroupRef for control plane", err) } @@ -594,7 +619,7 @@ func TestSetupAndValidateCreateClusterWorkerMachineGroupRefNil(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -606,6 +631,8 @@ func TestSetupAndValidateCreateClusterWorkerMachineGroupRefNil(t *testing.T) { } clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations[0].MachineGroupRef = nil + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "must specify machineGroupRef for worker nodes", err) } @@ -618,7 +645,7 @@ func TestSetupAndValidateCreateClusterEtcdMachineGroupRefNil(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -630,6 +657,8 @@ func TestSetupAndValidateCreateClusterEtcdMachineGroupRefNil(t *testing.T) { } clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef = nil + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "must specify machineGroupRef for etcd machines", err) } @@ -642,7 +671,7 @@ func TestSetupAndValidateCreateClusterCPMachineGroupRefNonexistent(t *testing.T) t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -654,6 +683,8 @@ func TestSetupAndValidateCreateClusterCPMachineGroupRefNonexistent(t *testing.T) } clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name = "nonexistent" + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "cannot find CloudStackMachineConfig nonexistent for control plane", err) } @@ -666,7 +697,7 @@ func TestSetupAndValidateCreateClusterWorkerMachineGroupRefNonexistent(t *testin t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -678,6 +709,8 @@ func TestSetupAndValidateCreateClusterWorkerMachineGroupRefNonexistent(t *testin } clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations[0].MachineGroupRef.Name = "nonexistent" + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "cannot find CloudStackMachineConfig nonexistent for worker nodes", err) } @@ -690,7 +723,7 @@ func TestSetupAndValidateCreateClusterEtcdMachineGroupRefNonexistent(t *testing. t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -702,6 +735,8 @@ func TestSetupAndValidateCreateClusterEtcdMachineGroupRefNonexistent(t *testing. } clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name = "nonexistent" + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "cannot find CloudStackMachineConfig nonexistent for etcd machines", err) } @@ -714,7 +749,7 @@ func TestSetupAndValidateCreateClusterTemplateDifferent(t *testing.T) { t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) } clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { t.Fatalf("unable to get datacenter config from file") @@ -727,6 +762,8 @@ func TestSetupAndValidateCreateClusterTemplateDifferent(t *testing.T) { controlPlaneMachineConfigName := clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name cloudStackClusterSpec.machineConfigsLookup[controlPlaneMachineConfigName].Spec.Template = v1alpha1.CloudStackResourceIdentifier{Name: "different"} + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + err = validator.ValidateClusterMachineConfigs(ctx, cloudStackClusterSpec) thenErrorExpected(t, "control plane and etcd machines must have the same template specified", err) } @@ -748,8 +785,9 @@ func TestValidateMachineConfigsHappyCase(t *testing.T) { datacenterConfig: datacenterConfig, machineConfigsLookup: machineConfigs, } - validator := NewValidator(cmk) - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Times(3).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) + setupMockForDatacenterConfigValidation(cmk, ctx, datacenterConfig) + cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), datacenterConfig.Spec.Account, testTemplate).Times(3) cmk.EXPECT().ValidateServiceOfferingPresent(ctx, gomock.Any(), testOffering).Times(3) @@ -771,16 +809,9 @@ func TestValidateCloudStackMachineConfig(t *testing.T) { if err != nil { t.Fatalf("unable to get datacenter config from file") } - validator := NewValidator(cmk) + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) for _, machineConfig := range machineConfigs { - cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) - cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), "admin", machineConfig.Spec.Template).Return(nil) - cmk.EXPECT().ValidateServiceOfferingPresent(ctx, gomock.Any(), machineConfig.Spec.ComputeOffering).Return(nil) - cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - if len(machineConfig.Spec.AffinityGroupIds) > 0 { - cmk.EXPECT().ValidateAffinityGroupsPresent(ctx, gomock.Any(), "admin", machineConfig.Spec.AffinityGroupIds).Return(nil) - } err := validator.validateMachineConfig(ctx, datacenterConfig, machineConfig) if err != nil { t.Fatalf("failed to validate CloudStackMachineConfig: %v", err) @@ -814,11 +845,14 @@ func TestValidateMachineConfigsWithAffinity(t *testing.T) { machineConfig.Spec.AffinityGroupIds = []string{} } - validator := NewValidator(cmk) - cmk.EXPECT().ValidateDomainPresent(gomock.Any(), gomock.Any()).AnyTimes() + validator := NewValidator(CmkClientMap{decoder.CloudStackGlobalAZ: cmk}) cmk.EXPECT().ValidateZonesPresent(gomock.Any(), gomock.Any()).AnyTimes().Return([]v1alpha1.CloudStackResourceIdentifier{{Name: "zone1", Id: "4e3b338d-87a6-4189-b931-a1747edeea8f"}}, nil) - cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), - gomock.Any(), datacenterConfig.Spec.Account, testTemplate).AnyTimes() + cmk.EXPECT().ValidateDomainPresent(gomock.Any(), gomock.Any()).AnyTimes() + cmk.EXPECT().ValidateAccountPresent(ctx, gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + cmk.EXPECT().ValidateNetworkPresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + cmk.EXPECT().GetManagementApiEndpoint(ctx).AnyTimes().Return("http://127.16.0.1:8080/client/api") + + cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), datacenterConfig.Spec.Account, testTemplate).AnyTimes() cmk.EXPECT().ValidateServiceOfferingPresent(ctx, gomock.Any(), testOffering).AnyTimes() cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() cmk.EXPECT().ValidateAffinityGroupsPresent(ctx, gomock.Any(), datacenterConfig.Spec.Account, gomock.Any()).AnyTimes()