Skip to content

Commit 5143b42

Browse files
committed
fix(vmop): format VMOP migration progress as percent string
Signed-off-by: Daniil Antoshin <daniil.antoshin@flant.com>
1 parent 4763958 commit 5143b42

9 files changed

Lines changed: 79 additions & 53 deletions

File tree

api/core/v1alpha2/virtual_machine_operation.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131
// +kubebuilder:subresource:status
3232
// +kubebuilder:resource:categories={virtualization},scope=Namespaced,shortName={vmop},singular=virtualmachineoperation
3333
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="VirtualMachineOperation phase."
34-
// +kubebuilder:printcolumn:name="Progress",type="integer",JSONPath=".status.progress",description="VirtualMachineOperation progress in percent."
34+
// +kubebuilder:printcolumn:name="Progress",type="string",JSONPath=".status.progress",description="VirtualMachineOperation progress in percent format."
3535
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type",description="VirtualMachineOperation type."
3636
// +kubebuilder:printcolumn:name="VirtualMachine",type="string",JSONPath=".spec.virtualMachineName",description="VirtualMachine name."
3737
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time of resource creation."
@@ -111,9 +111,8 @@ type VirtualMachineOperationCloneCustomization struct {
111111
type VirtualMachineOperationStatus struct {
112112
Phase VMOPPhase `json:"phase"`
113113
// Progress reports operation completion percentage for migration-related VMOPs (Evict/Migrate).
114-
// +kubebuilder:validation:Minimum=0
115-
// +kubebuilder:validation:Maximum=100
116-
Progress *int32 `json:"progress,omitempty"`
114+
// Example: `33%`.
115+
Progress string `json:"progress,omitempty"`
117116
// The latest detailed observations of the VirtualMachineOperation resource.
118117
Conditions []metav1.Condition `json:"conditions,omitempty"`
119118
// Resource generation last processed by the controller.

api/core/v1alpha2/zz_generated.deepcopy.go

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crds/doc-ru-virtualmachineoperations.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ spec:
131131
progress:
132132
description: |
133133
Прогресс выполнения операции в процентах для миграционных VMOP (`Evict`/`Migrate`).
134+
Например: `33%`.
134135
observedGeneration:
135136
description: |
136137
Поколение ресурса, которое в последний раз обрабатывалось контроллером.

crds/virtualmachineoperations.yaml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ spec:
2626
jsonPath: .status.phase
2727
name: Phase
2828
type: string
29-
- description: VirtualMachineOperation progress in percent.
29+
- description: VirtualMachineOperation progress in percent format.
3030
jsonPath: .status.progress
3131
name: Progress
32-
type: integer
32+
type: string
3333
- description: VirtualMachineOperation type.
3434
jsonPath: .spec.type
3535
name: Type
@@ -324,13 +324,10 @@ spec:
324324
- Terminating
325325
type: string
326326
progress:
327-
description:
328-
Progress reports operation completion percentage for
329-
migration-related VMOPs (Evict/Migrate).
330-
format: int32
331-
maximum: 100
332-
minimum: 0
333-
type: integer
327+
description: |-
328+
Progress reports operation completion percentage for migration-related VMOPs (Evict/Migrate).
329+
Example: `33%`.
330+
type: string
334331
resources:
335332
description:
336333
Resources contains the list of resources that are affected

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/fields"
2828
"k8s.io/apimachinery/pkg/types"
29-
"k8s.io/utils/ptr"
3029
virtv1 "kubevirt.io/api/core/v1"
3130
"sigs.k8s.io/controller-runtime/pkg/client"
3231
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -305,7 +304,7 @@ func (h LifecycleHandler) syncOperationComplete(ctx context.Context, vmop *v1alp
305304
}
306305
msg := h.getFailedMessage(reason, mig)
307306
progress := h.calculateMigrationProgress(vmop, mig, reason)
308-
vmop.Status.Progress = ptr.To(progress)
307+
vmop.Status.Progress = migrationprogress.FormatPercent(progress)
309308

310309
completedCond.
311310
Status(metav1.ConditionFalse).
@@ -316,7 +315,7 @@ func (h LifecycleHandler) syncOperationComplete(ctx context.Context, vmop *v1alp
316315
case virtv1.MigrationSucceeded:
317316
vmop.Status.Phase = v1alpha2.VMOPPhaseCompleted
318317
h.recorder.Event(vmop, corev1.EventTypeNormal, v1alpha2.ReasonVMOPSucceeded, "VirtualMachineOperation succeeded")
319-
vmop.Status.Progress = ptr.To(int32(100))
318+
vmop.Status.Progress = migrationprogress.FormatPercent(100)
320319

321320
completedCond.
322321
Status(metav1.ConditionTrue).
@@ -344,7 +343,7 @@ func (h LifecycleHandler) syncOperationComplete(ctx context.Context, vmop *v1alp
344343
vmop.Status.Phase = v1alpha2.VMOPPhasePending
345344
}
346345
progress := h.calculateMigrationProgress(vmop, mig, reason)
347-
vmop.Status.Progress = ptr.To(progress)
346+
vmop.Status.Progress = migrationprogress.FormatPercent(progress)
348347

349348
completedCond.
350349
Status(metav1.ConditionFalse).
@@ -407,7 +406,7 @@ func (h LifecycleHandler) canExecute(vmop *v1alpha2.VirtualMachineOperation, vm
407406

408407
if migratable.Status == metav1.ConditionTrue {
409408
vmop.Status.Phase = v1alpha2.VMOPPhasePending
410-
vmop.Status.Progress = ptr.To(int32(1))
409+
vmop.Status.Progress = migrationprogress.FormatPercent(1)
411410
conditions.SetCondition(
412411
conditions.NewConditionBuilder(vmopcondition.TypeCompleted).
413412
Generation(vmop.GetGeneration()).
@@ -461,7 +460,7 @@ func (h LifecycleHandler) execute(ctx context.Context, vmop *v1alpha2.VirtualMac
461460
vmop.Status.Phase = v1alpha2.VMOPPhasePending
462461
}
463462
progress := h.calculateMigrationProgress(vmop, mig, reason)
464-
vmop.Status.Progress = ptr.To(progress)
463+
vmop.Status.Progress = migrationprogress.FormatPercent(progress)
465464

466465
conditions.SetCondition(
467466
conditions.NewConditionBuilder(vmopcondition.TypeCompleted).
@@ -641,8 +640,8 @@ func (h LifecycleHandler) calculateMigrationProgress(
641640
return progressMigrationCompleted
642641
default:
643642
h.forgetProgress(vmop)
644-
if vmop != nil && vmop.Status.Progress != nil {
645-
return *vmop.Status.Progress
643+
if vmop != nil {
644+
return migrationprogress.ParsePercent(vmop.Status.Progress)
646645
}
647646
return 0
648647
}

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

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ var _ = Describe("LifecycleHandler", func() {
445445
),
446446
)
447447

448-
DescribeTable("should map progress by reason", func(reason vmopcondition.ReasonCompleted, initial *int32, expected int32) {
448+
DescribeTable("should map progress by reason", func(reason vmopcondition.ReasonCompleted, initial string, expected int32) {
449449
h := LifecycleHandler{progressStrategy: &progressStrategyStub{value: 55}}
450450
vmop := &v1alpha2.VirtualMachineOperation{Status: v1alpha2.VirtualMachineOperationStatus{Progress: initial}}
451451
mig := &virtv1.VirtualMachineInstanceMigration{}
@@ -461,14 +461,14 @@ var _ = Describe("LifecycleHandler", func() {
461461
Entry("source suspended", vmopcondition.ReasonSourceSuspended, nil, int32(91)),
462462
Entry("target resumed", vmopcondition.ReasonTargetResumed, nil, int32(92)),
463463
Entry("migration completed", vmopcondition.ReasonMigrationCompleted, nil, int32(100)),
464-
Entry("unknown keeps existing progress", vmopcondition.ReasonFailed, ptr.To[int32](44), int32(44)),
464+
Entry("unknown keeps existing progress", vmopcondition.ReasonFailed, "44%", int32(44)),
465465
)
466466

467467
It("should set syncing progress inside [10,90] for running migration", func() {
468468
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
469469
vmop := newVMOPMigrate()
470470
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
471-
vmop.Status.Progress = ptr.To[int32](10)
471+
vmop.Status.Progress = "10%"
472472

473473
mig := newSimpleMigration(fmt.Sprintf("vmop-%s", vmop.Name), name)
474474
mig.Status.Phase = virtv1.MigrationRunning
@@ -484,9 +484,9 @@ var _ = Describe("LifecycleHandler", func() {
484484
_, err := h.Handle(ctx, srv.Changed())
485485
Expect(err).NotTo(HaveOccurred())
486486
Expect(srv.Changed().Status.Phase).To(Equal(v1alpha2.VMOPPhaseInProgress))
487-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
488-
Expect(*srv.Changed().Status.Progress).To(BeNumerically(">=", migrationprogress.SyncRangeMin))
489-
Expect(*srv.Changed().Status.Progress).To(BeNumerically("<=", migrationprogress.SyncRangeMax))
487+
Expect(srv.Changed().Status.Progress).NotTo(BeEmpty())
488+
Expect(migrationprogress.ParsePercent(srv.Changed().Status.Progress)).To(BeNumerically(">=", migrationprogress.SyncRangeMin))
489+
Expect(migrationprogress.ParsePercent(srv.Changed().Status.Progress)).To(BeNumerically("<=", migrationprogress.SyncRangeMax))
490490

491491
completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, srv.Changed().Status.Conditions)
492492
Expect(found).To(BeTrue())
@@ -509,15 +509,14 @@ var _ = Describe("LifecycleHandler", func() {
509509
_, err := h.Handle(ctx, srv.Changed())
510510
Expect(err).NotTo(HaveOccurred())
511511
Expect(srv.Changed().Status.Phase).To(Equal(v1alpha2.VMOPPhasePending))
512-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
513-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(2)))
512+
Expect(srv.Changed().Status.Progress).To(Equal("2%"))
514513
})
515514

516515
It("should set aborted reason and preserve progress for failed migration", func() {
517516
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
518517
vmop := newVMOPMigrate()
519518
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
520-
vmop.Status.Progress = ptr.To[int32](55)
519+
vmop.Status.Progress = "55%"
521520

522521
mig := newSimpleMigration(fmt.Sprintf("vmop-%s", vmop.Name), name)
523522
mig.Status.Phase = virtv1.MigrationFailed
@@ -531,8 +530,7 @@ var _ = Describe("LifecycleHandler", func() {
531530
_, err := h.Handle(ctx, srv.Changed())
532531
Expect(err).NotTo(HaveOccurred())
533532
Expect(srv.Changed().Status.Phase).To(Equal(v1alpha2.VMOPPhaseFailed))
534-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
535-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(55)))
533+
Expect(srv.Changed().Status.Progress).To(Equal("55%"))
536534

537535
completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, srv.Changed().Status.Conditions)
538536
Expect(found).To(BeTrue())
@@ -555,15 +553,14 @@ var _ = Describe("LifecycleHandler", func() {
555553
_, err := h.Handle(ctx, srv.Changed())
556554
Expect(err).NotTo(HaveOccurred())
557555
Expect(srv.Changed().Status.Phase).To(Equal(v1alpha2.VMOPPhaseCompleted))
558-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
559-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(100)))
556+
Expect(srv.Changed().Status.Progress).To(Equal("100%"))
560557
})
561558

562559
It("should override Syncing with NotConverging when strategy detects stall", func() {
563560
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
564561
vmop := newVMOPMigrate()
565562
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
566-
vmop.Status.Progress = ptr.To[int32](50)
563+
vmop.Status.Progress = "50%"
567564

568565
mig := newSimpleMigration(fmt.Sprintf("vmop-%s", vmop.Name), name)
569566
mig.Status.Phase = virtv1.MigrationRunning
@@ -592,7 +589,7 @@ var _ = Describe("LifecycleHandler", func() {
592589
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
593590
vmop := newVMOPMigrate()
594591
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
595-
vmop.Status.Progress = ptr.To[int32](30)
592+
vmop.Status.Progress = "30%"
596593

597594
mig := newSimpleMigration(fmt.Sprintf("vmop-%s", vmop.Name), name)
598595
mig.Status.Phase = virtv1.MigrationRunning
@@ -691,8 +688,7 @@ var _ = Describe("LifecycleHandler", func() {
691688

692689
_, err := h.Handle(ctx, srv.Changed())
693690
Expect(err).NotTo(HaveOccurred())
694-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
695-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(3)))
691+
Expect(srv.Changed().Status.Progress).To(Equal("3%"))
696692

697693
completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, srv.Changed().Status.Conditions)
698694
Expect(found).To(BeTrue())
@@ -717,8 +713,7 @@ var _ = Describe("LifecycleHandler", func() {
717713

718714
_, err := h.Handle(ctx, srv.Changed())
719715
Expect(err).NotTo(HaveOccurred())
720-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
721-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(92)))
716+
Expect(srv.Changed().Status.Progress).To(Equal("92%"))
722717

723718
completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, srv.Changed().Status.Conditions)
724719
Expect(found).To(BeTrue())
@@ -744,8 +739,7 @@ var _ = Describe("LifecycleHandler", func() {
744739

745740
_, err := h.Handle(ctx, srv.Changed())
746741
Expect(err).NotTo(HaveOccurred())
747-
Expect(srv.Changed().Status.Progress).NotTo(BeNil())
748-
Expect(*srv.Changed().Status.Progress).To(Equal(int32(91)))
742+
Expect(srv.Changed().Status.Progress).To(Equal("91%"))
749743

750744
completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, srv.Changed().Status.Conditions)
751745
Expect(found).To(BeTrue())
@@ -756,7 +750,7 @@ var _ = Describe("LifecycleHandler", func() {
756750
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
757751
vmop := newVMOPMigrate()
758752
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
759-
vmop.Status.Progress = ptr.To[int32](60)
753+
vmop.Status.Progress = "60%"
760754
vmop.Status.Conditions = []metav1.Condition{
761755
{
762756
Type: vmopcondition.TypeSignalSent.String(),
@@ -791,7 +785,7 @@ var _ = Describe("LifecycleHandler", func() {
791785
vm := newVM(v1alpha2.PreferSafeMigrationPolicy)
792786
vmop := newVMOPMigrate()
793787
vmop.Status.Phase = v1alpha2.VMOPPhaseInProgress
794-
vmop.Status.Progress = ptr.To[int32](60)
788+
vmop.Status.Progress = "60%"
795789
vmop.Status.Conditions = []metav1.Condition{
796790
{
797791
Type: vmopcondition.TypeSignalSent.String(),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2026 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package progress
18+
19+
import (
20+
"fmt"
21+
"math"
22+
23+
commonpercent "github.com/deckhouse/virtualization-controller/pkg/common/percent"
24+
)
25+
26+
func FormatPercent(v int32) string {
27+
return fmt.Sprintf("%d%%", v)
28+
}
29+
30+
func ParsePercent(v string) int32 {
31+
if v == "" {
32+
return SyncRangeMin
33+
}
34+
35+
parsed := commonpercent.ExtractPercentageFloat(v)
36+
if math.IsNaN(parsed) {
37+
return SyncRangeMin
38+
}
39+
40+
return int32(parsed)
41+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ func mapBytesToMiB(v *uint64) float64 {
7373
}
7474

7575
func previousProgress(vmop *v1alpha2.VirtualMachineOperation) int32 {
76-
if vmop == nil || vmop.Status.Progress == nil {
76+
if vmop == nil {
7777
return SyncRangeMin
7878
}
79-
return *vmop.Status.Progress
79+
return ParsePercent(vmop.Status.Progress)
8080
}
8181

8282
func mapIteration(state *virtv1.VirtualMachineInstanceMigrationState) (uint32, bool) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestBuildRecord_UsesVMOPCreationTimestampAndPreviousProgress(t *testing.T)
5151
UID: types.UID("vmop-uid"),
5252
CreationTimestamp: metav1.NewTime(now.Add(-3 * time.Minute)),
5353
},
54-
Status: v1alpha2.VirtualMachineOperationStatus{Progress: ptr.To[int32](42)},
54+
Status: v1alpha2.VirtualMachineOperationStatus{Progress: "42%"},
5555
}
5656

5757
record := BuildRecord(vmop, nil, now)
@@ -135,7 +135,7 @@ func TestPreviousProgress(t *testing.T) {
135135
},
136136
{
137137
name: "explicit progress",
138-
vmop: &v1alpha2.VirtualMachineOperation{Status: v1alpha2.VirtualMachineOperationStatus{Progress: ptr.To[int32](37)}},
138+
vmop: &v1alpha2.VirtualMachineOperation{Status: v1alpha2.VirtualMachineOperationStatus{Progress: "37%"}},
139139
want: 37,
140140
},
141141
}

0 commit comments

Comments
 (0)