diff --git a/webhooks/elfmachine_webhook_validation.go b/webhooks/elfmachine_webhook_validation.go index fb64607..6985cc4 100644 --- a/webhooks/elfmachine_webhook_validation.go +++ b/webhooks/elfmachine_webhook_validation.go @@ -29,11 +29,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1" + annotationsutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/annotations" ) // Error messages. const ( - diskCapacityCanOnlyBeExpandedMsg = "the disk capacity can only be expanded" + canOnlyModifiedThroughElfMachineTemplate = "virtual machine resources should be the same as ElfMachineTemplate %s" + + diskCapacityCanOnlyBeExpandedMsg = "the disk capacity can only be expanded" + vcpuCapacityCanOnlyBeExpandedMsg = "the vcpu capacity can only be expanded" + memoryCapacityCanOnlyBeExpandedMsg = "the memory capacity can only be expanded" + numCoresPerSocketCannotBeChanged = "the number of cores per socket cannot be changed" ) func (v *ElfMachineValidator) SetupWebhookWithManager(mgr ctrl.Manager) error { @@ -70,9 +76,44 @@ func (v *ElfMachineValidator) ValidateUpdate(ctx goctx.Context, oldObj, newObj r var allErrs field.ErrorList + elfMachineTemplateName := annotationsutil.GetTemplateClonedFromName(elfMachine) + if elfMachineTemplateName != "" { + // If the ElfMachine was created using ElfMachineTemplate. ElfMachine's + // resources should be the same as this ElfMachineTemplate. + var elfMachineTemplate infrav1.ElfMachineTemplate + if err := v.Client.Get(ctx, client.ObjectKey{ + Namespace: elfMachine.Namespace, + Name: annotationsutil.GetTemplateClonedFromName(elfMachine), + }, &elfMachineTemplate); err != nil { + return nil, apierrors.NewInternalError(err) + } + + if elfMachine.Spec.DiskGiB != elfMachineTemplate.Spec.Template.Spec.DiskGiB { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "diskGiB"), elfMachine.Spec.DiskGiB, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplateName))) + } + if elfMachine.Spec.NumCPUs != elfMachineTemplate.Spec.Template.Spec.NumCPUs { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "numCPUs"), elfMachine.Spec.NumCPUs, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplateName))) + } + if elfMachine.Spec.NumCoresPerSocket != elfMachineTemplate.Spec.Template.Spec.NumCoresPerSocket { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "numCoresPerSocket"), elfMachine.Spec.NumCoresPerSocket, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplateName))) + } + if elfMachine.Spec.MemoryMiB != elfMachineTemplate.Spec.Template.Spec.MemoryMiB { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "memoryMiB"), elfMachine.Spec.MemoryMiB, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplateName))) + } + } + if elfMachine.Spec.DiskGiB < oldElfMachine.Spec.DiskGiB { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "diskGiB"), elfMachine.Spec.DiskGiB, diskCapacityCanOnlyBeExpandedMsg)) } + if elfMachine.Spec.NumCPUs < oldElfMachine.Spec.NumCPUs { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "numCPUs"), elfMachine.Spec.NumCPUs, vcpuCapacityCanOnlyBeExpandedMsg)) + } + if elfMachine.Spec.MemoryMiB < oldElfMachine.Spec.MemoryMiB { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "memoryMiB"), elfMachine.Spec.MemoryMiB, memoryCapacityCanOnlyBeExpandedMsg)) + } + if oldElfMachine.Spec.NumCoresPerSocket != 0 && elfMachine.Spec.NumCoresPerSocket != oldElfMachine.Spec.NumCoresPerSocket { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "numCoresPerSocket"), elfMachine.Spec.NumCoresPerSocket, numCoresPerSocketCannotBeChanged)) + } return nil, aggregateObjErrors(elfMachine.GroupVersionKind().GroupKind(), elfMachine.Name, allErrs) } diff --git a/webhooks/elfmachine_webhook_validation_test.go b/webhooks/elfmachine_webhook_validation_test.go index 0fadcc7..1919547 100644 --- a/webhooks/elfmachine_webhook_validation_test.go +++ b/webhooks/elfmachine_webhook_validation_test.go @@ -18,6 +18,7 @@ package webhooks import ( goctx "context" + "fmt" "testing" . "github.com/onsi/gomega" @@ -25,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -37,6 +39,19 @@ func TestElfMachineValidatorValidateUpdate(t *testing.T) { var tests []elfMachineTestCase scheme := newScheme(g) + elfMachineTemplate := &infrav1.ElfMachineTemplate{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.ElfMachineTemplateSpec{ + Template: infrav1.ElfMachineTemplateResource{ + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 1, + MemoryMiB: 1, + }, + }, + }, + } + tests = append(tests, elfMachineTestCase{ Name: "Cannot reduce disk capacity", OldEM: &infrav1.ElfMachine{ @@ -53,6 +68,147 @@ func TestElfMachineValidatorValidateUpdate(t *testing.T) { field.Invalid(field.NewPath("spec", "diskGiB"), 1, diskCapacityCanOnlyBeExpandedMsg), }, }) + tests = append(tests, elfMachineTestCase{ + Name: "Cannot reduce vcpu capacity", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCPUs: 2, + }, + }, + EM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCPUs: 1, + }, + }, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "numCPUs"), 1, vcpuCapacityCanOnlyBeExpandedMsg), + }, + }) + tests = append(tests, elfMachineTestCase{ + Name: "Cannot reduce memory capacity", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + MemoryMiB: 2, + }, + }, + EM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + MemoryMiB: 1, + }, + }, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "memoryMiB"), 1, memoryCapacityCanOnlyBeExpandedMsg), + }, + }) + tests = append(tests, elfMachineTestCase{ + Name: "Can update the default numCoresPerSocket", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCoresPerSocket: 0, + }, + }, + EM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCoresPerSocket: 1, + }, + }, + Errs: nil, + }) + tests = append(tests, elfMachineTestCase{ + Name: "Cannot update numCoresPerSocket", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCoresPerSocket: 1, + }, + }, + EM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + NumCoresPerSocket: 2, + }, + }, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "numCoresPerSocket"), 2, numCoresPerSocketCannotBeChanged), + }, + }) + + tests = append(tests, elfMachineTestCase{ + Name: "Disk cannot be modified directly", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 1, + MemoryMiB: 1, + }, + }, + EM: &infrav1.ElfMachine{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + clusterv1.TemplateClonedFromNameAnnotation: elfMachineTemplate.Name, + }, + }, + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 2, + NumCPUs: 1, + MemoryMiB: 1, + }, + }, + Objs: []client.Object{elfMachineTemplate}, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "diskGiB"), 2, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplate.Name)), + }, + }) + tests = append(tests, elfMachineTestCase{ + Name: "vcpu cannot be modified directly", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 1, + MemoryMiB: 1, + }, + }, + EM: &infrav1.ElfMachine{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + clusterv1.TemplateClonedFromNameAnnotation: elfMachineTemplate.Name, + }, + }, + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 2, + MemoryMiB: 1, + }, + }, + Objs: []client.Object{elfMachineTemplate}, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "numCPUs"), 2, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplate.Name)), + }, + }) + tests = append(tests, elfMachineTestCase{ + Name: "memory cannot be modified directly", + OldEM: &infrav1.ElfMachine{ + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 1, + MemoryMiB: 1, + }, + }, + EM: &infrav1.ElfMachine{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + clusterv1.TemplateClonedFromNameAnnotation: elfMachineTemplate.Name, + }, + }, + Spec: infrav1.ElfMachineSpec{ + DiskGiB: 1, + NumCPUs: 1, + MemoryMiB: 2, + }, + }, + Objs: []client.Object{elfMachineTemplate}, + Errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "memoryMiB"), 2, fmt.Sprintf(canOnlyModifiedThroughElfMachineTemplate, elfMachineTemplate.Name)), + }, + }) for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) {