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

kubevirt / kubevirt / 8d1f799c-a21c-45b4-8521-1cb61f922265

31 May 2026 09:21PM UTC coverage: 71.597% (+0.009%) from 71.588%
8d1f799c-a21c-45b4-8521-1cb61f922265

push

prow

web-flow
Merge pull request #17836 from iholder101/prometheus-panic-metric

Add Prometheus metric for guest OS panic events

37 of 51 new or added lines in 4 files covered. (72.55%)

16 existing lines in 3 files now uncovered.

78417 of 109525 relevant lines covered (71.6%)

555.12 hits per line

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

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

88
type netstat interface {
89
        UpdateStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) error
90
        Teardown(vmi *v1.VirtualMachineInstance)
91
}
92

93
type downwardMetricsManager interface {
94
        Run(stopCh chan struct{})
95
        StartServer(vmi *v1.VirtualMachineInstance, pid int) error
96
        StopServer(vmi *v1.VirtualMachineInstance)
97
}
98

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

119
var getCgroupManager = func(vmi *v1.VirtualMachineInstance, host string, hypervisorNodeInfo hypervisor.HypervisorNodeInformation, allowEmulation bool) (cgroup.Manager, error) {
×
120
        return cgroup.NewManagerFromVM(vmi, host, hypervisorNodeInfo.GetHypervisorDevice(), allowEmulation)
×
121
}
×
122

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

130✔
146
        queue := workqueue.NewTypedRateLimitingQueueWithConfig[string](
130✔
147
                workqueue.DefaultTypedControllerRateLimiter[string](),
130✔
148
                workqueue.TypedRateLimitingQueueConfig[string]{Name: "virt-handler-vm"},
130✔
149
        )
130✔
150
        logger := log.Log.With("controller", "vm")
130✔
151

130✔
152
        hypervisorName := clusterConfig.GetHypervisor().Name
130✔
153

130✔
154
        baseCtrl, err := NewBaseController(
130✔
155
                logger,
130✔
156
                host,
130✔
157
                recorder,
130✔
158
                clientset,
130✔
159
                queue,
130✔
160
                vmiInformer,
130✔
161
                domainInformer,
130✔
162
                clusterConfig,
130✔
163
                podIsolationDetector,
130✔
164
                launcherClients,
130✔
165
                migrationProxy,
130✔
166
                "/proc/%d/root/var/run",
130✔
167
                netStat,
130✔
168
                hypervisor.NewHypervisorNodeInformation(hypervisorName),
130✔
169
                hypervisor.GetVirtRuntime(podIsolationDetector, hypervisorName),
130✔
170
        )
130✔
171
        if err != nil {
130✔
172
                return nil, err
×
173
        }
×
174

175
        containerDiskState := filepath.Join(virtPrivateDir, "container-disk-mount-state")
130✔
176
        if err := os.MkdirAll(containerDiskState, 0700); err != nil {
130✔
177
                return nil, err
×
178
        }
×
179

180
        hotplugState := filepath.Join(virtPrivateDir, "hotplug-volume-mount-state")
130✔
181
        if err := os.MkdirAll(hotplugState, 0700); err != nil {
130✔
182
                return nil, err
×
183
        }
×
184

185
        c := &VirtualMachineController{
130✔
186
                BaseController:           baseCtrl,
130✔
187
                capabilities:             capabilities,
130✔
188
                clientset:                clientset,
130✔
189
                containerDiskMounter:     containerdisk.NewMounter(podIsolationDetector, containerDiskState, clusterConfig),
130✔
190
                downwardMetricsManager:   downwardMetricsManager,
130✔
191
                hotplugVolumeMounter:     hotplugvolume.NewVolumeMounter(hotplugState, kubeletPodsDir, host),
130✔
192
                hostCpuModel:             hostCpuModel,
130✔
193
                ioErrorRetryManager:      NewFailRetryManager("io-error-retry", 10*time.Second, 3*time.Minute, 30*time.Second),
130✔
194
                heartBeatInterval:        1 * time.Minute,
130✔
195
                netConf:                  netConf,
130✔
196
                sriovHotplugExecutorPool: executor.NewRateLimitedExecutorPool(executor.NewExponentialLimitedBackoffCreator()),
130✔
197
                vmiExpectations:          controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()),
130✔
198
                vmiGlobalStore:           vmiGlobalStore,
130✔
199
                multipathSocketMonitor:   multipathmonitor.NewMultipathSocketMonitor(),
130✔
200
                cbtHandler:               cbtHandler,
130✔
201
        }
130✔
202

130✔
203
        _, err = vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
130✔
204
                AddFunc:    c.addDeleteFunc,
130✔
205
                DeleteFunc: c.addDeleteFunc,
130✔
206
                UpdateFunc: c.updateFunc,
130✔
207
        })
130✔
208
        if err != nil {
130✔
209
                return nil, err
×
210
        }
×
211

212
        _, err = domainInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
130✔
213
                AddFunc:    c.addDomainFunc,
130✔
214
                DeleteFunc: c.deleteDomainFunc,
130✔
215
                UpdateFunc: c.updateDomainFunc,
130✔
216
        })
130✔
217
        if err != nil {
130✔
218
                return nil, err
×
219
        }
×
220

221
        permissions := "rw"
130✔
222
        if cgroups.IsCgroup2UnifiedMode() {
260✔
223
                // Need 'rwm' permissions otherwise ebpf filtering program attached by runc
130✔
224
                // will deny probing the device file with 'access' syscall. That in turn
130✔
225
                // will lead to virtqemud failure on VM startup.
130✔
226
                // This has been fixed upstream:
130✔
227
                //   https://github.com/opencontainers/runc/pull/2796
130✔
228
                // but the workaround is still needed to support previous versions without
130✔
229
                // the patch.
130✔
230
                permissions = "rwm"
130✔
231
        }
130✔
232

233
        c.deviceManagerController = deviceManager.NewDeviceController(
130✔
234
                c.host,
130✔
235
                maxDevices,
130✔
236
                permissions,
130✔
237
                deviceManager.PermanentHostDevicePlugins(c.hypervisorNodeInfo.GetHypervisorDevice(), maxDevices, permissions),
130✔
238
                clusterConfig,
130✔
239
                nodeStore)
130✔
240
        c.heartBeat = heartbeat.NewHeartBeat(clientset.CoreV1(), c.deviceManagerController, clusterConfig, host)
130✔
241

130✔
242
        return c, nil
130✔
243
}
244

245
func (c *VirtualMachineController) Run(threadiness int, stopCh chan struct{}) {
×
246
        defer c.queue.ShutDown()
×
247
        c.logger.Info("Starting virt-handler vms controller.")
×
248

×
249
        go c.deviceManagerController.Run(stopCh)
×
250

×
251
        go c.downwardMetricsManager.Run(stopCh)
×
252

×
253
        cache.WaitForCacheSync(stopCh, c.hasSynced)
×
254

×
255
        // queue keys for previous Domains on the host that no longer exist
×
256
        // in the cache. This ensures we perform local cleanup of deleted VMs.
×
257
        for _, domain := range c.domainStore.List() {
×
258
                d := domain.(*api.Domain)
×
259
                vmiRef := v1.NewVMIReferenceWithUUID(
×
260
                        d.ObjectMeta.Namespace,
×
261
                        d.ObjectMeta.Name,
×
262
                        d.Spec.Metadata.KubeVirt.UID)
×
263

×
264
                key := controller.VirtualMachineInstanceKey(vmiRef)
×
265

×
266
                _, exists, _ := c.vmiStore.GetByKey(key)
×
267
                if !exists {
×
268
                        c.queue.Add(key)
×
269
                }
×
270
        }
271
        c.multipathSocketMonitor.Run()
×
272

×
273
        heartBeatDone := c.heartBeat.Run(c.heartBeatInterval, stopCh)
×
274

×
275
        go c.ioErrorRetryManager.Run(stopCh)
×
276

×
277
        // Start the actual work
×
278
        for i := 0; i < threadiness; i++ {
×
279
                go wait.Until(c.runWorker, time.Second, stopCh)
×
280
        }
×
281

282
        <-heartBeatDone
×
283
        <-stopCh
×
284
        c.multipathSocketMonitor.Close()
×
285
        c.logger.Info("Stopping virt-handler vms controller.")
×
286
}
287

288
func (c *VirtualMachineController) runWorker() {
×
289
        for c.Execute() {
×
290
        }
×
291
}
292

293
func (c *VirtualMachineController) Execute() bool {
51✔
294
        key, quit := c.queue.Get()
51✔
295
        if quit {
51✔
296
                return false
×
297
        }
×
298
        defer c.queue.Done(key)
51✔
299
        if err := c.execute(key); err != nil {
57✔
300
                c.logger.Reason(err).Infof("re-enqueuing VirtualMachineInstance %v", key)
6✔
301
                c.queue.AddRateLimited(key)
6✔
302
        } else {
51✔
303
                c.logger.V(4).Infof("processed VirtualMachineInstance %v", key)
45✔
304
                c.queue.Forget(key)
45✔
305
        }
45✔
306
        return true
51✔
307
}
308

309
func (c *VirtualMachineController) execute(key string) error {
51✔
310
        vmi, vmiExists, err := c.getVMIFromCache(key)
51✔
311
        if err != nil {
52✔
312
                return err
1✔
313
        }
1✔
314

315
        if !vmiExists {
58✔
316
                // the vmiInformer probably has to catch up to the domainInformer
8✔
317
                // which already sees the vmi, so let's fetch it from the global
8✔
318
                // vmi informer to make sure the vmi has actually been deleted
8✔
319
                c.logger.V(4).Infof("fetching vmi for key %v from the global informer", key)
8✔
320
                obj, exists, err := c.vmiGlobalStore.GetByKey(key)
8✔
321
                if err != nil {
8✔
322
                        return err
×
323
                }
×
324
                if exists {
8✔
325
                        vmi = obj.(*v1.VirtualMachineInstance)
×
326
                }
×
327
                vmiExists = exists
8✔
328
        }
329

330
        if !vmiExists {
58✔
331
                c.vmiExpectations.DeleteExpectations(key)
8✔
332
        } else if !c.vmiExpectations.SatisfiedExpectations(key) {
50✔
333
                return nil
×
334
        }
×
335

336
        domain, domainExists, domainCachedUID, err := c.getDomainFromCache(key)
50✔
337
        if err != nil {
50✔
338
                return err
×
339
        }
×
340
        c.logger.Object(vmi).V(4).Infof("domain exists %v", domainExists)
50✔
341

50✔
342
        if !vmiExists && string(domainCachedUID) != "" {
58✔
343
                // it's possible to discover the UID from cache even if the domain
8✔
344
                // doesn't technically exist anymore
8✔
345
                vmi.UID = domainCachedUID
8✔
346
                c.logger.Object(vmi).Infof("Using cached UID for vmi found in domain cache")
8✔
347
        }
8✔
348

349
        // As a last effort, if the UID still can't be determined attempt
350
        // to retrieve it from the ghost record
351
        if string(vmi.UID) == "" {
51✔
352
                uid := virtcache.GhostRecordGlobalStore.LastKnownUID(key)
1✔
353
                if uid != "" {
1✔
354
                        c.logger.Object(vmi).V(3).Infof("ghost record cache provided %s as UID", uid)
×
355
                        vmi.UID = uid
×
356
                }
×
357
        }
358

359
        if vmiExists && domainExists && domain.Spec.Metadata.KubeVirt.UID != vmi.UID {
51✔
360
                oldVMI := v1.NewVMIReferenceFromNameWithNS(vmi.Namespace, vmi.Name)
1✔
361
                oldVMI.UID = domain.Spec.Metadata.KubeVirt.UID
1✔
362
                expired, initialized, err := c.launcherClients.IsLauncherClientUnresponsive(oldVMI)
1✔
363
                if err != nil {
1✔
364
                        return err
×
365
                }
×
366
                // If we found an outdated domain which is also not alive anymore, clean up
367
                if !initialized {
1✔
368
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
369
                        return nil
×
370
                } else if expired {
1✔
371
                        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)
×
372
                        err = c.processVmCleanup(oldVMI)
×
373
                        if err != nil {
×
374
                                return err
×
375
                        }
×
376
                        // Make sure we re-enqueue the key to ensure this new VMI is processed
377
                        // after the stale domain is removed
378
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*5)
×
379
                }
