• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / kubevirt / 233433ab-b0ba-4540-82e9-5448c4285cf5

26 Jun 2025 02:54PM UTC coverage: 70.492%. Remained the same
233433ab-b0ba-4540-82e9-5448c4285cf5

push

prow

web-flow
Merge pull request #14882 from awels/migration-controller-decentralize

Decentralized live migration core functionality.

408 of 936 new or added lines in 18 files covered. (43.59%)

1917 existing lines in 17 files now uncovered.

66894 of 94896 relevant lines covered (70.49%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.16
/pkg/virt-handler/controller.go
1
/*
2
 * This file is part of the KubeVirt project
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
 * Copyright 2025 The KubeVirt Authors.
17
 *
18
 */
19

20
package virthandler
21

22
import (
23
        "fmt"
24
        "path/filepath"
25

26
        "k8s.io/apimachinery/pkg/types"
27
        "k8s.io/client-go/tools/cache"
28
        "k8s.io/client-go/tools/record"
29

30
        v1 "kubevirt.io/api/core/v1"
31

32
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
33
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
34
        "kubevirt.io/kubevirt/pkg/safepath"
35
        "kubevirt.io/kubevirt/pkg/util"
36
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
37
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
38
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
39
        "kubevirt.io/kubevirt/pkg/virtiofs"
40
)
41

42
const (
43
        failedDetectIsolationFmt              = "failed to detect isolation for launcher pod: %v"
44
        unableCreateVirtLauncherConnectionFmt = "unable to create virt-launcher client connection: %v"
45
        // This value was determined after consulting with libvirt developers and performing extensive testing.
46
        parallelMultifdMigrationThreads = uint(8)
47
)
48

49
const (
50
        //VolumeReadyReason is the reason set when the volume is ready.
51
        VolumeReadyReason = "VolumeReady"
52
        //VolumeUnMountedFromPodReason is the reason set when the volume is unmounted from the virtlauncher pod
53
        VolumeUnMountedFromPodReason = "VolumeUnMountedFromPod"
54
        //VolumeMountedToPodReason is the reason set when the volume is mounted to the virtlauncher pod
55
        VolumeMountedToPodReason = "VolumeMountedToPod"
56
        //VolumeUnplugged is the reason set when the volume is completely unplugged from the VMI
57
        VolumeUnplugged = "VolumeUnplugged"
58
        //VMIDefined is the reason set when a VMI is defined
59
        VMIDefined = "VirtualMachineInstance defined."
60
        //VMIStarted is the reason set when a VMI is started
61
        VMIStarted = "VirtualMachineInstance started."
62
        //VMIShutdown is the reason set when a VMI is shutdown
63
        VMIShutdown = "The VirtualMachineInstance was shut down."
64
        //VMICrashed is the reason set when a VMI crashed
65
        VMICrashed = "The VirtualMachineInstance crashed."
66
        //VMIAbortingMigration is the reason set when migration is being aborted
67
        VMIAbortingMigration = "VirtualMachineInstance is aborting migration."
68
        //VMIMigrating in the reason set when the VMI is migrating
69
        VMIMigrating = "VirtualMachineInstance is migrating."
70
        //VMIMigrationTargetPrepared is the reason set when the migration target has been prepared
71
        VMIMigrationTargetPrepared = "VirtualMachineInstance Migration Target Prepared."
72
        //VMIStopping is the reason set when the VMI is stopping
73
        VMIStopping = "VirtualMachineInstance stopping"
74
        //VMIGracefulShutdown is the reason set when the VMI is gracefully shut down
75
        VMIGracefulShutdown = "Signaled Graceful Shutdown"
76
        //VMISignalDeletion is the reason set when the VMI has signal deletion
77
        VMISignalDeletion = "Signaled Deletion"
78

79
        // MemoryHotplugFailedReason is the reason set when the VM cannot hotplug memory
80
        memoryHotplugFailedReason = "Memory Hotplug Failed"
81
)
82

83
type netconf interface {
84
        Setup(vmi *v1.VirtualMachineInstance, networks []v1.Network, launcherPid int) error
85
        Teardown(vmi *v1.VirtualMachineInstance) error
86
}
87

88
type BaseController struct {
89
        host                 string
90
        vmiStore             cache.Store
91
        domainStore          cache.Store
92
        clusterConfig        *virtconfig.ClusterConfig
93
        podIsolationDetector isolation.PodIsolationDetector
94
        hasSynced            func() bool
95
}
96

97
func NewBaseController(
98
        host string,
99
        vmiInformer cache.SharedIndexInformer,
100
        domainInformer cache.SharedInformer,
101
        clusterConfig *virtconfig.ClusterConfig,
102
        podIsolationDetector isolation.PodIsolationDetector,
103
) (*BaseController, error) {
1✔
104

1✔
105
        c := &BaseController{
1✔
106
                host:                 host,
1✔
107
                vmiStore:             vmiInformer.GetStore(),
1✔
108
                domainStore:          domainInformer.GetStore(),
1✔
109
                clusterConfig:        clusterConfig,
1✔
110
                podIsolationDetector: podIsolationDetector,
1✔
111
                hasSynced:            func() bool { return domainInformer.HasSynced() && vmiInformer.HasSynced() },
1✔
112
        }
113

114
        return c, nil
1✔
115
}
116

117
func (c *BaseController) getVMIFromCache(key string) (vmi *v1.VirtualMachineInstance, exists bool, err error) {
1✔
118
        obj, exists, err := c.vmiStore.GetByKey(key)
1✔
119
        if err != nil {
1✔
120
                return nil, false, err
×
121
        }
×
122

123
        if !exists {
2✔
124
                namespace, name, err := cache.SplitMetaNamespaceKey(key)
1✔
125
                if err != nil {
2✔
126
                        // Invalid keys will be retried forever, but the queue has no reason to contain any
1✔
127
                        return nil, false, err
1✔
128
                }
1✔
129
                vmi = v1.NewVMIReferenceFromNameWithNS(namespace, name)
1✔
130
        } else {
1✔
131
                vmi = obj.(*v1.VirtualMachineInstance)
1✔
132
        }
1✔
133
        return vmi, exists, nil
1✔
134
}
135

136
func (c *BaseController) getDomainFromCache(key string) (domain *api.Domain, exists bool, cachedUID types.UID, err error) {
1✔
137

1✔
138
        obj, exists, err := c.domainStore.GetByKey(key)
1✔
139

1✔
140
        if err != nil {
1✔
141
                return nil, false, "", err
×
142
        }
×
143

144
        if exists {
2✔
145
                domain = obj.(*api.Domain)
1✔
146
                cachedUID = domain.Spec.Metadata.KubeVirt.UID
1✔
147

1✔
148
                // We're using the DeletionTimestamp to signify that the
1✔
149
                // Domain is deleted rather than sending the DELETE watch event.
1✔
150
                if domain.ObjectMeta.DeletionTimestamp != nil {
1✔
151
                        exists = false
×
152
                        domain = nil
×
153
                }
×
154
        }
155
        return domain, exists, cachedUID, nil
1✔
156
}
157

158
func (c *BaseController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
×
159
        return vmi.Status.MigrationState != nil &&
×
160
                vmi.Status.MigrationState.SourceNode == c.host &&
×
NEW
161
                (!vmi.IsDecentralizedMigration() || vmi.IsMigrationSource()) &&
×
162
                vmi.Status.MigrationState.TargetNodeAddress != "" &&
×
163
                !vmi.Status.MigrationState.Completed
×
164
}
×
165

166
func (c *BaseController) claimDeviceOwnership(virtLauncherRootMount *safepath.Path, deviceName string) error {
1✔
167
        softwareEmulation := c.clusterConfig.AllowEmulation()
1✔
168
        devicePath, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", deviceName))
1✔
169
        if err != nil {
2✔
170
                if softwareEmulation && deviceName == "kvm" {
2✔
171
                        return nil
1✔
172
                }
1✔
173
                return err
1✔
174
        }
175

176
        return diskutils.DefaultOwnershipManager.SetFileOwnership(devicePath)
1✔
177
}
178

179
func (c *BaseController) configureHostDisks(
180
        vmi *v1.VirtualMachineInstance,
181
        virtLauncherRootMount *safepath.Path,
182
        recorder record.EventRecorder) error {
1✔
183
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
1✔
184
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
1✔
185

1✔
186
        hostDiskCreator := hostdisk.NewHostDiskCreator(recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
1✔
187
        if err := hostDiskCreator.Create(vmi); err != nil {
1✔
UNCOV
188
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
189
        }
×
190
        return nil
1✔
191
}
192

193
func (c *BaseController) configureSEVDeviceOwnership(vmi *v1.VirtualMachineInstance, virtLauncherRootMount *safepath.Path) error {
1✔
194
        if util.IsSEVVMI(vmi) {
1✔
UNCOV
195
                sevDevice, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", "sev"))
×
196
                if err != nil {
×
197
                        return err
×
198
                }
×
199
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(sevDevice); err != nil {
×
200
                        return fmt.Errorf("failed to set SEV device owner: %v", err)
×
201
                }
×
202
        }
203
        return nil
1✔
204
}
205

206
func (c *BaseController) configureVirtioFS(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
1✔
207
        for _, fs := range vmi.Spec.Domain.Devices.Filesystems {
1✔
UNCOV
208
                socketPath, err := isolation.SafeJoin(isolationRes, virtiofs.VirtioFSSocketPath(fs.Name))
×
209
                if err != nil {
×
210
                        return err
×
211
                }
×
212
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath); err != nil {
×
213
                        return err
×
214
                }
×
215
        }
216
        return nil
1✔
217
}
218

219
func (c *BaseController) setupDevicesOwnerships(vmi *v1.VirtualMachineInstance, recorder record.EventRecorder) error {
1✔
220
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
221
        if err != nil {
1✔
UNCOV
222
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
223
        }
×
224

225
        virtLauncherRootMount, err := isolationRes.MountRoot()
1✔
226
        if err != nil {
1✔
UNCOV
227
                return err
×
228
        }
×
229

230
        err = c.claimDeviceOwnership(virtLauncherRootMount, "kvm")
1✔
231
        if err != nil {
1✔
UNCOV
232
                return fmt.Errorf("failed to set up file ownership for /dev/kvm: %v", err)
×
233
        }
×
234

235
        if util.IsAutoAttachVSOCK(vmi) {
1✔
UNCOV
236
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
237
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
238
                }
×
239
        }
240

241
        if err := c.configureHostDisks(vmi, virtLauncherRootMount, recorder); err != nil {
1✔
UNCOV
242
                return err
×
243
        }
×
244

245
        if err := c.configureSEVDeviceOwnership(vmi, virtLauncherRootMount); err != nil {
1✔
UNCOV
246
                return err
×
247
        }
×
248

249
        if util.IsNonRootVMI(vmi) {
1✔
UNCOV
250
                if err := c.nonRootSetup(vmi); err != nil {
×
251
                        return err
×
252
                }
×
253
        }
254

255
        if err := c.configureVirtioFS(vmi, isolationRes); err != nil {
1✔
UNCOV
256
                return err
×
257
        }
×
258

259
        return nil
1✔
260
}
261

