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

kubevirt / kubevirt / 918c2858-31bd-4c0e-ad1a-cde8d851acc7

03 Mar 2026 12:56PM UTC coverage: 71.395% (+0.02%) from 71.38%
918c2858-31bd-4c0e-ad1a-cde8d851acc7

push

prow

web-flow
Merge pull request #16836 from kaizentm/multihypervisor/3-handler

Decouple virt-handler from KVM hypervisor

82 of 356 new or added lines in 14 files covered. (23.03%)

7 existing lines in 4 files now uncovered.

75647 of 105955 relevant lines covered (71.4%)

576.35 hits per line

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

64.84
/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 The KubeVirt Authors.
17
 *
18
 */
19

20
package virthandler
21

22
import (
23
        "errors"
24
        "fmt"
25
        "path/filepath"
26
        "time"
27

28
        "k8s.io/apimachinery/pkg/types"
29
        "k8s.io/client-go/tools/cache"
30
        "k8s.io/client-go/tools/record"
31
        "k8s.io/client-go/util/workqueue"
32

33
        v1 "kubevirt.io/api/core/v1"
34
        "kubevirt.io/client-go/kubecli"
35
        "kubevirt.io/client-go/log"
36

37
        "kubevirt.io/kubevirt/pkg/controller"
38
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
39
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
40
        "kubevirt.io/kubevirt/pkg/hypervisor"
41
        "kubevirt.io/kubevirt/pkg/libvmi"
42
        "kubevirt.io/kubevirt/pkg/safepath"
43
        "kubevirt.io/kubevirt/pkg/util"
44
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
45
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
46
        launcherclients "kubevirt.io/kubevirt/pkg/virt-handler/launcher-clients"
47
        migrationproxy "kubevirt.io/kubevirt/pkg/virt-handler/migration-proxy"
48
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
49
        "kubevirt.io/kubevirt/pkg/virtiofs"
50
)
51

52
const (
53
        failedDetectIsolationFmt              = "failed to detect isolation for launcher pod: %v"
54
        unableCreateVirtLauncherConnectionFmt = "unable to create virt-launcher client connection: %v"
55
        // This value was determined after consulting with libvirt developers and performing extensive testing.
56
        parallelMultifdMigrationThreads = uint(8)
57
)
58

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

89
        // MemoryHotplugFailedReason is the reason set when the VM cannot hotplug memory
90
        memoryHotplugFailedReason = "Memory Hotplug Failed"
91
)
92

93
type netconf interface {
94
        Setup(vmi *v1.VirtualMachineInstance, networks []v1.Network, launcherPid int) error
95
        Teardown(vmi *v1.VirtualMachineInstance) error
96
}
97

98
type BaseController struct {
99
        logger                      *log.FilteredLogger
100
        host                        string
101
        clientset                   kubecli.KubevirtClient
102
        queue                       workqueue.TypedRateLimitingInterface[string]
103
        vmiStore                    cache.Store
104
        domainStore                 cache.Store
105
        clusterConfig               *virtconfig.ClusterConfig
106
        podIsolationDetector        isolation.PodIsolationDetector
107
        launcherClients             launcherclients.LauncherClientsManager
108
        migrationProxy              migrationproxy.ProxyManager
109
        virtLauncherFSRunDirPattern string
110
        netStat                     netstat
111
        recorder                    record.EventRecorder
112
        hasSynced                   func() bool
113
        hypervisorNodeInfo          hypervisor.HypervisorNodeInformation
114
        hypervisorRuntime           hypervisor.VirtRuntime
115
}
116

117
func NewBaseController(
118
        logger *log.FilteredLogger,
119
        host string,
120
        recorder record.EventRecorder,
121
        clientset kubecli.KubevirtClient,
122
        queue workqueue.TypedRateLimitingInterface[string],
123
        vmiInformer cache.SharedIndexInformer,
124
        domainInformer cache.SharedInformer,
125
        clusterConfig *virtconfig.ClusterConfig,
126
        podIsolationDetector isolation.PodIsolationDetector,
127
        launcherClients launcherclients.LauncherClientsManager,
128
        migrationProxy migrationproxy.ProxyManager,
129
        virtLauncherFSRunDirPattern string,
130
        netStat netstat,
131
        hypervisorNodeInfo hypervisor.HypervisorNodeInformation,
132
        hypervisorRuntime hypervisor.VirtRuntime,
133
) (*BaseController, error) {
150✔
134

150✔
135
        c := &BaseController{
150✔
136
                logger:                      logger,
150✔
137
                host:                        host,
150✔
138
                recorder:                    recorder,
150✔
139
                clientset:                   clientset,
150✔
140
                queue:                       queue,
150✔
141
                vmiStore:                    vmiInformer.GetStore(),
150✔
142
                domainStore:                 domainInformer.GetStore(),
150✔
143
                clusterConfig:               clusterConfig,
150✔
144
                podIsolationDetector:        podIsolationDetector,
150✔
145
                launcherClients:             launcherClients,
150✔
146
                migrationProxy:              migrationProxy,
150✔
147
                virtLauncherFSRunDirPattern: virtLauncherFSRunDirPattern,
150✔
148
                netStat:                     netStat,
150✔
149
                hasSynced:                   func() bool { return domainInformer.HasSynced() && vmiInformer.HasSynced() },
150✔
150
                hypervisorNodeInfo:          hypervisorNodeInfo,
151
                hypervisorRuntime:           hypervisorRuntime,
152
        }
153

154
        return c, nil
150✔
155
}
156