380

381
                return nil
1✔
382
        }
383

384
        if domainExists &&
49✔
385
                (domainMigrated(domain) || domain.DeletionTimestamp != nil) {
49✔
386
                c.logger.Object(vmi).V(4).Info("detected orphan vmi")
×
387
                return c.deleteVM(vmi)
×
388
        }
×
389

390
        if migrations.IsMigrating(vmi) && (vmi.Status.Phase == v1.Failed) {
49✔
391
                c.logger.V(1).Infof("cleaning up VMI key %v as migration is in progress and the vmi is failed", key)
×
392
                err = c.processVmCleanup(vmi)
×
393
                if err != nil {
×
394
                        return err
×
395
                }
×
396
        }
397

398
        if vmi.DeletionTimestamp == nil && isMigrationInProgress(vmi, domain) {
51✔
399
                c.logger.V(4).Infof("ignoring key %v as migration is in progress", key)
2✔
400
                return nil
2✔
401
        }
2✔
402

403
        if vmiExists && !c.isVMIOwnedByNode(vmi) {
48✔
404
                c.logger.Object(vmi).V(4).Info("ignoring vmi as it is not owned by this node")
1✔
405
                return nil
1✔
406
        }
1✔
407

408
        if vmiExists && vmi.IsMigrationSource() {
46✔
409
                c.logger.Object(vmi).V(4).Info("ignoring vmi as it is a migration source")
×
410
                return nil
×
411
        }
×
412

413
        err = c.sync(key,
46✔
414
                vmi.DeepCopy(),
46✔
415
                vmiExists,
46✔
416
                domain,
46✔
417
                domainExists)
46✔
418
        _, localExists, _ := c.getVMIFromCache(key)
46✔
419
        if !localExists {
54✔
420
                metrics.ResetVMISync(key)
8✔
421
        }
8✔
422
        return err
46✔
423

424
}
425

426
type vmiIrrecoverableError struct {
427
        msg string
428
}
429

430
func (e *vmiIrrecoverableError) Error() string { return e.msg }
3✔
431

432
func formatIrrecoverableErrorMessage(domain *api.Domain) string {
1✔
433
        msg := "unknown reason"
1✔
434
        if domainPausedFailedPostCopy(domain) {
2✔
435
                msg = "VMI is irrecoverable due to failed post-copy migration"
1✔
436
        }
1✔
437
        return msg
1✔
438
}
439

440
func domainPausedFailedPostCopy(domain *api.Domain) bool {
37✔
441
        return domain != nil && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedPostcopyFailed
37✔
442
}
37✔
443

444
// teardownNetwork performs network cache cleanup for a specific VMI.
445
func (c *VirtualMachineController) teardownNetwork(vmi *v1.VirtualMachineInstance) {
5✔
446
        if string(vmi.UID) == "" {
5✔
447
                return
×
448
        }
×
449
        if err := c.netConf.Teardown(vmi); err != nil {
5✔
450
                c.logger.Reason(err).Errorf("failed to delete VMI Network cache files: %s", err.Error())
×
451
        }
×
452
        c.netStat.Teardown(vmi)
5✔
453
}
454

455
func canUpdateToMounted(currentPhase v1.VolumePhase) bool {
12✔
456
        return currentPhase == v1.VolumeBound || currentPhase == v1.VolumePending || currentPhase == v1.HotplugVolumeAttachedToNode
12✔
457
}
12✔
458

459
func canUpdateToUnmounted(currentPhase v1.VolumePhase) bool {
8✔
460
        return currentPhase == v1.VolumeReady || currentPhase == v1.HotplugVolumeMounted || currentPhase == v1.HotplugVolumeAttachedToNode
8✔
461
}
8✔
462

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

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

521
func needToComputeChecksums(vmi *v1.VirtualMachineInstance) bool {
30✔
522
        containerDisks := map[string]*v1.Volume{}
30✔
523
        for _, volume := range vmi.Spec.Volumes {
33✔
524
                if volume.VolumeSource.ContainerDisk != nil {
5✔
525
                        containerDisks[volume.Name] = &volume
2✔
526
                }
2✔
527
        }
528

529
        for i := range vmi.Status.VolumeStatus {
34✔
530
                _, isContainerDisk := containerDisks[vmi.Status.VolumeStatus[i].Name]
4✔
531
                if !isContainerDisk {
7✔
532
                        continue
3✔
533
                }
534

535
                if vmi.Status.VolumeStatus[i].ContainerDiskVolume == nil ||
1✔
536
                        vmi.Status.VolumeStatus[i].ContainerDiskVolume.Checksum == 0 {
2✔
537
                        return true
1✔
538
                }
1✔
539
        }
540

541
        if util.HasKernelBootContainerImage(vmi) {
29✔
542
                if vmi.Status.KernelBootStatus == nil {
×
543
                        return true
×
544
                }
×
545

546
                kernelBootContainer := vmi.Spec.Domain.Firmware.KernelBoot.Container
×
547

×
548
                if kernelBootContainer.KernelPath != "" &&
×
549
                        (vmi.Status.KernelBootStatus.KernelInfo == nil ||
×
550
                                vmi.Status.KernelBootStatus.KernelInfo.Checksum == 0) {
×
551
                        return true
×
552

×
553
                }
×
554

555
                if kernelBootContainer.InitrdPath != "" &&
×
556
                        (vmi.Status.KernelBootStatus.InitrdInfo == nil ||
×
557
                                vmi.Status.KernelBootStatus.InitrdInfo.Checksum == 0) {
×
558
                        return true
×
559

×
560
                }
×
561
        }
562

563
        return false
29✔
564
}
565

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

578
        diskChecksums, err := c.containerDiskMounter.ComputeChecksums(vmi)
1✔
579
        if goerror.Is(err, containerdisk.ErrDiskContainerGone) {
1✔
580
                c.logger.Errorf("cannot compute checksums as containerdisk/kernelboot containers seem to have been terminated")
×
581
                return nil
×
582
        }
×
583
        if err != nil {
1✔
584
                return err
×
585
        }
×
586

587
        // containerdisks
588
        for i := range vmi.Status.VolumeStatus {
2✔
589
                checksum, exists := diskChecksums.ContainerDiskChecksums[vmi.Status.VolumeStatus[i].Name]
1✔
590
                if !exists {
1✔
591
                        // not a containerdisk
×
592
                        continue
×
593
                }
594

595
                vmi.Status.VolumeStatus[i].ContainerDiskVolume = &v1.ContainerDiskInfo{
1✔
596
                        Checksum: checksum,
1✔
597
                }
1✔
598
        }
599

600
        // kernelboot
601
        if util.HasKernelBootContainerImage(vmi) {
2✔
602
                vmi.Status.KernelBootStatus = &v1.KernelBootStatus{}
1✔
603

1✔
604
                if diskChecksums.KernelBootChecksum.Kernel != nil {
2✔
605
                        vmi.Status.KernelBootStatus.KernelInfo = &v1.KernelInfo{
1✔
606
                                Checksum: *diskChecksums.KernelBootChecksum.Kernel,
1✔
607
                        }
1✔
608
                }
1✔
609

610
                if diskChecksums.KernelBootChecksum.Initrd != nil {
2✔
611
                        vmi.Status.KernelBootStatus.InitrdInfo = &v1.InitrdInfo{
1✔
612
                                Checksum: *diskChecksums.KernelBootChecksum.Initrd,
1✔
613
                        }
1✔
614
                }
1✔
615
        }
616

617
        return nil
1✔
618
}
619

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

62✔
624
        if len(vmi.Status.VolumeStatus) == 0 {
96✔
625
                return false
34✔
626
        }
34✔
627

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

28✔
673
        return hasHotplug
28✔
674
}
675

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

37✔
678
        if domain == nil || domain.Status.OSInfo.Name == "" || vmi.Status.GuestOSInfo.Name == domain.Status.OSInfo.Name {
73✔
679
                return
36✔
680
        }
36✔
681

682
        vmi.Status.GuestOSInfo.Name = domain.Status.OSInfo.Name
1✔
683
        vmi.Status.GuestOSInfo.Version = domain.Status.OSInfo.Version
1✔
684
        vmi.Status.GuestOSInfo.KernelRelease = domain.Status.OSInfo.KernelRelease
1✔
685
        vmi.Status.GuestOSInfo.PrettyName = domain.Status.OSInfo.PrettyName
1✔
686
        vmi.Status.GuestOSInfo.VersionID = domain.Status.OSInfo.VersionId
1✔
687
        vmi.Status.GuestOSInfo.KernelVersion = domain.Status.OSInfo.KernelVersion
1✔
688
        vmi.Status.GuestOSInfo.Machine = domain.Status.OSInfo.Machine
1✔
689
        vmi.Status.GuestOSInfo.ID = domain.Status.OSInfo.Id
1✔
690
}
691

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

37✔
694
        if domain == nil || domain.Spec.Metadata.KubeVirt.AccessCredential == nil {
71✔
695
                return
34✔
696
        }
34✔
697

698
        message := domain.Spec.Metadata.KubeVirt.AccessCredential.Message
3✔
699
        status := k8sv1.ConditionFalse
3✔
700
        if domain.Spec.Metadata.KubeVirt.AccessCredential.Succeeded {
5✔
701
                status = k8sv1.ConditionTrue
2✔
702
        }
2✔
703

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

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

761
func guestAgentConnected(domain *api.Domain) bool {
37✔
762
        if domain != nil {
61✔
763
                for _, channel := range domain.Spec.Devices.Channels {
27✔
764
                        if channel.Target != nil && channel.Target.Name == "org.qemu.guest_agent.0" &&
3✔
765
                                channel.Target.State == "connected" {
5✔
766
                                return true
2✔
767
                        }
2✔
768
                }
769
        }
770
        return false
35✔
771
}
772

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

37✔
775
        // Update the condition when GA is connected
37✔
776
        switch {
37✔
777
        case channelConnected && !condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected):
1✔
778
                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
779
                        Type:          v1.VirtualMachineInstanceAgentConnected,
1✔
780
                        LastProbeTime: metav1.Now(),
1✔
781
                        Status:        k8sv1.ConditionTrue,
1✔
782
                }
1✔
783
                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
784
        case !channelConnected:
35✔
785
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAgentConnected)
35✔
786
        }
787

788
        if condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected) {
39✔
789
                client, err := c.launcherClients.GetLauncherClient(vmi)
2✔
790
                if err != nil {
2✔
791
                        return err
×
792
                }
×
793

794
                guestInfo, err := client.GetGuestInfo()
2✔
795
                if err != nil {
2✔
796
                        return err
×
797
                }
×
798

799
                var supported = false
2✔
800
                var reason = ""
2✔
801

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

818
                if !supported {
4✔
819
                        if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent) {
3✔
820
                                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
821
                                        Type:          v1.VirtualMachineInstanceUnsupportedAgent,
1✔
822
                                        LastProbeTime: metav1.Now(),
1✔
823
                                        Status:        k8sv1.ConditionTrue,
1✔
824
                                        Reason:        reason,
1✔
825
                                }
1✔
826
                                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
827
                        }
1✔
828
                } else {
×
829
                        condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent)
×
830
                }
×
831

832
        }
833
        return nil
37✔
834
}
835

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

37✔
838
        // Update paused condition in case VMI was paused / unpaused
37✔
839
        if domain != nil && domain.Status.Status == api.Paused {
39✔
840
                if !condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
4✔
841
                        c.calculatePausedCondition(vmi, domain.Status.Reason)
2✔
842
                }
2✔
843
        } else if condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
36✔
844
                c.logger.Object(vmi).V(3).Info("Removing paused condition")
1✔
845
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstancePaused)
1✔
846
        }
1✔
847
}
848

849
func dumpTargetFile(vmiName, volName string) string {
7✔
850
        targetFileName := fmt.Sprintf("%s-%s-%s.memory.dump", vmiName, volName, time.Now().Format("20060102-150405"))
7✔
851
        return targetFileName
7✔
852
}
7✔
853

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

891
        return volumeStatus, needsRefresh
5✔
892
}
893

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

37✔
896
        if domain == nil || domain.Status.FSFreezeStatus.Status == "" {
72✔
897
                return
35✔
898
        }
35✔
899

