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

kubevirt / kubevirt / 53be44c1-7ddc-4fc1-91f2-d2b3c0b5befa

07 Jun 2026 12:56PM UTC coverage: 71.747% (+0.04%) from 71.712%
53be44c1-7ddc-4fc1-91f2-d2b3c0b5befa

push

prow

web-flow
Merge pull request #17790 from iholder101/worktree-vep-190-pr1-plugin-crd

VEP 190: Plugin CRD, feature gate, RBAC, and admission webhook

384 of 479 new or added lines in 19 files covered. (80.17%)

5 existing lines in 1 file now uncovered.

79354 of 110603 relevant lines covered (71.75%)

550.14 hits per line

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

59.01
/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
        "encoding/json"
24
        "errors"
25
        "fmt"
26
        "path/filepath"
27
        "sync"
28
        "time"
29

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

35
        v1 "kubevirt.io/api/core/v1"
36
        pluginv1alpha1 "kubevirt.io/api/plugin/v1alpha1"
37
        "kubevirt.io/client-go/kubecli"
38
        "kubevirt.io/client-go/log"
39

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

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

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

93
        // MemoryHotplugFailedReason is the reason set when the VM cannot hotplug memory
94
        memoryHotplugFailedReason = "Memory Hotplug Failed"
95
)
96

97
type netconf interface {
98
        Setup(vmi *v1.VirtualMachineInstance, networks []v1.Network, launcherPid int) error
99
        Teardown(vmi *v1.VirtualMachineInstance) error
100
}
101

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

123
func NewBaseController(
124
        logger *log.FilteredLogger,
125
        host string,
126
        recorder record.EventRecorder,
127
        clientset kubecli.KubevirtClient,
128
        queue workqueue.TypedRateLimitingInterface[string],
129
        vmiInformer cache.SharedIndexInformer,
130
        domainInformer cache.SharedInformer,
131
        clusterConfig *virtconfig.ClusterConfig,
132
        podIsolationDetector isolation.PodIsolationDetector,
133
        launcherClients launcherclients.LauncherClientsManager,
134
        migrationProxy migrationproxy.ProxyManager,
135
        virtLauncherFSRunDirPattern string,
136
        netStat netstat,
137
        hypervisorNodeInfo hypervisor.HypervisorNodeInformation,
138
        hypervisorRuntime hypervisor.VirtRuntime,
139
        pluginStore cache.Store,
140
) (*BaseController, error) {
168✔
141

168✔
142
        c := &BaseController{
168✔
143
                logger:                      logger,
168✔
144
                host:                        host,
168✔
145
                recorder:                    recorder,
168✔
146
                clientset:                   clientset,
168✔
147
                queue:                       queue,
168✔
148
                vmiStore:                    vmiInformer.GetStore(),
168✔
149
                domainStore:                 domainInformer.GetStore(),
168✔
150
                clusterConfig:               clusterConfig,
168✔
151
                podIsolationDetector:        podIsolationDetector,
168✔
152
                launcherClients:             launcherClients,
168✔
153
                migrationProxy:              migrationProxy,
168✔
154
                virtLauncherFSRunDirPattern: virtLauncherFSRunDirPattern,
168✔
155
                netStat:                     netStat,
168✔
156
                hasSynced:                   func() bool { return domainInformer.HasSynced() && vmiInformer.HasSynced() },
168✔
157
                hypervisorNodeInfo:          hypervisorNodeInfo,
158
                hypervisorRuntime:           hypervisorRuntime,
159
                pluginStore:                 pluginStore,
160
        }
161

162
        return c, nil
168✔
163
}
164

165
type pluginCache struct {
166
        mu   sync.Mutex
167
        data []byte
168
}
169

170
func (c *BaseController) serializePlugins() ([]byte, error) {
20✔
171
        c.pluginCache.mu.Lock()
20✔
172
        defer c.pluginCache.mu.Unlock()
20✔
173

20✔
174
        if c.pluginCache.data != nil {
20✔
NEW
175
                return c.pluginCache.data, nil
×
NEW
176
        }
×
177

178
        data, err := c.buildPluginsJSON()
20✔
179
        if err != nil {
20✔
NEW
180
                return nil, err
×
NEW
181
        }
×
182

183
        c.pluginCache.data = data
20✔
184
        return data, nil
20✔
185
}
186

187
func (c *BaseController) buildPluginsJSON() ([]byte, error) {
20✔
188
        if c.pluginStore == nil || !c.clusterConfig.PluginsEnabled() {
40✔
189
                return nil, nil
20✔
190
        }
20✔
NEW
191
        objs := c.pluginStore.List()
×
NEW
192
        if len(objs) == 0 {
×
NEW
193
                return nil, nil
×
NEW
194
        }
×
NEW
195
        var pluginList []pluginv1alpha1.Plugin
×
NEW
196
        for _, obj := range objs {
×
NEW
197
                if p, ok := obj.(*pluginv1alpha1.Plugin); ok {
×
NEW
198
                        pluginList = append(pluginList, *p)
×
NEW
199
                }
×
200
        }
NEW
201
        if len(pluginList) == 0 {
×
NEW
202
                return nil, nil
×
NEW
203
        }
×
NEW
204
        data, err := json.Marshal(pluginList)
×
NEW
205
        if err != nil {
×
NEW
206
                return nil, fmt.Errorf("failed to serialize plugins: %w", err)
×
NEW
207
        }
×
NEW
208
        return data, nil
×
209
}
210

