Repository: kubevirt/kubevirt (Go)

Issue:
`ControllerRevisions` not removed when using `InstancetypeReferencePolicy` `Expand` or `ExpandAll`
/area instancetype

**What happened**:

$subject, these currently remain even after the `VirtualMachine` has been expanded and successfully updated.

**What you expected to happen**:

These `ControllerRevisions` should be removed once the `VirtualMachine` is successfully updated.

**How to reproduce it (as minimally and precisely as possible)**:

* Create a `VirtualMachine` using an instance type and/or preference
* Confirm that `ControllerRevisions` have been created 
* Enable the `Expand` or `ExpandAll` `InstancetypeReferencePolicy` within the `KubeVirt` CR.
* Confirm that `ControllerRevisions` still exist
* 
**Additional context**:
Add any other context about the problem here.

**Environment**:
- KubeVirt version (use `virtctl version`): N/A
- Kubernetes version (use `kubectl version`): N/A
- VM or VMI specifications: N/A
- Cloud provider or hardware configuration: N/A
- OS (e.g. from /etc/os-release): N/A
- Kernel (e.g. `uname -a`): N/A
- Install tools: N/A
- Others: N/A



Discussion:

Patch:
diff --git a/pkg/virt-controller/watch/vm/vm.go b/pkg/virt-controller/watch/vm/vm.go
index 8b947490890a..12fbb0c38a89 100644
--- a/pkg/virt-controller/watch/vm/vm.go
+++ b/pkg/virt-controller/watch/vm/vm.go
@@ -3380,6 +3380,30 @@ func (c *Controller) syncInstancetypes(vm *virtv1.VirtualMachine) (*virtv1.Virtu
 			if err != nil {
 				return vm, common.NewSyncError(fmt.Errorf("error encountered when trying to update VirtualMachine with expanded instance type and preference: %v", err), failedUpdateErrorReason)
 			}
+
+			// We should clean up any instance type or preference ControllerRevisions after successfully expanding the VM
+			if vm.Spec.Instancetype != nil && vm.Spec.Instancetype.RevisionName != "" {
+				revisionName := vm.Spec.Instancetype.RevisionName
+				if err = c.clientset.AppsV1().ControllerRevisions(vm.Namespace).Delete(
+					context.Background(), revisionName, metav1.DeleteOptions{}); err != nil {
+					return nil, common.NewSyncError(
+						fmt.Errorf("error encountered cleaning up instance type ControllerRevision %s after successfully expanding VirtualMachine %s: %v", revisionName, vm.Name, err),
+						failedUpdateErrorReason,
+					)
+				}
+			}
+
+			if vm.Spec.Preference != nil && vm.Spec.Preference.RevisionName != "" {
+				revisionName := vm.Spec.Preference.RevisionName
+				if err = c.clientset.AppsV1().ControllerRevisions(vm.Namespace).Delete(
+					context.Background(), revisionName, metav1.DeleteOptions{}); err != nil {
+					return nil, common.NewSyncError(
+						fmt.Errorf("error encountered cleaning up preference ControllerRevision %s after successfully expanding VirtualMachine %s: %v", revisionName, vm.Name, err),
+						failedUpdateErrorReason,
+					)
+				}
+			}
+
 			// Return at this point as the update will trigger another sync
 			return updatedVm, nil
 		}


Test patch:
diff --git a/pkg/virt-controller/watch/vm/vm_test.go b/pkg/virt-controller/watch/vm/vm_test.go
index d3e4f51d55f1..e27a16f5b28f 100644
--- a/pkg/virt-controller/watch/vm/vm_test.go
+++ b/pkg/virt-controller/watch/vm/vm_test.go
@@ -5683,6 +5683,9 @@ var _ = Describe("VirtualMachine", func() {
 					testutils.UpdateFakeKubeVirtClusterConfig(kvStore, kv)
 					updateVMFunc()
 
+					// Stash a copy of the original VM to assert ControllerRevision removal later
+					originalVM := vm.DeepCopy()
+
 					vm, err := virtFakeClient.KubevirtV1().VirtualMachines(vm.Namespace).Create(
 						context.TODO(), vm, metav1.CreateOptions{})
 					Expect(err).To(Succeed())
@@ -5696,6 +5699,16 @@ var _ = Describe("VirtualMachine", func() {
 					Expect(vm.Spec.Template.Spec.Domain.CPU.Sockets).To(Equal(instancetypeObj.Spec.CPU.Guest))
 					Expect(vm.Spec.Template.Spec.Domain.Memory.Guest.Value()).To(Equal(instancetypeObj.Spec.Memory.Guest.Value()))
 					Expect(vm.Spec.Preference).To(BeNil())
+
+					// Assert that the original ControllerRevisions have been cleaned up
+					if originalVM.Spec.Instancetype.RevisionName != "" {
+						_, err := virtClient.AppsV1().ControllerRevisions(vm.Namespace).Get(context.TODO(), originalVM.Spec.Instancetype.RevisionName, metav1.GetOptions{})
+						Expect(err).To(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+					}
+					if originalVM.Spec.Preference.RevisionName != "" {
+						_, err := virtClient.AppsV1().ControllerRevisions(vm.Namespace).Get(context.TODO(), originalVM.Spec.Preference.RevisionName, metav1.GetOptions{})
+						Expect(err).To(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+					}
 				},
 					Entry("with FG enabled and referencePolicy expand", kvWithFGEnabledReferencePolicyExpand, func() {}),
 					Entry("with FG enabled and referencePolicy expandAll", kvWithFGEnabledReferencePolicyExpandAll, func() {}),