From e5f630ebd9a647f740b68cb964c7180e74da9f19 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 16 Jun 2026 09:36:21 +0200 Subject: [PATCH 1/2] feat(vmop): add superseded phase Signed-off-by: Daniil Antoshin --- .../v1alpha2/virtual_machine_operation.go | 4 +- crds/doc-ru-virtualmachineoperations.yaml | 3 +- crds/virtualmachineoperations.yaml | 2 + docs/internal/vmop_migration_lifecycle.md | 3 ++ .../pkg/common/vmop/vmop.go | 2 +- .../pkg/common/vmop/vmop_test.go | 49 +++++++++++++++++++ .../pkg/controller/vm/internal/migrating.go | 2 +- .../pkg/controller/vmop/service/service.go | 2 +- .../controller/vmop/service/service_test.go | 8 +-- .../snapshot/internal/handler/lifecycle.go | 8 ++- .../monitoring/metrics/vmop/data_metric.go | 9 +++- .../metrics/vmop/data_metric_test.go | 39 +++++++++++++++ .../pkg/monitoring/metrics/vmop/scraper.go | 1 + src/cli/internal/cmd/lifecycle/vmop/vmop.go | 5 +- test/e2e/vm/evacuation.go | 3 +- test/e2e/vm/util.go | 2 +- 16 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 images/virtualization-artifact/pkg/common/vmop/vmop_test.go diff --git a/api/core/v1alpha2/virtual_machine_operation.go b/api/core/v1alpha2/virtual_machine_operation.go index 836b2f66d8..391cf3998c 100644 --- a/api/core/v1alpha2/virtual_machine_operation.go +++ b/api/core/v1alpha2/virtual_machine_operation.go @@ -162,7 +162,8 @@ type VirtualMachineOperationList struct { // * `Completed`: The operation has been completed successfully. // * `Failed`: The operation failed. For details, refer to the `conditions` field and events. // * `Terminating`: The operation is being deleted. -// +kubebuilder:validation:Enum={Pending,InProgress,Completed,Failed,Terminating} +// * `Superseded`: The operation has been superseded by another operation. +// +kubebuilder:validation:Enum={Pending,InProgress,Completed,Failed,Terminating,Superseded} type VMOPPhase string const ( @@ -171,6 +172,7 @@ const ( VMOPPhaseCompleted VMOPPhase = "Completed" VMOPPhaseFailed VMOPPhase = "Failed" VMOPPhaseTerminating VMOPPhase = "Terminating" + VMOPPhaseSuperseded VMOPPhase = "Superseded" ) // Type of the operation to execute on a virtual machine: diff --git a/crds/doc-ru-virtualmachineoperations.yaml b/crds/doc-ru-virtualmachineoperations.yaml index f70503bea6..e90d808c1b 100644 --- a/crds/doc-ru-virtualmachineoperations.yaml +++ b/crds/doc-ru-virtualmachineoperations.yaml @@ -127,7 +127,8 @@ spec: * `InProgress` — операция в процессе выполнения; * `Completed` — операция прошла успешно; * `Failed` — операция завершилась неудачно. За подробностями обратитесь к полю `conditions` и событиям; - * `Terminating` — операция удаляется. + * `Terminating` — операция удаляется; + * `Superseded` — операция заменена другой операцией. progress: description: | Прогресс выполнения операции в процентах для миграционных VMOP (`Evict`/`Migrate`). diff --git a/crds/virtualmachineoperations.yaml b/crds/virtualmachineoperations.yaml index 603d7df544..0511582973 100644 --- a/crds/virtualmachineoperations.yaml +++ b/crds/virtualmachineoperations.yaml @@ -316,12 +316,14 @@ spec: * `Completed`: The operation has been completed successfully. * `Failed`: The operation failed. For details, refer to the `conditions` field and events. * `Terminating`: The operation is being deleted. + * `Superseded`: The operation has been superseded by another operation. enum: - Pending - InProgress - Completed - Failed - Terminating + - Superseded type: string progress: description: |- diff --git a/docs/internal/vmop_migration_lifecycle.md b/docs/internal/vmop_migration_lifecycle.md index ded860a383..78fb8a3c60 100644 --- a/docs/internal/vmop_migration_lifecycle.md +++ b/docs/internal/vmop_migration_lifecycle.md @@ -33,6 +33,7 @@ The controller does not mirror KubeVirt migration phases one-to-one. It converts | `MigrationState.Completed == true` | `SourceSuspended` | `InProgress` | `91%` | | `TargetNodeDomainReadyTimestamp != nil` | `TargetResumed` | `InProgress` | `92%` | | `MigrationSucceeded` | `Completed` | `Completed` | `100%` | +| Superseded by a newer VMOP | `Superseded` | `Superseded` | keep current | ## Reason semantics @@ -50,6 +51,7 @@ The controller does not mirror KubeVirt migration phases one-to-one. It converts | `Completed` | Migration completed successfully. | | `Aborted` | Migration was aborted. | | `Failed` | Migration failed for an unspecified reason. | +| `Superseded` | Operation was superseded by a newer VMOP for the same VM. | ## Progress model @@ -235,3 +237,4 @@ Important messages: | Source suspended during final handoff | `InProgress` | `SourceSuspended` | `91%` | | Target resumed | `InProgress` | `TargetResumed` | `92%` | | Migration succeeds | `Completed` | `Completed` | `100%` | +| Migration VMOP is superseded | `Superseded` | `Superseded` | keep current | diff --git a/images/virtualization-artifact/pkg/common/vmop/vmop.go b/images/virtualization-artifact/pkg/common/vmop/vmop.go index 3cac1a3d9d..50578ac8b2 100644 --- a/images/virtualization-artifact/pkg/common/vmop/vmop.go +++ b/images/virtualization-artifact/pkg/common/vmop/vmop.go @@ -29,7 +29,7 @@ func IsInProgressOrPending(vmop *v1alpha2.VirtualMachineOperation) bool { } func IsFinished(vmop *v1alpha2.VirtualMachineOperation) bool { - return vmop != nil && (vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted) + return vmop != nil && (vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted || vmop.Status.Phase == v1alpha2.VMOPPhaseSuperseded) } func IsTerminating(vmop *v1alpha2.VirtualMachineOperation) bool { diff --git a/images/virtualization-artifact/pkg/common/vmop/vmop_test.go b/images/virtualization-artifact/pkg/common/vmop/vmop_test.go new file mode 100644 index 0000000000..4cb0c5d149 --- /dev/null +++ b/images/virtualization-artifact/pkg/common/vmop/vmop_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmop + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func TestVMOP(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Common VMOP Suite") +} + +var _ = Describe("IsFinished", func() { + DescribeTable("detects terminal phases", + func(phase v1alpha2.VMOPPhase, expected bool) { + vmop := &v1alpha2.VirtualMachineOperation{ + Status: v1alpha2.VirtualMachineOperationStatus{Phase: phase}, + } + + Expect(IsFinished(vmop)).To(Equal(expected)) + }, + Entry("completed", v1alpha2.VMOPPhaseCompleted, true), + Entry("failed", v1alpha2.VMOPPhaseFailed, true), + Entry("superseded", v1alpha2.VMOPPhaseSuperseded, true), + Entry("pending", v1alpha2.VMOPPhasePending, false), + Entry("in progress", v1alpha2.VMOPPhaseInProgress, false), + Entry("terminating", v1alpha2.VMOPPhaseTerminating, false), + ) +}) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go b/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go index 7ea604eb01..f89560adfb 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go @@ -207,7 +207,7 @@ func (h *MigratingHandler) syncMigrating(ctx context.Context, s state.VirtualMac fmt.Sprintf("Wait until operation is completed; VirtualMachineOperation: %s.", vmop.Name), ) - case v1alpha2.VMOPPhaseCompleted, v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseTerminating: + case v1alpha2.VMOPPhaseCompleted, v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseTerminating, v1alpha2.VMOPPhaseSuperseded: conditions.RemoveCondition(vmcondition.TypeMigrating, &vm.Status.Conditions) return nil } diff --git a/images/virtualization-artifact/pkg/controller/vmop/service/service.go b/images/virtualization-artifact/pkg/controller/vmop/service/service.go index da8224dbb3..f5ef6efcf3 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/service/service.go +++ b/images/virtualization-artifact/pkg/controller/vmop/service/service.go @@ -184,7 +184,7 @@ func (s *BaseVMOPService) markSuperseded(ctx context.Context, oldVMOP, newVMOP * } base := current.DeepCopy() - current.Status.Phase = v1alpha2.VMOPPhaseCompleted + current.Status.Phase = v1alpha2.VMOPPhaseSuperseded conditions.SetCondition( conditions.NewConditionBuilder(vmopcondition.TypeCompleted). Generation(current.GetGeneration()). diff --git a/images/virtualization-artifact/pkg/controller/vmop/service/service_test.go b/images/virtualization-artifact/pkg/controller/vmop/service/service_test.go index 7affa0750b..8d68f625b5 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/service/service_test.go +++ b/images/virtualization-artifact/pkg/controller/vmop/service/service_test.go @@ -56,7 +56,7 @@ var _ = Describe("BaseVMOPService", func() { changed := &v1alpha2.VirtualMachineOperation{} Expect(client.Get(ctx, types.NamespacedName{Name: oldVMOP.Name, Namespace: oldVMOP.Namespace}, changed)).To(Succeed()) - Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseCompleted)) + Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseSuperseded)) completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, changed.Status.Conditions) Expect(found).To(BeTrue()) @@ -78,7 +78,7 @@ var _ = Describe("BaseVMOPService", func() { changed := &v1alpha2.VirtualMachineOperation{} Expect(client.Get(ctx, types.NamespacedName{Name: oldVMOP.Name, Namespace: oldVMOP.Namespace}, changed)).To(Succeed()) - Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseCompleted)) + Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseSuperseded)) completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, changed.Status.Conditions) Expect(found).To(BeTrue()) @@ -100,7 +100,7 @@ var _ = Describe("BaseVMOPService", func() { changed := &v1alpha2.VirtualMachineOperation{} Expect(client.Get(ctx, types.NamespacedName{Name: oldVMOP.Name, Namespace: oldVMOP.Namespace}, changed)).To(Succeed()) - Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseCompleted)) + Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseSuperseded)) completed, found := conditions.GetCondition(vmopcondition.TypeCompleted, changed.Status.Conditions) Expect(found).To(BeTrue()) @@ -136,7 +136,7 @@ var _ = Describe("BaseVMOPService", func() { changed := &v1alpha2.VirtualMachineOperation{} Expect(client.Get(ctx, types.NamespacedName{Name: oldVMOP.Name, Namespace: oldVMOP.Namespace}, changed)).To(Succeed()) - Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseCompleted)) + Expect(changed.Status.Phase).To(Equal(v1alpha2.VMOPPhaseSuperseded)) deletedMigration := &virtv1.VirtualMachineInstanceMigration{} err = client.Get(ctx, types.NamespacedName{Name: migration.Name, Namespace: migration.Namespace}, deletedMigration) diff --git a/images/virtualization-artifact/pkg/controller/vmop/snapshot/internal/handler/lifecycle.go b/images/virtualization-artifact/pkg/controller/vmop/snapshot/internal/handler/lifecycle.go index e4a07639a7..6c3665111c 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/snapshot/internal/handler/lifecycle.go +++ b/images/virtualization-artifact/pkg/controller/vmop/snapshot/internal/handler/lifecycle.go @@ -65,7 +65,7 @@ func (h LifecycleHandler) Handle(ctx context.Context, vmop *v1alpha2.VirtualMach maintenanced, foundMaintenance := conditions.GetCondition(vmopcondition.TypeMaintenanceMode, vmop.Status.Conditions) // Ignore if VMOP is in final state or failed. - if vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted || vmop.Status.Phase == v1alpha2.VMOPPhaseFailed { + if commonvmop.IsFinished(vmop) { // Skip: not a restore operation OR already exited maintenance if vmop.Spec.Restore == nil || !foundMaintenance || maintenanced.Status == metav1.ConditionFalse { return reconcile.Result{}, nil @@ -74,7 +74,11 @@ func (h LifecycleHandler) Handle(ctx context.Context, vmop *v1alpha2.VirtualMach completed, completedFound := conditions.GetCondition(vmopcondition.TypeCompleted, vmop.Status.Conditions) if completedFound && completed.Status == metav1.ConditionTrue { - vmop.Status.Phase = v1alpha2.VMOPPhaseCompleted + if completed.Reason == vmopcondition.ReasonSuperseded.String() { + vmop.Status.Phase = v1alpha2.VMOPPhaseSuperseded + } else { + vmop.Status.Phase = v1alpha2.VMOPPhaseCompleted + } return reconcile.Result{}, nil } diff --git a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric.go b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric.go index e9d55dff69..144dccb4ea 100644 --- a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric.go +++ b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric.go @@ -33,12 +33,13 @@ type dataMetric struct { Phase v1alpha2.VMOPPhase CreatedAt int64 // Unix timestamp when operation was created (0 = not set) StartedAt int64 // Unix timestamp when operation transitioned to InProgress (0 = not set) - FinishedAt int64 // Unix timestamp when operation finished (Completed/Failed) (0 = not set) + FinishedAt int64 // Unix timestamp when operation finished (Completed/Failed/Superseded) (0 = not set) } var successfulTerminalReasons = map[string]struct{}{ vmopcondition.ReasonOperationCompleted.String(): {}, vmopcondition.ReasonMigrationCompleted.String(): {}, + vmopcondition.ReasonSuperseded.String(): {}, } var failedTerminalReasons = map[string]struct{}{ @@ -63,7 +64,7 @@ func newDataMetric(vmop *v1alpha2.VirtualMachineOperation) *dataMetric { } var finishedAt int64 - if vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted || vmop.Status.Phase == v1alpha2.VMOPPhaseFailed { + if isFinishedPhase(vmop.Status.Phase) { completedCond, _ := conditions.GetCondition(vmopcondition.TypeCompleted, vmop.Status.Conditions) if isTerminalCompletedCondition(completedCond) { finishedAt = completedCond.LastTransitionTime.Unix() @@ -83,6 +84,10 @@ func newDataMetric(vmop *v1alpha2.VirtualMachineOperation) *dataMetric { } } +func isFinishedPhase(phase v1alpha2.VMOPPhase) bool { + return phase == v1alpha2.VMOPPhaseCompleted || phase == v1alpha2.VMOPPhaseFailed || phase == v1alpha2.VMOPPhaseSuperseded +} + func isTerminalCompletedCondition(cond metav1.Condition) bool { if cond.Status == metav1.ConditionTrue { _, ok := successfulTerminalReasons[cond.Reason] diff --git a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric_test.go b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric_test.go index fd44252a5b..cb4160710f 100644 --- a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric_test.go +++ b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/data_metric_test.go @@ -37,6 +37,11 @@ func TestIsTerminalCompletedCondition(t *testing.T) { cond: metav1.Condition{Status: metav1.ConditionTrue, Reason: vmopcondition.ReasonMigrationCompleted.String()}, want: true, }, + { + name: "superseded reason is terminal", + cond: metav1.Condition{Status: metav1.ConditionTrue, Reason: vmopcondition.ReasonSuperseded.String()}, + want: true, + }, { name: "target disk error reason is terminal", cond: metav1.Condition{Status: metav1.ConditionFalse, Reason: vmopcondition.ReasonTargetDiskError.String()}, @@ -68,6 +73,40 @@ func TestIsTerminalCompletedCondition(t *testing.T) { } } +func TestNewDataMetric_SetsFinishedAtForSupersededPhase(t *testing.T) { + finishedAt := metav1.NewTime(time.Unix(1710000000, 0)) + vmop := &v1alpha2.VirtualMachineOperation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vmop-test", + Namespace: "default", + CreationTimestamp: metav1.NewTime(time.Unix(1700000000, 0)), + }, + Spec: v1alpha2.VirtualMachineOperationSpec{ + Type: v1alpha2.VMOPTypeStart, + VirtualMachine: "test-vm", + }, + Status: v1alpha2.VirtualMachineOperationStatus{ + Phase: v1alpha2.VMOPPhaseSuperseded, + Conditions: []metav1.Condition{ + { + Type: vmopcondition.TypeCompleted.String(), + Status: metav1.ConditionTrue, + Reason: vmopcondition.ReasonSuperseded.String(), + LastTransitionTime: finishedAt, + }, + }, + }, + } + + metric := newDataMetric(vmop) + if metric == nil { + t.Fatal("expected metric to be created") + } + if metric.FinishedAt != finishedAt.Unix() { + t.Fatalf("expected FinishedAt=%d, got %d", finishedAt.Unix(), metric.FinishedAt) + } +} + func TestNewDataMetric_SetsFinishedAtForTerminalMigrationReasons(t *testing.T) { finishedAt := metav1.NewTime(time.Unix(1710000000, 0)) vmop := &v1alpha2.VirtualMachineOperation{ diff --git a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/scraper.go b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/scraper.go index 0e21d623d8..531473e1ea 100644 --- a/images/virtualization-artifact/pkg/monitoring/metrics/vmop/scraper.go +++ b/images/virtualization-artifact/pkg/monitoring/metrics/vmop/scraper.go @@ -56,6 +56,7 @@ func (s *scraper) updateMetricVMOPStatusPhase(m *dataMetric) { {phase == v1alpha2.VMOPPhaseCompleted, string(v1alpha2.VMOPPhaseCompleted)}, {phase == v1alpha2.VMOPPhaseFailed, string(v1alpha2.VMOPPhaseFailed)}, {phase == v1alpha2.VMOPPhaseTerminating, string(v1alpha2.VMOPPhaseTerminating)}, + {phase == v1alpha2.VMOPPhaseSuperseded, string(v1alpha2.VMOPPhaseSuperseded)}, } for _, p := range phases { diff --git a/src/cli/internal/cmd/lifecycle/vmop/vmop.go b/src/cli/internal/cmd/lifecycle/vmop/vmop.go index 20f297c2c0..4ffe09c4c7 100644 --- a/src/cli/internal/cmd/lifecycle/vmop/vmop.go +++ b/src/cli/internal/cmd/lifecycle/vmop/vmop.go @@ -166,6 +166,9 @@ func (v VirtualMachineOperation) generateMsg(vmop *v1alpha2.VirtualMachineOperat case v1alpha2.VMOPPhaseFailed: cond, _ := getCondition(vmopcondition.TypeCompleted.String(), vmop.Status.Conditions) fmt.Fprintf(&sb, "failed. type=%q reason=%q, message=%q.", cond.Type, cond.Reason, cond.Message) + case v1alpha2.VMOPPhaseSuperseded: + cond, _ := getCondition(vmopcondition.TypeCompleted.String(), vmop.Status.Conditions) + fmt.Fprintf(&sb, "superseded. type=%q reason=%q, message=%q.", cond.Type, cond.Reason, cond.Message) case "": sb.WriteString("created.") default: @@ -233,7 +236,7 @@ func (v VirtualMachineOperation) isPhaseOrFailed(vmop *v1alpha2.VirtualMachineOp if vmop == nil { return false } - return vmop.Status.Phase == phase || vmop.Status.Phase == v1alpha2.VMOPPhaseFailed + return vmop.Status.Phase == phase || vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseSuperseded } func (v VirtualMachineOperation) newVMOP(vmName, vmNamespace string, t v1alpha2.VMOPType, force *bool) *v1alpha2.VirtualMachineOperation { diff --git a/test/e2e/vm/evacuation.go b/test/e2e/vm/evacuation.go index 73049fbefd..a54d5f783e 100644 --- a/test/e2e/vm/evacuation.go +++ b/test/e2e/vm/evacuation.go @@ -98,7 +98,8 @@ var _ = Describe("VirtualMachineEvacuation", Label(precheck.NoPrecheck), func() if _, exists := vmop.Annotations[evacuationAnnotation]; !exists { continue } - if vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted { + switch vmop.Status.Phase { + case v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseCompleted, v1alpha2.VMOPPhaseSuperseded: finishedVMOPs[vmop.Spec.VirtualMachine] = struct{}{} } } diff --git a/test/e2e/vm/util.go b/test/e2e/vm/util.go index b21b273fa6..fcc80e5578 100644 --- a/test/e2e/vm/util.go +++ b/test/e2e/vm/util.go @@ -251,7 +251,7 @@ func untilVirtualMachinesWillBeStartMigratingAndCancelImmediately(f *framework.F return err } } - case v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseCompleted: + case v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseCompleted, v1alpha2.VMOPPhaseSuperseded: someCompleted = true return nil } From 29ceb478d57e62281c6ca36df4d5f8e8951c02cb Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 16 Jun 2026 09:37:20 +0200 Subject: [PATCH 2/2] docs(vmop): clarify superseded phase in russian docs Signed-off-by: Daniil Antoshin --- crds/doc-ru-virtualmachineoperations.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crds/doc-ru-virtualmachineoperations.yaml b/crds/doc-ru-virtualmachineoperations.yaml index e90d808c1b..98f66f618d 100644 --- a/crds/doc-ru-virtualmachineoperations.yaml +++ b/crds/doc-ru-virtualmachineoperations.yaml @@ -128,7 +128,7 @@ spec: * `Completed` — операция прошла успешно; * `Failed` — операция завершилась неудачно. За подробностями обратитесь к полю `conditions` и событиям; * `Terminating` — операция удаляется; - * `Superseded` — операция заменена другой операцией. + * `Superseded` — операция прервана другой операцией. progress: description: | Прогресс выполнения операции в процентах для миграционных VMOP (`Evict`/`Migrate`).