Skip to content

Commit 412bfed

Browse files
committed
++ code cleanups, add unit and e2e tests, fix nil pointer
Signed-off-by: Ivan Mikheykin <ivan.mikheykin@flant.com>
1 parent 7f50f71 commit 412bfed

17 files changed

Lines changed: 469 additions & 91 deletions

File tree

images/virtualization-artifact/pkg/config/load_live_migration_settings.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"strconv"
2323

2424
"k8s.io/apimachinery/pkg/api/resource"
25+
26+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
2527
)
2628

2729
const (
@@ -43,6 +45,7 @@ const (
4345
DefaultBandwidthPerNode = "64Mi"
4446
DefaultMaxMigrationsPerNode = 2
4547
DefaultNetwork = LiveMigrationNetworkShared
48+
DefaultLiveMigrationPolicy = v1alpha2.PreferSafeMigrationPolicy
4649
)
4750

4851
type LiveMigrationSettings struct {

images/virtualization-artifact/pkg/controller/livemigration/internal/dynamic_settings_handler.go

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
"github.com/deckhouse/virtualization-controller/pkg/config"
2828
internalservice "github.com/deckhouse/virtualization-controller/pkg/controller/livemigration/internal/service"
2929
"github.com/deckhouse/virtualization-controller/pkg/logger"
30-
virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2"
30+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
3131
)
3232

3333
const dynamicSettingsHandlerName = "DynamicSettingsHandler"
@@ -51,13 +51,7 @@ func (h *DynamicSettingsHandler) Handle(ctx context.Context, kvvmi *virtv1.Virtu
5151
return reconcile.Result{}, nil
5252
}
5353

54-
// Get vm
55-
// Get vmop
56-
// Merge live migration policies.
57-
// Add global live migration settings.
58-
// Patch vmi status with migrationConfiguration.
59-
60-
var vm virtv2.VirtualMachine
54+
var vm v1alpha2.VirtualMachine
6155
vmKey := types.NamespacedName{
6256
Namespace: kvvmi.Namespace,
6357
Name: kvvmi.Name,
@@ -73,39 +67,17 @@ func (h *DynamicSettingsHandler) Handle(ctx context.Context, kvvmi *virtv1.Virtu
7367
return reconcile.Result{}, err
7468
}
7569

76-
// Merge
77-
// Detect policy: use default from vmclass if no policy on vm or policy is not allowed.
78-
// Use policy from vm if contains in allowed.
79-
// Set initial autoconverge value depending on policy.
80-
// Use force flag from vmop to override autoconverge flag for Manual, PreferSafe. Can't implement default force:true for PreferForced, do not override for now.
81-
82-
vmPolicy := virtv2.PreferSafeMigrationPolicy
83-
if vm.Spec.LiveMigrationPolicy != "" {
84-
vmPolicy = vm.Spec.LiveMigrationPolicy
85-
}
86-
87-
// Calculate final autoConverge value.
88-
autoConverge := false
89-
switch vmPolicy {
90-
case virtv2.PreferSafeMigrationPolicy:
91-
// User may override autoConverge with vmop.
92-
if vmopInProgress.Spec.Force != nil {
93-
autoConverge = *vmopInProgress.Spec.Force
94-
}
95-
case virtv2.PreferForcedMigrationPolicy:
96-
autoConverge = true
97-
if vmopInProgress.Spec.Force != nil {
98-
autoConverge = *vmopInProgress.Spec.Force
99-
}
100-
case virtv2.AlwaysForcedMigrationPolicy:
101-
autoConverge = true
102-
}
70+
effectivePolicy, autoConverge := internalservice.CalculateEffectivePolicy(vm, vmopInProgress)
10371

10472
conf := internalservice.NewMigrationConfiguration(h.moduleSettings, autoConverge)
10573

10674
kvvmi.Status.MigrationState.MigrationConfiguration = conf
10775

108-
log.Info("Patch KVVMI with migration settings", "migration.configuration", internalservice.DumpKVVMIMigrationConfiguration(kvvmi), "conf", internalservice.DumpMigrationConfiguration(conf))
76+
log.Debug("Set migrationConfiguration on KVVMI",
77+
"migrationConfiguration", internalservice.DumpKVVMIMigrationConfiguration(kvvmi),
78+
"policy", effectivePolicy,
79+
"autoConverge", autoConverge,
80+
)
10981

11082
return reconcile.Result{}, nil
11183
}
@@ -124,8 +96,8 @@ func (h *DynamicSettingsHandler) shouldUpdateMigrationConfiguration(kvvmi *virtv
12496
}
12597

12698
// GetVMOPInProgressForVM check if there is at least one VMOP for the same VM in progress phase.
127-
func (h *DynamicSettingsHandler) GetVMOPInProgressForVM(ctx context.Context, vmKey client.ObjectKey) (*virtv2.VirtualMachineOperation, error) {
128-
var vmopList virtv2.VirtualMachineOperationList
99+
func (h *DynamicSettingsHandler) GetVMOPInProgressForVM(ctx context.Context, vmKey client.ObjectKey) (*v1alpha2.VirtualMachineOperation, error) {
100+
var vmopList v1alpha2.VirtualMachineOperationList
129101
err := h.client.List(ctx, &vmopList, client.InNamespace(vmKey.Namespace))
130102
if err != nil {
131103
return nil, err
@@ -138,7 +110,7 @@ func (h *DynamicSettingsHandler) GetVMOPInProgressForVM(ctx context.Context, vmK
138110
}
139111

140112
// Return if VMOP has phase InProgress.
141-
if vmop.Status.Phase == virtv2.VMOPPhaseInProgress {
113+
if vmop.Status.Phase == v1alpha2.VMOPPhaseInProgress {
142114
return &vmop, nil
143115
}
144116
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package internal
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
"k8s.io/utils/ptr"
8+
virtv1 "kubevirt.io/api/core/v1"
9+
10+
vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm"
11+
"github.com/deckhouse/virtualization-controller/pkg/common/testutil"
12+
"github.com/deckhouse/virtualization-controller/pkg/config"
13+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
14+
)
15+
16+
var _ = Describe("TestEvacuationHandler", func() {
17+
const (
18+
vmName = "vm-migratable"
19+
vmNamespace = "default"
20+
)
21+
22+
ctx := testutil.ContextBackgroundWithNoOpLogger()
23+
24+
newVM := func() *v1alpha2.VirtualMachine {
25+
vm := vmbuilder.NewEmpty(vmName, vmNamespace)
26+
vm.Spec.LiveMigrationPolicy = v1alpha2.PreferSafeMigrationPolicy
27+
28+
return vm
29+
}
30+
31+
newKVVMI := func() *virtv1.VirtualMachineInstance {
32+
vmi := &virtv1.VirtualMachineInstance{
33+
TypeMeta: metav1.TypeMeta{
34+
APIVersion: virtv1.SchemeGroupVersion.String(),
35+
Kind: virtv1.VirtualMachineInstanceGroupVersionKind.Kind,
36+
},
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: vmName,
39+
Namespace: vmNamespace,
40+
},
41+
}
42+
return vmi
43+
}
44+
45+
newVMOPEvict := func(force *bool) *v1alpha2.VirtualMachineOperation {
46+
vmop := &v1alpha2.VirtualMachineOperation{
47+
TypeMeta: metav1.TypeMeta{
48+
APIVersion: v1alpha2.SchemeGroupVersion.String(),
49+
Kind: v1alpha2.VirtualMachineOperationKind,
50+
},
51+
ObjectMeta: metav1.ObjectMeta{
52+
Name: "some-vmop-name",
53+
Namespace: vmNamespace,
54+
},
55+
Spec: v1alpha2.VirtualMachineOperationSpec{
56+
Type: v1alpha2.VMOPTypeEvict,
57+
VirtualMachine: vmName,
58+
Force: force,
59+
},
60+
Status: v1alpha2.VirtualMachineOperationStatus{
61+
Phase: v1alpha2.VMOPPhaseInProgress,
62+
},
63+
}
64+
return vmop
65+
}
66+
67+
When("Observe KVVMI with migrateState", func() {
68+
It("Should set migrationConfiguration", func() {
69+
vm := newVM()
70+
kvvmi := newKVVMI()
71+
72+
kvvmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{}
73+
74+
fakeClient := setupEnvironment(kvvmi, vm)
75+
h := NewDynamicSettingsHandler(fakeClient, config.NewDefaultLiveMigrationSettings())
76+
_, err := h.Handle(ctx, kvvmi)
77+
Expect(err).NotTo(HaveOccurred())
78+
79+
Expect(kvvmi.Status.MigrationState.MigrationConfiguration).ShouldNot(BeNil(), "Should set migrationConfiguration")
80+
})
81+
})
82+
83+
When("Observe KVVMI with completed migration", func() {
84+
It("Should not set migrationConfiguration", func() {
85+
vm := newVM()
86+
kvvmi := newKVVMI()
87+
88+
kvvmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{
89+
Completed: true,
90+
}
91+
92+
fakeClient := setupEnvironment(kvvmi, vm)
93+
h := NewDynamicSettingsHandler(fakeClient, config.NewDefaultLiveMigrationSettings())
94+
_, err := h.Handle(ctx, kvvmi)
95+
Expect(err).NotTo(HaveOccurred())
96+
97+
Expect(kvvmi.Status.MigrationState.MigrationConfiguration).Should(BeNil(), "Should not set migrationConfiguration")
98+
})
99+
})
100+
101+
DescribeTable("When migration started with VMOP and force flag",
102+
func(policy v1alpha2.LiveMigrationPolicy, force *bool) {
103+
vm := newVM()
104+
vm.Spec.LiveMigrationPolicy = policy
105+
106+
kvvmi := newKVVMI()
107+
kvvmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{}
108+
109+
vmop := newVMOPEvict(force)
110+
111+
fakeClient := setupEnvironment(kvvmi, vm, vmop)
112+
h := NewDynamicSettingsHandler(fakeClient, config.NewDefaultLiveMigrationSettings())
113+
_, err := h.Handle(ctx, kvvmi)
114+
Expect(err).NotTo(HaveOccurred())
115+
116+
Expect(kvvmi.Status.MigrationState.MigrationConfiguration).ShouldNot(BeNil(), "Should set migrationConfiguration")
117+
Expect(kvvmi.Status.MigrationState.MigrationConfiguration.AllowAutoConverge).Should(Equal(force))
118+
},
119+
Entry("Should enable autoConverge for PreferSafe policy and force=true", v1alpha2.PreferSafeMigrationPolicy, ptr.To(true)),
120+
Entry("Should disable autoConverge for PreferForced policy and force=false", v1alpha2.PreferForcedMigrationPolicy, ptr.To(false)),
121+
)
122+
})

images/virtualization-artifact/pkg/controller/livemigration/internal/service/migration_configuration.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,6 @@ func NewMigrationConfiguration(moduleSettings config.LiveMigrationSettings, allo
6363
}
6464
}
6565

66-
func DumpMigrationConfiguration(conf *virtv1.MigrationConfiguration) string {
67-
out, err := json.Marshal(conf)
68-
if err != nil {
69-
return fmt.Sprintf("(json err %v, fmt dump)%#v", err, conf)
70-
}
71-
return string(out)
72-
}
73-
7466
func DumpKVVMIMigrationConfiguration(kvvmi *virtv1.VirtualMachineInstance) string {
7567
if kvvmi.Status.MigrationState == nil {
7668
return "status.migrationState is null"
@@ -86,7 +78,8 @@ func DumpKVVMIMigrationConfiguration(kvvmi *virtv1.VirtualMachineInstance) strin
8678
return string(out)
8779
}
8880

89-
func IsMigrationConfigurationChanged(current *virtv1.VirtualMachineInstance, changed *virtv1.VirtualMachineInstance) bool {
81+
// IsMigrationConfigurationChanged detects if MigrationConfiguration was changed.
82+
func IsMigrationConfigurationChanged(current, changed *virtv1.VirtualMachineInstance) bool {
9083
// true if migrationConfiguration was added.
9184
if current.Status.MigrationState != nil && current.Status.MigrationState.MigrationConfiguration == nil &&
9285
changed.Status.MigrationState != nil && changed.Status.MigrationState.MigrationConfiguration != nil {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package service
2+
3+
import (
4+
"k8s.io/utils/ptr"
5+
6+
"github.com/deckhouse/virtualization-controller/pkg/config"
7+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
8+
)
9+
10+
func AutoConvergeForPolicy(policy v1alpha2.LiveMigrationPolicy) (autoConverge *bool) {
11+
switch policy {
12+
case v1alpha2.AlwaysSafeMigrationPolicy, v1alpha2.PreferSafeMigrationPolicy:
13+
return ptr.To(false)
14+
case v1alpha2.AlwaysForcedMigrationPolicy, v1alpha2.PreferForcedMigrationPolicy:
15+
return ptr.To(true)
16+
}
17+
return nil
18+
}
19+
20+
// CalculateEffectivePolicy merges live migration policy from default value and from VM.
21+
// Also, autoConverge value may be overridden from VMOP.
22+
func CalculateEffectivePolicy(vm v1alpha2.VirtualMachine, vmop *v1alpha2.VirtualMachineOperation) (effectivePolicy v1alpha2.LiveMigrationPolicy, autoConverge bool) {
23+
effectivePolicy = config.DefaultLiveMigrationPolicy
24+
25+
if vm.Spec.LiveMigrationPolicy != "" {
26+
effectivePolicy = vm.Spec.LiveMigrationPolicy
27+
}
28+
29+
autoConvergePtr := AutoConvergeForPolicy(effectivePolicy)
30+
31+
// Override autoConverge value.
32+
if vmop != nil {
33+
switch effectivePolicy {
34+
case v1alpha2.PreferSafeMigrationPolicy,
35+
v1alpha2.PreferForcedMigrationPolicy:
36+
if vmop.Spec.Force != nil {
37+
autoConvergePtr = vmop.Spec.Force
38+
}
39+
}
40+
}
41+
42+
if autoConvergePtr == nil {
43+
// Should not be reached. Disable autoConverge is safe.
44+
autoConverge = false
45+
} else {
46+
autoConverge = *autoConvergePtr
47+
}
48+
49+
return
50+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package internal
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
virtv1 "kubevirt.io/api/core/v1"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
11+
"github.com/deckhouse/virtualization-controller/pkg/common/testutil"
12+
)
13+
14+
func TestHandlers(t *testing.T) {
15+
RegisterFailHandler(Fail)
16+
RunSpecs(t, "Live migration handlers suite")
17+
}
18+
19+
func setupEnvironment(kvvmi *virtv1.VirtualMachineInstance, objs ...client.Object) client.WithWatch {
20+
GinkgoHelper()
21+
Expect(kvvmi).ToNot(BeNil(), "Should set KVVMI for setupEnvironment")
22+
allObjects := []client.Object{kvvmi}
23+
allObjects = append(allObjects, objs...)
24+
25+
fakeClient, err := testutil.NewFakeClientWithObjects(allObjects...)
26+
Expect(err).NotTo(HaveOccurred())
27+
28+
return fakeClient
29+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2025 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 watcher
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
23+
virtv1 "kubevirt.io/api/core/v1"
24+
"sigs.k8s.io/controller-runtime/pkg/controller"
25+
"sigs.k8s.io/controller-runtime/pkg/event"
26+
"sigs.k8s.io/controller-runtime/pkg/handler"
27+
"sigs.k8s.io/controller-runtime/pkg/manager"
28+
"sigs.k8s.io/controller-runtime/pkg/predicate"
29+
"sigs.k8s.io/controller-runtime/pkg/source"
30+
)
31+
32+
func NewKVVMIWatcher() *KVVMIWatcher {
33+
return &KVVMIWatcher{}
34+
}
35+
36+
type KVVMIWatcher struct{}
37+
38+
func (w *KVVMIWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error {
39+
// Subscribe to KVVMI status updates.
40+
if err := ctr.Watch(
41+
source.Kind(mgr.GetCache(), &virtv1.VirtualMachineInstance{}),
42+
&handler.EnqueueRequestForObject{},
43+
predicate.Funcs{
44+
CreateFunc: func(e event.CreateEvent) bool { return false },
45+
DeleteFunc: func(e event.DeleteEvent) bool { return false },
46+
UpdateFunc: func(e event.UpdateEvent) bool {
47+
oldVMI := e.ObjectOld.(*virtv1.VirtualMachineInstance)
48+
newVMI := e.ObjectNew.(*virtv1.VirtualMachineInstance)
49+
return !reflect.DeepEqual(oldVMI.Status, newVMI.Status)
50+
},
51+
},
52+
); err != nil {
53+
return fmt.Errorf("error setting watch on VirtualMachineInstance: %w", err)
54+
}
55+
return nil
56+
}

0 commit comments

Comments
 (0)