-
Notifications
You must be signed in to change notification settings - Fork 8
Define behavior of retention policy in multi-namespace plans. #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -187,10 +187,23 @@ func (r *MultiNamespaceStorageMigPlanReconciler) validateNamespace(ctx context.C | |
|
|
||
| func (r *MultiNamespaceStorageMigPlanReconciler) createNamespacePlan(ctx context.Context, plan *migrations.MultiNamespaceVirtualMachineStorageMigrationPlan, namespace *migrations.VirtualMachineStorageMigrationPlanNamespaceSpec) error { | ||
| spec := namespace.VirtualMachineStorageMigrationPlanSpec.DeepCopy() | ||
| // When the multinamespace plan has RetentionPolicy set (e.g. deleteSource), apply it to the child plan | ||
| if plan.Spec.RetentionPolicy != nil { | ||
| spec.RetentionPolicy = plan.Spec.RetentionPolicy | ||
|
|
||
| // Retention policy priority (with CRD defaults in place): | ||
| // 1. Namespace-specific retentionPolicy (if explicitly set to non-default value) | ||
| // 2. Multi-namespace plan retentionPolicy (if set to non-default value) | ||
| // 3. CRD default (keepSource) | ||
| // | ||
| // Due to CRD defaulting, both spec.RetentionPolicy and plan.Spec.RetentionPolicy will be non-nil | ||
| // (defaulted to keepSource). We treat keepSource as "not explicitly set" when determining priority. | ||
| if plan.Spec.RetentionPolicy != nil && *plan.Spec.RetentionPolicy != migrations.RetentionPolicyKeepSource { | ||
| // Parent has non-default value | ||
| if spec.RetentionPolicy == nil || *spec.RetentionPolicy == migrations.RetentionPolicyKeepSource { | ||
| // Namespace has default or nil - inherit from parent | ||
| spec.RetentionPolicy = plan.Spec.RetentionPolicy | ||
| } | ||
| // else: namespace has explicit non-default value, use it (priority 1) | ||
|
Comment on lines
+198
to
+204
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to unit test the priority? doesn't have to involve etcd, could extract to small util and test that
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure give me a bit to update this. |
||
| } | ||
| // else: parent has default (keepSource), namespace keeps its value (default or explicit) | ||
| namespacePlan := &migrations.VirtualMachineStorageMigrationPlan{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: migrations.GetNamespacedPlanName(plan.Name, namespace.Name), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,7 +184,7 @@ var _ = Describe("MultiNamespaceStorageMigPlan Controller", func() { | |
| Expect(*childPlan.Spec.RetentionPolicy).To(Equal(migrations.RetentionPolicyDeleteSource)) | ||
| }) | ||
|
|
||
| It("Should allow setting the field for the first time, but not update/delete it", func() { | ||
| It("should enforce retentionPolicy immutability with type-level and field-level validation", func() { | ||
| key := types.NamespacedName{Name: "test-resource", Namespace: "default"} | ||
| created := &migrations.MultiNamespaceVirtualMachineStorageMigrationPlan{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: key.Name, Namespace: key.Namespace}, | ||
|
|
@@ -204,22 +204,32 @@ var _ = Describe("MultiNamespaceStorageMigPlan Controller", func() { | |
| existing := &migrations.MultiNamespaceVirtualMachineStorageMigrationPlan{} | ||
| Expect(k8sClient.Get(ctx, key, existing)).To(Succeed()) | ||
|
|
||
| // Attempt to change the value | ||
| By("Attempting to change value from deleteSource to keepSource - field-level validation should fail") | ||
| existing.Spec.RetentionPolicy = ptr.To(migrations.RetentionPolicyKeepSource) | ||
| err := k8sClient.Update(ctx, existing) | ||
|
|
||
| Expect(err).To(HaveOccurred()) | ||
| // Verify the specific CEL error message from your marker | ||
| Expect(err.Error()).To(ContainSubstring("retentionPolicy is immutable")) | ||
| Expect(err.Error()).To(ContainSubstring("retentionPolicy")) | ||
|
||
| existing = &migrations.MultiNamespaceVirtualMachineStorageMigrationPlan{} | ||
| Expect(k8sClient.Get(ctx, key, existing)).To(Succeed()) | ||
| Expect(*existing.Spec.RetentionPolicy).To(Equal(migrations.RetentionPolicyDeleteSource), | ||
| "Value should not have changed") | ||
|
|
||
| // Attempt to delete (set to nil) | ||
| By("Attempting to remove retentionPolicy (set to nil)") | ||
| existing.Spec.RetentionPolicy = nil | ||
| err = k8sClient.Update(ctx, existing) | ||
|
|
||
| // With CRD defaulting, nil becomes keepSource, so this triggers field-level validation | ||
| // The error will be about value change (deleteSource -> keepSource) not removal | ||
| Expect(err).To(HaveOccurred()) | ||
| Expect(err.Error()).To(ContainSubstring("retentionPolicy is immutable")) | ||
| Expect(err.Error()).To(Or( | ||
| ContainSubstring("retentionPolicy value cannot be changed"), | ||
| ContainSubstring("retentionPolicy cannot be removed"), | ||
| ), "Either field-level or type-level validation should prevent the change") | ||
| existing = &migrations.MultiNamespaceVirtualMachineStorageMigrationPlan{} | ||
| Expect(k8sClient.Get(ctx, key, existing)).To(Succeed()) | ||
| Expect(*existing.Spec.RetentionPolicy).To(Equal(migrations.RetentionPolicyDeleteSource), | ||
| "Value should still be deleteSource") | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure
self == oldSelfdoesn't cover this case? logically I would assume it doesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some weirdness that happens when going from nil to something or vice versa. Especially with a default defined. I believe this is to ensure that if we have nil and select the default, it actually 'works'.