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

kubevirt / kubevirt / 71566407-af62-4e37-9e87-5d67388e004d

24 Jun 2026 07:30PM UTC coverage: 71.962% (-0.007%) from 71.969%
71566407-af62-4e37-9e87-5d67388e004d

push

prow

web-flow
Merge pull request #18032 from 0xFelix/oci-export-phase2

Add OCI export support for VirtualMachineTemplates

432 of 589 new or added lines in 10 files covered. (73.34%)

669 existing lines in 8 files now uncovered.

81649 of 113462 relevant lines covered (71.96%)

468.97 hits per line

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

71.78
/pkg/virt-handler/vm.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
        "bytes"
24
        "context"
25
        "encoding/json"
26
        goerror "errors"
27
        "fmt"
28
        "os"
29
        "path/filepath"
30
        "regexp"
31
        "sort"
32
        "strings"
33
        "time"
34

35
        "github.com/opencontainers/cgroups"
36
        "libvirt.org/go/libvirtxml"
37

38
        k8sv1 "k8s.io/api/core/v1"
39
        "k8s.io/apimachinery/pkg/api/equality"
40
        "k8s.io/apimachinery/pkg/api/resource"
41
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
42
        "k8s.io/apimachinery/pkg/util/errors"
43
        "k8s.io/apimachinery/pkg/util/wait"
44
        "k8s.io/client-go/tools/cache"
45
        "k8s.io/client-go/tools/record"
46
        "k8s.io/client-go/util/workqueue"
47

48
        backupv1 "kubevirt.io/api/backup/v1alpha1"
49
        v1 "kubevirt.io/api/core/v1"
50
        "kubevirt.io/client-go/kubecli"
51
        "kubevirt.io/client-go/log"
52

53
        "kubevirt.io/kubevirt/pkg/config"
54
        "kubevirt.io/kubevirt/pkg/controller"
55
        "kubevirt.io/kubevirt/pkg/executor"
56
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
57
        hotplugdisk "kubevirt.io/kubevirt/pkg/hotplug-disk"
58
        "kubevirt.io/kubevirt/pkg/hypervisor"
59
        metrics "kubevirt.io/kubevirt/pkg/monitoring/metrics/common/vmisync"
60
        vhmetrics "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-handler"
61
        "kubevirt.io/kubevirt/pkg/network/domainspec"
62
        neterrors "kubevirt.io/kubevirt/pkg/network/errors"
63
        netsetup "kubevirt.io/kubevirt/pkg/network/setup"
64
        netvmispec "kubevirt.io/kubevirt/pkg/network/vmispec"
65

66
        pluginv1alpha1 "kubevirt.io/api/plugin/v1alpha1"
67

68
        "kubevirt.io/kubevirt/pkg/safepath"
69
        "kubevirt.io/kubevirt/pkg/storage/reservation"
70
        storagetypes "kubevirt.io/kubevirt/pkg/storage/types"
71
        "kubevirt.io/kubevirt/pkg/util"
72
        "kubevirt.io/kubevirt/pkg/util/migrations"
73
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
74
        "kubevirt.io/kubevirt/pkg/virt-config/featuregate"
75
        "kubevirt.io/kubevirt/pkg/virt-controller/watch/topology"
76
        virtcache "kubevirt.io/kubevirt/pkg/virt-handler/cache"
77
        "kubevirt.io/kubevirt/pkg/virt-handler/cgroup"
78
        cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
79
        containerdisk "kubevirt.io/kubevirt/pkg/virt-handler/container-disk"
80
        deviceManager "kubevirt.io/kubevirt/pkg/virt-handler/device-manager"
81
        "kubevirt.io/kubevirt/pkg/virt-handler/heartbeat"
82
        hotplugvolume "kubevirt.io/kubevirt/pkg/virt-handler/hotplug-disk"
83
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
84
        launcherclients "kubevirt.io/kubevirt/pkg/virt-handler/launcher-clients"
85
        migrationproxy "kubevirt.io/kubevirt/pkg/virt-handler/migration-proxy"
86
        multipathmonitor "kubevirt.io/kubevirt/pkg/virt-handler/multipath-monitor"
87
        "kubevirt.io/kubevirt/pkg/virt-handler/plugins"
88
        "kubevirt.io/kubevirt/pkg/virt-handler/selinux"
89
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
90
)
91

92
type netstat interface {
93
        UpdateStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) error
94
        Teardown(vmi *v1.VirtualMachineInstance)
95
}
96

97
type downwardMetricsManager interface {
98
        Run(stopCh chan struct{})
99
        StartServer(vmi *v1.VirtualMachineInstance, pid int) error
100
        StopServer(vmi *v1.VirtualMachineInstance)
101
}
102

103
type VirtualMachineController struct {
104
        *BaseController
105
        capabilities             *libvirtxml.Caps
106
        clientset                kubecli.KubevirtClient
107
        containerDiskMounter     containerdisk.Mounter
108
        downwardMetricsManager   downwardMetricsManager
109
        hotplugVolumeMounter     hotplugvolume.VolumeMounter
110
        hostCpuModel             string
111
        ioErrorRetryManager      *FailRetryManager
112
        pluginExecutor           plugins.NodeHookExecutor
113
        deviceManagerController  *deviceManager.DeviceController
114
        heartBeat                *heartbeat.HeartBeat
115
        heartBeatInterval        time.Duration
116
        netConf                  netconf
117
        sriovHotplugExecutorPool *executor.RateLimitedExecutorPool
118
        vmiExpectations          *controller.UIDTrackingControllerExpectations
119
        vmiGlobalStore           cache.Store
120
        multipathSocketMonitor   *multipathmonitor.MultipathSocketMonitor
121
        cbtHandler               *CBTHandler
122
}
123

UNCOV
124
var getCgroupManager = func(vmi *v1.VirtualMachineInstance, host string, hypervisorNodeInfo hypervisor.HypervisorNodeInformation, allowEmulation bool) (cgroup.Manager, error) {
×
UNCOV
125
        return cgroup.NewManagerFromVM(vmi, host, hypervisorNodeInfo.GetHypervisorDevice(), allowEmulation)
×
UNCOV
126
}
×
127

128
func NewVirtualMachineController(
129
        recorder record.EventRecorder,
130
        clientset kubecli.KubevirtClient,
131
        nodeStore cache.Store,
132
        host string,
133
        virtPrivateDir string,
134
        kubeletPodsDir string,
135
        launcherClients launcherclients.LauncherClientsManager,
136
        vmiInformer cache.SharedIndexInformer,
137
        vmiGlobalStore cache.Store,
138
        domainInformer cache.SharedInformer,
139
        maxDevices int,
140
        clusterConfig *virtconfig.ClusterConfig,
141
        podIsolationDetector isolation.PodIsolationDetector,
142
        migrationProxy migrationproxy.ProxyManager,
143
        downwardMetricsManager downwardMetricsManager,
144
        capabilities *libvirtxml.Caps,
145
        hostCpuModel string,
146
        netConf netconf,
147
        netStat netstat,
148
        cbtHandler *CBTHandler,
149
        pluginStore cache.Store,
150
        pluginExecutor plugins.NodeHookExecutor,
151
) (*VirtualMachineController, error) {
131✔
152

131✔
153
        queue := workqueue.NewTypedRateLimitingQueueWithConfig[string](
131✔
154
                workqueue.DefaultTypedControllerRateLimiter[string](),
131✔
155
                workqueue.TypedRateLimitingQueueConfig[string]{Name: "virt-handler-vm"},
131✔
156
        )
131✔
157
        logger := log.Log.With("controller", "vm")
131✔
158

131✔
159
        hypervisorName := clusterConfig.GetHypervisor().Name
131✔
160

131✔
161
        baseCtrl, err := NewBaseController(
131✔
162
                logger,
131✔
163
                host,
131✔
164
                recorder,
131✔
165
                clientset,
131✔
166
                queue,
131✔
167
                vmiInformer,
131✔
168
                domainInformer,
131✔
169
                clusterConfig,
131✔
170
                podIsolationDetector,
131✔
171
                launcherClients,
131✔
172
                migrationProxy,
131✔
173
                "/proc/%d/root/var/run",
131✔
174
                netStat,
131✔
175
                hypervisor.NewHypervisorNodeInformation(hypervisorName),
131✔
176
                hypervisor.GetVirtRuntime(podIsolationDetector, hypervisorName),
131✔
177
                pluginStore,
131✔
178
        )
131✔
179
        if err != nil {
131✔
180
                return nil, err
×
UNCOV
181
        }
×
182

183
        containerDiskState := filepath.Join(virtPrivateDir, "container-disk-mount-state")
131✔
184
        if err := os.MkdirAll(containerDiskState, 0700); err != nil {
131✔
185
                return nil, err
×
UNCOV
186
        }
×
187

188
        hotplugState := filepath.Join(virtPrivateDir, "hotplug-volume-mount-state")
131✔
189
        if err := os.MkdirAll(hotplugState, 0700); err != nil {
131✔
UNCOV
190
                return nil, err
×
UNCOV
191
        }
×
192

193
        c := &VirtualMachineController{
131✔
194
                BaseController:           baseCtrl,
131✔
195
                capabilities:             capabilities,
131✔
196
                clientset:                clientset,
131✔
197
                containerDiskMounter:     containerdisk.NewMounter(podIsolationDetector, containerDiskState, clusterConfig),
131✔
198
                downwardMetricsManager:   downwardMetricsManager,
131✔
199
                hotplugVolumeMounter:     hotplugvolume.NewVolumeMounter(hotplugState, kubeletPodsDir, host),
131✔
200
                hostCpuModel:             hostCpuModel,
131✔
201
                ioErrorRetryManager:      NewFailRetryManager("io-error-retry", 10*time.Second, 3*time.Minute, 30*time.Second),
131✔
202
                heartBeatInterval:        1 * time.Minute,
131✔
203
                netConf:                  netConf,
131✔
204
                sriovHotplugExecutorPool: executor.NewRateLimitedExecutorPool(executor.NewExponentialLimitedBackoffCreator()),
131✔
205
                vmiExpectations:          controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()),
131✔
206
                vmiGlobalStore:           vmiGlobalStore,
131✔
207
                multipathSocketMonitor:   multipathmonitor.NewMultipathSocketMonitor(),
131✔
208
                cbtHandler:               cbtHandler,
131✔
209
                pluginExecutor:           pluginExecutor,
131✔
210
        }
131✔
211

131✔
212
        _, err = vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
131✔
213
                AddFunc:    c.addDeleteFunc,
131✔
214
                DeleteFunc: c.addDeleteFunc,
131✔
215
                UpdateFunc: c.updateFunc,
131✔
216
        })
131✔
217
        if err != nil {
131✔
UNCOV
218
                return nil, err
×
UNCOV
219
        }
×
220

221
        _, err = domainInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
131✔
222
                AddFunc:    c.addDomainFunc,
131✔
223
                DeleteFunc: c.deleteDomainFunc,
131✔
224
                UpdateFunc: c.updateDomainFunc,
131✔
225
        })
131✔
226
        if err != nil {
131✔
UNCOV
227
                return nil, err
×
UNCOV
228
        }
×
229

230
        permissions := "rw"
131✔
231
        if cgroups.IsCgroup2UnifiedMode() {
262✔
232
                // Need 'rwm' permissions otherwise ebpf filtering program attached by runc
131✔
233
                // will deny probing the device file with 'access' syscall. That in turn
131✔
234
                // will lead to virtqemud failure on VM startup.
131✔
235
                // This has been fixed upstream:
131✔
236
                //   https://github.com/opencontainers/runc/pull/2796
131✔
237
                // but the workaround is still needed to support previous versions without
131✔
238
                // the patch.
131✔
239
                permissions = "rwm"
131✔
240
        }
131✔
241

242
        c.deviceManagerController = deviceManager.NewDeviceController(
131✔
243
                c.host,
131✔
244
                maxDevices,
131✔
245
                permissions,
131✔
246
                deviceManager.PermanentHostDevicePlugins(c.hypervisorNodeInfo.GetHypervisorDevice(), maxDevices, permissions),
131✔
247
                clusterConfig,
131✔
248
                nodeStore)
131✔
249
        c.heartBeat = heartbeat.NewHeartBeat(clientset.CoreV1(), c.deviceManagerController, clusterConfig, host)
131✔
250

131✔
251
        return c, nil
131✔
252
}
253

254
func (c *VirtualMachineController) Run(threadiness int, stopCh chan struct{}) {
×
255
        defer c.queue.ShutDown()
×
256
        c.logger.Info("Starting virt-handler vms controller.")
×
257

×
258
        go c.deviceManagerController.Run(stopCh)
×
259

×
260
        go c.downwardMetricsManager.Run(stopCh)
×
261

×
262
        cache.WaitForCacheSync(stopCh, c.hasSynced)
×
263

×
264
        // queue keys for previous Domains on the host that no longer exist
×
265
        // in the cache. This ensures we perform local cleanup of deleted VMs.
×
266
        for _, domain := range c.domainStore.List() {
×
267
                d := domain.(*api.Domain)
×
268
                vmiRef := v1.NewVMIReferenceWithUUID(
×
269
                        d.ObjectMeta.Namespace,
×
270
                        d.ObjectMeta.Name,
×
271
                        d.Spec.Metadata.KubeVirt.UID)
×
UNCOV
272

×
273
                key := controller.VirtualMachineInstanceKey(vmiRef)
×
274

×
275
                _, exists, _ := c.vmiStore.GetByKey(key)
×
276
                if !exists {
×
277
                        c.queue.Add(key)
×
278
                }
×
279
        }
280
        c.multipathSocketMonitor.Run()
×
281

×
282
        heartBeatDone := c.heartBeat.Run(c.heartBeatInterval, stopCh)
×
UNCOV
283

×
284
        go c.ioErrorRetryManager.Run(stopCh)
×
285

×
286
        // Start the actual work
×
287
        for i := 0; i < threadiness; i++ {
×
UNCOV
288
                go wait.Until(c.runWorker, time.Second, stopCh)
×
UNCOV
289
        }
×
290

291
        <-heartBeatDone
×
292
        <-stopCh
×
UNCOV
293
        c.multipathSocketMonitor.Close()
×
UNCOV
294
        c.logger.Info("Stopping virt-handler vms controller.")
×
295
}
296

UNCOV
297
func (c *VirtualMachineController) runWorker() {
×
298
        for c.Execute() {
×
299
        }
×
300
}
301

302
func (c *VirtualMachineController) Execute() bool {
51✔
303
        key, quit := c.queue.Get()
51✔
304
        if quit {
51✔
UNCOV
305
                return false
×
UNCOV
306
        }
×
307
        defer c.queue.Done(key)
51✔
308
        if err := c.execute(key); err != nil {
57✔
309
                c.logger.Reason(err).Infof("re-enqueuing VirtualMachineInstance %v", key)
6✔
310
                c.queue.AddRateLimited(key)
6✔
311
        } else {
51✔
312
                c.logger.V(4).Infof("processed VirtualMachineInstance %v", key)
45✔
313
                c.queue.Forget(key)
45✔
314
        }
45✔
315
        return true
51✔
316
}
317