900
        if domain.Status.FSFreezeStatus.Status == api.FSThawed {
3✔
901
                vmi.Status.FSFreezeStatus = ""
1✔
902
        } else {
2✔
903
                vmi.Status.FSFreezeStatus = domain.Status.FSFreezeStatus.Status
1✔
904
        }
1✔
905

906
}
907

908
func IsoGuestVolumePath(namespace, name string, volume *v1.Volume) string {
×
909
        const basepath = "/var/run"
×
910
        switch {
×
911
        case volume.CloudInitNoCloud != nil:
×
912
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "noCloud.iso")
×
913
        case volume.CloudInitConfigDrive != nil:
×
914
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "configdrive.iso")
×
915
        case volume.ConfigMap != nil:
×
916
                return config.GetConfigMapDiskPath(volume.Name)
×
917
        case volume.DownwardAPI != nil:
×
918
                return config.GetDownwardAPIDiskPath(volume.Name)
×
919
        case volume.Secret != nil:
×
920
                return config.GetSecretDiskPath(volume.Name)
×
921
        case volume.ServiceAccount != nil:
×
922
                return config.GetServiceAccountDiskPath()
×
923
        case volume.Sysprep != nil:
×
924
                return config.GetSysprepDiskPath(volume.Name)
×
925
        default:
×
926
                return ""
×
927
        }
928
}
929

930
func (c *VirtualMachineController) updateIsoSizeStatus(vmi *v1.VirtualMachineInstance) {
37✔
931
        var podUID string
37✔
932
        if vmi.Status.Phase != v1.Running {
54✔
933
                return
17✔
934
        }
17✔
935

936
        for k, v := range vmi.Status.ActivePods {
33✔
937
                if v == vmi.Status.NodeName {
13✔
938
                        podUID = string(k)
×
939
                        break
×
940
                }
941
        }
942
        if podUID == "" {
40✔
943
                log.DefaultLogger().Warningf("failed to find pod UID for VMI %s", vmi.Name)
20✔
944
                return
20✔
945
        }
20✔
946

947
        volumes := make(map[string]v1.Volume)
×
948
        for _, volume := range vmi.Spec.Volumes {
×
949
                volumes[volume.Name] = volume
×
950
        }
×
951

952
        for _, disk := range vmi.Spec.Domain.Devices.Disks {
×
953
                volume, ok := volumes[disk.Name]
×
954
                if !ok {
×
955
                        log.DefaultLogger().Warningf("No matching volume with name %s found", disk.Name)
×
956
                        continue
×
957
                }
958

959
                volPath := IsoGuestVolumePath(vmi.Namespace, vmi.Name, &volume)
×
960
                if volPath == "" {
×
961
                        continue
×
962
                }
963

964
                res, err := c.podIsolationDetector.Detect(vmi)
×
965
                if err != nil {
×
966
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
967
                        continue
×
968
                }
969

970
                rootPath, err := res.MountRoot()
×
971
                if err != nil {
×
972
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
973
                        continue
×
974
                }
975

976
                safeVolPath, err := rootPath.AppendAndResolveWithRelativeRoot(volPath)
×
977
                if err != nil {
×
978
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
979
                        continue
×
980
                }
981
                fileInfo, err := safepath.StatAtNoFollow(safeVolPath)
×
982
                if err != nil {
×
983
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
984
                        continue
×
985
                }
986

987
                for i := range vmi.Status.VolumeStatus {
×
988
                        if vmi.Status.VolumeStatus[i].Name == volume.Name {
×
989
                                vmi.Status.VolumeStatus[i].Size = fileInfo.Size()
×
990
                                continue
×
991
                        }
992
                }
993
        }
994
}
995

996
func (c *VirtualMachineController) updateSELinuxContext(vmi *v1.VirtualMachineInstance) error {
37✔
997
        _, present, err := selinux.NewSELinux()
37✔
998
        if err != nil {
74✔
999
                return err
37✔
1000
        }
37✔
1001
        if present {
×
1002
                context, err := selinux.GetVirtLauncherContext(vmi)
×
1003
                if err != nil {
×
1004
                        return err
×
1005
                }
×
1006
                vmi.Status.SelinuxContext = context
×
1007
        } else {
×
1008
                vmi.Status.SelinuxContext = "none"
×
1009
        }
×
1010

1011
        return nil
×
1012
}
1013

1014
func (c *VirtualMachineController) updateMemoryInfo(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
37✔
1015
        if domain == nil || vmi == nil || domain.Spec.CurrentMemory == nil {
73✔
1016
                return nil
36✔
1017
        }
36✔
1018
        if vmi.Status.Memory == nil {
1✔
1019
                vmi.Status.Memory = &v1.MemoryStatus{}
×
1020
        }
×
1021
        currentGuest := parseLibvirtQuantity(int64(domain.Spec.CurrentMemory.Value), domain.Spec.CurrentMemory.Unit)
1✔
1022
        vmi.Status.Memory.GuestCurrent = currentGuest
1✔
1023
        return nil
1✔
1024
}
1025

1026
func (c *VirtualMachineController) updateVMIStatusFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
37✔
1027
        c.updateIsoSizeStatus(vmi)
37✔
1028
        err := c.updateSELinuxContext(vmi)
37✔
1029
        if err != nil {
74✔
1030
                c.logger.Reason(err).Errorf("couldn't find the SELinux context for %s", vmi.Name)
37✔
1031
        }
37✔
1032
        c.updateGuestInfoFromDomain(vmi, domain)
37✔
1033
        c.updateVolumeStatusesFromDomain(vmi, domain)
37✔
1034
        c.updateFSFreezeStatus(vmi, domain)
37✔
1035
        c.updateBackupStatus(vmi, domain)
37✔
1036
        c.updateMachineType(vmi, domain)
37✔
1037
        if err = c.updateMemoryInfo(vmi, domain); err != nil {
37✔
1038
                return err
×
1039
        }
×
1040
        if err = c.cbtHandler.HandleChangedBlockTracking(vmi, domain); err != nil {
37✔
1041
                return err
×
1042
        }
×
1043
        err = c.netStat.UpdateStatus(vmi, domain)
37✔
1044
        return err
37✔
1045
}
1046

1047
func (c *VirtualMachineController) updateVMIConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) error {
37✔
1048
        c.updateAccessCredentialConditions(vmi, domain, condManager)
37✔
1049
        c.updateLiveMigrationConditions(vmi, condManager)
37✔
1050
        err := c.updateGuestAgentConditions(vmi, guestAgentConnected(domain), condManager)
37✔
1051
        if err != nil {
37✔
1052
                return err
×
1053
        }
×
1054
        c.updatePausedConditions(vmi, domain, condManager)
37✔
1055

37✔
1056
        return nil
37✔
1057
}
1058

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

38✔
1062
        // Don't update the VirtualMachineInstance if it is already in a final state
38✔
1063
        if vmi.IsFinal() {
39✔
1064
                return nil
1✔
1065
        }
1✔
1066

1067
        // Update VMI status fields based on what is reported on the domain
1068
        err = c.updateVMIStatusFromDomain(vmi, domain)
37✔
1069
        if err != nil {
37✔
1070
                return err
×
1071
        }
×
1072

1073
        // Calculate the new VirtualMachineInstance state based on what libvirt reported
1074
        err = c.setVmPhaseForStatusReason(domain, vmi)
37✔
1075
        if err != nil {
37✔
1076
                return err
×
1077
        }
×
1078

1079
        // Update conditions on VMI Status
1080
        err = c.updateVMIConditions(vmi, domain, condManager)
37✔
1081
        if err != nil {
37✔
1082
                return err
×
1083
        }
×
1084

1085
        // Store containerdisks and kernelboot checksums
1086
        if err := c.updateChecksumInfo(vmi, syncError); err != nil {
37✔
1087
                return err
×
1088
        }
×
1089

1090
        // Handle sync error
1091
        c.handleSyncError(vmi, condManager, syncError)
37✔
1092

37✔
1093
        controller.SetVMIPhaseTransitionTimestamp(oldStatus, &vmi.Status)
37✔
1094

37✔
1095
        // Only issue vmi update if status has changed
37✔
1096
        if !equality.Semantic.DeepEqual(*oldStatus, vmi.Status) {
73✔
1097
                key := controller.VirtualMachineInstanceKey(vmi)
36✔
1098
                c.vmiExpectations.SetExpectations(key, 1, 0)
36✔
1099
                _, err := c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmi, metav1.UpdateOptions{})
36✔
1100
                if err != nil {
36✔
1101
                        c.vmiExpectations.SetExpectations(key, 0, 0)
×
1102
                        return err
×
1103
                }
×
1104
                metrics.VMISynced(vmi.Namespace, vmi.Name)
36✔
1105
        }
1106

1107
        // Record an event on the VMI when the VMI's phase changes
1108
        if oldStatus.Phase != vmi.Status.Phase {
47✔
1109
                c.recordPhaseChangeEvent(vmi)
10✔
1110
        }
10✔
1111

1112
        return nil
37✔
1113
}
1114

1115
type virtLauncherCriticalSecurebootError struct {
1116
        msg string
1117
}
1118

1119
func (e *virtLauncherCriticalSecurebootError) Error() string { return e.msg }
×
1120

1121
func (c *VirtualMachineController) handleSyncError(vmi *v1.VirtualMachineInstance, condManager *controller.VirtualMachineInstanceConditionManager, syncError error) {
37✔
1122
        var criticalNetErr *neterrors.CriticalNetworkError
37✔
1123
        if goerror.As(syncError, &criticalNetErr) {
38✔
1124
                c.logger.Errorf("virt-launcher crashed due to a network error. Updating VMI %s status to Failed", vmi.Name)
1✔
1125
                vmi.Status.Phase = v1.Failed
1✔
1126
        }
1✔
1127
        if _, ok := syncError.(*virtLauncherCriticalSecurebootError); ok {
37✔
1128
                c.logger.Errorf("virt-launcher does not support the Secure Boot setting. Updating VMI %s status to Failed", vmi.Name)
×
1129
                vmi.Status.Phase = v1.Failed
×
1130
        }
×
1131

1132
        if _, ok := syncError.(*vmiIrrecoverableError); ok {
38✔
1133
                c.logger.Errorf("virt-launcher reached an irrecoverable error. Updating VMI %s status to Failed", vmi.Name)
1✔
1134
                vmi.Status.Phase = v1.Failed
1✔
1135
        }
1✔
1136
        condManager.CheckFailure(vmi, syncError, "Synchronizing with the Domain failed.")
37✔
1137
}
1138

1139
func (c *VirtualMachineController) recordPhaseChangeEvent(vmi *v1.VirtualMachineInstance) {
10✔
1140
        switch vmi.Status.Phase {
10✔
1141
        case v1.Running:
5✔
1142
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Started.String(), VMIStarted)
5✔
1143
        case v1.Succeeded:
×
1144
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Stopped.String(), VMIShutdown)
×
1145
        case v1.Failed:
5✔
1146
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Stopped.String(), VMICrashed)
5✔
1147
        }
1148
}
1149

1150
func (c *VirtualMachineController) calculatePausedCondition(vmi *v1.VirtualMachineInstance, reason api.StateChangeReason) {
2✔
1151
        now := metav1.NewTime(time.Now())
2✔
1152
        switch reason {
2✔
1153
        case api.ReasonPausedMigration:
×
1154
                if !isVMIPausedDuringMigration(vmi) || !c.isMigrationSource(vmi) {
×
1155
                        c.logger.Object(vmi).V(3).Infof("Domain is paused after migration by qemu, no condition needed")
×
1156
                        return
×
1157
                }
×
1158
                c.logger.Object(vmi).V(3).Info("Adding paused by migration monitor condition")
×
1159
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
1160
                        Type:               v1.VirtualMachineInstancePaused,
×
1161
                        Status:             k8sv1.ConditionTrue,
×
1162
                        LastProbeTime:      now,
×
1163
                        LastTransitionTime: now,
×
1164
                        Reason:             "PausedByMigrationMonitor",
×
1165
                        Message:            "VMI was paused by the migration monitor",
×
1166
                })
×
1167
        case api.ReasonPausedUser:
1✔
1168
                c.logger.Object(vmi).V(3).Info("Adding paused condition")
1✔
1169
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
1✔
1170
                        Type:               v1.VirtualMachineInstancePaused,
1✔
1171
                        Status:             k8sv1.ConditionTrue,
1✔
1172
                        LastProbeTime:      now,
1✔
1173
                        LastTransitionTime: now,
