Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/core/v1alpha2/vmopcondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ const (
// ReasonTargetPreparing indicates that target pod is being prepared.
ReasonTargetPreparing ReasonCompleted = "TargetPreparing"

// ReasonWaitingForSyncSlot indicates that the target pod is prepared but the
// migration is waiting for a free data-transfer (sync) slot on the source node.
ReasonWaitingForSyncSlot ReasonCompleted = "WaitingForSyncSlot"

// ReasonSyncing indicates that source and target are synchronizing migration data.
ReasonSyncing ReasonCompleted = "Syncing"

Expand Down
2 changes: 1 addition & 1 deletion build/components/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ firmware:
libvirt: v10.9.0
edk2: stable202411
core:
3p-kubevirt: v1.6.2-v12n.24
3p-kubevirt: feat/core/two-pool-migration
3p-containerized-data-importer: v1.60.3-v12n.18
distribution: 2.8.3
package:
Expand Down
8 changes: 8 additions & 0 deletions crds/embedded/kubevirt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,14 @@ spec:
allowed per node. Defaults to 2
format: int32
type: integer
parallelSyncMigrationsPerNode:
description: |-
ParallelSyncMigrationsPerNode is the maximum number of concurrent outgoing live migrations
per source node allowed to be in the data-transfer (sync) phase. Migrations above this cap
wait in PreparingTarget with a WaitingForSyncSlot condition until a slot frees.
Effective value is clamped to ParallelOutboundMigrationsPerNode. Defaults to 1.
format: int32
type: integer
progressTimeout:
description: |-
ProgressTimeout is the maximum number of seconds a live migration is allowed to make no progress.
Expand Down
10 changes: 9 additions & 1 deletion images/hooks/pkg/hooks/migration-config/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,21 @@ const (
bandwidthPerMigrationAnnotation = "virtualization.deckhouse.io/bandwidth-per-migration"
completionTimeoutPerGiBAnnotation = "virtualization.deckhouse.io/completion-timeout-per-gib"
parallelOutboundMigrationsPerNodeAnnotation = "virtualization.deckhouse.io/parallel-outbound-migrations-per-node"
parallelSyncMigrationsPerNodeAnnotation = "virtualization.deckhouse.io/parallel-sync-migrations-per-node"
progressTimeoutAnnotation = "virtualization.deckhouse.io/progress-timeout"
disableTLSAnnotation = "virtualization.deckhouse.io/disable-tls"

bandwidthPerMigrationValuesPath = "virtualization.internal.virtConfig.bandwidthPerMigration"
completionTimeoutPerGiBValuesPath = "virtualization.internal.virtConfig.completionTimeoutPerGiB"
parallelOutboundMigrationsPerNodeValuesPath = "virtualization.internal.virtConfig.parallelOutboundMigrationsPerNode"
parallelSyncMigrationsPerNodeValuesPath = "virtualization.internal.virtConfig.parallelSyncMigrationsPerNode"
progressTimeoutValuesPath = "virtualization.internal.virtConfig.progressTimeout"
disableTLSValuesPath = "virtualization.internal.virtConfig.disableTLS"

defaultBandwidthPerMigration = "640Mi"
defaultCompletionTimeoutPerGiB = 800
defaultParallelOutboundMigrationsPerNode = 1
defaultParallelOutboundMigrationsPerNode = 2
defaultParallelSyncMigrationsPerNode = 1
defaultProgressTimeout = 150
defaultDisableTLS = false
)
Expand All @@ -71,6 +74,11 @@ var migrationParams = []migrationParam{
valuesPath: parallelOutboundMigrationsPerNodeValuesPath,
defaultValue: defaultParallelOutboundMigrationsPerNode,
},
{
annotation: parallelSyncMigrationsPerNodeAnnotation,
valuesPath: parallelSyncMigrationsPerNodeValuesPath,
defaultValue: defaultParallelSyncMigrationsPerNode,
},
{
annotation: progressTimeoutAnnotation,
valuesPath: progressTimeoutValuesPath,
Expand Down
16 changes: 15 additions & 1 deletion images/hooks/pkg/hooks/migration-config/hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var _ = Describe("MigrationConfig", func() {
bandwidthPerMigrationAnnotation: "1Gi",
completionTimeoutPerGiBAnnotation: "1200",
parallelOutboundMigrationsPerNodeAnnotation: "5",
parallelSyncMigrationsPerNodeAnnotation: "3",
progressTimeoutAnnotation: "300",
disableTLSAnnotation: "true",
}))
Expand All @@ -95,6 +96,8 @@ var _ = Describe("MigrationConfig", func() {
return gjson.Result{Type: gjson.Number, Num: defaultCompletionTimeoutPerGiB}
case parallelOutboundMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelOutboundMigrationsPerNode}
case parallelSyncMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelSyncMigrationsPerNode}
case progressTimeoutValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultProgressTimeout}
case disableTLSValuesPath:
Expand All @@ -113,6 +116,7 @@ var _ = Describe("MigrationConfig", func() {
Expect(setValues).To(HaveKeyWithValue(bandwidthPerMigrationValuesPath, "1Gi"))
Expect(setValues).To(HaveKeyWithValue(completionTimeoutPerGiBValuesPath, 1200))
Expect(setValues).To(HaveKeyWithValue(parallelOutboundMigrationsPerNodeValuesPath, 5))
Expect(setValues).To(HaveKeyWithValue(parallelSyncMigrationsPerNodeValuesPath, 3))
Expect(setValues).To(HaveKeyWithValue(progressTimeoutValuesPath, 300))
Expect(setValues).To(HaveKeyWithValue(disableTLSValuesPath, true))
})
Expand All @@ -128,6 +132,8 @@ var _ = Describe("MigrationConfig", func() {
return gjson.Result{Type: gjson.Number, Num: 9999}
case parallelOutboundMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: 9999}
case parallelSyncMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: 9999}
case progressTimeoutValuesPath:
return gjson.Result{Type: gjson.Number, Num: 9999}
case disableTLSValuesPath:
Expand All @@ -146,6 +152,7 @@ var _ = Describe("MigrationConfig", func() {
Expect(setValues).To(HaveKeyWithValue(bandwidthPerMigrationValuesPath, defaultBandwidthPerMigration))
Expect(setValues).To(HaveKeyWithValue(completionTimeoutPerGiBValuesPath, defaultCompletionTimeoutPerGiB))
Expect(setValues).To(HaveKeyWithValue(parallelOutboundMigrationsPerNodeValuesPath, defaultParallelOutboundMigrationsPerNode))
Expect(setValues).To(HaveKeyWithValue(parallelSyncMigrationsPerNodeValuesPath, defaultParallelSyncMigrationsPerNode))
Expect(setValues).To(HaveKeyWithValue(progressTimeoutValuesPath, defaultProgressTimeout))
Expect(setValues).To(HaveKeyWithValue(disableTLSValuesPath, defaultDisableTLS))
})
Expand All @@ -154,7 +161,8 @@ var _ = Describe("MigrationConfig", func() {
setSnapshots(newSnapshot(map[string]string{
bandwidthPerMigrationAnnotation: defaultBandwidthPerMigration,
completionTimeoutPerGiBAnnotation: "800",
parallelOutboundMigrationsPerNodeAnnotation: "1",
parallelOutboundMigrationsPerNodeAnnotation: "2",
parallelSyncMigrationsPerNodeAnnotation: "1",
progressTimeoutAnnotation: "150",
disableTLSAnnotation: "false",
}))
Expand All @@ -167,6 +175,8 @@ var _ = Describe("MigrationConfig", func() {
return gjson.Result{Type: gjson.Number, Num: defaultCompletionTimeoutPerGiB}
case parallelOutboundMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelOutboundMigrationsPerNode}
case parallelSyncMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelSyncMigrationsPerNode}
case progressTimeoutValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultProgressTimeout}
case disableTLSValuesPath:
Expand Down Expand Up @@ -214,6 +224,8 @@ var _ = Describe("MigrationConfig", func() {
return gjson.Result{Type: gjson.Number, Num: defaultCompletionTimeoutPerGiB}
case parallelOutboundMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelOutboundMigrationsPerNode}
case parallelSyncMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelSyncMigrationsPerNode}
case progressTimeoutValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultProgressTimeout}
case disableTLSValuesPath:
Expand Down Expand Up @@ -243,6 +255,8 @@ var _ = Describe("MigrationConfig", func() {
return gjson.Result{Type: gjson.Number, Num: defaultCompletionTimeoutPerGiB}
case parallelOutboundMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelOutboundMigrationsPerNode}
case parallelSyncMigrationsPerNodeValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultParallelSyncMigrationsPerNode}
case progressTimeoutValuesPath:
return gjson.Result{Type: gjson.Number, Num: defaultProgressTimeout}
case disableTLSValuesPath:
Expand Down
2 changes: 2 additions & 0 deletions images/virt-artifact/werf.inc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
image: {{ .ModuleNamePrefix }}{{ .ImageName }}-src-artifact
final: false
fromImage: builder/src
fromCacheVersion: "two-pool-migration-1"
secrets:
- id: SOURCE_REPO
value: {{ $.SOURCE_REPO }}
Expand Down Expand Up @@ -44,6 +45,7 @@ packages:
image: {{ .ModuleNamePrefix }}{{ .ImageName }}
final: false
fromImage: {{ eq $.SVACE_ENABLED "false" | ternary "builder/golang-alt-1.25" "builder/golang-alt-svace-1.25" }}
fromCacheVersion: "two-pool-migration-1"
mount:
- fromPath: ~/go-pkg-cache
to: /go/pkg
Expand Down
1 change: 1 addition & 0 deletions images/virt-controller/werf.inc.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
image: {{ .ModuleNamePrefix }}{{ .ImageName }}
fromImage: {{ .ModuleNamePrefix }}distroless
fromCacheVersion: "two-pool-migration-1"
git:
{{- include "image mount points" . }}
import:
Expand Down
1 change: 1 addition & 0 deletions images/virt-operator/werf.inc.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
image: {{ .ModuleNamePrefix }}{{ .ImageName }}
fromImage: {{ .ModuleNamePrefix }}distroless
fromCacheVersion: "two-pool-migration-1"
git:
{{- include "image mount points" . }}
import:
Expand Down
2 changes: 1 addition & 1 deletion images/virtualization-artifact/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,4 @@ replace (
)