318
func (c *VirtualMachineController) execute(key string) error {
51✔
319
        vmi, vmiExists, err := c.getVMIFromCache(key)
51✔
320
        if err != nil {
52✔
321
                return err
1✔
322
        }
1✔
323

324
        if !vmiExists {
58✔
325
                // the vmiInformer probably has to catch up to the domainInformer
8✔
326
                // which already sees the vmi, so let's fetch it from the global
8✔
327
                // vmi informer to make sure the vmi has actually been deleted
8✔
328
                c.logger.V(4).Infof("fetching vmi for key %v from the global informer", key)
8✔
329
                obj, exists, err := c.vmiGlobalStore.GetByKey(key)
8✔
330
                if err != nil {
8✔
UNCOV
331
                        return err
×
UNCOV
332
                }
×
333
                if exists {
8✔
UNCOV
334
                        vmi = obj.(*v1.VirtualMachineInstance)
×
335
                }
×
336
                vmiExists = exists
8✔
337
        }
338

339
        if !vmiExists {
58✔
340
                c.vmiExpectations.DeleteExpectations(key)
8✔
341
        } else if !c.vmiExpectations.SatisfiedExpectations(key) {
50✔
UNCOV
342
                return nil
×
UNCOV
343
        }
×
344

345
        domain, domainExists, domainCachedUID, err := c.getDomainFromCache(key)
50✔
346
        if err != nil {
50✔
UNCOV
347
                return err
×
UNCOV
348
        }
×
349
        c.logger.Object(vmi).V(4).Infof("domain exists %v", domainExists)
50✔
350

50✔
351
        if !vmiExists && string(domainCachedUID) != "" {
58✔
352
                // it's possible to discover the UID from cache even if the domain
8✔
353
                // doesn't technically exist anymore
8✔
354
                vmi.UID = domainCachedUID
8✔
355
                c.logger.Object(vmi).Infof("Using cached UID for vmi found in domain cache")
8✔
356
        }
8✔
357

358
        // As a last effort, if the UID still can't be determined attempt
359
        // to retrieve it from the ghost record
360
        if string(vmi.UID) == "" {
51✔
361
                uid := virtcache.GhostRecordGlobalStore.LastKnownUID(key)
1✔
362
                if uid != "" {
1✔
UNCOV
363
                        c.logger.Object(vmi).V(3).Infof("ghost record cache provided %s as UID", uid)
×
UNCOV
364
                        vmi.UID = uid
×
UNCOV
365
                }
×
366
        }
367

368
        if vmiExists && domainExists && domain.Spec.Metadata.KubeVirt.UID != vmi.UID {
51✔
369
                oldVMI := v1.NewVMIReferenceFromNameWithNS(vmi.Namespace, vmi.Name)
1✔
370
                oldVMI.UID = domain.Spec.Metadata.KubeVirt.UID
1✔
371
                expired, initialized, err := c.launcherClients.IsLauncherClientUnresponsive(oldVMI)
1✔
372
                if err != nil {
1✔
373
                        return err
×
374
                }
×
375
                // If we found an outdated domain which is also not alive anymore, clean up
376
                if !initialized {
1✔
377
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
UNCOV
378
                        return nil
×
379
                } else if expired {
1✔
380
                        c.logger.Object(oldVMI).Infof("Detected stale vmi %s that still needs cleanup before new vmi %s with identical name/namespace can be processed", oldVMI.UID, vmi.UID)
×
UNCOV
381
                        err = c.processVmCleanup(oldVMI)
×
UNCOV
382
                        if err != nil {
×
UNCOV
383
                                return err
×
UNCOV
384
                        }
×
385
                        // Make sure we re-enqueue the key to ensure this new VMI is processed
386
                        // after the stale domain is removed
UNCOV
387
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*5)
×
388
                }
389

390
                return nil
1✔
391
        }
392

393
        if domainExists &&
49✔
394
                (domainMigrated(domain) || domain.DeletionTimestamp != nil) {
49✔
395
                c.logger.Object(vmi).V(4).Info("detected orphan vmi")
×
396
                return c.deleteVM(vmi)
×
397
        }
×
398

399
        if migrations.IsMigrating(vmi) && (vmi.Status.Phase == v1.Failed) {
49✔
UNCOV
400
                c.logger.V(1).Infof("cleaning up VMI key %v as migration is in progress and the vmi is failed", key)
×
UNCOV
401
                err = c.processVmCleanup(vmi)
×
UNCOV
402
                if err != nil {
×
UNCOV
403
                        return err
×
UNCOV
404
                }
×
405
        }
406

407
        if vmi.DeletionTimestamp == nil && isMigrationInProgress(vmi, domain) {
51✔
408
                c.logger.V(4).Infof("ignoring key %v as migration is in progress", key)
2✔
409
                return nil
2✔
410
        }
2✔
411

412
        if vmiExists && !c.isVMIOwnedByNode(vmi) {
48✔
413
                c.logger.Object(vmi).V(4).Info("ignoring vmi as it is not owned by this node")
1✔
414
                return nil
1✔
415
        }
1✔
416

417
        if vmiExists && vmi.IsMigrationSource() {
46✔
UNCOV
418
                c.logger.Object(vmi).V(4).Info("ignoring vmi as it is a migration source")
×
UNCOV
419
                return nil
×
UNCOV
420
        }
×
421

422
        err = c.sync(key,
46✔
423
                vmi.DeepCopy(),
46✔
424
                vmiExists,
46✔
425
                domain,
46✔
426
                domainExists)
46✔
427
        _, localExists, _ := c.getVMIFromCache(key)
46✔
428
        if !localExists {
54✔
429
                metrics.ResetVMISync(key)
8✔
430
        }
8✔
431
        return err
46✔
432

433
}
434

435
type vmiIrrecoverableError struct {
436
        msg string
437
}
438

439
func (e *vmiIrrecoverableError) Error() string { return e.msg }
3✔
440

441
func formatIrrecoverableErrorMessage(domain *api.Domain) string {
1✔
442
        msg := "unknown reason"
1✔
443
        if domainPausedFailedPostCopy(domain) {
2✔
444
                msg = "VMI is irrecoverable due to failed post-copy migration"
1✔
445
        }
1✔
446
        return msg
1✔
447
}
448

449
func domainPausedFailedPostCopy(domain *api.Domain) bool {
37✔
450
        return domain != nil && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedPostcopyFailed
37✔
451
}
37✔
452

453
// teardownNetwork performs network cache cleanup for a specific VMI.
454
func (c *VirtualMachineController) teardownNetwork(vmi *v1.VirtualMachineInstance) {
5✔
455
        if string(vmi.UID) == "" {
5✔
UNCOV
456
                return
×
UNCOV
457
        }
×
458
        if err := c.netConf.Teardown(vmi); err != nil {
5✔
UNCOV
459
                c.logger.Reason(err).Errorf("failed to delete VMI Network cache files: %s", err.Error())
×
UNCOV
460
        }
×
461
        c.netStat.Teardown(vmi)
5✔
462
}
463

464
func canUpdateToMounted(currentPhase v1.VolumePhase) bool {
12✔
465
        return currentPhase == v1.VolumeBound || currentPhase == v1.VolumePending || currentPhase == v1.HotplugVolumeAttachedToNode
12✔
466
}
12✔
467

468
func canUpdateToUnmounted(currentPhase v1.VolumePhase) bool {
8✔
469
        return currentPhase == v1.VolumeReady || currentPhase == v1.HotplugVolumeMounted || currentPhase == v1.HotplugVolumeAttachedToNode
8✔
470
}
8✔
471

472
func (c *VirtualMachineController) generateEventsForVolumeStatusChange(vmi *v1.VirtualMachineInstance, newStatusMap map[string]v1.VolumeStatus) {
29✔
473
        newStatusMapCopy := make(map[string]v1.VolumeStatus)
29✔
474
        for k, v := range newStatusMap {
61✔
475
                newStatusMapCopy[k] = v
32✔
476
        }
32✔
477
        for _, oldStatus := range vmi.Status.VolumeStatus {
59✔
478
                newStatus, ok := newStatusMap[oldStatus.Name]
30✔
479
                if !ok {
30✔
UNCOV
480
                        // status got removed
×
UNCOV
481
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, VolumeUnplugged, fmt.Sprintf("Volume %s has been unplugged", oldStatus.Name))
×
UNCOV
482
                        continue
×
483
                }
484
                if newStatus.Phase != oldStatus.Phase {
42✔
485
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, newStatus.Reason, newStatus.Message)
12✔
486
                }
12✔
487
                delete(newStatusMapCopy, newStatus.Name)
30✔
488
        }
489
        // Send events for any new statuses.
490
        for _, v := range newStatusMapCopy {
31✔
491
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v.Reason, v.Message)
2✔
492
        }
2✔
493
}
494

495
func (c *VirtualMachineController) updateHotplugVolumeStatus(vmi *v1.VirtualMachineInstance, volumeStatus v1.VolumeStatus, specVolumeMap map[string]struct{}) (v1.VolumeStatus, bool) {
26✔
496
        needsRefresh := false
26✔
497
        if volumeStatus.Target == "" {
47✔
498
                needsRefresh = true
21✔
499
                mounted, err := c.hotplugVolumeMounter.IsMounted(vmi, volumeStatus.Name, volumeStatus.HotplugVolume.AttachPodUID)
21✔
500
                if err != nil {
21✔
UNCOV
501
                        c.logger.Object(vmi).Errorf("error occurred while checking if volume is mounted: %v", err)
×
UNCOV
502
                }
×
503
                if mounted {
33✔
504
                        if _, ok := specVolumeMap[volumeStatus.Name]; ok && canUpdateToMounted(volumeStatus.Phase) {
15✔
505
                                log.DefaultLogger().Infof("Marking volume %s as mounted in pod, it can now be attached", volumeStatus.Name)
3✔
506
                                // mounted, and still in spec, and in phase we can change, update status to mounted.
3✔
507
                                volumeStatus.Phase = v1.HotplugVolumeMounted
3✔
508
                                volumeStatus.Message = fmt.Sprintf("Volume %s has been mounted in virt-launcher pod", volumeStatus.Name)
3✔
509
                                volumeStatus.Reason = VolumeMountedToPodReason
3✔
510
                        }
3✔
511
                } else {
9✔
512
                        // Not mounted, check if the volume is in the spec, if not update status
9✔
513
                        if _, ok := specVolumeMap[volumeStatus.Name]; !ok && canUpdateToUnmounted(volumeStatus.Phase) {
13✔
514
                                log.DefaultLogger().Infof("Marking volume %s as unmounted from pod, it can now be detached", volumeStatus.Name)
4✔
515
                                // Not mounted.
4✔
516
                                volumeStatus.Phase = v1.HotplugVolumeUnMounted
4✔
517
                                volumeStatus.Message = fmt.Sprintf("Volume %s has been unmounted from virt-launcher pod", volumeStatus.Name)
4✔
518
                                volumeStatus.Reason = VolumeUnMountedFromPodReason
4✔
519
                        }
4✔
520
                }
521
        } else {
5✔
522
                // Successfully attached to VM.
5✔
523
                volumeStatus.Phase = v1.VolumeReady
5✔
524
                volumeStatus.Message = fmt.Sprintf("Successfully attach hotplugged volume %s to VM", volumeStatus.Name)
5✔
525
                volumeStatus.Reason = VolumeReadyReason
5✔
526
        }
5✔
527
        return volumeStatus, needsRefresh
26✔
528
}
529

530
func needToComputeChecksums(vmi *v1.VirtualMachineInstance) bool {
30✔
531
        containerDisks := map[string]*v1.Volume{}
30✔
532
        for _, volume := range vmi.Spec.Volumes {
33✔
533
                if volume.VolumeSource.ContainerDisk != nil {
5✔
534
                        containerDisks[volume.Name] = &volume
2✔
535
                }
2✔
536
        }
537

538
        for i := range vmi.Status.VolumeStatus {
34✔
539
                _, isContainerDisk := containerDisks[vmi.Status.VolumeStatus[i].Name]
4✔
540
                if !isContainerDisk {
7✔
541
                        continue
3✔
542
                }
543

544
                if vmi.Status.VolumeStatus[i].ContainerDiskVolume == nil ||
1✔
545
                        vmi.Status.VolumeStatus[i].ContainerDiskVolume.Checksum == 0 {
2✔
546
                        return true
1✔
547
                }
1✔
548
        }
549

550
        if util.HasKernelBootContainerImage(vmi) {
29✔
551
                if vmi.Status.KernelBootStatus == nil {
×
552
                        return true
×
553
                }
×
554

555
                kernelBootContainer := vmi.Spec.Domain.Firmware.KernelBoot.Container
×
UNCOV
556

×
557
                if kernelBootContainer.KernelPath != "" &&
×
558
                        (vmi.Status.KernelBootStatus.KernelInfo == nil ||
×
559
                                vmi.Status.KernelBootStatus.KernelInfo.Checksum == 0) {
×
560
                        return true
×
561

×
562
                }
×
563

UNCOV
564
                if kernelBootContainer.InitrdPath != "" &&
×
UNCOV
565
                        (vmi.Status.KernelBootStatus.InitrdInfo == nil ||
×
UNCOV
566
                                vmi.Status.KernelBootStatus.InitrdInfo.Checksum == 0) {
×
UNCOV
567
                        return true
×
UNCOV
568

×
UNCOV
569
                }
×
570
        }
571

572
        return false
29✔
573
}
574

575
// updateChecksumInfo is kept for compatibility with older virt-handlers
576
// that validate checksum calculations in vmi.status. This validation was
577
// removed in PR #14021, but we had to keep the checksum calculations for upgrades.
578
// Once we're sure old handlers won't interrupt upgrades, this can be removed.
579
func (c *VirtualMachineController) updateChecksumInfo(vmi *v1.VirtualMachineInstance, syncError error) error {
37✔
580
        // If the imageVolume feature gate is enabled, upgrade support isn't required,
37✔
581
        // and we can skip the checksum calculation. By the time the feature gate is GA,
37✔
582
        // the checksum calculation should be removed.
37✔
583
        if syncError != nil || vmi.DeletionTimestamp != nil || !needToComputeChecksums(vmi) || c.clusterConfig.ImageVolumeEnabled() {
73✔
584
                return nil
36✔
585
        }
36✔
586

587
        diskChecksums, err := c.containerDiskMounter.ComputeChecksums(vmi)
1✔
588
        if goerror.Is(err, containerdisk.ErrDiskContainerGone) {
1✔
UNCOV
589
                c.logger.Errorf("cannot compute checksums as containerdisk/kernelboot containers seem to have been terminated")
×
UNCOV
590
                return nil
×
UNCOV
591
        }
×
592
        if err != nil {
1✔
593
                return err
×
594
        }
×
595

596
        // containerdisks
597
        for i := range vmi.Status.VolumeStatus {
2✔
598
                checksum, exists := diskChecksums.ContainerDiskChecksums[vmi.Status.VolumeStatus[i].Name]
1✔
599
                if !exists {
1✔
UNCOV
600
                        // not a containerdisk
×
UNCOV
601
                        continue
×
602
                }
603

604
                vmi.Status.VolumeStatus[i].ContainerDiskVolume = &v1.ContainerDiskInfo{
1✔
605
                        Checksum: checksum,
1✔
606
                }
1✔
607
        }
608

609
        // kernelboot
610
        if util.HasKernelBootContainerImage(vmi) {
2✔
611
                vmi.Status.KernelBootStatus = &v1.KernelBootStatus{}
1✔
612

1✔
613
                if diskChecksums.KernelBootChecksum.Kernel != nil {
2✔
614
                        vmi.Status.KernelBootStatus.KernelInfo = &v1.KernelInfo{
1✔
615
                                Checksum: *diskChecksums.KernelBootChecksum.Kernel,
1✔
616
                        }
1✔
617
                }
1✔
618

619
                if diskChecksums.KernelBootChecksum.Initrd != nil {
2✔
620
                        vmi.Status.KernelBootStatus.InitrdInfo = &v1.InitrdInfo{
1✔
621
                                Checksum: *diskChecksums.KernelBootChecksum.Initrd,
1✔
622
                        }
1✔
623
                }
1✔
624
        }
625

626
        return nil
1✔
627
}
628

629
func (c *VirtualMachineController) updateVolumeStatusesFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
62✔
630
        // The return value is only used by unit tests
62✔
631
        hasHotplug := false
62✔
632

62✔
633
        if len(vmi.Status.VolumeStatus) == 0 {
96✔
634
                return false
34✔
635
        }
34✔
636

637
        diskDeviceMap := make(map[string]string)
28✔
638
        if domain != nil {
55✔
639
                for _, disk := range domain.Spec.Devices.Disks {
35✔
640
                        // don't care about empty cdroms
8✔
641
                        if disk.Source.File != "" || disk.Source.Dev != "" {
14✔
642
                                diskDeviceMap[disk.Alias.GetName()] = disk.Target.Device
6✔
643
                        }
6✔
644
                }
645
        }
646
        specVolumeMap := make(map[string]struct{})
28✔
647
        for _, volume := range vmi.Spec.Volumes {
44✔
648
                specVolumeMap[volume.Name] = struct{}{}
16✔
649
        }
16✔
650
        for _, utilityVolume := range vmi.Spec.UtilityVolumes {
28✔
UNCOV
651
                specVolumeMap[utilityVolume.Name] = struct{}{}
×
UNCOV
652
        }
