diff --git a/pkg/providers/cloudstack/validator.go b/pkg/providers/cloudstack/validator.go index ee65d17a2103..e7bdc9793a07 100644 --- a/pkg/providers/cloudstack/validator.go +++ b/pkg/providers/cloudstack/validator.go @@ -118,6 +118,11 @@ func (v *Validator) ValidateClusterMachineConfigs(ctx context.Context, clusterSp return fmt.Errorf("cannot find CloudStackMachineConfig %v for control plane", clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name) } + // validate template field of each CloudStackMachineConfigs with the cluster spec kubernetes version. + if err := v.validateTemplateMatchesKubernetesVersion(ctx, controlPlaneMachineConfig.Spec.Template.Name, string(clusterSpec.Cluster.Spec.KubernetesVersion)); err != nil { + return fmt.Errorf("machine config %s validation failed: %v", controlPlaneMachineConfig.Name, err) + } + if clusterSpec.Cluster.Spec.ExternalEtcdConfiguration != nil { etcdMachineConfig := etcdMachineConfig(clusterSpec) if etcdMachineConfig == nil { @@ -130,15 +135,22 @@ func (v *Validator) ValidateClusterMachineConfigs(ctx context.Context, clusterSp if !ok { return fmt.Errorf("cannot find CloudStackMachineConfig %v for worker nodes", workerNodeGroupConfiguration.MachineGroupRef.Name) } + + version := string(clusterSpec.Cluster.Spec.KubernetesVersion) + // validate template field of worker group spec with the kubernetes version of each workerNodeGroup - in case of modular upgrade. + if workerNodeGroupConfiguration.KubernetesVersion != nil { + version = string(*workerNodeGroupConfiguration.KubernetesVersion) + } + templateName := clusterSpec.CloudStackMachineConfigs[workerNodeGroupConfiguration.MachineGroupRef.Name].Spec.Template.Name + if err := v.validateTemplateMatchesKubernetesVersion(ctx, templateName, version); err != nil { + return fmt.Errorf("machine config %s validation failed: %v", workerNodeGroupConfiguration.Name, err) + } } for _, machineConfig := range clusterSpec.CloudStackMachineConfigs { if err := v.validateMachineConfig(ctx, clusterSpec.CloudStackDatacenter, machineConfig); err != nil { return fmt.Errorf("machine config %s validation failed: %v", machineConfig.Name, err) } - if err := v.validateTemplateMatchesKubernetesVersion(ctx, machineConfig, clusterSpec); err != nil { - return fmt.Errorf("machine config %s validation failed: %v", machineConfig.Name, err) - } } logger.MarkPass("Validated cluster Machine Configs") @@ -194,18 +206,18 @@ func (v *Validator) validateMachineConfig(ctx context.Context, datacenterConfig return nil } -func (v *Validator) validateTemplateMatchesKubernetesVersion(ctx context.Context, machineConfig *anywherev1.CloudStackMachineConfig, spec *cluster.Spec) error { +func (v *Validator) validateTemplateMatchesKubernetesVersion(ctx context.Context, templateName string, kubernetesVersionName string) error { // Replace 1.23, 1-23, 1_23 to 123 in the template name string. templateReplacer := strings.NewReplacer("-", "", ".", "", "_", "") - templateName := templateReplacer.Replace(machineConfig.Spec.Template.Name) + template := templateReplacer.Replace(templateName) // Replace 1-23 to 123 in the kubernetesversion string. replacer := strings.NewReplacer(".", "") - kubernetesVersion := replacer.Replace(string(spec.Cluster.Spec.KubernetesVersion)) + kubernetesVersion := replacer.Replace(string(kubernetesVersionName)) // This will return an error if the template name does not contain specified kubernetes version. // For ex if the kubernetes version is 1.23, // the template name should include 1.23 or 1-23, 1_23 or 123 i.e. kubernetes-1-23-eks in the string. - if !strings.Contains(templateName, kubernetesVersion) { - return fmt.Errorf("invalid template: cluster kubernetes version is %s but template for machineconfig %s is %s. If the kubernetes version is 1.23, the template name should include 1.23, 1_23, 1-23 or 123", string(spec.Cluster.Spec.KubernetesVersion), machineConfig.Name, machineConfig.Spec.Template.Name) + if !strings.Contains(template, kubernetesVersion) { + return fmt.Errorf("missing kube version from the machine config template name: template=%s, version=%s", templateName, string(kubernetesVersionName)) } return nil } diff --git a/pkg/providers/cloudstack/validator_test.go b/pkg/providers/cloudstack/validator_test.go index 9e8a3989426e..2410d545ea4f 100644 --- a/pkg/providers/cloudstack/validator_test.go +++ b/pkg/providers/cloudstack/validator_test.go @@ -306,26 +306,90 @@ func TestValidateCloudStackMachineConfig(t *testing.T) { } } -func TestValidateTemplateMatchesKubernetesVersionError(t *testing.T) { +func TestValidateClusterMachineConfigsError(t *testing.T) { ctx := context.Background() cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) - config, err := cluster.ParseConfigFromFile(path.Join(testDataDir, testClusterConfigMainFilename)) + clusterSpec.Cluster.Spec.KubernetesVersion = "1.22" + + validator := NewValidator(cmk, &DummyNetClient{}, true) + + err := validator.ValidateClusterMachineConfigs(ctx, clusterSpec) + if err == nil { + t.Fatalf("validation should not pass: %v", err) + } +} + +func TestValidateClusterMachineConfigsModularUpgradeError(t *testing.T) { + ctx := context.Background() + cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) + clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) + kube122 := v1alpha1.KubernetesVersion("1.22") + clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations[0].KubernetesVersion = &kube122 + + validator := NewValidator(cmk, &DummyNetClient{}, true) + + err := validator.ValidateClusterMachineConfigs(ctx, clusterSpec) + if err == nil { + t.Fatalf("validation should not pass: %v", err) + } +} + +func TestValidateClusterMachineConfigsSuccess(t *testing.T) { + ctx := context.Background() + cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) + clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) + + clusterSpec.Cluster.Spec.KubernetesVersion = "1.22" + clusterSpec.CloudStackMachineConfigs["test-cp"].Spec.Template.Name = "kubernetes_1_22" + clusterSpec.CloudStackMachineConfigs["test-etcd"].Spec.Template.Name = "kubernetes_1_22" + clusterSpec.CloudStackMachineConfigs["test"].Spec.Template.Name = "kubernetes_1_22" + + datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) if err != nil { - t.Fatalf("unable to get machine configs from file: %v", err) + t.Fatalf("unable to get datacenter config from file") } - machineConfigs := config.CloudStackMachineConfigs + + validator := NewValidator(cmk, &DummyNetClient{}, true) + + cmk.EXPECT().ValidateZoneAndGetId(ctx, gomock.Any(), gomock.Any()).Times(3).Return("4e3b338d-87a6-4189-b931-a1747edeea82", nil) + cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), datacenterConfig.Spec.AvailabilityZones[0].Account, v1alpha1.CloudStackResourceIdentifier{Name: "kubernetes_1_22"}).Times(3) + cmk.EXPECT().ValidateServiceOfferingPresent(ctx, gomock.Any(), gomock.Any(), testOffering).Times(3) + cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) + cmk.EXPECT().ValidateAffinityGroupsPresent(ctx, gomock.Any(), gomock.Any(), datacenterConfig.Spec.AvailabilityZones[0].Account, gomock.Any()).Times(3) + + err = validator.ValidateClusterMachineConfigs(ctx, clusterSpec) if err != nil { - t.Fatalf("unable to get machine configs from file %s", testClusterConfigMainFilename) + t.Fatalf("validation should pass: %v", err) } - clusterSpec.Cluster.Spec.KubernetesVersion = "1.22" +} + +func TestValidateClusterMachineConfigsModularUpgradeSuccess(t *testing.T) { + ctx := context.Background() + cmk := mocks.NewMockProviderCmkClient(gomock.NewController(t)) + clusterSpec := test.NewFullClusterSpec(t, path.Join(testDataDir, testClusterConfigMainFilename)) + + kube122 := v1alpha1.KubernetesVersion("1.22") + clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations[0].KubernetesVersion = &kube122 + clusterSpec.CloudStackMachineConfigs["test"].Spec.Template.Name = "kubernetes_1_22" + + datacenterConfig, err := v1alpha1.GetCloudStackDatacenterConfig(path.Join(testDataDir, testClusterConfigMainFilename)) + if err != nil { + t.Fatalf("unable to get datacenter config from file") + } + validator := NewValidator(cmk, &DummyNetClient{}, true) - for _, machineConfig := range machineConfigs { - err := validator.validateTemplateMatchesKubernetesVersion(ctx, machineConfig, clusterSpec) - if err == nil { - t.Fatalf("failed to validate CloudStackMachineConfig: %v", err) - } + cmk.EXPECT().ValidateZoneAndGetId(ctx, gomock.Any(), gomock.Any()).Times(3).Return("4e3b338d-87a6-4189-b931-a1747edeea82", nil) + cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), datacenterConfig.Spec.AvailabilityZones[0].Account, v1alpha1.CloudStackResourceIdentifier{Name: "kubernetes_1_22"}) + cmk.EXPECT().ValidateTemplatePresent(ctx, gomock.Any(), gomock.Any(), gomock.Any(), datacenterConfig.Spec.AvailabilityZones[0].Account, testTemplate).Times(2) + cmk.EXPECT().ValidateServiceOfferingPresent(ctx, gomock.Any(), gomock.Any(), testOffering).Times(3) + cmk.EXPECT().ValidateDiskOfferingPresent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3) + cmk.EXPECT().ValidateAffinityGroupsPresent(ctx, gomock.Any(), gomock.Any(), datacenterConfig.Spec.AvailabilityZones[0].Account, gomock.Any()).Times(3) + + err = validator.ValidateClusterMachineConfigs(ctx, clusterSpec) + if err != nil { + t.Fatalf("validation should pass: %v", err) } }