1✔
1174
                        Reason:             "PausedByUser",
1✔
1175
                        Message:            "VMI was paused by user",
1✔
1176
                })
1✔
1177
        case api.ReasonPausedIOError:
×
1178
                c.logger.Object(vmi).V(3).Info("Adding paused condition")
×
1179
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
1180
                        Type:               v1.VirtualMachineInstancePaused,
×
1181
                        Status:             k8sv1.ConditionTrue,
×
1182
                        LastProbeTime:      now,
×
1183
                        LastTransitionTime: now,
×
1184
                        Reason:             "PausedIOError",
×
1185
                        Message:            "VMI was paused, low-level IO error detected",
×
1186
                })
×
1187
        default:
1✔
1188
                c.logger.Object(vmi).V(3).Infof("Domain is paused for unknown reason, %s", reason)
1✔
1189
        }
1190
}
1191

1192
func newNonMigratableCondition(msg string, reason string) *v1.VirtualMachineInstanceCondition {
17✔
1193
        return &v1.VirtualMachineInstanceCondition{
17✔
1194
                Type:    v1.VirtualMachineInstanceIsMigratable,
17✔
1195
                Status:  k8sv1.ConditionFalse,
17✔
1196
                Message: msg,
17✔
1197
                Reason:  reason,
17✔
1198
        }
17✔
1199
}
17✔
1200

1201
func (c *VirtualMachineController) calculateLiveMigrationCondition(vmi *v1.VirtualMachineInstance) (*v1.VirtualMachineInstanceCondition, bool) {
62✔
1202
        isBlockMigration, blockErr := c.checkVolumesForMigration(vmi)
62✔
1203

62✔
1204
        err := c.checkNetworkInterfacesForMigration(vmi)
62✔
1205
        if err != nil {
63✔
1206
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonInterfaceNotMigratable), isBlockMigration
1✔
1207
        }
1✔
1208

1209
        if err := c.isHostModelMigratable(vmi); err != nil {
61✔
1210
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonCPUModeNotMigratable), isBlockMigration
×
1211
        }
×
1212

1213
        reason, ok := vmiContainsNonMigratablePCIHostDevices(vmi, c.clusterConfig)
61✔
1214
        if ok {
66✔
1215
                return newNonMigratableCondition(reason, v1.VirtualMachineInstanceReasonHostDeviceNotMigratable), isBlockMigration
5✔
1216
        }
5✔
1217

1218
        if util.IsSEVVMI(vmi) {
57✔
1219
                return newNonMigratableCondition("VMI uses SEV", v1.VirtualMachineInstanceReasonSEVNotMigratable), isBlockMigration
1✔
1220
        } else if util.IsTDXVMI(vmi) {
57✔
1221
                return newNonMigratableCondition("VMI uses TDX", v1.VirtualMachineInstanceReasonTDXNotMigratable), isBlockMigration
1✔
1222
        }
1✔
1223

1224
        if util.IsSecureExecutionVMI(vmi) {
55✔
1225
                return newNonMigratableCondition("VMI uses Secure Execution", v1.VirtualMachineInstanceReasonSecureExecutionNotMigratable), isBlockMigration
1✔
1226
        }
1✔
1227

1228
        if reservation.HasVMIPersistentReservation(vmi) {
54✔
1229
                return newNonMigratableCondition("VMI uses SCSI persistent reservation", v1.VirtualMachineInstanceReasonPRNotMigratable), isBlockMigration
1✔
1230
        }
1✔
1231

1232
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
53✔
1233
                return newNonMigratableCondition(tscRequirement.Reason, v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable), isBlockMigration
1✔
1234
        }
1✔
1235

1236
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
52✔
1237
                return newNonMigratableCondition("VMI uses hyperv passthrough", v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable), isBlockMigration
1✔
1238
        }
1✔
1239

1240
        if blockErr != nil {
55✔
1241
                return newNonMigratableCondition(blockErr.Error(), v1.VirtualMachineInstanceReasonDisksNotMigratable), isBlockMigration
5✔
1242
        }
5✔
1243

1244
        return &v1.VirtualMachineInstanceCondition{
45✔
1245
                Type:   v1.VirtualMachineInstanceIsMigratable,
45✔
1246
                Status: k8sv1.ConditionTrue,
45✔
1247
        }, isBlockMigration
45✔
1248
}
1249

1250
func isMdevGPU(gpu v1.GPU, config *v1.KubeVirtConfiguration) bool {
2✔
1251
        if config.PermittedHostDevices == nil {
2✔
1252
                return false
×
1253
        }
×
1254
        for _, mdev := range config.PermittedHostDevices.MediatedDevices {
3✔
1255
                if mdev.ResourceName == gpu.DeviceName {
2✔
1256
                        return true
1✔
1257
                }
1✔
1258
        }
1259
        return false
1✔
1260
}
1261

1262
func vmiContainsNonMigratablePCIHostDevices(vmi *v1.VirtualMachineInstance, config *virtconfig.ClusterConfig) (string, bool) {
99✔
1263

99✔
1264
        if len(vmi.Spec.Domain.Devices.HostDevices) > 0 {
101✔
1265
                return "VMI specifies non-migratable generic PCI host device", true
2✔
1266
        }
2✔
1267

1268
        if len(vmi.Spec.Domain.Devices.GPUs) > 1 {
98✔
1269
                return "VMI specifies too many GPUs", true
1✔
1270
        }
1✔
1271

1272
        if len(vmi.Spec.Domain.Devices.GPUs) == 1 && !config.VGPULiveMigrationEnabled() {
97✔
1273
                return "VMI specifies a GPU but feature gate " + featuregate.VGPULiveMigration + " is not enabled", true
1✔
1274
        }
1✔
1275

1276
        if len(vmi.Spec.Domain.Devices.GPUs) == 1 && !isMdevGPU(vmi.Spec.Domain.Devices.GPUs[0], config.GetConfig()) {
96✔
1277
                return "VMI specifies non-migratable GPU device", true
1✔
1278
        }
1✔
1279

1280
        return "", false
94✔
1281
}
1282

1283
type multipleNonMigratableCondition struct {
1284
        reasons []string
1285
        msgs    []string
1286
}
1287

1288
func newMultipleNonMigratableCondition() *multipleNonMigratableCondition {
38✔
1289
        return &multipleNonMigratableCondition{}
38✔
1290
}
38✔
1291

1292
func (cond *multipleNonMigratableCondition) addNonMigratableCondition(reason, msg string) {
1✔
1293
        cond.reasons = append(cond.reasons, reason)
1✔
1294
        cond.msgs = append(cond.msgs, msg)
1✔
1295
}
1✔
1296

1297
func (cond *multipleNonMigratableCondition) String() string {
1✔
1298
        var buffer bytes.Buffer
1✔
1299
        for i, c := range cond.reasons {
2✔
1300
                if i > 0 {
1✔
1301
                        buffer.WriteString(", ")
×
1302
                }
×
1303
                buffer.WriteString(fmt.Sprintf("%s: %s", c, cond.msgs[i]))
1✔
1304
        }
1305
        return buffer.String()
1✔
1306
}
1307

1308
func (cond *multipleNonMigratableCondition) generateStorageLiveMigrationCondition() *v1.VirtualMachineInstanceCondition {
38✔
1309
        switch len(cond.reasons) {
38✔
1310
        case 0:
37✔
1311
                return &v1.VirtualMachineInstanceCondition{
37✔
1312
                        Type:   v1.VirtualMachineInstanceIsStorageLiveMigratable,
37✔
1313
                        Status: k8sv1.ConditionTrue,
37✔
1314
                }
37✔
1315
        default:
1✔
1316
                return &v1.VirtualMachineInstanceCondition{
1✔
1317
                        Type:    v1.VirtualMachineInstanceIsStorageLiveMigratable,
1✔
1318
                        Status:  k8sv1.ConditionFalse,
1✔
1319
                        Message: cond.String(),
1✔
1320
                        Reason:  v1.VirtualMachineInstanceReasonNotMigratable,
1✔
1321
                }
1✔
1322
        }
1323
}
1324

1325
func (c *VirtualMachineController) calculateLiveStorageMigrationCondition(vmi *v1.VirtualMachineInstance) *v1.VirtualMachineInstanceCondition {
38✔
1326
        multiCond := newMultipleNonMigratableCondition()
38✔
1327

38✔
1328
        if err := c.checkNetworkInterfacesForMigration(vmi); err != nil {
39✔
1329
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonInterfaceNotMigratable, err.Error())
1✔
1330
        }
1✔
1331

1332
        if err := c.isHostModelMigratable(vmi); err != nil {
38✔
1333
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonCPUModeNotMigratable, err.Error())
×
1334
        }
×
1335

1336
        reason, ok := vmiContainsNonMigratablePCIHostDevices(vmi, c.clusterConfig)
38✔
1337
        if ok {
38✔
1338
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHostDeviceNotMigratable, reason)
×
1339
        }
×
1340

1341
        if util.IsSEVVMI(vmi) {
38✔
1342
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonSEVNotMigratable, "VMI uses SEV")
×
1343
        } else if util.IsTDXVMI(vmi) {
38✔
1344
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonTDXNotMigratable, "VMI uses TDX")
×
1345
        }
×
1346

1347
        if reservation.HasVMIPersistentReservation(vmi) {
38✔
1348
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonPRNotMigratable, "VMI uses SCSI persistent reservation")
×
1349
        }
×
1350

1351
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
38✔
1352
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable, tscRequirement.Reason)
×
1353
        }
×
1354

1355
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
38✔
1356
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable, "VMI uses hyperv passthrough")
×
1357
        }
×
1358

1359
        return multiCond.generateStorageLiveMigrationCondition()
38✔
1360
}
1361

1362
func (c *VirtualMachineController) deleteVM(vmi *v1.VirtualMachineInstance) error {
4✔
1363
        err := c.processVmDelete(vmi)
4✔
1364
        if err != nil {
4✔
1365
                return err
×
1366
        }
×
1367
        // we can perform the cleanup immediately after
1368
        // the successful delete here because we don't have
1369
        // to report the deletion results on the VMI status
1370
        // in this case.
1371
        err = c.processVmCleanup(vmi)
4✔
1372
        if err != nil {
4✔
1373
                return err
×
1374
        }
×
1375

1376
        return nil
4✔
1377
}
1378

1379
// Determine if gracefulShutdown has been triggered by virt-launcher
1380
func (c *VirtualMachineController) hasGracefulShutdownTrigger(domain *api.Domain) bool {
46✔
1381
        if domain == nil {
60✔
1382
                return false
14✔
1383
        }
14✔
1384
        gracePeriod := domain.Spec.Metadata.KubeVirt.GracePeriod
32✔
1385

32✔
1386
        return gracePeriod != nil &&
32✔
1387
                gracePeriod.MarkedForGracefulShutdown != nil &&
32✔
1388
                *gracePeriod.MarkedForGracefulShutdown
32✔
1389
}
1390