×
653
        newStatusMap := make(map[string]v1.VolumeStatus)
28✔
654
        var newStatuses []v1.VolumeStatus
28✔
655
        needsRefresh := false
28✔
656
        for _, volumeStatus := range vmi.Status.VolumeStatus {
57✔
657
                tmpNeedsRefresh := false
29✔
658
                // relying on the fact that target will be "" if not in the map
29✔
659
                // see updateHotplugVolumeStatus
29✔
660
                volumeStatus.Target = diskDeviceMap[volumeStatus.Name]
29✔
661
                if volumeStatus.HotplugVolume != nil {
55✔
662
                        hasHotplug = true
26✔
663
                        volumeStatus, tmpNeedsRefresh = c.updateHotplugVolumeStatus(vmi, volumeStatus, specVolumeMap)
26✔
664
                        needsRefresh = needsRefresh || tmpNeedsRefresh
26✔
665
                }
26✔
666
                if volumeStatus.MemoryDumpVolume != nil {
35✔
667
                        volumeStatus, tmpNeedsRefresh = c.updateMemoryDumpInfo(vmi, volumeStatus, domain)
6✔
668
                        needsRefresh = needsRefresh || tmpNeedsRefresh
6✔
669
                }
6✔
670
                newStatuses = append(newStatuses, volumeStatus)
29✔
671
                newStatusMap[volumeStatus.Name] = volumeStatus
29✔
672
        }
673
        sort.SliceStable(newStatuses, func(i, j int) bool {
29✔
674
                return strings.Compare(newStatuses[i].Name, newStatuses[j].Name) == -1
1✔
675
        })
1✔
676
        if needsRefresh {
49✔
677
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second)
21✔
678
        }
21✔
679
        c.generateEventsForVolumeStatusChange(vmi, newStatusMap)
28✔
680
        vmi.Status.VolumeStatus = newStatuses
28✔
681

28✔
682
        return hasHotplug
28✔
683
}
684

685
func (c *VirtualMachineController) updateGuestInfoFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
37✔
686

37✔
687
        if domain == nil || domain.Status.OSInfo.Name == "" || vmi.Status.GuestOSInfo.Name == domain.Status.OSInfo.Name {
73✔
688
                return
36✔
689
        }
36✔
690

691
        vmi.Status.GuestOSInfo.Name = domain.Status.OSInfo.Name
1✔
692
        vmi.Status.GuestOSInfo.Version = domain.Status.OSInfo.Version
1✔
693
        vmi.Status.GuestOSInfo.KernelRelease = domain.Status.OSInfo.KernelRelease
1✔
694
        vmi.Status.GuestOSInfo.PrettyName = domain.Status.OSInfo.PrettyName
1✔
695
        vmi.Status.GuestOSInfo.VersionID = domain.Status.OSInfo.VersionId
1✔
696
        vmi.Status.GuestOSInfo.KernelVersion = domain.Status.OSInfo.KernelVersion
1✔
697
        vmi.Status.GuestOSInfo.Machine = domain.Status.OSInfo.Machine
1✔
698
        vmi.Status.GuestOSInfo.ID = domain.Status.OSInfo.Id
1✔
699
}
700

701
func (c *VirtualMachineController) updateAccessCredentialConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) {
37✔
702

37✔
703
        if domain == nil || domain.Spec.Metadata.KubeVirt.AccessCredential == nil {
71✔
704
                return
34✔
705
        }
34✔
706

707
        message := domain.Spec.Metadata.KubeVirt.AccessCredential.Message
3✔
708
        status := k8sv1.ConditionFalse
3✔
709
        if domain.Spec.Metadata.KubeVirt.AccessCredential.Succeeded {
5✔
710
                status = k8sv1.ConditionTrue
2✔
711
        }
2✔
712

713
        add := false
3✔
714
        condition := condManager.GetCondition(vmi, v1.VirtualMachineInstanceAccessCredentialsSynchronized)
3✔
715
        if condition == nil {
4✔
716
                add = true
1✔
717
        } else if condition.Status != status || condition.Message != message {
4✔
718
                // if not as expected, remove, then add.
1✔
719
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAccessCredentialsSynchronized)
1✔
720
                add = true
1✔
721
        }
1✔
722
        if add {
5✔
723
                newCondition := v1.VirtualMachineInstanceCondition{
2✔
724
                        Type:               v1.VirtualMachineInstanceAccessCredentialsSynchronized,
2✔
725
                        LastTransitionTime: metav1.Now(),
2✔
726
                        Status:             status,
2✔
727
                        Message:            message,
2✔
728
                }
2✔
729
                vmi.Status.Conditions = append(vmi.Status.Conditions, newCondition)
2✔
730
                if status == k8sv1.ConditionTrue {
3✔
731
                        eventMessage := "Access credentials sync successful."
1✔
732
                        if message != "" {
1✔
UNCOV
733
                                eventMessage = fmt.Sprintf("Access credentials sync successful: %s", message)
×
UNCOV
734
                        }
×
735
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.AccessCredentialsSyncSuccess.String(), eventMessage)
1✔
736
                } else {
1✔
737
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.AccessCredentialsSyncFailed.String(),
1✔
738
                                fmt.Sprintf("Access credentials sync failed: %s", message),
1✔
739
                        )
1✔
740
                }
1✔
741
        }
742
}
743

744
func (c *VirtualMachineController) updateLiveMigrationConditions(vmi *v1.VirtualMachineInstance, condManager *controller.VirtualMachineInstanceConditionManager) {
38✔
745
        // Calculate whether the VM is migratable
38✔
746
        liveMigrationCondition, isBlockMigration := c.calculateLiveMigrationCondition(vmi)
38✔
747
        if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceIsMigratable) {
67✔
748
                vmi.Status.Conditions = append(vmi.Status.Conditions, *liveMigrationCondition)
29✔
749
        } else {
38✔
750
                cond := condManager.GetCondition(vmi, v1.VirtualMachineInstanceIsMigratable)
9✔
751
                if !equality.Semantic.DeepEqual(cond, liveMigrationCondition) {
10✔
752
                        condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceIsMigratable)
1✔
753
                        vmi.Status.Conditions = append(vmi.Status.Conditions, *liveMigrationCondition)
1✔
754
                }
1✔
755
        }
756
        // Set VMI Migration Method
757
        if isBlockMigration {
43✔
758
                vmi.Status.MigrationMethod = v1.BlockMigration
5✔
759
        } else {
38✔
760
                vmi.Status.MigrationMethod = v1.LiveMigration
33✔
761
        }
33✔
762
        storageLiveMigCond := c.calculateLiveStorageMigrationCondition(vmi)
38✔
763
        condManager.UpdateCondition(vmi, storageLiveMigCond)
38✔
764
        evictable := migrations.VMIMigratableOnEviction(c.clusterConfig, vmi)
38✔
765
        if evictable && liveMigrationCondition.Status == k8sv1.ConditionFalse {
39✔
766
                c.recorder.Eventf(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), "EvictionStrategy is set but vmi is not migratable; %s", liveMigrationCondition.Message)
1✔
767
        }
1✔
768
}
769

770
func guestAgentConnected(domain *api.Domain) bool {
37✔
771
        if domain != nil {
61✔
772
                for _, channel := range domain.Spec.Devices.Channels {
27✔
773
                        if channel.Target != nil && channel.Target.Name == "org.qemu.guest_agent.0" &&
3✔
774
                                channel.Target.State == "connected" {
5✔
775
                                return true
2✔
776
                        }
2✔
777
                }
778
        }
779
        return false
35✔
780
}
781

782
func (c *VirtualMachineController) updateGuestAgentConditions(vmi *v1.VirtualMachineInstance, channelConnected bool, condManager *controller.VirtualMachineInstanceConditionManager) error {
37✔
783

37✔
784
        // Update the condition when GA is connected
37✔
785
        switch {
37✔
786
        case channelConnected && !condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected):
1✔
787
                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
788
                        Type:          v1.VirtualMachineInstanceAgentConnected,
1✔
789
                        LastProbeTime: metav1.Now(),
1✔
790
                        Status:        k8sv1.ConditionTrue,
1✔
791
                }
1✔
792
                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
793
        case !channelConnected:
35✔
794
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAgentConnected)
35✔
795
        }
796

797
        if condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected) {
39✔
798
                client, err := c.launcherClients.GetLauncherClient(vmi)
2✔
799
                if err != nil {
2✔
UNCOV
800
                        return err
×
UNCOV
801
                }
×
802

803
                guestInfo, err := client.GetGuestInfo()
2✔
804
                if err != nil {
2✔
UNCOV
805
                        return err
×
UNCOV
806
                }
×
807

808
                var supported = false
2✔
809
                var reason = ""
2✔
810

2✔
811
                // For current versions, virt-launcher's supported commands will always contain data.
2✔
812
                // For backwards compatibility: during upgrade from a previous version of KubeVirt,
2✔
813
                // virt-launcher might not provide any supported commands. If the list of supported
2✔
814
                // commands is empty, fall back to previous behavior.
2✔
815
                if len(guestInfo.SupportedCommands) > 0 {
2✔
UNCOV
816
                        supported, reason = isGuestAgentSupported(vmi, guestInfo.SupportedCommands)
×
UNCOV
817
                        c.logger.V(3).Object(vmi).Info(reason)
×
818
                } else {
2✔
819
                        for _, version := range c.clusterConfig.GetSupportedAgentVersions() {
10✔
820
                                supported = supported || regexp.MustCompile(version).MatchString(guestInfo.GAVersion)
8✔
821
                        }
8✔
822
                        if !supported {
4✔
823
                                reason = fmt.Sprintf("Guest agent version '%s' is not supported", guestInfo.GAVersion)
2✔
824
                        }
2✔
825
                }
826

827
                if !supported {
4✔
828
                        if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent) {
3✔
829
                                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
830
                                        Type:          v1.VirtualMachineInstanceUnsupportedAgent,
1✔
831
                                        LastProbeTime: metav1.Now(),
1✔
832
                                        Status:        k8sv1.ConditionTrue,
1✔
833
                                        Reason:        reason,
1✔
834
                                }
1✔
835
                                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
836
                        }
1✔
UNCOV
837
                } else {
×
UNCOV
838
                        condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent)
×
UNCOV
839
                }
×
840

841
        }
842
        return nil
37✔
843
}
844

845
func (c *VirtualMachineController) updatePausedConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) {
37✔
846

37✔
847
        // Update paused condition in case VMI was paused / unpaused
37✔
848
        if domain != nil && domain.Status.Status == api.Paused {
39✔
849
                if !condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
4✔
850
                        c.calculatePausedCondition(vmi, domain.Status.Reason)
2✔
851
                }
2✔
852
        } else if condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
36✔
853
                c.logger.Object(vmi).V(3).Info("Removing paused condition")
1✔
854
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstancePaused)
1✔
855
        }
1✔
856
}
857

858
func dumpTargetFile(vmiName, volName string) string {
7✔
859
        targetFileName := fmt.Sprintf("%s-%s-%s.memory.dump", vmiName, volName, time.Now().Format("20060102-150405"))
7✔
860
        return targetFileName
7✔
861
}
7✔
862

863
func (c *VirtualMachineController) updateMemoryDumpInfo(vmi *v1.VirtualMachineInstance, volumeStatus v1.VolumeStatus, domain *api.Domain) (v1.VolumeStatus, bool) {
6✔
864
        needsRefresh := false
6✔
865
        switch volumeStatus.Phase {
6✔
866
        case v1.HotplugVolumeMounted:
1✔
867
                needsRefresh = true
1✔
868
                c.logger.Object(vmi).V(3).Infof("Memory dump volume %s attached, marking it in progress", volumeStatus.Name)
1✔
869
                volumeStatus.Phase = v1.MemoryDumpVolumeInProgress
1✔
870
                volumeStatus.Message = fmt.Sprintf("Memory dump Volume %s is attached, getting memory dump", volumeStatus.Name)
1✔
871
                volumeStatus.Reason = VolumeMountedToPodReason
1✔
872
                volumeStatus.MemoryDumpVolume.TargetFileName = dumpTargetFile(vmi.Name, volumeStatus.Name)
1✔
873
        case v1.MemoryDumpVolumeInProgress:
3✔
874
                var memoryDumpMetadata *api.MemoryDumpMetadata
3✔
875
                if domain != nil {
6✔
876
                        memoryDumpMetadata = domain.Spec.Metadata.KubeVirt.MemoryDump
3✔
877
                }
3✔
878
                if memoryDumpMetadata == nil || memoryDumpMetadata.FileName != volumeStatus.MemoryDumpVolume.TargetFileName {
4✔
879
                        // memory dump wasnt triggered yet
1✔
880
                        return volumeStatus, needsRefresh
1✔
881
                }
1✔
882
                needsRefresh = true
2✔
883
                if memoryDumpMetadata.StartTimestamp != nil {
4✔
884
                        volumeStatus.MemoryDumpVolume.StartTimestamp = memoryDumpMetadata.StartTimestamp
2✔
885
                }
2✔
886
                if memoryDumpMetadata.EndTimestamp != nil && memoryDumpMetadata.Failed {
3✔
887
                        c.logger.Object(vmi).Errorf("Memory dump to pvc %s failed: %v", volumeStatus.Name, memoryDumpMetadata.FailureReason)
1✔
888
                        volumeStatus.Message = fmt.Sprintf("Memory dump to pvc %s failed: %v", volumeStatus.Name, memoryDumpMetadata.FailureReason)
1✔
889
                        volumeStatus.Phase = v1.MemoryDumpVolumeFailed
1✔
890
                        volumeStatus.MemoryDumpVolume.EndTimestamp = memoryDumpMetadata.EndTimestamp
1✔
891
                } else if memoryDumpMetadata.Completed {
3✔
892
                        c.logger.Object(vmi).V(3).Infof("Marking memory dump to volume %s has completed", volumeStatus.Name)
1✔
893
                        volumeStatus.Phase = v1.MemoryDumpVolumeCompleted
1✔
894
                        volumeStatus.Message = fmt.Sprintf("Memory dump to Volume %s has completed successfully", volumeStatus.Name)
1✔
895
                        volumeStatus.Reason = VolumeReadyReason
1✔
896
                        volumeStatus.MemoryDumpVolume.EndTimestamp = memoryDumpMetadata.EndTimestamp
1✔
897
                }
1✔
898
        }
899

900
        return volumeStatus, needsRefresh
5✔
901
}
902

903
func (c *VirtualMachineController) updateFSFreezeStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
37✔
904

37✔
905
        if domain == nil || domain.Status.FSFreezeStatus.Status == "" {
72✔
906
                return
35✔
907
        }
35✔
908

909
        if domain.Status.FSFreezeStatus.Status == api.FSThawed {
3✔
910
                vmi.Status.FSFreezeStatus = ""
1✔
911
        } else {
2✔
912
                vmi.Status.FSFreezeStatus = domain.Status.FSFreezeStatus.Status
1✔
913
        }
1✔
914

915
}
916

917
func IsoGuestVolumePath(namespace, name string, volume *v1.Volume) string {
×
918
        const basepath = "/var/run"
×
919
        switch {
×
920
        case volume.CloudInitNoCloud != nil:
×
921
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "noCloud.iso")
×
922
        case volume.CloudInitConfigDrive != nil:
×
923
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "configdrive.iso")
×
924
        case volume.ConfigMap != nil:
×
925
                return config.GetConfigMapDiskPath(volume.Name)
×
926
        case volume.DownwardAPI != nil:
×
927
                return config.GetDownwardAPIDiskPath(volume.Name)
×
928
        case volume.Secret != nil:
×
UNCOV
929
                return config.GetSecretDiskPath(volume.Name)
×
UNCOV
930
        case volume.ServiceAccount != nil:
×
UNCOV
931
                return config.GetServiceAccountDiskPath()
×
UNCOV
932
        case volume.Sysprep != nil:
×
UNCOV
933
                return config.GetSysprepDiskPath(volume.Name)
×
UNCOV
934
        default:
×
UNCOV
935
                return ""
×
936
        }
937
}
938