NEW
211
func (c *BaseController) InvalidatePluginsCache() {
×
NEW
212
        c.pluginCache.mu.Lock()
×
NEW
213
        defer c.pluginCache.mu.Unlock()
×
NEW
214

×
NEW
215
        c.pluginCache.data = nil
×
NEW
216
}
×
217

218
func (c *BaseController) getVMIFromCache(key string) (vmi *v1.VirtualMachineInstance, exists bool, err error) {
114✔
219
        obj, exists, err := c.vmiStore.GetByKey(key)
114✔
220
        if err != nil {
114✔
221
                return nil, false, err
×
222
        }
×
223

224
        if !exists {
131✔
225
                namespace, name, err := cache.SplitMetaNamespaceKey(key)
17✔
226
                if err != nil {
18✔
227
                        // Invalid keys will be retried forever, but the queue has no reason to contain any
1✔
228
                        return nil, false, err
1✔
229
                }
1✔
230
                vmi = libvmi.New(libvmi.WithName(name), libvmi.WithNamespace(namespace))
16✔
231
        } else {
97✔
232
                vmi = obj.(*v1.VirtualMachineInstance)
97✔
233
        }
97✔
234
        return vmi, exists, nil
113✔
235
}
236

237
func (c *BaseController) getDomainFromCache(key string) (domain *api.Domain, exists bool, cachedUID types.UID, err error) {
66✔
238

66✔
239
        obj, exists, err := c.domainStore.GetByKey(key)
66✔
240

66✔
241
        if err != nil {
66✔
242
                return nil, false, "", err
×
243
        }
×
244

245
        if exists {
112✔
246
                domain = obj.(*api.Domain)
46✔
247
                cachedUID = domain.Spec.Metadata.KubeVirt.UID
46✔
248

46✔
249
                // We're using the DeletionTimestamp to signify that the
46✔
250
                // Domain is deleted rather than sending the DELETE watch event.
46✔
251
                if domain.ObjectMeta.DeletionTimestamp != nil {
46✔
252
                        exists = false
×
253
                        domain = nil
×
254
                }
×
255
        }
256
        return domain, exists, cachedUID, nil
66✔
257
}
258

259
func (c *BaseController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
×
260
        return vmi.Status.MigrationState != nil &&
×
261
                vmi.Status.MigrationState.SourceNode == c.host &&
×
262
                (!vmi.IsDecentralizedMigration() || vmi.IsMigrationSource()) &&
×
263
                vmi.Status.MigrationState.TargetNodeAddress != "" &&
×
264
                !vmi.Status.MigrationState.Completed
×
265
}
×
266

267
func (c *BaseController) claimDeviceOwnership(virtLauncherRootMount *safepath.Path, deviceName string) error {
9✔
268
        softwareEmulation := c.clusterConfig.AllowEmulation()
9✔
269
        devicePath, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", deviceName))
9✔
270
        if err != nil {
13✔
271
                if softwareEmulation && deviceName == c.hypervisorNodeInfo.GetHypervisorDevice() {
5✔
272
                        return nil
1✔
273
                }
1✔
274
                return err
3✔
275
        }
276

277
        return diskutils.DefaultOwnershipManager.SetFileOwnership(devicePath)
5✔
278
}
279

280
func (c *BaseController) configureHostDisks(
281
        vmi *v1.VirtualMachineInstance,
282
        virtLauncherRootMount *safepath.Path,
283
        recorder record.EventRecorder) error {
4✔
284
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
4✔
285
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
4✔
286

4✔
287
        hostDiskCreator := hostdisk.NewHostDiskCreator(recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
4✔
288
        if err := hostDiskCreator.Create(vmi); err != nil {
4✔
289
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
290
        }
×
291
        return nil
4✔
292
}
293

294
func (c *BaseController) configureSEVDeviceOwnership(vmi *v1.VirtualMachineInstance, virtLauncherRootMount *safepath.Path) error {
4✔
295
        if util.IsSEVVMI(vmi) {
4✔
296
                sevDevice, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", "sev"))
×
297
                if err != nil {
×
298
                        return err
×
299
                }
×
300
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(sevDevice); err != nil {
×
301
                        return fmt.Errorf("failed to set SEV device owner: %v", err)
×
302
                }
×
303
        }
304
        return nil
4✔
305
}
306

307
func (c *BaseController) configureVirtioFS(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
4✔
308
        for _, fs := range vmi.Spec.Domain.Devices.Filesystems {
4✔
309
                socketPath, err := isolation.SafeJoin(isolationRes, virtiofs.VirtioFSSocketPath(fs.Name))
×
310
                if err != nil {
×
311
                        return err
×
312
                }
×
313
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath); err != nil {
×
314
                        return err
×
315
                }
×
316
        }
317
        return nil
4✔
318
}
319