1391
func (c *VirtualMachineController) sync(key string,
1392
        vmi *v1.VirtualMachineInstance,
1393
        vmiExists bool,
1394
        domain *api.Domain,
1395
        domainExists bool) error {
46✔
1396

46✔
1397
        oldStatus := vmi.Status.DeepCopy()
46✔
1398
        oldSpec := vmi.Spec.DeepCopy()
46✔
1399

46✔
1400
        // set to true when domain needs to be shutdown.
46✔
1401
        shouldShutdown := false
46✔
1402
        // set to true when domain needs to be removed from libvirt.
46✔
1403
        shouldDelete := false
46✔
1404
        // set to true when VirtualMachineInstance is active or about to become active.
46✔
1405
        shouldUpdate := false
46✔
1406
        // set to true when unrecoverable domain needs to be destroyed non-gracefully.
46✔
1407
        forceShutdownIrrecoverable := false
46✔
1408

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

46✔
1411
        if vmiExists && domainExists {
70✔
1412
                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✔
1413
        } else if vmiExists {
60✔
1414
                c.logger.Object(vmi).Infof("VMI is in phase: %v | Domain does not exist", vmi.Status.Phase)
14✔
1415
        } else if domainExists {
30✔
1416
                vmiRef := v1.NewVMIReferenceWithUUID(domain.ObjectMeta.Namespace, domain.ObjectMeta.Name, domain.Spec.Metadata.KubeVirt.UID)
8✔
1417
                c.logger.Object(vmiRef).Infof("VMI does not exist | Domain status: %v, reason: %v", domain.Status.Status, domain.Status.Reason)
8✔
1418
        } else {
8✔
1419
                c.logger.Info("VMI does not exist | Domain does not exist")
×
1420
        }
×
1421

1422
        domainAlive := domainExists &&
46✔
1423
                domain.Status.Status != api.Shutoff &&
46✔
1424
                domain.Status.Status != api.Crashed &&
46✔
1425
                domain.Status.Status != ""
46✔
1426

46✔
1427
        forceShutdownIrrecoverable = domainExists && domainPausedFailedPostCopy(domain)
46✔
1428

46✔
1429
        gracefulShutdown := c.hasGracefulShutdownTrigger(domain)
46✔
1430
        if gracefulShutdown && vmi.IsRunning() {
46✔
1431
                if domainAlive {
×
1432
                        c.logger.Object(vmi).V(3).Info("Shutting down due to graceful shutdown signal.")
×
1433
                        shouldShutdown = true
×
1434
                } else {
×
1435
                        shouldDelete = true
×
1436
                }
×
1437
        }
1438

1439
        // Determine removal of VirtualMachineInstance from cache should result in deletion.
1440
        if !vmiExists {
54✔
1441
                if domainAlive {
14✔
1442
                        // The VirtualMachineInstance is deleted on the cluster, and domain is alive,
6✔
1443
                        // then shut down the domain.
6✔
1444
                        c.logger.Object(vmi).V(3).Info("Shutting down domain for deleted VirtualMachineInstance object.")
6✔
1445
                        shouldShutdown = true
6✔
1446
                } else {
8✔
1447
                        // The VirtualMachineInstance is deleted on the cluster, and domain is not alive
2✔
1448
                        // then delete the domain.
2✔
1449
                        c.logger.Object(vmi).V(3).Info("Deleting domain for deleted VirtualMachineInstance object.")
2✔
1450
                        shouldDelete = true
2✔
1451
                }
2✔
1452
        }
1453

1454
        // Determine if VirtualMachineInstance is being deleted.
1455
        if vmiExists && vmi.ObjectMeta.DeletionTimestamp != nil {
48✔
1456
                if domainAlive {
3✔
1457
                        c.logger.Object(vmi).V(3).Info("Shutting down domain for VirtualMachineInstance with deletion timestamp.")
1✔
1458
                        shouldShutdown = true
1✔
1459
                } else {
2✔
1460
                        c.logger.Object(vmi).V(3).Info("Deleting domain for VirtualMachineInstance with deletion timestamp.")
1✔
1461
                        shouldDelete = true
1✔
1462
                }
1✔
1463
        }
1464

1465
        // Determine if domain needs to be deleted as a result of VirtualMachineInstance
1466
        // shutting down naturally (guest internal invoked shutdown)
1467
        if vmiExists && vmi.IsFinal() {
47✔
1468
                c.logger.Object(vmi).V(3).Info("Removing domain and ephemeral data for finalized vmi.")
1✔
1469
                shouldDelete = true
1✔
1470
        }
1✔
1471

1472
        if !domainAlive && domainExists && !vmi.IsFinal() {
48✔
1473
                c.logger.Object(vmi).V(3).Info("Deleting inactive domain for vmi.")
2✔
1474
                shouldDelete = true
2✔
1475
        }
2✔
1476

1477
        // Determine if an active (or about to be active) VirtualMachineInstance should be updated.
1478
        if vmiExists && !vmi.IsFinal() {
83✔
1479
                // requiring the phase of the domain and VirtualMachineInstance to be in sync is an
37✔
1480
                // optimization that prevents unnecessary re-processing VMIs during the start flow.
37✔
1481
                phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
37✔
1482
                if err != nil {
37✔
1483
                        return err
×
1484
                }
×
1485
                if vmi.Status.Phase == phase {
65✔
1486
                        shouldUpdate = true
28✔
1487
                }
28✔
1488

1489
                if shouldDelay, delay := c.ioErrorRetryManager.ShouldDelay(string(vmi.UID), func() bool {
74✔
1490
                        return isIOError(shouldUpdate, domainExists, domain)
37✔
1491
                }); shouldDelay {
37✔
1492
                        shouldUpdate = false
×
1493
                        c.logger.Object(vmi).Infof("Delay vm update for %f seconds", delay.Seconds())
×
1494
                        c.queue.AddAfter(key, delay)
×
1495
                }
×
1496
        }
1497

1498
        var syncErr error
46✔
1499

46✔
1500
        // Process the VirtualMachineInstance update in this order.
46✔
1501
        // * Shutdown and Deletion due to VirtualMachineInstance deletion, process stopping, graceful shutdown trigger, etc...
46✔
1502
        // * Cleanup of already shutdown and Deleted VMIs
46✔
1503
        // * Update due to spec change and initial start flow.
46✔
1504
        switch {
46✔
1505
        case shouldShutdown:
7✔
1506
                c.logger.Object(vmi).V(3).Info("Processing shutdown.")
7✔
1507
                syncErr = c.processVmShutdown(vmi, domain)
7✔
1508
        case forceShutdownIrrecoverable:
1✔
1509
                msg := formatIrrecoverableErrorMessage(domain)
1✔
1510
                c.logger.Object(vmi).V(3).Infof("Processing a destruction of an irrecoverable domain - %s.", msg)
1✔
1511
                syncErr = c.processVmDestroy(vmi, domain)
1✔
1512
                if syncErr == nil {
2✔
1513
                        syncErr = &vmiIrrecoverableError{msg}
1✔
1514
                }
1✔
1515
        case shouldDelete:
4✔
1516
                c.logger.Object(vmi).V(3).Info("Processing deletion.")
4✔
1517
                syncErr = c.deleteVM(vmi)
4✔
1518
        case shouldUpdate:
26✔
1519
                c.logger.Object(vmi).V(3).Info("Processing vmi update")
26✔
1520
                syncErr = c.processVmUpdate(vmi, domain)
26✔
1521
        default:
8✔
1522
                c.logger.Object(vmi).V(3).Info("No update processing required")
8✔
1523
        }
1524
        if syncErr != nil && !vmi.IsFinal() {
51✔
1525
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.SyncFailed.String(), syncErr.Error())
5✔
1526

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

1532
        // Update the VirtualMachineInstance status, if the VirtualMachineInstance exists
1533
        if vmiExists {
84✔
1534
                vmi.Spec = *oldSpec
38✔
1535
                if err := c.updateVMIStatus(oldStatus, vmi, domain, syncErr); err != nil {
38✔
1536
                        c.logger.Object(vmi).Reason(err).Error("Updating the VirtualMachineInstance status failed.")
×
1537
                        return err
×
1538
                }
×
1539
        }
1540

1541
        if syncErr != nil {
51✔
1542
                return syncErr
5✔
1543
        }
5✔
1544

1545
        c.logger.Object(vmi).V(3).Info("Synchronization loop succeeded.")
41✔
1546
        return nil
41✔
1547

1548
}
1549

1550
func (c *VirtualMachineController) processVmCleanup(vmi *v1.VirtualMachineInstance) error {
5✔
1551
        vmiId := string(vmi.UID)
5✔
1552

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

5✔
1555
        c.migrationProxy.StopTargetListener(vmiId)
5✔
1556
        c.migrationProxy.StopSourceListener(vmiId)
5✔
1557

5✔
1558
        c.downwardMetricsManager.StopServer(vmi)
5✔
1559

5✔
1560
        // Unmount container disks and clean up remaining files
5✔
1561
        if err := c.containerDiskMounter.Unmount(vmi); err != nil {
5✔
1562
                return err
×
1563
        }
×
1564

1565
        // UnmountAll does the cleanup on the "best effort" basis: it is
1566
        // safe to pass a nil cgroupManager.
1567
        cgroupManager, _ := getCgroupManager(vmi, c.host, c.hypervisorNodeInfo, c.clusterConfig.AllowEmulation())
5✔
1568
        if err := c.hotplugVolumeMounter.UnmountAll(vmi, cgroupManager); err != nil {
5✔
1569
                return err
×
1570
        }
×
1571

1572
        c.teardownNetwork(vmi)
5✔
1573

5✔
1574
        c.sriovHotplugExecutorPool.Delete(vmi.UID)
5✔
1575

5✔
1576
        // Watch dog file and command client must be the last things removed here
5✔
1577
        c.launcherClients.CloseLauncherClient(vmi)
5✔
1578

5✔
1579
        // Remove the domain from cache in the event that we're performing
5✔
1580
        // a final cleanup and never received the "DELETE" event. This is
5✔
1581
        // possible if the VMI pod goes away before we receive the final domain
5✔
1582
        // "DELETE"
5✔
1583
        domain := api.NewDomainReferenceFromName(vmi.Namespace, vmi.Name)
5✔
1584
        c.logger.Object(domain).Infof("Removing domain from cache during final cleanup")
5✔
1585
        return c.domainStore.Delete(domain)
5✔
1586
}
1587

1588
func (c *VirtualMachineController) processVmDestroy(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
1589
        tryGracefully := false
1✔
1590
        return c.helperVmShutdown(vmi, domain, tryGracefully)
1✔
1591
}
1✔
1592

1593
func (c *VirtualMachineController) processVmShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
7✔
1594
        tryGracefully := true
7✔
1595
        return c.helperVmShutdown(vmi, domain, tryGracefully)
7✔
1596
}
7✔
1597

1598
const firstGracefulShutdownAttempt = -1
1599

1600
// Determines if a domain's grace period has expired during shutdown.
1601
// If the grace period has started but not expired, timeLeft represents
1602
// the time in seconds left until the period expires.
1603
// If the grace period has not started, timeLeft will be set to -1.
1604
func (c *VirtualMachineController) hasGracePeriodExpired(terminationGracePeriod *int64, dom *api.Domain) (bool, int64) {
6✔
1605
        var hasExpired bool
6✔
1606
        var timeLeft int64
6✔
1607

6✔
1608
        gracePeriod := int64(0)
6✔
1609
        if terminationGracePeriod != nil {
7✔
1610
                gracePeriod = *terminationGracePeriod
1✔
1611
        } else if dom != nil && dom.Spec.Metadata.KubeVirt.GracePeriod != nil {
11✔
1612
                gracePeriod = dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds
5✔
1613
        }
5✔
1614

1615
        // If gracePeriod == 0, then there will be no startTime set, deletion
1616
        // should occur immediately during shutdown.
1617
        if gracePeriod == 0 {
7✔
1618
                hasExpired = true
1✔
1619
                return hasExpired, timeLeft
1✔
1620
        }
1✔
1621

1622
        startTime := int64(0)
5✔
1623
        if dom != nil && dom.Spec.Metadata.KubeVirt.GracePeriod != nil && dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp != nil {
8✔
1624
                startTime = dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp.UTC().Unix()
3✔
1625
        }
3✔
1626

1627
        if startTime == 0 {
7✔
1628
                // If gracePeriod > 0, then the shutdown signal needs to be sent
2✔
1629
                // and the gracePeriod start time needs to be set.
2✔
1630
                timeLeft = firstGracefulShutdownAttempt
2✔
1631
                return hasExpired, timeLeft
2✔
1632
        }
2✔
1633

1634
        now := time.Now().UTC().Unix()
3✔
1635
        diff := now - startTime
3✔
1636

3✔
1637
        if diff >= gracePeriod {
4✔
1638
                hasExpired = true
1✔
1639
                return hasExpired, timeLeft
1✔
1640
        }
1✔
1641

1642
        timeLeft = gracePeriod - diff
2✔
1643
        if timeLeft < 1 {
2✔
1644
                timeLeft = 1
×
1645
        }
×
1646
        return hasExpired, timeLeft
2✔
1647
}
1648

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

8✔
1651
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
8✔
1652
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
8✔
1653
        if err != nil {
8✔
1654
                return err
×
1655
        }
×
1656