939
func (c *VirtualMachineController) updateIsoSizeStatus(vmi *v1.VirtualMachineInstance) {
37✔
940
        var podUID string
37✔
941
        if vmi.Status.Phase != v1.Running {
54✔
942
                return
17✔
943
        }
17✔
944

945
        for k, v := range vmi.Status.ActivePods {
33✔
946
                if v == vmi.Status.NodeName {
13✔
UNCOV
947
                        podUID = string(k)
×
UNCOV
948
                        break
×
949
                }
950
        }
951
        if podUID == "" {
40✔
952
                log.DefaultLogger().Warningf("failed to find pod UID for VMI %s", vmi.Name)
20✔
953
                return
20✔
954
        }
20✔
955

956
        volumes := make(map[string]v1.Volume)
×
957
        for _, volume := range vmi.Spec.Volumes {
×
958
                volumes[volume.Name] = volume
×
UNCOV
959
        }
×
960

961
        for _, disk := range vmi.Spec.Domain.Devices.Disks {
×
962
                volume, ok := volumes[disk.Name]
×
963
                if !ok {
×
UNCOV
964
                        log.DefaultLogger().Warningf("No matching volume with name %s found", disk.Name)
×
UNCOV
965
                        continue
×
966
                }
967

968
                volPath := IsoGuestVolumePath(vmi.Namespace, vmi.Name, &volume)
×
969
                if volPath == "" {
×
UNCOV
970
                        continue
×
971
                }
972

973
                res, err := c.podIsolationDetector.Detect(vmi)
×
974
                if err != nil {
×
975
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
UNCOV
976
                        continue
×
977
                }
978

979
                rootPath, err := res.MountRoot()
×
980
                if err != nil {
×
981
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
UNCOV
982
                        continue
×
983
                }
984

985
                safeVolPath, err := rootPath.AppendAndResolveWithRelativeRoot(volPath)
×
986
                if err != nil {
×
UNCOV
987
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
UNCOV
988
                        continue
×
989
                }
990
                fileInfo, err := safepath.StatAtNoFollow(safeVolPath)
×
991
                if err != nil {
×
992
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
UNCOV
993
                        continue
×
994
                }
995

UNCOV
996
                for i := range vmi.Status.VolumeStatus {
×
UNCOV
997
                        if vmi.Status.VolumeStatus[i].Name == volume.Name {
×
UNCOV
998
                                vmi.Status.VolumeStatus[i].Size = fileInfo.Size()
×
UNCOV
999
                                continue
×
1000
                        }
1001
                }
1002
        }
1003
}
1004

1005
func (c *VirtualMachineController) updateSELinuxContext(vmi *v1.VirtualMachineInstance) error {
37✔
1006
        _, present, err := selinux.NewSELinux()
37✔
1007
        if err != nil {
74✔
1008
                return err
37✔
1009
        }
37✔
1010
        if present {
×
1011
                context, err := selinux.GetVirtLauncherContext(vmi)
×
UNCOV
1012
                if err != nil {
×
1013
                        return err
×
UNCOV
1014
                }
×
UNCOV
1015
                vmi.Status.SelinuxContext = context
×
UNCOV
1016
        } else {
×
UNCOV
1017
                vmi.Status.SelinuxContext = "none"
×
UNCOV
1018
        }
×
1019

UNCOV
1020
        return nil
×
1021
}
1022

1023
func (c *VirtualMachineController) updateMemoryInfo(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
37✔
1024
        if domain == nil || vmi == nil || domain.Spec.CurrentMemory == nil {
73✔
1025
                return nil
36✔
1026
        }
36✔
1027
        if vmi.Status.Memory == nil {
1✔
UNCOV
1028
                vmi.Status.Memory = &v1.MemoryStatus{}
×
UNCOV
1029
        }
×
1030
        currentGuest := parseLibvirtQuantity(int64(domain.Spec.CurrentMemory.Value), domain.Spec.CurrentMemory.Unit)
1✔
1031
        vmi.Status.Memory.GuestCurrent = currentGuest
1✔
1032
        return nil
1✔
1033
}
1034

1035
func (c *VirtualMachineController) updateVMIStatusFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
37✔
1036
        c.updateIsoSizeStatus(vmi)
37✔
1037
        err := c.updateSELinuxContext(vmi)
37✔
1038
        if err != nil {
74✔
1039
                c.logger.Reason(err).Errorf("couldn't find the SELinux context for %s", vmi.Name)
37✔
1040
        }
37✔
1041
        c.updateGuestInfoFromDomain(vmi, domain)
37✔
1042
        c.updateVolumeStatusesFromDomain(vmi, domain)
37✔
1043
        c.updateFSFreezeStatus(vmi, domain)
37✔
1044
        c.updateBackupStatus(vmi, domain)
37✔
1045
        c.updateMachineType(vmi, domain)
37✔
1046
        if err = c.updateMemoryInfo(vmi, domain); err != nil {
37✔
UNCOV
1047
                return err
×
UNCOV
1048
        }
×
1049
        if err = c.cbtHandler.HandleChangedBlockTracking(vmi, domain); err != nil {
37✔
UNCOV
1050
                return err
×
UNCOV
1051
        }
×
1052
        err = c.netStat.UpdateStatus(vmi, domain)
37✔
1053
        return err
37✔
1054
}
1055

1056
func (c *VirtualMachineController) updateVMIConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) error {
37✔
1057
        c.updateAccessCredentialConditions(vmi, domain, condManager)
37✔
1058
        c.updateLiveMigrationConditions(vmi, condManager)
37✔
1059
        err := c.updateGuestAgentConditions(vmi, guestAgentConnected(domain), condManager)
37✔
1060
        if err != nil {
37✔
UNCOV
1061
                return err
×
UNCOV
1062
        }
×
1063
        c.updatePausedConditions(vmi, domain, condManager)
37✔
1064

37✔
1065
        return nil
37✔
1066
}
1067

1068
func (c *VirtualMachineController) updateVMIStatus(oldStatus *v1.VirtualMachineInstanceStatus, vmi *v1.VirtualMachineInstance, domain *api.Domain, syncError error) (err error) {
38✔
1069
        condManager := controller.NewVirtualMachineInstanceConditionManager()
38✔
1070

38✔
1071
        // Don't update the VirtualMachineInstance if it is already in a final state
38✔
1072
        if vmi.IsFinal() {
39✔
1073
                return nil
1✔
1074
        }
1✔
1075

1076
        // Update VMI status fields based on what is reported on the domain
1077
        err = c.updateVMIStatusFromDomain(vmi, domain)
37✔
1078
        if err != nil {
37✔
1079
                return err
×
UNCOV
1080
        }
×
1081

1082
        // Calculate the new VirtualMachineInstance state based on what libvirt reported
1083
        err = c.setVmPhaseForStatusReason(domain, vmi)
37✔
1084
        if err != nil {
37✔
1085
                return err
×
UNCOV
1086
        }
×
1087

1088
        // Update conditions on VMI Status
1089
        err = c.updateVMIConditions(vmi, domain, condManager)
37✔
1090
        if err != nil {
37✔
UNCOV
1091
                return err
×
UNCOV
1092
        }
×
1093

1094
        // Store containerdisks and kernelboot checksums
1095
        if err := c.updateChecksumInfo(vmi, syncError); err != nil {
37✔
UNCOV
1096
                return err
×
UNCOV
1097
        }
×
1098

1099
        // Handle sync error
1100
        c.handleSyncError(vmi, condManager, syncError)
37✔
1101

37✔
1102
        controller.SetVMIPhaseTransitionTimestamp(oldStatus, &vmi.Status)
37✔
1103

37✔
1104
        // Only issue vmi update if status has changed
37✔
1105
        if !equality.Semantic.DeepEqual(*oldStatus, vmi.Status) {
73✔
1106
                key := controller.VirtualMachineInstanceKey(vmi)
36✔
1107
                c.vmiExpectations.SetExpectations(key, 1, 0)
36✔
1108
                _, err := c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmi, metav1.UpdateOptions{})
36✔
1109
                if err != nil {
36✔
UNCOV
1110
                        c.vmiExpectations.SetExpectations(key, 0, 0)
×
UNCOV
1111
                        return err
×
UNCOV
1112
                }
×
1113
                metrics.VMISynced(vmi.Namespace, vmi.Name)
36✔
1114
        }
1115

1116
        // Record an event on the VMI when the VMI's phase changes
1117
        if oldStatus.Phase != vmi.Status.Phase {
47✔
1118
                c.recordPhaseChangeEvent(vmi)
10✔
1119

10✔
1120
                if c.pluginExecutor != nil && vmi.Status.Phase == v1.Running && !vmi.IsMigrationTarget() {
15✔
1121
                        if err := c.pluginExecutor.CallNodeHooks(pluginv1alpha1.NodeHookPostVMStart, vmi, c.host); err != nil {
5✔
UNCOV
1122
                                return err
×
UNCOV
1123
                        }
×
1124
                }
1125
        }
1126

1127
        return nil
37✔
1128
}
1129

1130
type virtLauncherCriticalSecurebootError struct {
1131
        msg string
1132
}
1133

UNCOV
1134
func (e *virtLauncherCriticalSecurebootError) Error() string { return e.msg }
×
1135

1136
func (c *VirtualMachineController) handleSyncError(vmi *v1.VirtualMachineInstance, condManager *controller.VirtualMachineInstanceConditionManager, syncError error) {
37✔
1137
        var criticalNetErr *neterrors.CriticalNetworkError
37✔
1138
        if goerror.As(syncError, &criticalNetErr) {
38✔
1139
                c.logger.Errorf("virt-launcher crashed due to a network error. Updating VMI %s status to Failed", vmi.Name)
1✔
1140
                vmi.Status.Phase = v1.Failed
1✔
1141
        }
1✔
1142
        if _, ok := syncError.(*virtLauncherCriticalSecurebootError); ok {
37✔
UNCOV
1143
                c.logger.Errorf("virt-launcher does not support the Secure Boot setting. Updating VMI %s status to Failed", vmi.Name)
×
UNCOV
1144
                vmi.Status.Phase = v1.Failed
×
1145
        }
×
1146

1147
        if _, ok := syncError.(*vmiIrrecoverableError); ok {
38✔
1148
                c.logger.Errorf("virt-launcher reached an irrecoverable error. Updating VMI %s status to Failed", vmi.Name)
1✔
1149
                vmi.Status.Phase = v1.Failed
1✔
1150
        }
1✔
1151
        condManager.CheckFailure(vmi, syncError, "Synchronizing with the Domain failed.")
37✔
1152
}
1153

1154
func (c *VirtualMachineController) recordPhaseChangeEvent(vmi *v1.VirtualMachineInstance) {
10✔
1155
        switch vmi.Status.Phase {
10✔
1156
        case v1.Running:
5✔
1157
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Started.String(), VMIStarted)
5✔
1158
        case v1.Succeeded:
×
1159
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Stopped.String(), VMIShutdown)
×
1160
        case v1.Failed:
5✔
1161
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Stopped.String(), VMICrashed)
5✔
1162
        }
1163
}
1164

1165
func (c *VirtualMachineController) calculatePausedCondition(vmi *v1.VirtualMachineInstance, reason api.StateChangeReason) {
2✔
1166
        now := metav1.NewTime(time.Now())
2✔
1167
        switch reason {
2✔
1168
        case api.ReasonPausedMigration:
×
UNCOV
1169
                if !isVMIPausedDuringMigration(vmi) || !c.isMigrationSource(vmi) {
×
UNCOV
1170
                        c.logger.Object(vmi).V(3).Infof("Domain is paused after migration by qemu, no condition needed")
×
UNCOV
1171
                        return
×
UNCOV
1172
                }
×
UNCOV
1173
                c.logger.Object(vmi).V(3).Info("Adding paused by migration monitor condition")
×
UNCOV
1174
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
UNCOV
1175
                        Type:               v1.VirtualMachineInstancePaused,
×
UNCOV
1176
                        Status:             k8sv1.ConditionTrue,
×
UNCOV
1177
                        LastProbeTime:      now,
×
UNCOV
1178
                        LastTransitionTime: now,
×
1179
                        Reason:             "PausedByMigrationMonitor",
×
1180
                        Message:            "VMI was paused by the migration monitor",
×
1181
                })
×
1182
        case api.ReasonPausedUser:
1✔
1183
                c.logger.Object(vmi).V(3).Info("Adding paused condition")
1✔
1184
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
1✔
1185
                        Type:               v1.VirtualMachineInstancePaused,
1✔
1186
                        Status:             k8sv1.ConditionTrue,
1✔
1187
                        LastProbeTime:      now,
1✔
1188
                        LastTransitionTime: now,
1✔
1189
                        Reason:             "PausedByUser",
1✔
1190
                        Message:            "VMI was paused by user",
1✔
1191
                })
1✔
UNCOV
1192
        case api.ReasonPausedIOError:
×
UNCOV
1193
                c.logger.Object(vmi).V(3).Info("Adding paused condition")
×
UNCOV
1194
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
UNCOV
1195
                        Type:               v1.VirtualMachineInstancePaused,
×
UNCOV
1196
                        Status:             k8sv1.ConditionTrue,
×
UNCOV
1197
                        LastProbeTime:      now,
×
UNCOV
1198
                        LastTransitionTime: now,
×
UNCOV
1199
                        Reason:             "PausedIOError",
×
UNCOV
1200
                        Message:            "VMI was paused, low-level IO error detected",
×
UNCOV
1201
                })
×
1202
        default:
1✔
1203
                c.logger.Object(vmi).V(3).Infof("Domain is paused for unknown reason, %s", reason)
1✔
1204
        }
1205
}
1206

1207
func newNonMigratableCondition(msg string, reason string) *v1.VirtualMachineInstanceCondition {
16✔
1208
        return &v1.VirtualMachineInstanceCondition{
16✔
1209
                Type:    v1.VirtualMachineInstanceIsMigratable,
16✔
1210
                Status:  k8sv1.ConditionFalse,
16✔
1211
                Message: msg,
16✔
1212
                Reason:  reason,
16✔
1213
        }
16✔
1214
}
16✔
1215

1216
func (c *VirtualMachineController) calculateLiveMigrationCondition(vmi *v1.VirtualMachineInstance) (*v1.VirtualMachineInstanceCondition, bool) {
62✔
1217
        isBlockMigration, blockErr := c.checkVolumesForMigration(vmi)
62✔
1218

62✔
1219
        err := c.checkNetworkInterfacesForMigration(vmi)
62✔
1220
        if err != nil {
63✔
1221
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonInterfaceNotMigratable), isBlockMigration
1✔
1222
        }
1✔
1223

1224
        if err := c.isHostModelMigratable(vmi); err != nil {
61✔
UNCOV
1225
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonCPUModeNotMigratable), isBlockMigration
×
UNCOV
1226
        }
×
1227

1228
        reason, ok := vmiContainsNonMigratablePCIHostDevices(vmi, c.clusterConfig)
61✔
1229
        if ok {
66✔
1230
                return newNonMigratableCondition(reason, v1.VirtualMachineInstanceReasonHostDeviceNotMigratable), isBlockMigration
5✔
1231
        }
5✔
1232

1233
        if util.IsSEVVMI(vmi) {
57✔
1234
                return newNonMigratableCondition("VMI uses SEV", v1.VirtualMachineInstanceReasonSEVNotMigratable), isBlockMigration
1✔
1235
        } else if util.IsTDXVMI(vmi) {
57✔
1236
                return newNonMigratableCondition("VMI uses TDX", v1.VirtualMachineInstanceReasonTDXNotMigratable), isBlockMigration
1✔
1237
        }
1✔
1238

1239
        if util.IsSecureExecutionVMI(vmi) {
55✔
1240
                return newNonMigratableCondition("VMI uses Secure Execution", v1.VirtualMachineInstanceReasonSecureExecutionNotMigratable), isBlockMigration
1✔
1241
        }
1✔
1242

1243
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
54✔
1244
                return newNonMigratableCondition(tscRequirement.Reason, v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable), isBlockMigration
1✔
1245
        }
1✔
1246

1247
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
53✔
1248
                return newNonMigratableCondition("VMI uses hyperv passthrough", v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable), isBlockMigration
1✔
1249
        }
1✔
1250

1251
        if blockErr != nil {
56✔
1252
                return newNonMigratableCondition(blockErr.Error(), v1.VirtualMachineInstanceReasonDisksNotMigratable), isBlockMigration
5✔
1253
        }