262
func (c *BaseController) setupNetwork(vmi *v1.VirtualMachineInstance, networks []v1.Network, netConf netconf) error {
1✔
263
        if len(networks) == 0 {
2✔
264
                return nil
1✔
265
        }
1✔
266

267
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
268
        if err != nil {
1✔
UNCOV
269
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
270
        }
×
271

272
        return netConf.Setup(vmi, networks, isolationRes.Pid())
1✔
273
}
274

275
func isMigrationInProgress(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
1✔
276
        var domainMigrationMetadata *api.MigrationMetadata
1✔
277
        if vmi != nil &&
1✔
278
                vmi.Status.MigrationState != nil &&
1✔
279
                vmi.Status.MigrationState.StartTimestamp != nil &&
1✔
280
                vmi.Status.MigrationState.EndTimestamp == nil {
2✔
281
                return true
1✔
282
        }
1✔
283

284
        if domain != nil {
2✔
285
                domainMigrationMetadata = domain.Spec.Metadata.KubeVirt.Migration
1✔
286

1✔
287
                if domainMigrationMetadata != nil &&
1✔
288
                        domainMigrationMetadata.StartTimestamp != nil &&
1✔
289
                        domainMigrationMetadata.EndTimestamp == nil {
1✔
290
                        return true
×
291
                }
×
292

293
                if domain.Status.Status == api.Paused &&
1✔
294
                        (domain.Status.Reason == api.ReasonPausedMigration ||
1✔
295
                                domain.Status.Reason == api.ReasonPausedStartingUp ||
1✔
296
                                domain.Status.Reason == api.ReasonPausedPostcopy) {
2✔
297
                        return true
1✔
298
                }
1✔
299
        }
300

301
        if vmi != nil && vmi.IsMigrationTarget() {
1✔
NEW
302
                return vmi.IsMigrationTarget() && !vmi.IsMigrationCompleted()
×
NEW
303
        }
×
304
        return false
1✔
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc