Skip to content

Commit 9b64c6f

Browse files
authored
feat(vmop): surface "quota exceeded" error during migration (#1310)
Signed-off-by: Valeriy Khorunzhin <[email protected]>
1 parent 2f416d7 commit 9b64c6f

File tree

10 files changed

+84
-21
lines changed

10 files changed

+84
-21
lines changed

api/core/v1alpha2/events.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ const (
5858
// ReasonErrVMOPFailed is event reason that operation is failed
5959
ReasonErrVMOPFailed = "VirtualMachineOperationFailed"
6060

61+
// ReasonErrVMOPPending is event reason that operation is pending
62+
ReasonErrVMOPPending = "VirtualMachineOperationPending"
63+
6164
// ReasonVMOPSucceeded is event reason that the operation is successfully completed
6265
ReasonVMOPSucceeded = "VirtualMachineOperationSucceeded"
6366

api/core/v1alpha2/vmcondition/condition.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const (
9292
ReasonVmIsRunning Reason = "VirtualMachineRunning"
9393
ReasonInternalVirtualMachineError Reason = "InternalVirtualMachineError"
9494
ReasonPodNotStarted Reason = "PodNotStarted"
95+
ReasonMigrationIsPending Reason = "MigrationIsPending"
9596

9697
// ReasonFilesystemFrozen indicates that virtual machine's filesystem has been successfully frozen.
9798
ReasonFilesystemFrozen Reason = "Frozen"

api/core/v1alpha2/vmopcondition/condition.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ const (
8080
// ReasonOtherMigrationInProgress is a ReasonCompleted indicating that there are other migrations in progress.
8181
ReasonOtherMigrationInProgress ReasonCompleted = "OtherMigrationInProgress"
8282

83+
// ReasonQuotaExceeded is a completed reason that indicates the project's quota has been exceeded and the migration has been paused.
84+
ReasonQuotaExceeded ReasonCompleted = "QuotaExceeded"
85+
8386
// ReasonOperationFailed is a ReasonCompleted indicating that operation has failed.
8487
ReasonOperationFailed ReasonCompleted = "OperationFailed"
8588

images/virtualization-artifact/pkg/controller/conditions/getter.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,18 @@ func GetKVVMICondition(condType virtv1.VirtualMachineInstanceConditionType, cond
6666
return virtv1.VirtualMachineInstanceCondition{}, false
6767
}
6868

69+
func GetKVVMIMCondition(condType virtv1.VirtualMachineInstanceMigrationConditionType, conditions []virtv1.VirtualMachineInstanceMigrationCondition) (virtv1.VirtualMachineInstanceMigrationCondition, bool) {
70+
for _, condition := range conditions {
71+
if condition.Type == condType {
72+
return condition, true
73+
}
74+
}
75+
76+
return virtv1.VirtualMachineInstanceMigrationCondition{}, false
77+
}
78+
6979
const (
70-
VirtualMachineInstanceNodePlacementNotMatched virtv1.VirtualMachineInstanceConditionType = "NodePlacementNotMatched"
71-
VirtualMachineSynchronized virtv1.VirtualMachineConditionType = "Synchronized"
80+
VirtualMachineInstanceNodePlacementNotMatched virtv1.VirtualMachineInstanceConditionType = "NodePlacementNotMatched"
81+
KubevirtMigrationRejectedByResourceQuotaType virtv1.VirtualMachineInstanceMigrationConditionType = "migrationRejectedByResourceQuota"
82+
VirtualMachineSynchronized virtv1.VirtualMachineConditionType = "Synchronized"
7283
)

images/virtualization-artifact/pkg/controller/vm/internal/migrating.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package internal
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"log/slog"
2223

2324
corev1 "k8s.io/api/core/v1"
@@ -136,7 +137,7 @@ func (h *MigratingHandler) syncMigrating(vm *virtv2.VirtualMachine, kvvmi *virtv
136137
{
137138
var inProgressVmops []*virtv2.VirtualMachineOperation
138139
for _, op := range vmops {
139-
if commonvmop.IsMigration(op) && op.Status.Phase == virtv2.VMOPPhaseInProgress {
140+
if commonvmop.IsMigration(op) && isOperationInProgress(op) {
140141
inProgressVmops = append(inProgressVmops, op)
141142
}
142143
}
@@ -167,6 +168,9 @@ func (h *MigratingHandler) syncMigrating(vm *virtv2.VirtualMachine, kvvmi *virtv
167168
cb.Message("Migration is awaiting execution.")
168169
case vmopcondition.ReasonMigrationRunning.String():
169170
cb.Status(metav1.ConditionTrue).Reason(vmcondition.ReasonVmIsMigrating)
171+
case vmopcondition.ReasonQuotaExceeded.String():
172+
cb.Reason(vmcondition.ReasonMigrationIsPending)
173+
cb.Message(fmt.Sprintf("Migration is pending: %s", completed.Message))
170174
}
171175
conditions.SetCondition(cb, &vm.Status.Conditions)
172176

@@ -203,3 +207,9 @@ func liveMigrationInProgress(migrationState *virtv2.VirtualMachineMigrationState
203207
func liveMigrationFailed(migrationState *virtv2.VirtualMachineMigrationState) bool {
204208
return migrationState != nil && migrationState.EndTimestamp != nil && migrationState.Result == virtv2.MigrationResultFailed
205209
}
210+
211+
func isOperationInProgress(vmop *virtv2.VirtualMachineOperation) bool {
212+
sent, _ := conditions.GetCondition(vmopcondition.TypeSignalSent, vmop.Status.Conditions)
213+
completed, _ := conditions.GetCondition(vmopcondition.TypeCompleted, vmop.Status.Conditions)
214+
return sent.Status == metav1.ConditionTrue && completed.Status != metav1.ConditionTrue
215+
}

images/virtualization-artifact/pkg/controller/vm/internal/migrating_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ var _ = Describe("MigratingHandler", func() {
6666
return kvvmi
6767
}
6868

69-
newVMOP := func(phase virtv2.VMOPPhase, reason string) *virtv2.VirtualMachineOperation {
69+
newVMOP := func(phase virtv2.VMOPPhase, reason string, isSignalSent bool) *virtv2.VirtualMachineOperation {
7070
vmop := vmopbuilder.New(
7171
vmopbuilder.WithGenerateName("test-vmop-"),
7272
vmopbuilder.WithNamespace(namespace),
@@ -81,6 +81,12 @@ var _ = Describe("MigratingHandler", func() {
8181
Reason: reason,
8282
},
8383
}
84+
if isSignalSent {
85+
vmop.Status.Conditions = append(vmop.Status.Conditions, metav1.Condition{
86+
Type: vmopcondition.TypeSignalSent.String(),
87+
Status: metav1.ConditionTrue,
88+
})
89+
}
8490
return vmop
8591
}
8692

@@ -160,7 +166,7 @@ var _ = Describe("MigratingHandler", func() {
160166
It("Should set condition when vmop is in progress with pending reason", func() {
161167
vm := newVM()
162168
kvvmi := newKVVMI(nil)
163-
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationPending.String())
169+
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationPending.String(), true)
164170
fakeClient, resource, vmState = setupEnvironment(vm, kvvmi, vmop)
165171

166172
reconcile()
@@ -179,7 +185,7 @@ var _ = Describe("MigratingHandler", func() {
179185
It("Should set condition when vmop is in progress with target ready reason", func() {
180186
vm := newVM()
181187
kvvmi := newKVVMI(nil)
182-
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationTargetReady.String())
188+
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationTargetReady.String(), true)
183189
fakeClient, resource, vmState = setupEnvironment(vm, kvvmi, vmop)
184190

185191
reconcile()
@@ -198,7 +204,7 @@ var _ = Describe("MigratingHandler", func() {
198204
It("Should set condition when vmop is in progress with running reason", func() {
199205
vm := newVM()
200206
kvvmi := newKVVMI(nil)
201-
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationRunning.String())
207+
vmop := newVMOP(virtv2.VMOPPhaseInProgress, vmopcondition.ReasonMigrationRunning.String(), true)
202208
fakeClient, resource, vmState = setupEnvironment(vm, kvvmi, vmop)
203209

204210
reconcile()

images/virtualization-artifact/pkg/controller/vm/internal/watcher/vmop_watcher.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,6 @@ func (w *VMOPWatcher) Watch(mgr manager.Manager, ctr controller.Controller) erro
6161
return commonvmop.IsMigration(e.Object)
6262
},
6363
UpdateFunc: func(e event.TypedUpdateEvent[*virtv2.VirtualMachineOperation]) bool {
64-
if commonvmop.IsMigration(e.ObjectNew) {
65-
return false
66-
}
67-
if e.ObjectNew.Status.Phase != virtv2.VMOPPhaseInProgress {
68-
return false
69-
}
70-
7164
oldCompleted, _ := conditions.GetCondition(vmopcondition.TypeCompleted, e.ObjectOld.Status.Conditions)
7265
newCompleted, _ := conditions.GetCondition(vmopcondition.TypeCompleted, e.ObjectNew.Status.Conditions)
7366

images/virtualization-artifact/pkg/controller/vmop/internal/lifecycle.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ func (h LifecycleHandler) Handle(ctx context.Context, vmop *virtv2.VirtualMachin
106106
return reconcile.Result{}, nil
107107
}
108108

109+
// Pending if quota exceeded.
110+
isQuotaExceededDuringMigration, err := service.IsKubeVirtMigrationRejectedDueToQuota(ctx, h.client, vmop)
111+
if err != nil {
112+
return reconcile.Result{}, err
113+
}
114+
if isQuotaExceededDuringMigration {
115+
h.recorder.Event(vmop, corev1.EventTypeWarning, virtv2.ReasonErrVMOPPending, "Project quota exceeded")
116+
conditions.SetCondition(
117+
completedCond.
118+
Reason(vmopcondition.ReasonQuotaExceeded).
119+
Status(metav1.ConditionFalse).
120+
Message("Project quota exceeded"),
121+
&vmop.Status.Conditions)
122+
return reconcile.Result{}, nil
123+
}
124+
109125
// Get VM for Pending and InProgress checks.
110126
vm, err := object.FetchObject(ctx, types.NamespacedName{Name: vmop.Spec.VirtualMachine, Namespace: vmop.Namespace}, h.client, &virtv2.VirtualMachine{})
111127
if err != nil {

images/virtualization-artifact/pkg/controller/vmop/internal/service/migrate.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"sigs.k8s.io/controller-runtime/pkg/client"
2929

3030
"github.com/deckhouse/virtualization-controller/pkg/common/object"
31+
commonvmop "github.com/deckhouse/virtualization-controller/pkg/common/vmop"
3132
"github.com/deckhouse/virtualization-controller/pkg/controller/conditions"
3233
virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2"
3334
"github.com/deckhouse/virtualization/api/core/v1alpha2/vmopcondition"
@@ -177,3 +178,29 @@ var mapMigrationPhaseToReason = map[virtv1.VirtualMachineInstanceMigrationPhase]
177178
virtv1.MigrationSucceeded: vmopcondition.ReasonOperationCompleted,
178179
virtv1.MigrationFailed: vmopcondition.ReasonOperationFailed,
179180
}
181+
182+
func IsKubeVirtMigrationRejectedDueToQuota(ctx context.Context, client client.Client, vmop *virtv2.VirtualMachineOperation) (bool, error) {
183+
if !commonvmop.IsMigration(vmop) {
184+
return false, nil
185+
}
186+
187+
kubevirtMigrationName := migrationName(vmop)
188+
kubevirtMigration, err := object.FetchObject(ctx, types.NamespacedName{
189+
Namespace: vmop.GetNamespace(),
190+
Name: kubevirtMigrationName,
191+
}, client, &virtv1.VirtualMachineInstanceMigration{})
192+
if err != nil {
193+
return false, err
194+
}
195+
196+
if kubevirtMigration == nil {
197+
return false, nil
198+
}
199+
200+
_, ok := conditions.GetKVVMIMCondition(conditions.KubevirtMigrationRejectedByResourceQuotaType, kubevirtMigration.Status.Conditions)
201+
if ok {
202+
return true, nil
203+
}
204+
205+
return false, nil
206+
}

images/virtualization-artifact/pkg/controller/vmop/internal/watcher/migration.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import (
2121

2222
virtv1 "kubevirt.io/api/core/v1"
2323
"sigs.k8s.io/controller-runtime/pkg/controller"
24-
"sigs.k8s.io/controller-runtime/pkg/event"
2524
"sigs.k8s.io/controller-runtime/pkg/handler"
2625
"sigs.k8s.io/controller-runtime/pkg/manager"
27-
"sigs.k8s.io/controller-runtime/pkg/predicate"
2826
"sigs.k8s.io/controller-runtime/pkg/source"
2927

3028
"github.com/deckhouse/virtualization/api/core/v1alpha2"
@@ -45,11 +43,6 @@ func (w MigrationWatcher) Watch(mgr manager.Manager, ctr controller.Controller)
4543
&v1alpha2.VirtualMachineOperation{},
4644
handler.OnlyControllerOwner(),
4745
),
48-
predicate.TypedFuncs[*virtv1.VirtualMachineInstanceMigration]{
49-
UpdateFunc: func(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstanceMigration]) bool {
50-
return e.ObjectOld.Status.Phase != e.ObjectNew.Status.Phase
51-
},
52-
},
5346
),
5447
); err != nil {
5548
return fmt.Errorf("error setting watch on VirtualMachineInstanceMigration: %w", err)

0 commit comments

Comments
 (0)