5✔
1254

1255
        return &v1.VirtualMachineInstanceCondition{
46✔
1256
                Type:   v1.VirtualMachineInstanceIsMigratable,
46✔
1257
                Status: k8sv1.ConditionTrue,
46✔
1258
        }, isBlockMigration
46✔
1259
}
1260

1261
func isMdevGPU(gpu v1.GPU, config *v1.KubeVirtConfiguration) bool {
2✔
1262
        if config.PermittedHostDevices == nil {
2✔
UNCOV
1263
                return false
×
UNCOV
1264
        }
×
1265
        for _, mdev := range config.PermittedHostDevices.MediatedDevices {
3✔
1266
                if mdev.ResourceName == gpu.DeviceName {
2✔
1267
                        return true
1✔
1268
                }
1✔
1269
        }
1270
        return false
1✔
1271
}
1272

1273
func vmiContainsNonMigratablePCIHostDevices(vmi *v1.VirtualMachineInstance, config *virtconfig.ClusterConfig) (string, bool) {
100✔
1274

100✔
1275
        if len(vmi.Spec.Domain.Devices.HostDevices) > 0 {
102✔
1276
                return "VMI specifies non-migratable generic PCI host device", true
2✔
1277
        }
2✔
1278

1279
        if len(vmi.Spec.Domain.Devices.GPUs) > 1 {
99✔
1280
                return "VMI specifies too many GPUs", true
1✔
1281
        }
1✔
1282

1283
        if len(vmi.Spec.Domain.Devices.GPUs) == 1 && !config.VGPULiveMigrationEnabled() {
98✔
1284
                return "VMI specifies a GPU but feature gate " + featuregate.VGPULiveMigration + " is not enabled", true
1✔
1285
        }
1✔
1286

1287
        if len(vmi.Spec.Domain.Devices.GPUs) == 1 && !isMdevGPU(vmi.Spec.Domain.Devices.GPUs[0], config.GetConfig()) {
97✔
1288
                return "VMI specifies non-migratable GPU device", true
1✔
1289
        }
1✔
1290

1291
        return "", false
95✔
1292
}
1293

1294
type multipleNonMigratableCondition struct {
1295
        reasons []string
1296
        msgs    []string
1297
}
1298

1299
func newMultipleNonMigratableCondition() *multipleNonMigratableCondition {
39✔
1300
        return &multipleNonMigratableCondition{}
39✔
1301
}
39✔
1302

1303
func (cond *multipleNonMigratableCondition) addNonMigratableCondition(reason, msg string) {
2✔
1304
        cond.reasons = append(cond.reasons, reason)
2✔
1305
        cond.msgs = append(cond.msgs, msg)
2✔
1306
}
2✔
1307

1308
func (cond *multipleNonMigratableCondition) String() string {
2✔
1309
        var buffer bytes.Buffer
2✔
1310
        for i, c := range cond.reasons {
4✔
1311
                if i > 0 {
2✔
UNCOV
1312
                        buffer.WriteString(", ")
×
UNCOV
1313
                }
×
1314
                buffer.WriteString(fmt.Sprintf("%s: %s", c, cond.msgs[i]))
2✔
1315
        }
1316
        return buffer.String()
2✔
1317
}
1318

1319
func (cond *multipleNonMigratableCondition) generateStorageLiveMigrationCondition() *v1.VirtualMachineInstanceCondition {
39✔
1320
        switch len(cond.reasons) {
39✔
1321
        case 0:
37✔
1322
                return &v1.VirtualMachineInstanceCondition{
37✔
1323
                        Type:   v1.VirtualMachineInstanceIsStorageLiveMigratable,
37✔
1324
                        Status: k8sv1.ConditionTrue,
37✔
1325
                }
37✔
1326
        default:
2✔
1327
                return &v1.VirtualMachineInstanceCondition{
2✔
1328
                        Type:    v1.VirtualMachineInstanceIsStorageLiveMigratable,
2✔
1329
                        Status:  k8sv1.ConditionFalse,
2✔
1330
                        Message: cond.String(),
2✔
1331
                        Reason:  v1.VirtualMachineInstanceReasonNotMigratable,
2✔
1332
                }
2✔
1333
        }
1334
}
1335

1336
func (c *VirtualMachineController) calculateLiveStorageMigrationCondition(vmi *v1.VirtualMachineInstance) *v1.VirtualMachineInstanceCondition {
39✔
1337
        multiCond := newMultipleNonMigratableCondition()
39✔
1338

39✔
1339
        if err := c.checkNetworkInterfacesForMigration(vmi); err != nil {
40✔
1340
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonInterfaceNotMigratable, err.Error())
1✔
1341
        }
1✔
1342

1343
        if err := c.isHostModelMigratable(vmi); err != nil {
39✔
UNCOV
1344
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonCPUModeNotMigratable, err.Error())
×
UNCOV
1345
        }
×
1346

1347
        reason, ok := vmiContainsNonMigratablePCIHostDevices(vmi, c.clusterConfig)
39✔
1348
        if ok {
39✔
UNCOV
1349
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHostDeviceNotMigratable, reason)
×
1350
        }
×
1351

1352
        if util.IsSEVVMI(vmi) {
39✔
UNCOV
1353
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonSEVNotMigratable, "VMI uses SEV")
×
1354
        } else if util.IsTDXVMI(vmi) {
39✔
1355
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonTDXNotMigratable, "VMI uses TDX")
×
UNCOV
1356
        }
×
1357

1358
        if reservation.HasVMIPersistentReservation(vmi) {
40✔
1359
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonPRNotMigratable, "VMI uses SCSI persistent reservation")
1✔
1360
        }
1✔
1361

1362
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
39✔
1363
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable, tscRequirement.Reason)
×
1364
        }
×
1365

1366
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
39✔
UNCOV
1367
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable, "VMI uses hyperv passthrough")
×
UNCOV
1368
        }
×
1369

1370
        return multiCond.generateStorageLiveMigrationCondition()
39✔
1371
}
1372

1373
func (c *VirtualMachineController) deleteVM(vmi *v1.VirtualMachineInstance) error {
4✔
1374
        err := c.processVmDelete(vmi)
4✔
1375
        if err != nil {
4✔
UNCOV
1376
                return err
×
UNCOV
1377
        }
×
1378
        // we can perform the cleanup immediately after
1379
        // the successful delete here because we don't have
1380
        // to report the deletion results on the VMI status
1381
        // in this case.
1382
        err = c.processVmCleanup(vmi)
4✔
1383
        if err != nil {
4✔
UNCOV
1384
                return err
×
UNCOV
1385
        }
×
1386

1387
        return nil
4✔
1388
}
1389

1390
// Determine if gracefulShutdown has been triggered by virt-launcher
1391
func (c *VirtualMachineController) hasGracefulShutdownTrigger(domain *api.Domain) bool {
46✔
1392
        if domain == nil {
60✔
1393
                return false
14✔
1394
        }
14✔
1395
        gracePeriod := domain.Spec.Metadata.KubeVirt.GracePeriod
32✔
1396

32✔
1397
        return gracePeriod != nil &&
32✔
1398
                gracePeriod.MarkedForGracefulShutdown != nil &&
32✔
1399
                *gracePeriod.MarkedForGracefulShutdown
32✔
1400
}
1401

1402
func (c *VirtualMachineController) sync(key string,
1403
        vmi *v1.VirtualMachineInstance,
1404
        vmiExists bool,
1405
        domain *api.Domain,
1406
        domainExists bool) error {
46✔
1407

46✔
1408
        oldStatus := vmi.Status.DeepCopy()
46✔
1409
        oldSpec := vmi.Spec.DeepCopy()
46✔
1410

46✔
1411
        // set to true when domain needs to be shutdown.
46✔
1412
        shouldShutdown := false
46✔
1413
        // set to true when domain needs to be removed from libvirt.
46✔
1414
        shouldDelete := false
46✔
1415
        // set to true when VirtualMachineInstance is active or about to become active.
46✔
1416
        shouldUpdate := false
46✔
1417
        // set to true when unrecoverable domain needs to be destroyed non-gracefully.
46✔
1418
        forceShutdownIrrecoverable := false
46✔
1419

46✔
1420
        c.logger.V(3).Infof("Processing event %v", key)
46✔
1421

46✔
1422
        if vmiExists && domainExists {
70✔
1423
                c.logger.Object(vmi).Infof("VMI is in phase: %v | Domain status: %v, reason: %v", vmi.Status.Phase, domain.Status.Status, domain.Status.Reason)
24✔
1424
        } else if vmiExists {
60✔
1425
                c.logger.Object(vmi).Infof("VMI is in phase: %v | Domain does not exist", vmi.Status.Phase)
14✔
1426
        } else if domainExists {
30✔
1427
                vmiRef := v1.NewVMIReferenceWithUUID(domain.ObjectMeta.Namespace, domain.ObjectMeta.Name, domain.Spec.Metadata.KubeVirt.UID)
8✔
1428
                c.logger.Object(vmiRef).Infof("VMI does not exist | Domain status: %v, reason: %v", domain.Status.Status, domain.Status.Reason)
8✔
1429
        } else {
8✔
1430
                c.logger.Info("VMI does not exist | Domain does not exist")
×
1431
        }
×
1432

1433
        domainAlive := domainExists &&
46✔
1434
                domain.Status.Status != api.Shutoff &&
46✔
1435
                domain.Status.Status != api.Crashed &&
46✔
1436
                domain.Status.Status != ""
46✔
1437

46✔
1438
        forceShutdownIrrecoverable = domainExists && domainPausedFailedPostCopy(domain)
46✔
1439

46✔
1440
        gracefulShutdown := c.hasGracefulShutdownTrigger(domain)
46✔
1441
        if gracefulShutdown && vmi.IsRunning() {
46✔
UNCOV
1442
                if domainAlive {
×
UNCOV
1443
                        c.logger.Object(vmi).V(3).Info("Shutting down due to graceful shutdown signal.")
×
UNCOV
1444
                        shouldShutdown = true
×
UNCOV
1445
                } else {
×
UNCOV
1446
                        shouldDelete = true
×
UNCOV
1447
                }
×
1448
        }
1449

1450
        // Determine removal of VirtualMachineInstance from cache should result in deletion.
1451
        if !vmiExists {
54✔
1452
                if domainAlive {
14✔
1453
                        // The VirtualMachineInstance is deleted on the cluster, and domain is alive,
6✔
1454
                        // then shut down the domain.
6✔
1455
                        c.logger.Object(vmi).V(3).Info("Shutting down domain for deleted VirtualMachineInstance object.")
6✔
1456
                        shouldShutdown = true
6✔
1457
                } else {
8✔
1458
                        // The VirtualMachineInstance is deleted on the cluster, and domain is not alive
2✔
1459
                        // then delete the domain.
2✔
1460
                        c.logger.Object(vmi).V(3).Info("Deleting domain for deleted VirtualMachineInstance object.")
2✔
1461
                        shouldDelete = true
2✔
1462
                }
2✔
1463
        }
1464

1465
        // Determine if VirtualMachineInstance is being deleted.
1466
        if vmiExists && vmi.ObjectMeta.DeletionTimestamp != nil {
48✔
1467
                if domainAlive {
3✔
1468
                        c.logger.Object(vmi).V(3).Info("Shutting down domain for VirtualMachineInstance with deletion timestamp.")
1✔
1469
                        shouldShutdown = true
1✔
1470
                } else {
2✔
1471
                        c.logger.Object(vmi).V(3).Info("Deleting domain for VirtualMachineInstance with deletion timestamp.")
1✔
1472
                        shouldDelete = true
1✔
1473
                }
1✔
1474
        }
1475

1476
        // Determine if domain needs to be deleted as a result of VirtualMachineInstance
1477
        // shutting down naturally (guest internal invoked shutdown)
1478
        if vmiExists && vmi.IsFinal() {
47✔
1479
                c.logger.Object(vmi).V(3).Info("Removing domain and ephemeral data for finalized vmi.")
1✔
1480
                shouldDelete = true
1✔
1481
        }
1✔
1482

1483
        if !domainAlive && domainExists && !vmi.IsFinal() {
48✔
1484
                c.logger.Object(vmi).V(3).Info("Deleting inactive domain for vmi.")
2✔
1485
                shouldDelete = true
2✔
1486
        }
2✔
1487

1488
        // Determine if an active (or about to be active) VirtualMachineInstance should be updated.
1489
        if vmiExists && !vmi.IsFinal() {
83✔
1490
                // requiring the phase of the domain and VirtualMachineInstance to be in sync is an
37✔
1491
                // optimization that prevents unnecessary re-processing VMIs during the start flow.
37✔
1492
                phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
37✔
1493
                if err != nil {
37✔
UNCOV
1494
                        return err
×
UNCOV
1495
                }
×
1496
                if vmi.Status.Phase == phase {
65✔
1497
                        shouldUpdate = true
28✔
1498
                }
28✔
1499

1500
                if shouldDelay, delay := c.ioErrorRetryManager.ShouldDelay(string(vmi.UID), func() bool {
74✔
1501
                        return isIOError(shouldUpdate, domainExists, domain)
37✔
1502
                }); shouldDelay {
37✔
UNCOV
1503
                        shouldUpdate = false
×
UNCOV
1504
                        c.logger.Object(vmi).Infof("Delay vm update for %f seconds", delay.Seconds())
×
UNCOV
1505
                        c.queue.AddAfter(key, delay)
×
UNCOV
1506
                }
×
1507
        }
1508

1509
        if c.pluginExecutor != nil && vmiExists && vmi.IsFinal() && !domainAlive {
47✔
1510
                if err := c.pluginExecutor.CallNodeHooks(pluginv1alpha1.NodeHookPostVMStop, vmi, c.host); err != nil {
1✔
UNCOV
1511
                        return err
×
UNCOV
1512
                }
×
1513
        }
1514

1515
        var syncErr error
46✔
1516

46✔
1517
        // Process the VirtualMachineInstance update in this order.
46✔
1518
        // * Shutdown and Deletion due to VirtualMachineInstance deletion, process stopping, graceful shutdown trigger, etc...
46✔
1519
        // * Cleanup of already shutdown and Deleted VMIs
46✔
1520
        // * Update due to spec change and initial start flow.
46✔
1521
        switch {
46✔
1522
        case shouldShutdown:
7✔
1523
                c.logger.Object(vmi).V(3).Info("Processing shutdown.")
7✔
1524
                syncErr = c.processVmShutdown(vmi, domain)
7✔
1525
        case forceShutdownIrrecoverable:
1✔
1526
                msg := formatIrrecoverableErrorMessage(domain)
1✔
1527
                c.logger.Object(vmi).V(3).Infof("Processing a destruction of an irrecoverable domain - %s.", msg)
1✔
1528
                syncErr = c.processVmDestroy(vmi, domain)
1✔
1529
                if syncErr == nil {
2✔
1530
                        syncErr = &vmiIrrecoverableError{msg}
1✔
1531
                }
1✔
1532
        case shouldDelete:
4✔
1533
                c.logger.Object(vmi).V(3).Info("Processing deletion.")
4✔
1534
                syncErr = c.deleteVM(vmi)
4✔
1535
        case shouldUpdate:
26✔
1536
                c.logger.Object(vmi).V(3).Info("Processing vmi update")
26✔
1537
                syncErr = c.processVmUpdate(vmi, domain)
26✔
1538
        default:
8✔
1539
                c.logger.Object(vmi).V(3).Info("No update processing required")
8✔
1540
        }
1541
        if syncErr != nil && !vmi.IsFinal() {
51✔
1542
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.SyncFailed.String(), syncErr.Error())
5✔
1543

5✔
1544
                // `syncErr` will be propagated anyway, and it will be logged in `re-enqueueing`
5✔
1545
                // so there is no need to log it twice in hot path without increased verbosity.
5✔
1546
                c.logger.Object(vmi).Reason(syncErr).Error("Synchronizing the VirtualMachineInstance failed.")
5✔
1547
        }
5✔
1548

1549
        // Update the VirtualMachineInstance status, if the VirtualMachineInstance exists