1657
        if domainHasGracePeriod(domain) && tryGracefully {
12✔
1658
                if expired, timeLeft := c.hasGracePeriodExpired(vmi.Spec.TerminationGracePeriodSeconds, domain); !expired {
7✔
1659
                        return c.handleVMIShutdown(vmi, domain, client, timeLeft)
3✔
1660
                }
3✔
1661
                c.logger.Object(vmi).Infof("Grace period expired, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
1✔
1662
        } else {
4✔
1663
                c.logger.Object(vmi).Infof("Graceful shutdown not set, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
4✔
1664
        }
4✔
1665

1666
        err = client.KillVirtualMachine(vmi)
5✔
1667
        if err != nil && !cmdclient.IsDisconnected(err) {
5✔
1668
                // Only report err if it wasn't the result of a disconnect.
×
1669
                //
×
1670
                // Both virt-launcher and virt-handler are trying to destroy
×
1671
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
1672
                // disconnected during the kill request, which shouldn't be
×
1673
                // considered an error.
×
1674
                return err
×
1675
        }
×
1676

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

5✔
1679
        return nil
5✔
1680
}
1681

1682
func (c *VirtualMachineController) handleVMIShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain, client cmdclient.LauncherClient, timeLeft int64) error {
3✔
1683
        if domain.Status.Status != api.Shutdown {
6✔
1684
                return c.shutdownVMI(vmi, client, timeLeft)
3✔
1685
        }
3✔
1686
        c.logger.V(4).Object(vmi).Infof("%s is already shutting down.", vmi.GetObjectMeta().GetName())
×
1687
        return nil
×
1688
}
1689

1690
func (c *VirtualMachineController) shutdownVMI(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient, timeLeft int64) error {
3✔
1691
        err := client.ShutdownVirtualMachine(vmi)
3✔
1692
        if err != nil && !cmdclient.IsDisconnected(err) {
3✔
1693
                // Only report err if it wasn't the result of a disconnect.
×
1694
                //
×
1695
                // Both virt-launcher and virt-handler are trying to destroy
×
1696
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
1697
                // disconnected during the kill request, which shouldn't be
×
1698
                // considered an error.
×
1699
                return err
×
1700
        }
×
1701

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

3✔
1704
        // Only create a VMIGracefulShutdown event for the first attempt as we can
3✔
1705
        // easily hit the default burst limit of 25 for the
3✔
1706
        // EventSourceObjectSpamFilter when gracefully shutting down VMIs with a
3✔
1707
        // large TerminationGracePeriodSeconds value set. Hitting this limit can
3✔
1708
        // result in the eventual VMIShutdown event being dropped.
3✔
1709
        if timeLeft == firstGracefulShutdownAttempt {
5✔
1710
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.ShuttingDown.String(), VMIGracefulShutdown)
2✔
1711
        }
2✔
1712

1713
        // Make sure that we don't hot-loop in case we send the first domain notification
1714
        if timeLeft == firstGracefulShutdownAttempt {
5✔
1715
                timeLeft = 5
2✔
1716
                if vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds < timeLeft {
2✔
1717
                        timeLeft = *vmi.Spec.TerminationGracePeriodSeconds
×
1718
                }
×
1719
        }
1720
        // In case we have a long grace period, we want to resend the graceful shutdown every 5 seconds
1721
        // That's important since a booting OS can miss ACPI signals
1722
        if timeLeft > 5 {
4✔
1723
                timeLeft = 5
1✔
1724
        }
1✔
1725

1726
        // pending graceful shutdown.
1727
        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Duration(timeLeft)*time.Second)
3✔
1728
        return nil
3✔
1729
}
1730

1731
func (c *VirtualMachineController) processVmDelete(vmi *v1.VirtualMachineInstance) error {
4✔
1732

4✔
1733
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
4✔
1734
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
4✔
1735

4✔
1736
        // If the pod has been torn down, we know the VirtualMachineInstance is down.
4✔
1737
        if err == nil {
8✔
1738

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

4✔
1741
                // pending deletion.
4✔
1742
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Deleted.String(), VMISignalDeletion)
4✔
1743

4✔
1744
                err = client.DeleteDomain(vmi)
4✔
1745
                if err != nil && !cmdclient.IsDisconnected(err) {
4✔
1746
                        // Only report err if it wasn't the result of a disconnect.
×
1747
                        //
×
1748
                        // Both virt-launcher and virt-handler are trying to destroy
×
1749
                        // the VirtualMachineInstance at the same time. It's possible the client may get
×
1750
                        // disconnected during the kill request, which shouldn't be
×
1751
                        // considered an error.
×
1752
                        return err
×
1753
                }
×
1754
        }
1755

1756
        return nil
4✔
1757

1758
}
1759

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

39✔
1763
        if ok && nodeName != "" && nodeName == c.host {
77✔
1764
                return true
38✔
1765
        }
38✔
1766

1767
        return vmi.Status.NodeName != "" && vmi.Status.NodeName == c.host
1✔
1768
}
1769

1770
func (c *VirtualMachineController) checkNetworkInterfacesForMigration(vmi *v1.VirtualMachineInstance) error {
103✔
1771
        return netvmispec.VerifyVMIMigratable(vmi, c.clusterConfig.GetNetworkBindings())
103✔
1772
}
103✔
1773

1774
func isReadOnlyDisk(disk *v1.Disk) bool {
8✔
1775
        isReadOnlyCDRom := disk.CDRom != nil && (disk.CDRom.ReadOnly == nil || *disk.CDRom.ReadOnly)
8✔
1776

8✔
1777
        return isReadOnlyCDRom
8✔
1778
}
8✔
1779

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

73✔
1783
        for _, volumeStatus := range vmi.Status.VolumeStatus {
88✔
1784
                volumeStatusMap[volumeStatus.Name] = volumeStatus
15✔
1785
        }
15✔
1786

1787
        if len(vmi.Status.MigratedVolumes) > 0 {
73✔
1788
                blockMigrate = true
×
1789
        }
×
1790

1791
        filesystems := storagetypes.GetFilesystemsFromVolumes(vmi)
73✔
1792

73✔
1793
        // Check if all VMI volumes can be shared between the source and the destination
73✔
1794
        // of a live migration. blockMigrate will be returned as false, only if all volumes
73✔
1795
        // are shared and the VMI has no local disks
73✔
1796
        // Some combinations of disks makes the VMI no suitable for live migration.
73✔
1797
        // A relevant error will be returned in this case.
73✔
1798
        for _, volume := range vmi.Spec.Volumes {
102✔
1799
                volSrc := volume.VolumeSource
29✔
1800
                if volSrc.PersistentVolumeClaim != nil || volSrc.DataVolume != nil {
41✔
1801
                        var claimName string
12✔
1802
                        if volSrc.PersistentVolumeClaim != nil {
19✔
1803
                                claimName = volSrc.PersistentVolumeClaim.ClaimName
7✔
1804
                        } else {
12✔
1805
                                claimName = volSrc.DataVolume.Name
5✔
1806
                        }
5✔
1807

1808
                        volumeStatus, ok := volumeStatusMap[volume.Name]
12✔
1809

12✔
1810
                        if !ok || volumeStatus.PersistentVolumeClaimInfo == nil {
13✔
1811
                                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✔
1812
                        } else if !storagetypes.HasSharedAccessMode(volumeStatus.PersistentVolumeClaimInfo.AccessModes) && !storagetypes.IsMigratedVolume(volumeStatus.Name, vmi) {
18✔
1813
                                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✔
1814
                        }
6✔
1815

1816
                } else if volSrc.HostDisk != nil {
20✔
1817
                        // Check if this is a translated PVC.
3✔
1818
                        volumeStatus, ok := volumeStatusMap[volume.Name]
3✔
1819
                        if ok && volumeStatus.PersistentVolumeClaimInfo != nil {
3✔
1820
                                if !storagetypes.HasSharedAccessMode(volumeStatus.PersistentVolumeClaimInfo.AccessModes) && !storagetypes.IsMigratedVolume(volumeStatus.Name, vmi) {
×
1821
                                        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)
×
1822
                                } else {
×
1823
                                        continue
×
1824
                                }
1825
                        }
1826

1827
                        shared := volSrc.HostDisk.Shared != nil && *volSrc.HostDisk.Shared
3✔
1828
                        if !shared {
4✔
1829
                                return true, fmt.Errorf("cannot migrate VMI with non-shared HostDisk")
1✔
1830
                        }
1✔
1831
                } else {
14✔
1832
                        if _, ok := filesystems[volume.Name]; ok {
18✔
1833
                                c.logger.Object(vmi).Infof("Volume %s is shared with virtiofs, allow live migration", volume.Name)
4✔
1834
                                continue
4✔
1835
                        }
1836

1837
                        isVolumeUsedByReadOnlyDisk := false
10✔
1838
                        for _, disk := range vmi.Spec.Domain.Devices.Disks {
18✔
1839
                                if isReadOnlyDisk(&disk) && disk.Name == volume.Name {
10✔
1840
                                        isVolumeUsedByReadOnlyDisk = true
2✔
1841
                                        break
2✔
1842
                                }
1843
                        }
1844

1845
                        if isVolumeUsedByReadOnlyDisk {
12✔
1846
                                continue
2✔
1847
                        }
1848

1849
                        if vmi.Status.MigrationMethod == "" || vmi.Status.MigrationMethod == v1.LiveMigration {
16✔
1850
                                c.logger.Object(vmi).Infof("migration is block migration because of %s volume", volume.Name)
8✔
1851
                        }
8✔
1852
                        blockMigrate = true
8✔
1853
                }
1854
        }
1855
        return
65✔
1856
}
1857

1858
func isVMIPausedDuringMigration(vmi *v1.VirtualMachineInstance) bool {
×
1859
        return vmi.Status.MigrationState != nil &&
×
1860
                vmi.Status.MigrationState.Mode == v1.MigrationPaused &&
×
1861
                !vmi.Status.MigrationState.Completed
×
1862
}
×
1863

1864
func (c *VirtualMachineController) vmUpdateHelperDefault(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
25✔
1865
        client, err := c.launcherClients.GetLauncherClient(vmi)
25✔
1866
        if err != nil {
25✔
1867
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
1868
        }
×
1869

1870
        preallocatedVolumes := c.getPreallocatedVolumes(vmi)
25✔
1871

25✔
1872
        err = hostdisk.ReplacePVCByHostDisk(vmi)
25✔
1873
        if err != nil {
25✔
1874
                return err
×
1875
        }
×
1876

1877
        cgroupManager, err := getCgroupManager(vmi, c.host, c.hypervisorNodeInfo, c.clusterConfig.AllowEmulation())
25✔
1878
        if err != nil {
25✔
1879
                return err
×
1880
        }
×
1881

1882
        var errorTolerantFeaturesError []error
25✔
1883
        readyToProceed, err := c.handleVMIState(vmi, cgroupManager, &errorTolerantFeaturesError)
25✔
1884
        if err != nil {
29✔
1885
                return err
4✔
1886
        }
4✔
1887

1888
        if !readyToProceed {
23✔
1889
                return nil
2✔
1890
        }
2✔
1891

1892
        // Synchronize the VirtualMachineInstance state
1893
        err = c.syncVirtualMachine(client, vmi, preallocatedVolumes)
19✔
1894
        if err != nil {
19✔
1895
                return err
×
1896
        }
×
1897

1898
        if domain == nil {
22✔
1899
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Created.String(), VMIDefined)
3✔
1900
        }
3✔
1901

1902
        // Post-sync housekeeping
1903
        err = c.hypervisorRuntime.HandleHousekeeping(vmi, cgroupManager, domain)
19✔
1904
        if err != nil {
19✔
1905
                return err
×
1906
        }
×
1907

1908
        if vmi.IsRunning() {
35✔
1909
                // Umount any disks no longer mounted
16✔
1910
                if err := c.hotplugVolumeMounter.Unmount(vmi, cgroupManager); err != nil {
16✔
1911
                        return err
×
1912
                }
×
1913
        }
1914

1915
        return errors.NewAggregate(errorTolerantFeaturesError)
19✔
1916
}
1917

1918
// handleVMIState: Decides whether to call handleRunningVMI or handleStartingVMI based on the VMI's state.
1919
func (c *VirtualMachineController) handleVMIState(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) (bool, error) {
25✔
1920
        if vmi.IsRunning() {
42✔
1921
                return true, c.handleRunningVMI(vmi, cgroupManager, errorTolerantFeaturesError)
17✔
1922
        } else if !vmi.IsFinal() {
33✔
1923
                return c.handleStartingVMI(vmi, cgroupManager)
8✔
1924
        }
8✔
1925
        return true, nil
×
1926
}
1927