// Kubevirt API replaces
replace kubevirt.io/api => github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260403154920-301347b413ce
replace kubevirt.io/api => github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260417064344-ebd8172aaa7b
2 changes: 2 additions & 0 deletions images/virtualization-artifact/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260403154920-301347b413ce h1:b6I/SUcA30j2wcOBBERbN20cKARaDAHNtSzP4XB6kgg=
github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260403154920-301347b413ce/go.mod h1:wGZLfRa/b4w/V/hakmfcK0CmrAZGfpj+jN7BMt0s19E=
github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260417064344-ebd8172aaa7b h1:MnsWSUYWW/xMg9b2gU5Oqi72DTG1eYSQHpioPNFW/zQ=
github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v0.0.0-20260417064344-ebd8172aaa7b/go.mod h1:wGZLfRa/b4w/V/hakmfcK0CmrAZGfpj+jN7BMt0s19E=
github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250226105106-176cd3afcdd5 h1:PsN1E0oxC/+4zdA977txrqUCuObFL3HAuu5Xnud8m8c=
github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250226105106-176cd3afcdd5/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
progressDisksPreparing int32 = 1
progressTargetScheduling int32 = 2
progressTargetPreparing int32 = 3
progressWaitingForSyncSlot int32 = 4
progressSourceSuspended int32 = 91
progressTargetResumed int32 = 92
progressMigrationCompleted int32 = 100
Expand All @@ -59,6 +60,7 @@ const (
messageSyncingSourceAndTarget = "Syncing source and target"
messageTargetPodScheduling = "Target pod is being scheduled"
messageTargetPodPreparing = "Target pod is being prepared"
messageWaitingForSyncSlot = "Target prepared; waiting for sync slot on source node"
messageTargetVMResumed = "Target VM resumed"
messageSourceVMSuspended = "Source VM suspended"
)
Expand Down Expand Up @@ -579,6 +581,13 @@ func (h LifecycleHandler) getInProgressReasonAndMessage(
case virtv1.MigrationScheduled, virtv1.MigrationPreparingTarget:
reason = vmopcondition.ReasonTargetPreparing
message = messageTargetPodPreparing
if cond, found := conditions.GetKVVMIMCondition(virtv1.VirtualMachineInstanceMigrationWaitingForSyncSlot, mig.Status.Conditions); found && cond.Status == corev1.ConditionTrue {
reason = vmopcondition.ReasonWaitingForSyncSlot
message = messageWaitingForSyncSlot
if cond.Message != "" {
message = cond.Message
}
}
case virtv1.MigrationTargetReady, virtv1.MigrationWaitingForSync, virtv1.MigrationSynchronizing, virtv1.MigrationRunning:
reason = vmopcondition.ReasonSyncing
message = messageSyncingSourceAndTarget
Expand Down Expand Up @@ -624,6 +633,8 @@ func (h LifecycleHandler) calculateMigrationProgress(
return progressTargetScheduling
case vmopcondition.ReasonTargetPreparing:
return progressTargetPreparing
case vmopcondition.ReasonWaitingForSyncSlot:
return progressWaitingForSyncSlot
case vmopcondition.ReasonTargetDiskError:
return progressTargetPreparing
case vmopcondition.ReasonSyncing, vmopcondition.ReasonNotConverging:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

const (
ParallelOutboundMigrationsPerNodeDefault uint32 = 2
ParallelSyncMigrationsPerNodeDefault uint32 = 1
ParallelMigrationsPerClusterDefault uint32 = 5
BandwidthPerMigrationDefault = "0Mi"
NodeDrainTaintDefaultKey string = "kubevirt.io/drain"
Expand All @@ -52,6 +53,13 @@ func NewMigrationConfiguration(allowAutoConverge bool, kvconfig virtv1.KubeVirt)
if kvconfig.Spec.Configuration.MigrationConfiguration != nil && kvconfig.Spec.Configuration.MigrationConfiguration.ParallelOutboundMigrationsPerNode != nil {
parallelOutboundMigrationsPerNode = *kvconfig.Spec.Configuration.MigrationConfiguration.ParallelOutboundMigrationsPerNode
}
parallelSyncMigrationsPerNode := ParallelSyncMigrationsPerNodeDefault
if kvconfig.Spec.Configuration.MigrationConfiguration != nil && kvconfig.Spec.Configuration.MigrationConfiguration.ParallelSyncMigrationsPerNode != nil {
parallelSyncMigrationsPerNode = *kvconfig.Spec.Configuration.MigrationConfiguration.ParallelSyncMigrationsPerNode
}
if parallelSyncMigrationsPerNode > parallelOutboundMigrationsPerNode {
parallelSyncMigrationsPerNode = parallelOutboundMigrationsPerNode
}
// Reuse default value of BandwidthPerNode as bandwidthPerMigration.
bandwidthPerMigration := resource.MustParse(BandwidthPerMigrationDefault)
if kvconfig.Spec.Configuration.MigrationConfiguration != nil && kvconfig.Spec.Configuration.MigrationConfiguration.BandwidthPerMigration != nil {
Expand All @@ -73,6 +81,7 @@ func NewMigrationConfiguration(allowAutoConverge bool, kvconfig virtv1.KubeVirt)
return &virtv1.MigrationConfiguration{
ParallelMigrationsPerCluster: &parallelMigrationsPerCluster,
ParallelOutboundMigrationsPerNode: &parallelOutboundMigrationsPerNode,
ParallelSyncMigrationsPerNode: &parallelSyncMigrationsPerNode,
NodeDrainTaintKey: &nodeDrainTaintDefaultKey,
BandwidthPerMigration: &bandwidthPerMigration,
ProgressTimeout: &progressTimeout,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
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 livemigration

import (
"testing"

"github.com/stretchr/testify/require"
"k8s.io/utils/ptr"
virtv1 "kubevirt.io/api/core/v1"
)

func TestNewMigrationConfiguration_ParallelSyncDefault(t *testing.T) {
kv := virtv1.KubeVirt{}
cfg := NewMigrationConfiguration(false, kv)

require.NotNil(t, cfg.ParallelOutboundMigrationsPerNode)
require.Equal(t, ParallelOutboundMigrationsPerNodeDefault, *cfg.ParallelOutboundMigrationsPerNode)
require.NotNil(t, cfg.ParallelSyncMigrationsPerNode)
require.Equal(t, ParallelSyncMigrationsPerNodeDefault, *cfg.ParallelSyncMigrationsPerNode)
}

func TestNewMigrationConfiguration_ParallelSyncPassthrough(t *testing.T) {
kv := virtv1.KubeVirt{
Spec: virtv1.KubeVirtSpec{
Configuration: virtv1.KubeVirtConfiguration{
MigrationConfiguration: &virtv1.MigrationConfiguration{
ParallelOutboundMigrationsPerNode: ptr.To[uint32](4),
ParallelSyncMigrationsPerNode: ptr.To[uint32](2),
},
},
},
}
cfg := NewMigrationConfiguration(false, kv)
require.Equal(t, uint32(4), *cfg.ParallelOutboundMigrationsPerNode)
require.Equal(t, uint32(2), *cfg.ParallelSyncMigrationsPerNode)
}

func TestNewMigrationConfiguration_ParallelSyncClampedToOutbound(t *testing.T) {
kv := virtv1.KubeVirt{
Spec: virtv1.KubeVirtSpec{
Configuration: virtv1.KubeVirtConfiguration{
MigrationConfiguration: &virtv1.MigrationConfiguration{
ParallelOutboundMigrationsPerNode: ptr.To[uint32](1),
ParallelSyncMigrationsPerNode: ptr.To[uint32](5),
},
},
},
}
cfg := NewMigrationConfiguration(false, kv)
require.Equal(t, uint32(1), *cfg.ParallelOutboundMigrationsPerNode)
require.Equal(t, uint32(1), *cfg.ParallelSyncMigrationsPerNode,
"sync cap must be clamped to outbound cap")
}
2 changes: 2 additions & 0 deletions openapi/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ properties:
type: integer
parallelOutboundMigrationsPerNode:
type: integer
parallelSyncMigrationsPerNode:
type: integer
progressTimeout:
type: integer
disableTLS:
Expand Down
5 changes: 5 additions & 0 deletions templates/kubevirt/_kubevirt_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ spec:
{{- .Values.virtualization.internal | dig "virtConfig" "parallelOutboundMigrationsPerNode" 2 -}}
{{- end -}}

{{- define "kubevirt.parallel_sync_migrations_per_node" -}}
{{- .Values.virtualization.internal | dig "virtConfig" "parallelSyncMigrationsPerNode" 1 -}}
{{- end -}}

{{- define "kubevirt.progress_timeout" -}}
{{- .Values.virtualization.internal | dig "virtConfig" "progressTimeout" 150 -}}
{{- end -}}
Expand All @@ -112,5 +116,6 @@ completionTimeoutPerGiB: {{ include "kubevirt.completion_timeout_per_gib" . }}
disableTLS: {{ include "kubevirt.disable_tls" . }}
parallelMigrationsPerCluster: {{ include "kubevirt.parallel_migrations_per_cluster" . }}
parallelOutboundMigrationsPerNode: {{ include "kubevirt.parallel_outbound_migrations_per_node" . }}
parallelSyncMigrationsPerNode: {{ include "kubevirt.parallel_sync_migrations_per_node" . }}
progressTimeout: {{ include "kubevirt.progress_timeout" . }}
{{- end -}}
Loading