1550
        if vmiExists {
84✔
1551
                vmi.Spec = *oldSpec
38✔
1552
                if err := c.updateVMIStatus(oldStatus, vmi, domain, syncErr); err != nil {
38✔
UNCOV
1553
                        c.logger.Object(vmi).Reason(err).Error("Updating the VirtualMachineInstance status failed.")
×
UNCOV
1554
                        return err
×
UNCOV
1555
                }
×
1556
        }
1557

1558
        if syncErr != nil {
51✔
1559
                return syncErr
5✔
1560
        }
5✔
1561

1562
        c.logger.Object(vmi).V(3).Info("Synchronization loop succeeded.")
41✔
1563
        return nil
41✔
1564

1565
}
1566

1567
func (c *VirtualMachineController) processVmCleanup(vmi *v1.VirtualMachineInstance) error {
5✔
1568
        vmiId := string(vmi.UID)
5✔
1569

5✔
1570
        c.logger.Object(vmi).Infof("Performing final local cleanup for vmi with uid %s", vmiId)
5✔
1571

5✔
1572
        c.migrationProxy.StopTargetListener(vmiId)
5✔
1573
        c.migrationProxy.StopSourceListener(vmiId)
5✔
1574

5✔
1575
        c.downwardMetricsManager.StopServer(vmi)
5✔
1576

5✔
1577
        // Unmount container disks and clean up remaining files
5✔
1578
        if err := c.containerDiskMounter.Unmount(vmi); err != nil {
5✔
UNCOV
1579
                return err
×
UNCOV
1580
        }
×
1581

1582
        // UnmountAll does the cleanup on the "best effort" basis: it is
1583
        // safe to pass a nil cgroupManager.
1584
        cgroupManager, _ := getCgroupManager(vmi, c.host, c.hypervisorNodeInfo, c.clusterConfig.AllowEmulation())
5✔
1585
        if err := c.hotplugVolumeMounter.UnmountAll(vmi, cgroupManager); err != nil {
5✔
UNCOV
1586
                return err
×
UNCOV
1587
        }
×
1588

1589
        c.teardownNetwork(vmi)
5✔
1590

5✔
1591
        c.sriovHotplugExecutorPool.Delete(vmi.UID)
5✔
1592

5✔
1593
        // Watch dog file and command client must be the last things removed here
5✔
1594
        c.launcherClients.CloseLauncherClient(vmi)
5✔
1595

5✔
1596
        // Remove the domain from cache in the event that we're performing
5✔
1597
        // a final cleanup and never received the "DELETE" event. This is
5✔
1598
        // possible if the VMI pod goes away before we receive the final domain
5✔
1599
        // "DELETE"
5✔
1600
        domain := api.NewDomainReferenceFromName(vmi.Namespace, vmi.Name)
5✔
1601
        c.logger.Object(domain).Infof("Removing domain from cache during final cleanup")
5✔
1602
        return c.domainStore.Delete(domain)
5✔
1603
}
1604

1605
func (c *VirtualMachineController) processVmDestroy(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
1606
        if c.pluginExecutor != nil {
2✔
1607
                if err := c.pluginExecutor.CallNodeHooks(pluginv1alpha1.NodeHookPreVMStop, vmi, c.host); err != nil {
1✔
UNCOV
1608
                        return err
×
UNCOV
1609
                }
×
1610
        }
1611

1612
        tryGracefully := false
1✔
1613
        return c.helperVmShutdown(vmi, domain, tryGracefully)
1✔
1614
}
1615

1616
func (c *VirtualMachineController) processVmShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
7✔
1617
        if c.pluginExecutor != nil {
14✔
1618
                if err := c.pluginExecutor.CallNodeHooks(pluginv1alpha1.NodeHookPreVMStop, vmi, c.host); err != nil {
7✔
UNCOV
1619
                        return err
×
UNCOV
1620
                }
×
1621
        }
1622

1623
        tryGracefully := true
7✔
1624
        return c.helperVmShutdown(vmi, domain, tryGracefully)
7✔
1625
}
1626

1627
const firstGracefulShutdownAttempt = -1
1628

1629
// Determines if a domain's grace period has expired during shutdown.
1630
// If the grace period has started but not expired, timeLeft represents
1631
// the time in seconds left until the period expires.
1632
// If the grace period has not started, timeLeft will be set to -1.
1633
func (c *VirtualMachineController) hasGracePeriodExpired(terminationGracePeriod *int64, dom *api.Domain) (bool, int64) {
6✔
1634
        var hasExpired bool
6✔
1635
        var timeLeft int64
6✔
1636

6✔
1637
        gracePeriod := int64(0)
6✔
1638
        if terminationGracePeriod != nil {
7✔
1639
                gracePeriod = *terminationGracePeriod
1✔
1640
        } else if dom != nil && dom.Spec.Metadata.KubeVirt.GracePeriod != nil {
11✔
1641
                gracePeriod = dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds
5✔
1642
        }
5✔
1643

1644
        // If gracePeriod == 0, then there will be no startTime set, deletion
1645
        // should occur immediately during shutdown.
1646
        if gracePeriod == 0 {
7✔
1647
                hasExpired = true
1✔
1648
                return hasExpired, timeLeft
1✔
1649
        }
1✔
1650

1651
        startTime := int64(0)
5✔
1652
        if dom != nil && dom.Spec.Metadata.KubeVirt.GracePeriod != nil && dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp != nil {
8✔
1653
                startTime = dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp.UTC().Unix()
3✔
1654
        }
3✔
1655

1656
        if startTime == 0 {
7✔
1657
                // If gracePeriod > 0, then the shutdown signal needs to be sent
2✔
1658
                // and the gracePeriod start time needs to be set.
2✔
1659
                timeLeft = firstGracefulShutdownAttempt
2✔
1660
                return hasExpired, timeLeft
2✔
1661
        }
2✔
1662

1663
        now := time.Now().UTC().Unix()
3✔
1664
        diff := now - startTime
3✔
1665

3✔
1666
        if diff >= gracePeriod {
4✔
1667
                hasExpired = true
1✔
1668
                return hasExpired, timeLeft
1✔
1669
        }
1✔
1670

1671
        timeLeft = gracePeriod - diff
2✔
1672
        if timeLeft < 1 {
2✔
1673
                timeLeft = 1
×
UNCOV
1674
        }
×
1675
        return hasExpired, timeLeft
2✔
1676
}
1677

1678
func (c *VirtualMachineController) helperVmShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain, tryGracefully bool) error {
8✔
1679

8✔
1680
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
8✔
1681
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
8✔
1682
        if err != nil {
8✔
UNCOV
1683
                return err
×
1684
        }
×
1685

1686
        if domainHasGracePeriod(domain) && tryGracefully {
12✔
1687
                if expired, timeLeft := c.hasGracePeriodExpired(vmi.Spec.TerminationGracePeriodSeconds, domain); !expired {
7✔
1688
                        return c.handleVMIShutdown(vmi, domain, client, timeLeft)
3✔
1689
                }
3✔
1690
                c.logger.Object(vmi).Infof("Grace period expired, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
1✔
1691
        } else {
4✔
1692
                c.logger.Object(vmi).Infof("Graceful shutdown not set, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
4✔
1693
        }
4✔
1694

1695
        err = client.KillVirtualMachine(vmi)
5✔
1696
        if err != nil && !cmdclient.IsDisconnected(err) {
5✔
1697
                // Only report err if it wasn't the result of a disconnect.
×
1698
                //
×
UNCOV
1699
                // Both virt-launcher and virt-handler are trying to destroy
×
UNCOV
1700
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
UNCOV
1701
                // disconnected during the kill request, which shouldn't be
×
UNCOV
1702
                // considered an error.
×
UNCOV
1703
                return err
×
UNCOV
1704
        }
×
1705

1706
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Deleted.String(), VMIStopping)
5✔
1707

5✔
1708
        return nil
5✔
1709
}
1710

1711
func (c *VirtualMachineController) handleVMIShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain, client cmdclient.LauncherClient, timeLeft int64) error {
3✔
1712
        if domain.Status.Status != api.Shutdown {
6✔
1713
                return c.shutdownVMI(vmi, client, timeLeft)
3✔
1714
        }
3✔
1715
        c.logger.V(4).Object(vmi).Infof("%s is already shutting down.", vmi.GetObjectMeta().GetName())
×
1716
        return nil
×
1717
}
1718

1719
func (c *VirtualMachineController) shutdownVMI(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient, timeLeft int64) error {
3✔
1720
        err := client.ShutdownVirtualMachine(vmi)
3✔
1721
        if err != nil && !cmdclient.IsDisconnected(err) {
3✔
UNCOV
1722
                // Only report err if it wasn't the result of a disconnect.
×
UNCOV
1723
                //
×
UNCOV
1724
                // Both virt-launcher and virt-handler are trying to destroy
×
UNCOV
1725
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
UNCOV
1726
                // disconnected during the kill request, which shouldn't be
×
UNCOV
1727
                // considered an error.
×
UNCOV
1728
                return err
×
UNCOV
1729
        }
×
1730

1731
        c.logger.Object(vmi).Infof("Signaled graceful shutdown for %s", vmi.GetObjectMeta().GetName())
3✔
1732

3✔
1733
        // Only create a VMIGracefulShutdown event for the first attempt as we can
3✔
1734
        // easily hit the default burst limit of 25 for the
3✔
1735
        // EventSourceObjectSpamFilter when gracefully shutting down VMIs with a
3✔
1736
        // large TerminationGracePeriodSeconds value set. Hitting this limit can
3✔
1737
        // result in the eventual VMIShutdown event being dropped.
3✔
1738
        if timeLeft == firstGracefulShutdownAttempt {
5✔
1739
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.ShuttingDown.String(), VMIGracefulShutdown)
2✔
1740
        }
2✔
1741

1742
        // Make sure that we don't hot-loop in case we send the first domain notification
1743
        if timeLeft == firstGracefulShutdownAttempt {
5✔
1744
                timeLeft = 5
2✔
1745
                if vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds < timeLeft {
2✔
UNCOV
1746
                        timeLeft = *vmi.Spec.TerminationGracePeriodSeconds
×
1747
                }
×
1748
        }
1749
        // In case we have a long grace period, we want to resend the graceful shutdown every 5 seconds
1750
        // That's important since a booting OS can miss ACPI signals
1751
        if timeLeft > 5 {
4✔
1752
                timeLeft = 5
1✔
1753
        }
1✔
1754

1755
        // pending graceful shutdown.
1756
        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Duration(timeLeft)*time.Second)
3✔
1757
        return nil
3✔
1758
}
1759

1760
func (c *VirtualMachineController) processVmDelete(vmi *v1.VirtualMachineInstance) error {
4✔
1761

4✔
1762
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
4✔
1763
        // Always clear the cached client afterward so that a stale entry from a
4✔
1764
        // dying launcher cannot poison the cache for subsequent controllers.
4✔
1765
        defer c.launcherClients.CloseLauncherClient(vmi)
4✔
1766
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
4✔
1767

4✔
1768
        // If the pod has been torn down, we know the VirtualMachineInstance is down.
4✔
1769
        if err == nil {
8✔
1770

4✔
1771
                c.logger.Object(vmi).Infof("Signaled deletion for %s", vmi.GetObjectMeta().GetName())
4✔
1772

4✔
1773
                // pending deletion.
4✔
1774
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Deleted.String(), VMISignalDeletion)
4✔
1775

4✔
1776
                err = client.DeleteDomain(vmi)
4✔
1777
                if err != nil && !cmdclient.IsDisconnected(err) {
4✔
UNCOV
1778
                        // Only report err if it wasn't the result of a disconnect.
×
UNCOV
1779
                        //
×
UNCOV
1780
                        // Both virt-launcher and virt-handler are trying to destroy
×
UNCOV
1781
                        // the VirtualMachineInstance at the same time. It's possible the client may get
×
UNCOV
1782
                        // disconnected during the kill request, which shouldn't be
×
UNCOV
1783
                        // considered an error.
×
UNCOV
1784
                        return err
×
UNCOV
1785
                }
×
1786
        }
1787

1788
        return nil
4✔
1789

1790
}
1791

1792
func (c *VirtualMachineController) isVMIOwnedByNode(vmi *v1.VirtualMachineInstance) bool {
39✔
1793
        nodeName, ok := vmi.Labels[v1.NodeNameLabel]
39✔
1794

39✔
1795
        if ok && nodeName != "" && nodeName == c.host {
77✔
1796
                return true
38✔
1797
        }
38✔
1798

1799
        return vmi.Status.NodeName != "" && vmi.Status.NodeName == c.host
1✔
1800
}
1801

1802
func (c *VirtualMachineController) checkNetworkInterfacesForMigration(vmi *v1.VirtualMachineInstance) error {
104✔
1803
        return netvmispec.VerifyVMIMigratable(vmi, c.clusterConfig.GetNetworkBindings())
104✔
1804
}
104✔
1805

1806
func isReadOnlyDisk(disk *v1.Disk) bool {
8✔
1807
        isReadOnlyCDRom := disk.CDRom != nil && (disk.CDRom.ReadOnly == nil || *disk.CDRom.ReadOnly)
8✔
1808

8✔
1809
        return isReadOnlyCDRom
8✔
1810
}
8✔
1811

1812
func (c *VirtualMachineController) checkVolumesForMigration(vmi *v1.VirtualMachineInstance) (blockMigrate bool, err error) {
73✔
1813
        volumeStatusMap := make(map[string]v1.VolumeStatus)
73✔
1814

73✔
1815
        for _, volumeStatus := range vmi.Status.VolumeStatus {
88✔
1816
                volumeStatusMap[volumeStatus.Name] = volumeStatus
15✔
1817
        }
15✔
1818

1819
        if len(vmi.Status.MigratedVolumes) > 0 {
73✔
UNCOV
1820
                blockMigrate = true
×
1821
        }
×
1822

1823
        filesystems := storagetypes.GetFilesystemsFromVolumes(vmi)
73✔
1824

73✔
1825
        // Check if all VMI volumes can be shared between the source and the destination
73✔
1826
        // of a live migration. blockMigrate will be returned as false, only if all volumes
73✔
1827
        // are shared and the VMI has no local disks
73✔
1828
        // Some combinations of disks makes the VMI no suitable for live migration.
73✔
1829
        // A relevant error will be returned in this case.
73✔
1830
        for _, volume := range vmi.Spec.Volumes {
102✔
1831
                volSrc := volume.VolumeSource
29✔
1832
                if volSrc.PersistentVolumeClaim != nil || volSrc.DataVolume != nil {
41✔
1833
                        var claimName string
12✔
1834
                        if volSrc.PersistentVolumeClaim != nil {
19✔
1835
                                claimName = volSrc.PersistentVolumeClaim.ClaimName
7✔
1836
                        } else {
12✔
1837
                                claimName = volSrc.DataVolume.Name
5✔
1838
                        }
5✔
1839

1840
                        volumeStatus, ok := volumeStatusMap[volume.Name]
12✔
1841

12✔
1842
                        if !ok || volumeStatus.PersistentVolumeClaimInfo == nil {
13✔
1843
                                return true, fmt.Errorf("cannot migrate VMI: Unable to determine if PVC %v is shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)", claimName)
1✔
1844
                        } else if !storagetypes.HasSharedAccessMode(volumeStatus.PersistentVolumeClaimInfo.AccessModes) && !storagetypes.IsMigratedVolume(volumeStatus.Name, vmi) {
18✔
1845
                                return true, fmt.Errorf("cannot migrate VMI: PVC %v is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)", claimName)
6✔
1846
                        }
6✔
1847

1848
                } else if volSrc.HostDisk != nil {
20✔
1849
                        // Check if this is a translated PVC.
3✔
1850
                        volumeStatus, ok := volumeStatusMap[volume.Name]
3✔
1851
                        if ok && volumeStatus.PersistentVolumeClaimInfo != nil {
3✔
UNCOV
1852
                                if !storagetypes.HasSharedAccessMode(volumeStatus.PersistentVolumeClaimInfo.AccessModes) && !storagetypes.IsMigratedVolume(volumeStatus.Name, vmi) {
×
UNCOV
1853
                                        return true, fmt.Errorf("cannot migrate VMI: PVC %v is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)", volumeStatus.PersistentVolumeClaimInfo.ClaimName)
×
UNCOV
1854
                                } else {
×
UNCOV
1855
                                        continue
×
1856
                                }
1857
                        }
1858

1859
                        shared := volSrc.HostDisk.Shared != nil && *volSrc.HostDisk.Shared
3✔
1860
                        if !shared {
4✔
1861
                                return true, fmt.Errorf("cannot migrate VMI with non-shared HostDisk")
1✔
1862
                        }
1✔
1863
                } else {
14✔
1864
                        if _, ok := filesystems[volume.Name]; ok {
18✔
1865
                                c.logger.Object(vmi).Infof("Volume %s is shared with virtiofs, allow live migration", volume.Name)
4✔
1866
                                continue
4✔
1867
                        }
1868

1869
                        isVolumeUsedByReadOnlyDisk := false
10✔
1870
                        for _, disk := range vmi.Spec.Domain.Devices.Disks {
18✔
1871
                                if isReadOnlyDisk(&disk) && disk.Name == volume.Name {
10✔
1872
                                        isVolumeUsedByReadOnlyDisk = true
2✔
1873
                                        break
2✔
1874
                                }
1875
                        }
1876

1877
                        if isVolumeUsedByReadOnlyDisk {
12✔
1878
                                continue
2✔
1879
                        }
1880

1881
                        if vmi.Status.MigrationMethod == "" || vmi.Status.MigrationMethod == v1.LiveMigration {
16✔
1882
                                c.logger.Object(vmi).Infof("migration is block migration because of %s volume", volume.Name)
8✔
1883
                        }
8✔
1884
                        blockMigrate = true
8✔
1885
                }
1886
        }
1887
        return
65✔
1888
}
1889

UNCOV
1890
func isVMIPausedDuringMigration(vmi *v1.VirtualMachineInstance) bool {
×
UNCOV
1891
        return vmi.Status.MigrationState != nil &&
×
UNCOV
1892
                vmi.Status.MigrationState.Mode == v1.MigrationPaused &&
×
UNCOV
1893
                !vmi.Status.MigrationState.Completed
×
UNCOV
1894
}
×
1895

1896
func (c *VirtualMachineController) vmUpdateHelperDefault(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
25✔
1897
        client, err := c.launcherClients.GetLauncherClient(vmi)
25✔
1898
        if err != nil {
25✔
UNCOV
1899
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
UNCOV
1900
        }
×
1901

1902
        preallocatedVolumes := c.getPreallocatedVolumes(vmi)
25✔
1903

25✔
1904
        err = hostdisk.ReplacePVCByHostDisk(vmi)
25✔
1905
        if err != nil {
25✔
1906
                return err
×
1907
        }
×
1908

1909
        cgroupManager, err := getCgroupManager(vmi, c.host, c.hypervisorNodeInfo, c.clusterConfig.AllowEmulation())
25✔
1910
        if err != nil {
25✔
UNCOV
1911
                return err
×
1912
        }
×
1913

1914
        var errorTolerantFeaturesError []error
25✔
1915
        readyToProceed, err := c.handleVMIState(vmi, cgroupManager, &errorTolerantFeaturesError)
25✔
1916
        if err != nil {
29✔
1917
                return err
4✔
1918
        }
4✔
1919

1920
        if !readyToProceed {
23✔
1921
                return nil
2✔
1922
        }
2✔
1923

1924
        if c.pluginExecutor != nil && !vmi.IsRunning() {
22✔
1925
                if err := c.pluginExecutor.CallNodeHooks(pluginv1alpha1.NodeHookPreVMStart, vmi, c.host); err != nil {
3✔
1926
                        return err
×
UNCOV
1927
                }
×
1928
        }
1929

1930
        // Synchronize the VirtualMachineInstance state
1931
        err = c.syncVirtualMachine(client, vmi, preallocatedVolumes)
19✔
1932
        if err != nil {
19✔
1933
                return err
×
UNCOV
1934
        }
×
1935

1936
        if domain == nil {
22✔
1937
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Created.String(), VMIDefined)
3✔
1938
        }
3✔
1939

1940
        // Post-sync housekeeping
1941
        err = c.hypervisorRuntime.HandleHousekeeping(vmi, cgroupManager, domain)
19✔
1942
        if err != nil {
19✔
UNCOV
1943
                return err
×
UNCOV
1944
        }
×
1945

1946
        if vmi.IsRunning() {
35✔
1947
                // Umount any disks no longer mounted
16✔
1948
                if err := c.hotplugVolumeMounter.Unmount(vmi, cgroupManager); err != nil {
16✔
1949
                        return err
×
UNCOV
1950
                }
×
1951
        }
1952

1953
        return errors.NewAggregate(errorTolerantFeaturesError)
19✔
1954
}
1955

1956
// handleVMIState: Decides whether to call handleRunningVMI or handleStartingVMI based on the VMI's state.
1957
func (c *VirtualMachineController) handleVMIState(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) (bool, error) {
25✔
1958
        if vmi.IsRunning() {
42✔
1959
                return true, c.handleRunningVMI(vmi, cgroupManager, errorTolerantFeaturesError)
17✔
1960
        } else if !vmi.IsFinal() {
33✔
1961
                return c.handleStartingVMI(vmi, cgroupManager)
8✔
1962
        }
8✔
1963
        return true, nil
×
1964
}
1965

1966
// handleRunningVMI contains the logic specifically for running VMs (hotplugging in running state, metrics, network updates)
1967
func (c *VirtualMachineController) handleRunningVMI(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) error {
17✔
1968
        if err := c.hotplugSriovInterfaces(vmi); err != nil {
17✔
UNCOV
1969
                c.logger.Object(vmi).Error(err.Error())
×
UNCOV
1970
        }
×
1971

1972
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
20✔
1973
                switch {
3✔
1974
                case goerror.Is(err, hotplugvolume.ErrWaitingForHotplugMount):
1✔
1975
                        c.logger.Object(vmi).V(4).Infof("waiting for hotplug volumes to be mounted: %v", err)
1✔
1976
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
1977
                case goerror.Is(err, os.ErrNotExist):
1✔
1978
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, "HotplugFailed", err.Error())
1✔
1979
                default:
1✔
1980
                        return err
1✔
1981
                }
1982
        }
1983

1984
        if err := c.getMemoryDump(vmi); err != nil {
16✔
UNCOV
1985
                return err
×
UNCOV
1986
        }
×
1987

1988
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
16✔
1989
        if err != nil {
16✔
1990
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
1991
        }
×
1992

1993
        if err := c.downwardMetricsManager.StartServer(vmi, isolationRes.Pid()); err != nil {
16✔
1994
                return err
×
1995
        }
×
1996

1997
        if err := c.setupNetwork(vmi, netsetup.FilterNetsForLiveUpdate(vmi), c.netConf); err != nil {
16✔
UNCOV
1998
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, "NicHotplug", err.Error())
×
UNCOV
1999
                *errorTolerantFeaturesError = append(*errorTolerantFeaturesError, err)
×
UNCOV
2000
        }
×
2001

2002
        return nil
16✔
2003
}
2004

2005
// handleStartingVMI: Contains the logic for starting VMs (container disks, initial network setup, device ownership).
2006
func (c *VirtualMachineController) handleStartingVMI(
2007
        vmi *v1.VirtualMachineInstance,
2008
        cgroupManager cgroup.Manager,
2009
) (bool, error) {
8✔
2010
        // give containerDisks some time to become ready before throwing errors on retries
8✔
2011
        info := c.launcherClients.GetLauncherClientInfo(vmi)
8✔
2012
        if ready, err := c.containerDiskMounter.ContainerDisksReady(vmi, info.NotInitializedSince); !ready {
10✔
2013
                if err != nil {
3✔
2014
                        return false, err
1✔
2015
                }
1✔
2016
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
2017
                return false, nil
1✔
2018
        }
2019

2020
        var err error
6✔
2021
        err = c.containerDiskMounter.MountAndVerify(vmi)
6✔
2022
        if err != nil {
7✔
2023
                return false, err
1✔
2024
        }
1✔
2025

2026
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
5✔
UNCOV
2027
                switch {
×
UNCOV
2028
                case goerror.Is(err, hotplugvolume.ErrWaitingForHotplugMount):
×
2029
                        c.logger.Object(vmi).V(4).Infof("waiting for hotplug volumes to be mounted: %v", err)
×
2030
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
UNCOV
2031
                case goerror.Is(err, os.ErrNotExist):
×
UNCOV
2032
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, "HotplugFailed", err.Error())
×
UNCOV
2033
                default:
×
UNCOV
2034
                        return false, err
×
2035
                }
2036
        }
2037

2038
        if !c.hotplugVolumesReady(vmi) {
6✔
2039
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
2040
                return false, nil
1✔
2041
        }
1✔
2042

2043
        if err := c.setupNetwork(vmi, netsetup.FilterNetsForVMStartup(vmi), c.netConf); err != nil {
5✔
2044
                return false, fmt.Errorf("failed to configure vmi network: %w", err)
1✔
2045
        }
1✔
2046

2047
        if err := c.setupDevicesOwnerships(vmi, c.recorder); err != nil {
3✔
UNCOV
2048
                return false, err
×
UNCOV
2049
        }
×
2050

2051
        if err := c.adjustResources(vmi); err != nil {
3✔
2052
                return false, err
×
UNCOV
2053
        }
×
2054

2055
        if c.shouldWaitForSEVAttestation(vmi) {
3✔
UNCOV
2056
                return false, nil
×
2057
        }
×
2058

2059
        return true, nil
3✔
2060
}
2061

2062
func (c *VirtualMachineController) adjustResources(vmi *v1.VirtualMachineInstance) error {
3✔
2063
        err := c.hypervisorRuntime.AdjustResources(vmi, c.clusterConfig.GetConfig())
3✔
2064

3✔
2065
        if err != nil {
3✔
UNCOV
2066
                return fmt.Errorf("failed to adjust resources: %v", err)
×
UNCOV
2067
        }
×
2068
        return nil
3✔
2069
}
2070

2071
func (c *VirtualMachineController) shouldWaitForSEVAttestation(vmi *v1.VirtualMachineInstance) bool {
3✔
2072
        if util.IsSEVAttestationRequested(vmi) {
3✔
UNCOV
2073
                sev := vmi.Spec.Domain.LaunchSecurity.SEV
×
UNCOV
2074
                // Wait for the session parameters to be provided
×
UNCOV
2075
                return sev.Session == "" || sev.DHCert == ""
×
UNCOV
2076
        }
×
2077
        return false
3✔
2078
}
2079

2080
func (c *VirtualMachineController) syncVirtualMachine(client cmdclient.LauncherClient, vmi *v1.VirtualMachineInstance, preallocatedVolumes []string) error {
19✔
2081
        smbios := c.clusterConfig.GetSMBIOS()
19✔
2082
        period := c.clusterConfig.GetMemBalloonStatsPeriod()
19✔
2083

19✔
2084
        options := virtualMachineOptions(smbios, period, preallocatedVolumes, c.capabilities, c.clusterConfig)
19✔
2085
        options.InterfaceDomainAttachment = domainspec.DomainAttachmentByInterfaceName(vmi.Spec.Domain.Devices.Interfaces, c.clusterConfig.GetNetworkBindings())
19✔
2086
        pluginsJSON, err := c.serializePlugins()
19✔
2087
        if err != nil {
19✔
2088
                return err
×
UNCOV
2089
        }
×
2090
        options.PluginsJson = pluginsJSON
19✔
2091

19✔
2092
        err = client.SyncVirtualMachine(vmi, options)
19✔
2093
        if err != nil {
19✔
UNCOV
2094
                if strings.Contains(err.Error(), "EFI OVMF rom missing") {
×
2095
                        return &virtLauncherCriticalSecurebootError{fmt.Sprintf("mismatch of Secure Boot setting and bootloaders: %v", err)}
×
2096
                }
×
2097
        }
2098

2099
        return err
19✔
2100
}
2101

2102
func (c *VirtualMachineController) getPreallocatedVolumes(vmi *v1.VirtualMachineInstance) []string {
25✔
2103
        var preallocatedVolumes []string
25✔
2104
        for _, volumeStatus := range vmi.Status.VolumeStatus {
29✔
2105
                if volumeStatus.PersistentVolumeClaimInfo != nil && volumeStatus.PersistentVolumeClaimInfo.Preallocated {
4✔
2106
                        preallocatedVolumes = append(preallocatedVolumes, volumeStatus.Name)
×
2107
                }
×
2108
        }
2109
        return preallocatedVolumes
25✔
2110
}
2111

2112
func (c *VirtualMachineController) hotplugSriovInterfaces(vmi *v1.VirtualMachineInstance) error {
17✔
2113
        sriovSpecInterfaces := netvmispec.FilterSRIOVInterfaces(vmi.Spec.Domain.Devices.Interfaces)
17✔
2114

17✔
2115
        sriovSpecIfacesNames := netvmispec.IndexInterfaceSpecByName(sriovSpecInterfaces)
17✔
2116
        attachedSriovStatusIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
17✔
2117
                _, exist := sriovSpecIfacesNames[iface.Name]
×
UNCOV
2118
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceDomain) &&
×
2119
                        netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
UNCOV
2120
        })
×
2121

2122
        desiredSriovMultusPluggedIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
17✔
2123
                _, exist := sriovSpecIfacesNames[iface.Name]
×
2124
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
2125
        })
×
2126

2127
        if len(desiredSriovMultusPluggedIfaces) == len(attachedSriovStatusIfaces) {
34✔
2128
                c.sriovHotplugExecutorPool.Delete(vmi.UID)
17✔
2129
                return nil
17✔
2130
        }
17✔
2131

UNCOV
2132
        rateLimitedExecutor := c.sriovHotplugExecutorPool.LoadOrStore(vmi.UID)
×
UNCOV
2133
        return rateLimitedExecutor.Exec(func() error {
×
UNCOV
2134
                return c.hotplugSriovInterfacesCommand(vmi)
×
2135
        })
×
2136
}
2137

2138
func (c *VirtualMachineController) hotplugSriovInterfacesCommand(vmi *v1.VirtualMachineInstance) error {
×
UNCOV
2139
        const errMsgPrefix = "failed to hot-plug SR-IOV interfaces"
×
2140

×
2141
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
×
2142
        if err != nil {
×
2143
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2144
        }
×
2145

UNCOV
2146
        if err := c.hypervisorRuntime.AdjustResources(vmi, c.clusterConfig.GetConfig()); err != nil {
×
UNCOV
2147
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), err.Error())
×
UNCOV
2148
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
UNCOV
2149
        }
×
2150

UNCOV
2151
        c.logger.V(3).Object(vmi).Info("sending hot-plug host-devices command")
×
UNCOV
2152
        if err := client.HotplugHostDevices(vmi); err != nil {
×
UNCOV
2153
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
UNCOV
2154
        }
×
2155

UNCOV
2156
        return nil
×
2157
}
2158

2159
func memoryDumpPath(volumeStatus v1.VolumeStatus) string {
×
2160
        target := hotplugdisk.GetVolumeMountDir(volumeStatus.Name)
×
UNCOV
2161
        dumpPath := filepath.Join(target, volumeStatus.MemoryDumpVolume.TargetFileName)
×
UNCOV
2162
        return dumpPath
×
UNCOV
2163
}
×
2164

2165
func (c *VirtualMachineController) getMemoryDump(vmi *v1.VirtualMachineInstance) error {
16✔
2166
        const errMsgPrefix = "failed to getting memory dump"
16✔
2167

16✔
2168
        for _, volumeStatus := range vmi.Status.VolumeStatus {
19✔
2169
                if volumeStatus.MemoryDumpVolume == nil || volumeStatus.Phase != v1.MemoryDumpVolumeInProgress {
6✔
2170
                        continue
3✔
2171
                }
UNCOV
2172
                client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
×
2173
                if err != nil {
×
UNCOV
2174
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
UNCOV
2175
                }
×
2176

UNCOV
2177
                c.logger.V(3).Object(vmi).Info("sending memory dump command")
×
UNCOV
2178
                err = client.VirtualMachineMemoryDump(vmi, memoryDumpPath(volumeStatus))
×
UNCOV
2179
                if err != nil {
×
UNCOV
2180
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
UNCOV
2181
                }
×
2182
        }
2183

2184
        return nil
16✔
2185
}
2186