320
func (c *BaseController) setupDevicesOwnerships(vmi *v1.VirtualMachineInstance, recorder record.EventRecorder) error {
4✔
321
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
4✔
322
        if err != nil {
4✔
323
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
324
        }
×
325

326
        virtLauncherRootMount, err := isolationRes.MountRoot()
4✔
327
        if err != nil {
4✔
328
                return err
×
329
        }
×
330

331
        hypervisorDevice := c.hypervisorNodeInfo.GetHypervisorDevice()
4✔
332
        if err := c.claimDeviceOwnership(virtLauncherRootMount, hypervisorDevice); err != nil {
4✔
333
                return fmt.Errorf("failed to set up file ownership for /dev/%s: %v", hypervisorDevice, err)
×
334
        }
×
335

336
        if util.IsAutoAttachVSOCK(vmi) {
4✔
337
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
338
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
339
                }
×
340
        }
341

342
        if err := c.configureHostDisks(vmi, virtLauncherRootMount, recorder); err != nil {
4✔
343
                return err
×
344
        }
×
345

346
        if err := c.configureSEVDeviceOwnership(vmi, virtLauncherRootMount); err != nil {
4✔
347
                return err
×
348
        }
×
349

350
        if vmitrait.IsNonRoot(vmi) {
4✔
351
                if err := c.nonRootSetup(vmi); err != nil {
×
352
                        return err
×
353
                }
×
354
        }
355

356
        if err := c.configureVirtioFS(vmi, isolationRes); err != nil {
4✔
357
                return err
×
358
        }
×
359

360
        return nil
4✔
361
}
362

363
func (c *BaseController) setupNetwork(vmi *v1.VirtualMachineInstance, networks []v1.Network, netConf netconf) error {
21✔
364
        if len(networks) == 0 {
41✔
365
                return nil
20✔
366
        }
20✔
367

368
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
369
        if err != nil {
1✔
370
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
371
        }
×
372

373
        return netConf.Setup(vmi, networks, isolationRes.Pid())
1✔
374
}
375

376
func isMigrationInProgress(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
52✔
377
        var domainMigrationMetadata *api.MigrationMetadata
52✔
378
        if vmi != nil &&
52✔
379
                vmi.Status.MigrationState != nil &&
52✔
380
                vmi.Status.MigrationState.StartTimestamp != nil &&
52✔
381
                vmi.Status.MigrationState.EndTimestamp == nil {
53✔
382
                return true
1✔
383
        }
1✔
384

385
        if domain != nil {
88✔
386
                domainMigrationMetadata = domain.Spec.Metadata.KubeVirt.Migration
37✔
387

37✔
388
                if domainMigrationMetadata != nil &&
37✔
389
                        domainMigrationMetadata.StartTimestamp != nil &&
37✔
390
                        domainMigrationMetadata.EndTimestamp == nil {
37✔
391
                        return true
×
392
                }
×
393

394
                if domain.Status.Status == api.Paused &&
37✔
395
                        (domain.Status.Reason == api.ReasonPausedMigration ||
37✔
396
                                domain.Status.Reason == api.ReasonPausedStartingUp ||
37✔
397
                                domain.Status.Reason == api.ReasonPausedPostcopy) {
38✔
398
                        return true
1✔
399
                }
1✔
400
        }
401

402
        if vmi != nil && vmi.IsMigrationTarget() {
50✔
403
                return vmi.IsMigrationTarget() && !vmi.IsMigrationCompleted()
×
404
        }
×
405
        return false
50✔
406
}
407

408
func (c *BaseController) passtSocketDirOnHostForVMI(vmi *v1.VirtualMachineInstance) (string, error) {
×
409
        path, err := c.podIsolationDetector.Detect(vmi)
×
410
        if err != nil {
×
411
                return "", err
×
412
        }
×
413
        return passtSocketDirOnHost(path)
×
414
}
415

416
func (c *BaseController) checkLauncherClient(vmi *v1.VirtualMachineInstance) (bool, error) {
34✔
417
        isUnresponsive, isInitialized, err := c.launcherClients.IsLauncherClientUnresponsive(vmi)
34✔
418
        if err != nil {
34✔
419
                return true, err
×
420
        }
×
421
        if !isInitialized {
35✔
422
                c.logger.Object(vmi).V(4).Info("launcher client is not initialized")
1✔
423
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
424
                return true, nil
1✔
425
        } else if isUnresponsive {
34✔
426
                return true, errors.New(fmt.Sprintf("Can not update a VirtualMachineInstance with unresponsive command server."))
×
427
        }
×
428

429
        return false, nil
33✔
430
}
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