1928
// handleRunningVMI contains the logic specifically for running VMs (hotplugging in running state, metrics, network updates)
1929
func (c *VirtualMachineController) handleRunningVMI(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) error {
17✔
1930
        if err := c.hotplugSriovInterfaces(vmi); err != nil {
17✔
1931
                c.logger.Object(vmi).Error(err.Error())
×
1932
        }
×
1933

1934
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
20✔
1935
                switch {
3✔
1936
                case goerror.Is(err, hotplugvolume.ErrWaitingForHotplugMount):
1✔
1937
                        c.logger.Object(vmi).V(4).Infof("waiting for hotplug volumes to be mounted: %v", err)
1✔
1938
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
1939
                case goerror.Is(err, os.ErrNotExist):
1✔
1940
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, "HotplugFailed", err.Error())
1✔
1941
                default:
1✔
1942
                        return err
1✔
1943
                }
1944
        }
1945

1946
        if err := c.getMemoryDump(vmi); err != nil {
16✔
1947
                return err
×
1948
        }
×
1949

1950
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
16✔
1951
        if err != nil {
16✔
1952
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
1953
        }
×
1954

1955
        if err := c.downwardMetricsManager.StartServer(vmi, isolationRes.Pid()); err != nil {
16✔
1956
                return err
×
1957
        }
×
1958

1959
        if err := c.setupNetwork(vmi, netsetup.FilterNetsForLiveUpdate(vmi), c.netConf); err != nil {
16✔
1960
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, "NicHotplug", err.Error())
×
1961
                *errorTolerantFeaturesError = append(*errorTolerantFeaturesError, err)
×
1962
        }
×
1963

1964
        return nil
16✔
1965
}
1966

1967
// handleStartingVMI: Contains the logic for starting VMs (container disks, initial network setup, device ownership).
1968
func (c *VirtualMachineController) handleStartingVMI(
1969
        vmi *v1.VirtualMachineInstance,
1970
        cgroupManager cgroup.Manager,
1971
) (bool, error) {
8✔
1972
        // give containerDisks some time to become ready before throwing errors on retries
8✔
1973
        info := c.launcherClients.GetLauncherClientInfo(vmi)
8✔
1974
        if ready, err := c.containerDiskMounter.ContainerDisksReady(vmi, info.NotInitializedSince); !ready {
10✔
1975
                if err != nil {
3✔
1976
                        return false, err
1✔
1977
                }
1✔
1978
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
1979
                return false, nil
1✔
1980
        }
1981

1982
        var err error
6✔
1983
        err = c.containerDiskMounter.MountAndVerify(vmi)
6✔
1984
        if err != nil {
7✔
1985
                return false, err
1✔
1986
        }
1✔
1987

1988
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
5✔
1989
                switch {
×
1990
                case goerror.Is(err, hotplugvolume.ErrWaitingForHotplugMount):
×
1991
                        c.logger.Object(vmi).V(4).Infof("waiting for hotplug volumes to be mounted: %v", err)
×
1992
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
1993
                case goerror.Is(err, os.ErrNotExist):
×
1994
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, "HotplugFailed", err.Error())
×
1995
                default:
×
1996
                        return false, err
×
1997
                }
1998
        }
1999

2000
        if !c.hotplugVolumesReady(vmi) {
6✔
2001
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
2002
                return false, nil
1✔
2003
        }
1✔
2004

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

2009
        if err := c.setupDevicesOwnerships(vmi, c.recorder); err != nil {
3✔
2010
                return false, err
×
2011
        }
×
2012

2013
        if err := c.adjustResources(vmi); err != nil {
3✔
2014
                return false, err
×
2015
        }
×
2016

2017
        if c.shouldWaitForSEVAttestation(vmi) {
3✔
2018
                return false, nil
×
2019
        }
×
2020

2021
        return true, nil
3✔
2022
}
2023

2024
func (c *VirtualMachineController) adjustResources(vmi *v1.VirtualMachineInstance) error {
3✔
2025
        err := c.hypervisorRuntime.AdjustResources(vmi, c.clusterConfig.GetConfig())
3✔
2026

3✔
2027
        if err != nil {
3✔
2028
                return fmt.Errorf("failed to adjust resources: %v", err)
×
2029
        }
×
2030
        return nil
3✔
2031
}
2032

2033
func (c *VirtualMachineController) shouldWaitForSEVAttestation(vmi *v1.VirtualMachineInstance) bool {
3✔
2034
        if util.IsSEVAttestationRequested(vmi) {
3✔
2035
                sev := vmi.Spec.Domain.LaunchSecurity.SEV
×
2036
                // Wait for the session parameters to be provided
×
2037
                return sev.Session == "" || sev.DHCert == ""
×
2038
        }
×
2039
        return false
3✔
2040
}
2041

2042
func (c *VirtualMachineController) syncVirtualMachine(client cmdclient.LauncherClient, vmi *v1.VirtualMachineInstance, preallocatedVolumes []string) error {
19✔
2043
        smbios := c.clusterConfig.GetSMBIOS()
19✔
2044
        period := c.clusterConfig.GetMemBalloonStatsPeriod()
19✔
2045

19✔
2046
        options := virtualMachineOptions(smbios, period, preallocatedVolumes, c.capabilities, c.clusterConfig)
19✔
2047
        options.InterfaceDomainAttachment = domainspec.DomainAttachmentByInterfaceName(vmi.Spec.Domain.Devices.Interfaces, c.clusterConfig.GetNetworkBindings())
19✔
2048

19✔
2049
        err := client.SyncVirtualMachine(vmi, options)
19✔
2050
        if err != nil {
19✔
2051
                if strings.Contains(err.Error(), "EFI OVMF rom missing") {
×
2052
                        return &virtLauncherCriticalSecurebootError{fmt.Sprintf("mismatch of Secure Boot setting and bootloaders: %v", err)}
×
2053
                }
×
2054
        }
2055

2056
        return err
19✔
2057
}
2058

2059
func (c *VirtualMachineController) getPreallocatedVolumes(vmi *v1.VirtualMachineInstance) []string {
25✔
2060
        var preallocatedVolumes []string
25✔
2061
        for _, volumeStatus := range vmi.Status.VolumeStatus {
29✔
2062
                if volumeStatus.PersistentVolumeClaimInfo != nil && volumeStatus.PersistentVolumeClaimInfo.Preallocated {
4✔
2063
                        preallocatedVolumes = append(preallocatedVolumes, volumeStatus.Name)
×
2064
                }
×
2065
        }
2066
        return preallocatedVolumes
25✔
2067
}
2068

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

17✔
2072
        sriovSpecIfacesNames := netvmispec.IndexInterfaceSpecByName(sriovSpecInterfaces)
17✔
2073
        attachedSriovStatusIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
17✔
2074
                _, exist := sriovSpecIfacesNames[iface.Name]
×
2075
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceDomain) &&
×
2076
                        netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
2077
        })
×
2078

2079
        desiredSriovMultusPluggedIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
17✔
2080
                _, exist := sriovSpecIfacesNames[iface.Name]
×
2081
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
2082
        })
×
2083

2084
        if len(desiredSriovMultusPluggedIfaces) == len(attachedSriovStatusIfaces) {
34✔
2085
                c.sriovHotplugExecutorPool.Delete(vmi.UID)
17✔
2086
                return nil
17✔
2087
        }
17✔
2088

2089
        rateLimitedExecutor := c.sriovHotplugExecutorPool.LoadOrStore(vmi.UID)
×
2090
        return rateLimitedExecutor.Exec(func() error {
×
2091
                return c.hotplugSriovInterfacesCommand(vmi)
×
2092
        })
×
2093
}
2094

2095
func (c *VirtualMachineController) hotplugSriovInterfacesCommand(vmi *v1.VirtualMachineInstance) error {
×
2096
        const errMsgPrefix = "failed to hot-plug SR-IOV interfaces"
×
2097

×
2098
        client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
×
2099
        if err != nil {
×
2100
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2101
        }
×
2102

2103
        if err := c.hypervisorRuntime.AdjustResources(vmi, c.clusterConfig.GetConfig()); err != nil {
×
2104
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), err.Error())
×
2105
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2106
        }
×
2107

2108
        c.logger.V(3).Object(vmi).Info("sending hot-plug host-devices command")
×
2109
        if err := client.HotplugHostDevices(vmi); err != nil {
×
2110
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2111
        }
×
2112

2113
        return nil
×
2114
}
2115

2116
func memoryDumpPath(volumeStatus v1.VolumeStatus) string {
×
2117
        target := hotplugdisk.GetVolumeMountDir(volumeStatus.Name)
×
2118
        dumpPath := filepath.Join(target, volumeStatus.MemoryDumpVolume.TargetFileName)
×
2119
        return dumpPath
×
2120
}
×
2121

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

16✔
2125
        for _, volumeStatus := range vmi.Status.VolumeStatus {
19✔
2126
                if volumeStatus.MemoryDumpVolume == nil || volumeStatus.Phase != v1.MemoryDumpVolumeInProgress {
6✔
2127
                        continue
3✔
2128
                }
2129
                client, err := c.launcherClients.GetVerifiedLauncherClient(vmi)
×
2130
                if err != nil {
×
2131
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2132
                }
×
2133

2134
                c.logger.V(3).Object(vmi).Info("sending memory dump command")
×
2135
                err = client.VirtualMachineMemoryDump(vmi, memoryDumpPath(volumeStatus))
×
2136
                if err != nil {
×
2137
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
2138
                }
×
2139
        }
2140

2141
        return nil
16✔
2142
}
2143

2144
func (c *VirtualMachineController) hotplugVolumesReady(vmi *v1.VirtualMachineInstance) bool {
5✔
2145
        hasHotplugVolume := false
5✔
2146
        for _, v := range vmi.Spec.Volumes {
6✔
2147
                if storagetypes.IsHotplugVolume(&v) {
2✔
2148
                        hasHotplugVolume = true
1✔
2149
                        break
1✔
2150
                }
2151
        }
2152
        if len(vmi.Spec.UtilityVolumes) > 0 {
5✔
2153
                hasHotplugVolume = true
×
2154
        }
×
2155
        if !hasHotplugVolume {
9✔
2156
                return true
4✔
2157
        }
4✔
2158
        if len(vmi.Status.VolumeStatus) == 0 {
1✔
2159
                return false
×
2160
        }
×
2161
        for _, vs := range vmi.Status.VolumeStatus {
2✔
2162
                if vs.HotplugVolume != nil && !(vs.Phase == v1.VolumeReady || vs.Phase == v1.HotplugVolumeMounted) {
2✔
2163
                        // wait for volume to be mounted
1✔
2164
                        return false
1✔
2165
                }
1✔
2166
        }
2167
        return true
×
2168
}
2169

2170
func (c *VirtualMachineController) processVmUpdate(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
26✔
2171
        shouldReturn, err := c.checkLauncherClient(vmi)
26✔
2172
        if shouldReturn {
27✔
2173
                return err
1✔
2174
        }
1✔
2175

2176
        return c.vmUpdateHelperDefault(vmi, domain)
25✔
2177
}
2178

2179
func (c *VirtualMachineController) setVmPhaseForStatusReason(domain *api.Domain, vmi *v1.VirtualMachineInstance) error {
43✔
2180
        phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
43✔
2181
        if err != nil {
43✔
2182
                return err
×
2183
        }
×
2184
        if phase == v1.Failed && vmi.Status.Phase != v1.Failed {
52✔
2185
                panicInfo := c.getGuestPanicInfo(domain, vmi)
9✔
2186
                if panicInfo != nil {
13✔
2187
                        panicType := "unknown"
4✔
2188
                        if panicInfo.Type != "" {
6✔
2189
                                panicType = panicInfo.Type
2✔
2190
                        }
2✔
2191
                        bugcheckCode := panicInfo.Arg1
4✔
2192
                        vhmetrics.IncGuestOSPanic(vmi.Namespace, vmi.Name, panicType, bugcheckCode)
4✔
2193
                }
2194
        }
2195
        vmi.Status.Phase = phase
43✔
2196
        return nil
43✔
2197
}
2198

2199
func (c *VirtualMachineController) getGuestPanicInfo(domain *api.Domain, vmi *v1.VirtualMachineInstance) *api.GuestPanicInfo {
9✔
2200
        if domain != nil {
14✔
2201
                return domain.Status.GuestPanicInfo
5✔
2202
        }
5✔
2203
        obj, exists, err := c.domainStore.GetByKey(controller.VirtualMachineInstanceKey(vmi))
4✔
2204
        if err != nil || !exists {
7✔
2205
                return nil
3✔
2206
        }
3✔
2207
        if d, ok := obj.(*api.Domain); ok {
2✔
2208
                return d.Status.GuestPanicInfo
1✔
2209
        }
1✔
NEW
2210
        return nil
×
2211
}
2212

2213
func vmiHasTerminationGracePeriod(vmi *v1.VirtualMachineInstance) bool {
×
2214
        // if not set we use the default graceperiod
×
2215
        return vmi.Spec.TerminationGracePeriodSeconds == nil ||
×
2216
                (vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds != 0)
×
2217
}
×
2218

2219
func domainHasGracePeriod(domain *api.Domain) bool {
8✔
2220
        return domain != nil &&
8✔
2221
                domain.Spec.Metadata.KubeVirt.GracePeriod != nil &&
8✔
2222
                domain.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds != 0
8✔
2223
}
8✔
2224

2225
func isACPIEnabled(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
×
2226
        return (vmiHasTerminationGracePeriod(vmi) || (vmi.Spec.TerminationGracePeriodSeconds == nil && domainHasGracePeriod(domain))) &&
×
2227
                domain != nil &&
×
2228
                domain.Spec.Features != nil &&
×
2229
                domain.Spec.Features.ACPI != nil
×
2230
}
×
2231

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

80✔
2234
        if domain == nil {
107✔
2235
                if vmi.IsMigrationTarget() && vmi.Status.MigrationState != nil && vmi.Status.MigrationState.Failed {
27✔
2236
                        return vmi.Status.Phase, nil
×
2237
                }
×
2238
                switch {
27✔
2239
                case vmi.IsScheduled():
24✔
2240
                        isUnresponsive, isInitialized, err := c.launcherClients.IsLauncherClientUnresponsive(vmi)
24✔
2241

24✔
2242
                        if err != nil {
24✔
2243
                                return vmi.Status.Phase, err
×
2244
                        }
×
2245
                        if !isInitialized {
26✔
2246
                                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
2✔
2247
                                return vmi.Status.Phase, err
2✔
2248
                        } else if isUnresponsive {
28✔
2249
                                if vmi.IsMigrationTarget() {
4✔
2250
                                        return v1.WaitingForSync, nil
×
2251
                                }
×
2252
                                // virt-launcher is gone and VirtualMachineInstance never transitioned
2253
                                // from scheduled to Running.
2254
                                return v1.Failed, nil
4✔
2255
                        }
2256
                        return v1.Scheduled, nil
18✔
2257
                case !vmi.IsRunning() && !vmi.IsFinal():
×
2258
                        return v1.Scheduled, nil
×
2259
                case !vmi.IsFinal():
3✔
2260
                        if vmi.IsMigrationTarget() {
3✔
2261
                                return v1.WaitingForSync, nil
×
2262
                        }
×
2263
                        // That is unexpected. We should not be able to delete a VirtualMachineInstance before we stop it.
2264
                        // However, if someone directly interacts with libvirt it is possible
2265
                        return v1.Failed, nil
3✔
2266
                }
2267
        } else {
53✔
2268
                switch domain.Status.Status {
53✔
2269
                case api.Shutoff, api.Crashed:
5✔
2270
                        switch domain.Status.Reason {
5✔
2271
                        case api.ReasonCrashed, api.ReasonPanicked:
5✔
2272
                                return v1.Failed, nil
5✔
2273
                        case api.ReasonDestroyed:
×
2274
                                if isACPIEnabled(vmi, domain) {
×
2275
                                        // When ACPI is available, the domain was tried to be shutdown,
×
2276
                                        // and destroyed means that the domain was destroyed after the graceperiod expired.
×
2277
                                        // Without ACPI a destroyed domain is ok.
×
2278
                                        return v1.Failed, nil
×
2279
                                }
×
2280
                                if vmi.Status.MigrationState != nil && vmi.Status.MigrationState.Failed && vmi.Status.MigrationState.Mode == v1.MigrationPostCopy {
×
2281
                                        // A VMI that failed a post-copy migration should never succeed
×
2282
                                        return v1.Failed, nil
×
2283
                                }
×
2284
                                return v1.Succeeded, nil
×
2285
                        case api.ReasonShutdown, api.ReasonSaved, api.ReasonFromSnapshot:
×
2286
                                return v1.Succeeded, nil
×
2287
                        case api.ReasonMigrated:
×
2288
                                // if the domain migrated, we no longer know the phase.
×
2289
                                return vmi.Status.Phase, nil
×
2290
                        }
2291
                case api.Paused:
4✔
2292
                        switch domain.Status.Reason {
4✔
2293
                        case api.ReasonPausedPostcopyFailed:
2✔
2294
                                return v1.Failed, nil
2✔
2295
                        default:
2✔
2296
                                return v1.Running, nil
2✔
2297
                        }
2298
                case api.Running, api.Blocked, api.PMSuspended:
44✔
2299
                        return v1.Running, nil
44✔
2300
                }
2301
        }
2302
        return vmi.Status.Phase, nil
×
2303
}
2304

2305
func (c *VirtualMachineController) addDeleteFunc(obj interface{}) {
×
2306
        key, err := controller.KeyFunc(obj)
×
2307
        if err == nil {
×
2308
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
2309
                c.queue.Add(key)
×
2310
        }
×
2311
}
2312

2313
func (c *VirtualMachineController) updateFunc(_, new interface{}) {
×
2314
        key, err := controller.KeyFunc(new)
×
2315
        if err == nil {
×
2316
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
2317
                c.queue.Add(key)
×
2318
        }
×
2319
}
2320

2321
func (c *VirtualMachineController) addDomainFunc(obj interface{}) {
×
2322
        key, err := controller.KeyFunc(obj)
×
2323
        if err == nil {
×
2324
                c.queue.Add(key)
×
2325
        }
×
2326
}
2327
func (c *VirtualMachineController) deleteDomainFunc(obj interface{}) {
×
2328
        domain, ok := obj.(*api.Domain)
×
2329
        if !ok {
×
2330
                tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
×
2331
                if !ok {
×
2332
                        c.logger.Reason(fmt.Errorf("couldn't get object from tombstone %+v", obj)).Error("Failed to process delete notification")
×
2333
                        return
×
2334
                }
×
2335
                domain, ok = tombstone.Obj.(*api.Domain)
×
2336
                if !ok {
×
2337
                        c.logger.Reason(fmt.Errorf("tombstone contained object that is not a domain %#v", obj)).Error("Failed to process delete notification")
×
2338
                        return
×
2339
                }
×
2340
        }
2341
        c.logger.V(3).Object(domain).Info("Domain deleted")
×
2342
        key, err := controller.KeyFunc(obj)
×
2343
        if err == nil {
×
2344
                c.queue.Add(key)
×
2345
        }
×
2346
}
2347
func (c *VirtualMachineController) updateDomainFunc(_, new interface{}) {
×
2348
        key, err := controller.KeyFunc(new)
×
2349
        if err == nil {
×
2350
                c.queue.Add(key)
×
2351
        }
×
2352
}
2353

2354
func (c *VirtualMachineController) isHostModelMigratable(vmi *v1.VirtualMachineInstance) error {
101✔
2355
        if cpu := vmi.Spec.Domain.CPU; cpu != nil && cpu.Model == v1.CPUModeHostModel {
103✔
2356
                if c.hostCpuModel == "" {
3✔
2357
                        err := fmt.Errorf("the node \"%s\" does not allow migration with host-model", vmi.Status.NodeName)
1✔
2358
                        c.logger.Object(vmi).Errorf("%s", err.Error())
1✔
2359
                        return err
1✔
2360
                }
1✔
2361
        }
2362
        return nil
100✔
2363
}
2364

2365
func isIOError(shouldUpdate, domainExists bool, domain *api.Domain) bool {
37✔
2366
        return shouldUpdate && domainExists && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedIOError
37✔
2367
}
37✔
2368

2369
func (c *VirtualMachineController) updateMachineType(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
37✔
2370
        if domain == nil || vmi == nil {
50✔
2371
                return
13✔
2372
        }
13✔
2373
        if domain.Spec.OS.Type.Machine != "" {
25✔
2374
                vmi.Status.Machine = &v1.Machine{Type: domain.Spec.OS.Type.Machine}
1✔
2375
        }
1✔
2376
}
2377

2378
func parseLibvirtQuantity(value int64, unit string) *resource.Quantity {
15✔
2379
        switch unit {
15✔
2380
        case "b", "bytes":
2✔
2381
                return resource.NewQuantity(value, resource.BinarySI)
2✔
2382
        case "KB":
1✔
2383
                return resource.NewQuantity(value*1000, resource.DecimalSI)
1✔
2384
        case "MB":
1✔
2385
                return resource.NewQuantity(value*1000*1000, resource.DecimalSI)
1✔
2386
        case "GB":
1✔
2387
                return resource.NewQuantity(value*1000*1000*1000, resource.DecimalSI)
1✔
2388
        case "TB":
1✔
2389
                return resource.NewQuantity(value*1000*1000*1000*1000, resource.DecimalSI)
1✔
2390
        case "k", "KiB":
3✔
2391
                return resource.NewQuantity(value*1024, resource.BinarySI)
3✔
2392
        case "M", "MiB":
2✔
2393
                return resource.NewQuantity(value*1024*1024, resource.BinarySI)
2✔
2394
        case "G", "GiB":
2✔
2395
                return resource.NewQuantity(value*1024*1024*1024, resource.BinarySI)
2✔
2396
        case "T", "TiB":
2✔
2397
                return resource.NewQuantity(value*1024*1024*1024*1024, resource.BinarySI)
2✔
2398
        }
2399
        return nil
×
2400
}
2401

2402
func (c *VirtualMachineController) updateBackupStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
42✔
2403
        if domain == nil ||
42✔
2404
                domain.Spec.Metadata.KubeVirt.Backup == nil ||
42✔
2405
                vmi.Status.ChangedBlockTracking == nil ||
42✔
2406
                vmi.Status.ChangedBlockTracking.BackupStatus == nil {
81✔
2407
                return
39✔
2408
        }
39✔
2409
        backupMetadata := domain.Spec.Metadata.KubeVirt.Backup
3✔
2410
        // Handle the case where a new backupStatus was initiated but
3✔
2411
        // the backupMetadata wasnt reinitialized yet
3✔
2412
        timestampMatch := backupMetadata.StartTimestamp.Equal(vmi.Status.ChangedBlockTracking.BackupStatus.StartTimestamp)
3✔
2413
        if vmi.Status.ChangedBlockTracking.BackupStatus.BackupName != backupMetadata.Name || !timestampMatch {
5✔
2414
                return
2✔
2415
        }
2✔
2416
        vmi.Status.ChangedBlockTracking.BackupStatus.Completed = backupMetadata.Completed
1✔
2417
        vmi.Status.ChangedBlockTracking.BackupStatus.Failed = backupMetadata.Failed
1✔
2418
        if backupMetadata.StartTimestamp != nil {
2✔
2419
                vmi.Status.ChangedBlockTracking.BackupStatus.StartTimestamp = backupMetadata.StartTimestamp
1✔
2420
        }
1✔
2421
        if backupMetadata.EndTimestamp != nil {
2✔
2422
                vmi.Status.ChangedBlockTracking.BackupStatus.EndTimestamp = backupMetadata.EndTimestamp
1✔
2423
        }
1✔
2424
        if backupMetadata.BackupMsg != "" {
2✔
2425
                vmi.Status.ChangedBlockTracking.BackupStatus.BackupMsg = &backupMetadata.BackupMsg
1✔
2426
        }
1✔
2427
        if backupMetadata.CheckpointName != "" {
2✔
2428
                vmi.Status.ChangedBlockTracking.BackupStatus.CheckpointName = &backupMetadata.CheckpointName
1✔
2429
        }
1✔
2430
        if backupMetadata.Volumes != "" {
2✔
2431
                var volumes []backupv1.BackupVolumeInfo
1✔
2432
                if err := json.Unmarshal([]byte(backupMetadata.Volumes), &volumes); err == nil && len(volumes) > 0 {
2✔
2433
                        vmi.Status.ChangedBlockTracking.BackupStatus.Volumes = volumes
1✔
2434
                }
1✔
2435
        }
2436
}
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