2187
func (c *VirtualMachineController) hotplugVolumesReady(vmi *v1.VirtualMachineInstance) bool {
5✔
2188
        hasHotplugVolume := false
5✔
2189
        for _, v := range vmi.Spec.Volumes {
6✔
2190
                if storagetypes.IsHotplugVolume(&v) {
2✔
2191
                        hasHotplugVolume = true
1✔
2192
                        break
1✔
2193
                }
2194
        }
2195
        if len(vmi.Spec.UtilityVolumes) > 0 {
5✔
UNCOV
2196
                hasHotplugVolume = true
×
UNCOV
2197
        }
×
2198
        if !hasHotplugVolume {
9✔
2199
                return true
4✔
2200
        }
4✔
2201
        if len(vmi.Status.VolumeStatus) == 0 {
1✔
UNCOV
2202
                return false
×
UNCOV
2203
        }
×
2204
        for _, vs := range vmi.Status.VolumeStatus {
2✔
2205
                if vs.HotplugVolume != nil && !(vs.Phase == v1.VolumeReady || vs.Phase == v1.HotplugVolumeMounted) {
2✔
2206
                        // wait for volume to be mounted
1✔
2207
                        return false
1✔
2208
                }
1✔
2209
        }
UNCOV
2210
        return true
×
2211
}
2212

2213
func (c *VirtualMachineController) processVmUpdate(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
26✔
2214
        shouldReturn, err := c.checkLauncherClient(vmi)
26✔
2215
        if shouldReturn {
27✔
2216
                return err
1✔
2217
        }
1✔
2218

2219
        return c.vmUpdateHelperDefault(vmi, domain)
25✔
2220
}
2221

2222
func (c *VirtualMachineController) setVmPhaseForStatusReason(domain *api.Domain, vmi *v1.VirtualMachineInstance) error {
43✔
2223
        phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
43✔
2224
        if err != nil {
43✔
UNCOV
2225
                return err
×
UNCOV
2226
        }
×
2227
        if phase == v1.Failed && vmi.Status.Phase != v1.Failed {
52✔
2228
                panicInfo := c.getGuestPanicInfo(domain, vmi)
9✔
2229
                if panicInfo != nil {
13✔
2230
                        panicType := "unknown"
4✔
2231
                        if panicInfo.Type != "" {
6✔
2232
                                panicType = panicInfo.Type
2✔
2233
                        }
2✔
2234
                        bugcheckCode := panicInfo.Arg1
4✔
2235
                        vhmetrics.IncGuestOSPanic(vmi.Namespace, vmi.Name, panicType, bugcheckCode)
4✔
2236
                }
2237
        }
2238
        vmi.Status.Phase = phase
43✔
2239
        return nil
43✔
2240
}
2241

2242
func (c *VirtualMachineController) getGuestPanicInfo(domain *api.Domain, vmi *v1.VirtualMachineInstance) *api.GuestPanicInfo {
9✔
2243
        if domain != nil {
14✔
2244
                return domain.Status.GuestPanicInfo
5✔
2245
        }
5✔
2246
        obj, exists, err := c.domainStore.GetByKey(controller.VirtualMachineInstanceKey(vmi))
4✔
2247
        if err != nil || !exists {
7✔
2248
                return nil
3✔
2249
        }
3✔
2250
        if d, ok := obj.(*api.Domain); ok {
2✔
2251
                return d.Status.GuestPanicInfo
1✔
2252
        }
1✔
UNCOV
2253
        return nil
×
2254
}
2255

2256
func vmiHasTerminationGracePeriod(vmi *v1.VirtualMachineInstance) bool {
×
2257
        // if not set we use the default graceperiod
×
UNCOV
2258
        return vmi.Spec.TerminationGracePeriodSeconds == nil ||
×
UNCOV
2259
                (vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds != 0)
×
UNCOV
2260
}
×
2261

2262
func domainHasGracePeriod(domain *api.Domain) bool {
8✔
2263
        return domain != nil &&
8✔
2264
                domain.Spec.Metadata.KubeVirt.GracePeriod != nil &&
8✔
2265
                domain.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds != 0
8✔
2266
}
8✔
2267

2268
func isACPIEnabled(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
×
UNCOV
2269
        return (vmiHasTerminationGracePeriod(vmi) || (vmi.Spec.TerminationGracePeriodSeconds == nil && domainHasGracePeriod(domain))) &&
×
UNCOV
2270
                domain != nil &&
×
UNCOV
2271
                domain.Spec.Features != nil &&
×
UNCOV
2272
                domain.Spec.Features.ACPI != nil
×
UNCOV
2273
}
×
2274

2275
func (c *VirtualMachineController) calculateVmPhaseForStatusReason(domain *api.Domain, vmi *v1.VirtualMachineInstance) (v1.VirtualMachineInstancePhase, error) {
80✔
2276

80✔
2277
        if domain == nil {
107✔
2278
                if vmi.IsMigrationTarget() && vmi.Status.MigrationState != nil && vmi.Status.MigrationState.Failed {
27✔
2279
                        return vmi.Status.Phase, nil
×
2280
                }
×
2281
                switch {
27✔
2282
                case vmi.IsScheduled():
24✔
2283
                        isUnresponsive, isInitialized, err := c.launcherClients.IsLauncherClientUnresponsive(vmi)
24✔
2284

24✔
2285
                        if err != nil {
24✔
2286
                                return vmi.Status.Phase, err
×
2287
                        }
×
2288
                        if !isInitialized {
26✔
2289
                                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
2✔
2290
                                return vmi.Status.Phase, err
2✔
2291
                        } else if isUnresponsive {
28✔
2292
                                if vmi.IsMigrationTarget() {
4✔
2293
                                        return v1.WaitingForSync, nil
×
2294
                                }
×
2295
                                // virt-launcher is gone and VirtualMachineInstance never transitioned
2296
                                // from scheduled to Running.
2297
                                return v1.Failed, nil
4✔
2298
                        }
2299
                        return v1.Scheduled, nil
18✔
UNCOV
2300
                case !vmi.IsRunning() && !vmi.IsFinal():
×
UNCOV
2301
                        return v1.Scheduled, nil
×
2302
                case !vmi.IsFinal():
3✔
2303
                        if vmi.IsMigrationTarget() {
3✔
UNCOV
2304
                                return v1.WaitingForSync, nil
×
UNCOV
2305
                        }
×
2306
                        // That is unexpected. We should not be able to delete a VirtualMachineInstance before we stop it.
2307
                        // However, if someone directly interacts with libvirt it is possible
2308
                        return v1.Failed, nil
3✔
2309
                }
2310
        } else {
53✔
2311
                switch domain.Status.Status {
53✔
2312
                case api.Shutoff, api.Crashed:
5✔
2313
                        switch domain.Status.Reason {
5✔
2314
                        case api.ReasonCrashed, api.ReasonPanicked:
5✔
2315
                                return v1.Failed, nil
5✔
2316
                        case api.ReasonDestroyed:
×
UNCOV
2317
                                if isACPIEnabled(vmi, domain) {
×
UNCOV
2318
                                        // When ACPI is available, the domain was tried to be shutdown,
×
2319
                                        // and destroyed means that the domain was destroyed after the graceperiod expired.
×
2320
                                        // Without ACPI a destroyed domain is ok.
×
2321
                                        return v1.Failed, nil
×
2322
                                }
×
2323
                                if vmi.Status.MigrationState != nil && vmi.Status.MigrationState.Failed && vmi.Status.MigrationState.Mode == v1.MigrationPostCopy {
×
2324
                                        // A VMI that failed a post-copy migration should never succeed
×
UNCOV
2325
                                        return v1.Failed, nil
×
UNCOV
2326
                                }
×
2327
                                return v1.Succeeded, nil
×
2328
                        case api.ReasonShutdown, api.ReasonSaved, api.ReasonFromSnapshot:
×
2329
                                return v1.Succeeded, nil
×
2330
                        case api.ReasonMigrated:
×
2331
                                // if the domain migrated, we no longer know the phase.
×
UNCOV
2332
                                return vmi.Status.Phase, nil
×
2333
                        }
2334
                case api.Paused:
4✔
2335
                        switch domain.Status.Reason {
4✔
2336
                        case api.ReasonPausedPostcopyFailed:
2✔
2337
                                return v1.Failed, nil
2✔
2338
                        default:
2✔
2339
                                return v1.Running, nil
2✔
2340
                        }
2341
                case api.Running, api.Blocked, api.PMSuspended:
44✔
2342
                        return v1.Running, nil
44✔
2343
                }
2344
        }
2345
        return vmi.Status.Phase, nil
×
2346
}
2347

2348
func (c *VirtualMachineController) addDeleteFunc(obj interface{}) {
×
2349
        key, err := controller.KeyFunc(obj)
×
2350
        if err == nil {
×
2351
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
UNCOV
2352
                c.queue.Add(key)
×
2353
        }
×
2354
}
2355

2356
func (c *VirtualMachineController) updateFunc(_, new interface{}) {
×
2357
        key, err := controller.KeyFunc(new)
×
UNCOV
2358
        if err == nil {
×
UNCOV
2359
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
UNCOV
2360
                c.queue.Add(key)
×
UNCOV
2361
        }
×
2362
}
2363

UNCOV
2364
func (c *VirtualMachineController) addDomainFunc(obj interface{}) {
×
UNCOV
2365
        key, err := controller.KeyFunc(obj)
×
UNCOV
2366
        if err == nil {
×
UNCOV
2367
                c.queue.Add(key)
×
UNCOV
2368
        }
×
2369
}
UNCOV
2370
func (c *VirtualMachineController) deleteDomainFunc(obj interface{}) {
×
UNCOV
2371
        domain, ok := obj.(*api.Domain)
×
UNCOV
2372
        if !ok {
×
UNCOV
2373
                tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
×
UNCOV
2374
                if !ok {
×
UNCOV
2375
                        c.logger.Reason(fmt.Errorf("couldn't get object from tombstone %+v", obj)).Error("Failed to process delete notification")
×
UNCOV
2376
                        return
×
UNCOV
2377
                }
×
UNCOV
2378
                domain, ok = tombstone.Obj.(*api.Domain)
×
UNCOV
2379
                if !ok {
×
UNCOV
2380
                        c.logger.Reason(fmt.Errorf("tombstone contained object that is not a domain %#v", obj)).Error("Failed to process delete notification")
×
UNCOV
2381
                        return
×
UNCOV
2382
                }
×
2383
        }
UNCOV
2384
        c.logger.V(3).Object(domain).Info("Domain deleted")
×
UNCOV
2385
        key, err := controller.KeyFunc(obj)
×
UNCOV
2386
        if err == nil {
×
UNCOV
2387
                c.queue.Add(key)
×
UNCOV
2388
        }
×
2389
}
UNCOV
2390
func (c *VirtualMachineController) updateDomainFunc(_, new interface{}) {
×
UNCOV
2391
        key, err := controller.KeyFunc(new)
×
UNCOV
2392
        if err == nil {
×
UNCOV
2393
                c.queue.Add(key)
×
UNCOV
2394
        }
×
2395
}
2396

2397
func (c *VirtualMachineController) isHostModelMigratable(vmi *v1.VirtualMachineInstance) error {
102✔
2398
        if cpu := vmi.Spec.Domain.CPU; cpu != nil && cpu.Model == v1.CPUModeHostModel {
104✔
2399
                if c.hostCpuModel == "" {
3✔
2400
                        err := fmt.Errorf("the node \"%s\" does not allow migration with host-model", vmi.Status.NodeName)
1✔
2401
                        c.logger.Object(vmi).Errorf("%s", err.Error())
1✔
2402
                        return err
1✔
2403
                }
1✔
2404
        }
2405
        return nil
101✔
2406
}
2407

2408
func isIOError(shouldUpdate, domainExists bool, domain *api.Domain) bool {
37✔
2409
        return shouldUpdate && domainExists && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedIOError
37✔
2410
}
37✔
2411

2412
func (c *VirtualMachineController) updateMachineType(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
37✔
2413
        if domain == nil || vmi == nil {
50✔
2414
                return
13✔
2415
        }
13✔
2416
        if domain.Spec.OS.Type.Machine != "" {
25✔
2417
                vmi.Status.Machine = &v1.Machine{Type: domain.Spec.OS.Type.Machine}
1✔
2418
        }
1✔
2419
}
2420

2421
func parseLibvirtQuantity(value int64, unit string) *resource.Quantity {
15✔
2422
        switch unit {
15✔
2423
        case "b", "bytes":
2✔
2424
                return resource.NewQuantity(value, resource.BinarySI)
2✔
2425
        case "KB":
1✔
2426
                return resource.NewQuantity(value*1000, resource.DecimalSI)
1✔
2427
        case "MB":
1✔
2428
                return resource.NewQuantity(value*1000*1000, resource.DecimalSI)
1✔
2429
        case "GB":
1✔
2430
                return resource.NewQuantity(value*1000*1000*1000, resource.DecimalSI)
1✔
2431
        case "TB":
1✔
2432
                return resource.NewQuantity(value*1000*1000*1000*1000, resource.DecimalSI)
1✔
2433
        case "k", "KiB":
3✔
2434
                return resource.NewQuantity(value*1024, resource.BinarySI)
3✔
2435
        case "M", "MiB":
2✔
2436
                return resource.NewQuantity(value*1024*1024, resource.BinarySI)
2✔
2437
        case "G", "GiB":
2✔
2438
                return resource.NewQuantity(value*1024*1024*1024, resource.BinarySI)
2✔
2439
        case "T", "TiB":
2✔
2440
                return resource.NewQuantity(value*1024*1024*1024*1024, resource.BinarySI)
2✔
2441
        }
UNCOV
2442
        return nil
×
2443
}
2444

2445
func (c *VirtualMachineController) updateBackupStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
42✔
2446
        if domain == nil ||
42✔
2447
                domain.Spec.Metadata.KubeVirt.Backup == nil ||
42✔
2448
                vmi.Status.ChangedBlockTracking == nil ||
42✔
2449
                vmi.Status.ChangedBlockTracking.BackupStatus == nil {
81✔
2450
                return
39✔
2451
        }
39✔
2452
        backupMetadata := domain.Spec.Metadata.KubeVirt.Backup
3✔
2453
        // Handle the case where a new backupStatus was initiated but
3✔
2454
        // the backupMetadata wasnt reinitialized yet
3✔
2455
        timestampMatch := backupMetadata.StartTimestamp.Equal(vmi.Status.ChangedBlockTracking.BackupStatus.StartTimestamp)
3✔
2456
        if vmi.Status.ChangedBlockTracking.BackupStatus.BackupName != backupMetadata.Name || !timestampMatch {
5✔
2457
                return
2✔
2458
        }
2✔
2459
        vmi.Status.ChangedBlockTracking.BackupStatus.Completed = backupMetadata.Completed
1✔
2460
        vmi.Status.ChangedBlockTracking.BackupStatus.Failed = backupMetadata.Failed
1✔
2461
        if backupMetadata.StartTimestamp != nil {
2✔
2462
                vmi.Status.ChangedBlockTracking.BackupStatus.StartTimestamp = backupMetadata.StartTimestamp
1✔
2463
        }
1✔
2464
        if backupMetadata.EndTimestamp != nil {
2✔
2465
                vmi.Status.ChangedBlockTracking.BackupStatus.EndTimestamp = backupMetadata.EndTimestamp
1✔
2466
        }
1✔
2467
        if backupMetadata.BackupMsg != "" {
2✔
2468
                vmi.Status.ChangedBlockTracking.BackupStatus.BackupMsg = &backupMetadata.BackupMsg
1✔
2469
        }
1✔
2470
        if backupMetadata.CheckpointName != "" {
2✔
2471
                vmi.Status.ChangedBlockTracking.BackupStatus.CheckpointName = &backupMetadata.CheckpointName
1✔
2472
        }
1✔
2473
        if backupMetadata.Volumes != "" {
2✔
2474
                var volumes []backupv1.BackupVolumeInfo
1✔
2475
                if err := json.Unmarshal([]byte(backupMetadata.Volumes), &volumes); err == nil && len(volumes) > 0 {
2✔
2476
                        vmi.Status.ChangedBlockTracking.BackupStatus.Volumes = volumes
1✔
2477
                }
1✔
2478
        }
2479
}
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