157
func (c *BaseController) getVMIFromCache(key string) (vmi *v1.VirtualMachineInstance, exists bool, err error) {
64✔
158
        obj, exists, err := c.vmiStore.GetByKey(key)
64✔
159
        if err != nil {
64✔
160
                return nil, false, err
×
161
        }
×
162

163
        if !exists {
73✔
164
                namespace, name, err := cache.SplitMetaNamespaceKey(key)
9✔
165
                if err != nil {
10✔
166
                        // Invalid keys will be retried forever, but the queue has no reason to contain any
1✔
167
                        return nil, false, err
1✔
168
                }
1✔
169
                vmi = libvmi.New(libvmi.WithName(name), libvmi.WithNamespace(namespace))
8✔
170
        } else {
55✔
171
                vmi = obj.(*v1.VirtualMachineInstance)
55✔
172
        }
55✔
173
        return vmi, exists, nil
63✔
174
}
175

176
func (c *BaseController) getDomainFromCache(key string) (domain *api.Domain, exists bool, cachedUID types.UID, err error) {
62✔
177

62✔
178
        obj, exists, err := c.domainStore.GetByKey(key)
62✔
179

62✔
180
        if err != nil {
62✔
181
                return nil, false, "", err
×
182
        }
×
183

184
        if exists {
106✔
185
                domain = obj.(*api.Domain)
44✔
186
                cachedUID = domain.Spec.Metadata.KubeVirt.UID
44✔
187

44✔
188
                // We're using the DeletionTimestamp to signify that the
44✔
189
                // Domain is deleted rather than sending the DELETE watch event.
44✔
190
                if domain.ObjectMeta.DeletionTimestamp != nil {
44✔
191
                        exists = false
×
192
                        domain = nil
×
193
                }
×
194
        }
195
        return domain, exists, cachedUID, nil
62✔
196
}
197

198
func (c *BaseController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
×
199
        return vmi.Status.MigrationState != nil &&
×
200
                vmi.Status.MigrationState.SourceNode == c.host &&
×
201
                (!vmi.IsDecentralizedMigration() || vmi.IsMigrationSource()) &&
×
202
                vmi.Status.MigrationState.TargetNodeAddress != "" &&
×
203
                !vmi.Status.MigrationState.Completed
×
204
}
×
205

206
func (c *BaseController) claimDeviceOwnership(virtLauncherRootMount *safepath.Path, deviceName string) error {
9✔
207
        softwareEmulation := c.clusterConfig.AllowEmulation()
9✔
208
        devicePath, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", deviceName))
9✔
209
        if err != nil {
13✔
210
                if softwareEmulation && deviceName == c.hypervisorNodeInfo.GetHypervisorDevice() {
5✔
211
                        return nil
1✔
212
                }
1✔
213
                return err
3✔
214
        }
215

216
        return diskutils.DefaultOwnershipManager.SetFileOwnership(devicePath)
5✔
217
}
218

219
func (c *BaseController) configureHostDisks(
220
        vmi *v1.VirtualMachineInstance,
221
        virtLauncherRootMount *safepath.Path,
222
        recorder record.EventRecorder) error {
4✔
223
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
4✔
224
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
4✔
225

4✔
226
        hostDiskCreator := hostdisk.NewHostDiskCreator(recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
4✔
227
        if err := hostDiskCreator.Create(vmi); err != nil {
4✔
228
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
229
        }
×
230
        return nil
4✔
231
}
232

233
func (c *BaseController) configureSEVDeviceOwnership(vmi *v1.VirtualMachineInstance, virtLauncherRootMount *safepath.Path) error {
4✔
234
        if util.IsSEVVMI(vmi) {
4✔
235
                sevDevice, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", "sev"))
×
236
                if err != nil {
×
237
                        return err
×
238
                }
×
239
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(sevDevice); err != nil {
×
240
                        return fmt.Errorf("failed to set SEV device owner: %v", err)
×
241
                }
×
242
        }
243
        return nil
4✔
244
}
245

246
func (c *BaseController) configureVirtioFS(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
4✔
247
        for _, fs := range vmi.Spec.Domain.Devices.Filesystems {
4✔
248
                socketPath, err := isolation.SafeJoin(isolationRes, virtiofs.VirtioFSSocketPath(fs.Name))
×
249
                if err != nil {
×
250
                        return err
×
251
                }
×
252
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath); err != nil {
×
253
                        return err
×
254
                }
×
255
        }
256
        return nil
4✔
257
}
258

