diff --git a/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement.go b/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement.go index 2f954f483b..46604bea7d 100644 --- a/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement.go +++ b/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement.go @@ -71,6 +71,10 @@ func (h *NodePlacementHandler) Handle(ctx context.Context, vm *v1alpha2.VirtualM return reconcile.Result{}, err } + if shouldSkipNodePlacementMigration(kvvmi) { + return reconcile.Result{}, ensureAnnotation(ctx, h.client, kvvmi, annotations.AnnVMOPWorkloadUpdateNodePlacementSum, sum) + } + log := logger.FromContext(ctx).With(logger.SlogHandler(nodePlacementHandler)) ctx = logger.ToContext(ctx, log) @@ -86,6 +90,32 @@ func (h *NodePlacementHandler) Name() string { return nodePlacementHandler } +func shouldSkipNodePlacementMigration(kvvmi *virtv1.VirtualMachineInstance) bool { + volumesChange, _ := conditions.GetKVVMICondition(virtv1.VirtualMachineInstanceVolumesChange, kvvmi.Status.Conditions) + if volumesChange.Status == corev1.ConditionTrue || len(kvvmi.Status.MigratedVolumes) > 0 { + return true + } + + migrationState := kvvmi.Status.MigrationState + return migrationState != nil && migrationState.StartTimestamp != nil && migrationState.EndTimestamp == nil +} + +func ensureAnnotation(ctx context.Context, cl client.Client, obj client.Object, annoKey, annoValue string) error { + if obj.GetAnnotations()[annoKey] == annoValue { + return nil + } + + base := obj.DeepCopyObject().(client.Object) + annotations := make(map[string]string, len(obj.GetAnnotations())+1) + for key, value := range obj.GetAnnotations() { + annotations[key] = value + } + annotations[annoKey] = annoValue + obj.SetAnnotations(annotations) + + return cl.Patch(ctx, obj, client.MergeFrom(base)) +} + func genNodePlacementSum(kvvmi *virtv1.VirtualMachineInstance) (string, error) { if kvvmi == nil { return "", fmt.Errorf("kvvmi is nil") diff --git a/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement_test.go b/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement_test.go index ea1b41d3cd..9bc92151f3 100644 --- a/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement_test.go +++ b/images/virtualization-artifact/pkg/controller/workload-updater/internal/handler/nodeplacement_test.go @@ -31,6 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/interceptor" vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + "github.com/deckhouse/virtualization-controller/pkg/common/object" "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -165,6 +167,50 @@ var _ = Describe("TestNodePlacementHandler", func() { Expect(err).NotTo(HaveOccurred()) }) + DescribeTable("should skip migration while volume migration is active", + func(kvvmiMutate func(*virtv1.VirtualMachineInstance)) { + vm, kvvmi := newVMAndKVVMI(true) + kvvmiMutate(kvvmi) + expectedSum, err := genNodePlacementSum(kvvmi) + Expect(err).NotTo(HaveOccurred()) + fakeClient = setupEnvironment(vm, kvvmi) + + mockMigration := &OneShotMigrationMock{ + OnceMigrateFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine, annotationKey, annotationExpectedValue string) (bool, error) { + Fail("migration should not be executed") + return false, nil + }, + } + + h := NewNodePlacementHandler(fakeClient, mockMigration) + _, err = h.Handle(ctx, vm) + + Expect(err).NotTo(HaveOccurred()) + Expect(mockMigration.OnceMigrateCalls()).To(BeEmpty()) + + updatedKVVMI := &virtv1.VirtualMachineInstance{} + Expect(fakeClient.Get(ctx, object.NamespacedName(kvvmi), updatedKVVMI)).To(Succeed()) + Expect(updatedKVVMI.GetAnnotations()).To(HaveKeyWithValue(annotations.AnnVMOPWorkloadUpdateNodePlacementSum, expectedSum)) + }, + Entry("VolumesChange condition is true", func(kvvmi *virtv1.VirtualMachineInstance) { + kvvmi.Status.Conditions = append(kvvmi.Status.Conditions, virtv1.VirtualMachineInstanceCondition{ + Type: virtv1.VirtualMachineInstanceVolumesChange, + Status: corev1.ConditionTrue, + }) + }), + Entry("migrated volumes are present", func(kvvmi *virtv1.VirtualMachineInstance) { + kvvmi.Status.MigratedVolumes = []virtv1.StorageMigratedVolumeInfo{ + {VolumeName: "root"}, + } + }), + Entry("migration state is active", func(kvvmi *virtv1.VirtualMachineInstance) { + start := metav1.Now() + kvvmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{ + StartTimestamp: &start, + } + }), + ) + It("should return node placement handler name", func() { h := NewNodePlacementHandler(nil, nil) Expect(h.Name()).To(Equal(nodePlacementHandler))