259
func (c *BaseController) setupDevicesOwnerships(vmi *v1.VirtualMachineInstance, recorder record.EventRecorder) error {
4✔
260
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
4✔
261
        if err != nil {
4✔
262
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
263
        }
×
264

265
        virtLauncherRootMount, err := isolationRes.MountRoot()
4✔
266
        if err != nil {
4✔
267
                return err
×
268
        }
×
269

270
        hypervisorDevice := c.hypervisorNodeInfo.GetHypervisorDevice()
4✔
271
        if err := c.claimDeviceOwnership(virtLauncherRootMount, hypervisorDevice); err != nil {
4✔
NEW
272
                return fmt.Errorf("failed to set up file ownership for /dev/%s: %v", hypervisorDevice, err)
×
UNCOV
273
        }
×
274

275
        if util.IsAutoAttachVSOCK(vmi) {
4✔
276
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
277
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
278
                }
×
279
        }
280

281
        if err := c.configureHostDisks(vmi, virtLauncherRootMount, recorder); err != nil {
4✔
282
                return err
×
283
        }
×
284

285
        if err := c.configureSEVDeviceOwnership(vmi, virtLauncherRootMount); err != nil {
4✔
286
                return err
×
287
        }
×
288

289
        if util.IsNonRootVMI(vmi) {
4✔
290
                if err := c.nonRootSetup(vmi); err != nil {
×
291
                        return err
×
292
                }
×
293
        }
294

295
        if err := c.configureVirtioFS(vmi, isolationRes); err != nil {
4✔
296
                return err
×
297
        }
×
298

299
        return nil
4✔
300
}
301

302
func (c *BaseController) setupNetwork(vmi *v1.VirtualMachineInstance, networks []v1.Network, netConf netconf) error {
19✔
303
        if len(networks) == 0 {
37✔
304
                return nil
18✔
305
        }
18✔
306

307
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
308
        if err != nil {
1✔
309
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
310
        }
×
311

312
        return netConf.Setup(vmi, networks, isolationRes.Pid())
1✔
313
}
314

315
func isMigrationInProgress(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
51✔
316
        var domainMigrationMetadata *api.MigrationMetadata
51✔
317
        if vmi != nil &&
51✔
318
                vmi.Status.MigrationState != nil &&
51✔
319
                vmi.Status.MigrationState.StartTimestamp != nil &&
51✔
320
                vmi.Status.MigrationState.EndTimestamp == nil {
52✔
321
                return true
1✔
322
        }
1✔
323

324
        if domain != nil {
86✔
325
                domainMigrationMetadata = domain.Spec.Metadata.KubeVirt.Migration
36✔
326

36✔
327
                if domainMigrationMetadata != nil &&
36✔
328
                        domainMigrationMetadata.StartTimestamp != nil &&
36✔
329
                        domainMigrationMetadata.EndTimestamp == nil {
36✔
330
                        return true
×
331
                }
×
332

333
                if domain.Status.Status == api.Paused &&
36✔
334
                        (domain.Status.Reason == api.ReasonPausedMigration ||
36✔
335
                                domain.Status.Reason == api.ReasonPausedStartingUp ||
36✔
336
                                domain.Status.Reason == api.ReasonPausedPostcopy) {
37✔
337
                        return true
1✔
338
                }
1✔
339
        }
340

341
        if vmi != nil && vmi.IsMigrationTarget() {
49✔
342
                return vmi.IsMigrationTarget() && !vmi.IsMigrationCompleted()
×
343
        }
×
344
        return false
49✔
345
}
346

347
func (c *BaseController) passtSocketDirOnHostForVMI(vmi *v1.VirtualMachineInstance) (string, error) {
×
348
        path, err := c.podIsolationDetector.Detect(vmi)
×
349
        if err != nil {
×
350
                return "", err
×
351
        }
×
352
        return passtSocketDirOnHost(path)
×
353
}
354

355
func (c *BaseController) checkLauncherClient(vmi *v1.VirtualMachineInstance) (bool, error) {
33✔
356
        isUnresponsive, isInitialized, err := c.launcherClients.IsLauncherClientUnresponsive(vmi)
33✔
357
        if err != nil {
33✔
358
                return true, err
×
359
        }
×
360
        if !isInitialized {
34✔
361
                c.logger.Object(vmi).V(4).Info("launcher client is not initialized")
1✔
362
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
363
                return true, nil
1✔
364
        } else if isUnresponsive {
33✔
365
                return true, errors.New(fmt.Sprintf("Can not update a VirtualMachineInstance with unresponsive command server."))
×
366
        }
×
367

368
        return false, nil
32✔
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc