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

kubevirt / kubevirt / 5a96d2af-e032-4916-a105-5ca39aa14333

13 Mar 2025 03:24PM UTC coverage: 71.432% (-0.003%) from 71.435%
5a96d2af-e032-4916-a105-5ca39aa14333

push

prow

web-flow
Merge pull request #14213 from akrejcir/improve-ssh-event-messages

virt-handler: Improve event messages for SSH key sync

6 of 8 new or added lines in 1 file covered. (75.0%)

2 existing lines in 1 file now uncovered.

61906 of 86664 relevant lines covered (71.43%)

0.8 hits per line

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

64.75
/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 2017 Red Hat, Inc.
17
 *
18
 */
19

20
package virthandler
21

22
import (
23
        "bytes"
24
        "context"
25
        "encoding/json"
26
        goerror "errors"
27
        "fmt"
28
        "io"
29
        "net"
30
        "os"
31
        "path/filepath"
32
        "regexp"
33
        "runtime"
34
        "sort"
35
        "strconv"
36
        "strings"
37
        "time"
38

39
        "github.com/mitchellh/go-ps"
40
        "github.com/opencontainers/runc/libcontainer/cgroups"
41
        "golang.org/x/sys/unix"
42
        "libvirt.org/go/libvirtxml"
43

44
        k8sv1 "k8s.io/api/core/v1"
45
        "k8s.io/apimachinery/pkg/api/equality"
46
        "k8s.io/apimachinery/pkg/api/resource"
47
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48
        "k8s.io/apimachinery/pkg/types"
49
        "k8s.io/apimachinery/pkg/util/errors"
50
        "k8s.io/apimachinery/pkg/util/wait"
51
        "k8s.io/client-go/tools/cache"
52
        "k8s.io/client-go/tools/record"
53
        "k8s.io/client-go/util/workqueue"
54

55
        v1 "kubevirt.io/api/core/v1"
56
        "kubevirt.io/client-go/kubecli"
57
        "kubevirt.io/client-go/log"
58

59
        "kubevirt.io/kubevirt/pkg/config"
60
        "kubevirt.io/kubevirt/pkg/controller"
61
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
62
        "kubevirt.io/kubevirt/pkg/executor"
63
        cmdv1 "kubevirt.io/kubevirt/pkg/handler-launcher-com/cmd/v1"
64
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
65
        hotplugdisk "kubevirt.io/kubevirt/pkg/hotplug-disk"
66
        netcache "kubevirt.io/kubevirt/pkg/network/cache"
67
        "kubevirt.io/kubevirt/pkg/network/domainspec"
68
        neterrors "kubevirt.io/kubevirt/pkg/network/errors"
69
        netsetup "kubevirt.io/kubevirt/pkg/network/setup"
70
        netvmispec "kubevirt.io/kubevirt/pkg/network/vmispec"
71
        "kubevirt.io/kubevirt/pkg/pointer"
72
        "kubevirt.io/kubevirt/pkg/safepath"
73
        "kubevirt.io/kubevirt/pkg/storage/reservation"
74
        pvctypes "kubevirt.io/kubevirt/pkg/storage/types"
75
        storagetypes "kubevirt.io/kubevirt/pkg/storage/types"
76
        "kubevirt.io/kubevirt/pkg/util"
77
        virtutil "kubevirt.io/kubevirt/pkg/util"
78
        "kubevirt.io/kubevirt/pkg/util/hardware"
79
        "kubevirt.io/kubevirt/pkg/util/migrations"
80
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
81
        "kubevirt.io/kubevirt/pkg/virt-controller/services"
82
        "kubevirt.io/kubevirt/pkg/virt-controller/watch/topology"
83
        virtcache "kubevirt.io/kubevirt/pkg/virt-handler/cache"
84
        "kubevirt.io/kubevirt/pkg/virt-handler/cgroup"
85
        cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
86
        container_disk "kubevirt.io/kubevirt/pkg/virt-handler/container-disk"
87
        device_manager "kubevirt.io/kubevirt/pkg/virt-handler/device-manager"
88
        "kubevirt.io/kubevirt/pkg/virt-handler/heartbeat"
89
        hotplug_volume "kubevirt.io/kubevirt/pkg/virt-handler/hotplug-disk"
90
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
91
        migrationproxy "kubevirt.io/kubevirt/pkg/virt-handler/migration-proxy"
92
        "kubevirt.io/kubevirt/pkg/virt-handler/selinux"
93
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
94
        "kubevirt.io/kubevirt/pkg/virtiofs"
95
)
96

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

102
type netstat interface {
103
        UpdateStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) error
104
        Teardown(vmi *v1.VirtualMachineInstance)
105
        PodInterfaceVolatileDataIsCached(vmi *v1.VirtualMachineInstance, ifaceName string) bool
106
        CachePodInterfaceVolatileData(vmi *v1.VirtualMachineInstance, ifaceName string, data *netcache.PodIfaceCacheData)
107
}
108

109
type downwardMetricsManager interface {
110
        Run(stopCh chan struct{})
111
        StartServer(vmi *v1.VirtualMachineInstance, pid int) error
112
        StopServer(vmi *v1.VirtualMachineInstance)
113
}
114

115
const (
116
        failedDetectIsolationFmt              = "failed to detect isolation for launcher pod: %v"
117
        unableCreateVirtLauncherConnectionFmt = "unable to create virt-launcher client connection: %v"
118
        // This value was determined after consulting with libvirt developers and performing extensive testing.
119
        parallelMultifdMigrationThreads = uint(8)
120
)
121

122
const (
123
        //VolumeReadyReason is the reason set when the volume is ready.
124
        VolumeReadyReason = "VolumeReady"
125
        //VolumeUnMountedFromPodReason is the reason set when the volume is unmounted from the virtlauncher pod
126
        VolumeUnMountedFromPodReason = "VolumeUnMountedFromPod"
127
        //VolumeMountedToPodReason is the reason set when the volume is mounted to the virtlauncher pod
128
        VolumeMountedToPodReason = "VolumeMountedToPod"
129
        //VolumeUnplugged is the reason set when the volume is completely unplugged from the VMI
130
        VolumeUnplugged = "VolumeUnplugged"
131
        //VMIDefined is the reason set when a VMI is defined
132
        VMIDefined = "VirtualMachineInstance defined."
133
        //VMIStarted is the reason set when a VMI is started
134
        VMIStarted = "VirtualMachineInstance started."
135
        //VMIShutdown is the reason set when a VMI is shutdown
136
        VMIShutdown = "The VirtualMachineInstance was shut down."
137
        //VMICrashed is the reason set when a VMI crashed
138
        VMICrashed = "The VirtualMachineInstance crashed."
139
        //VMIAbortingMigration is the reason set when migration is being aborted
140
        VMIAbortingMigration = "VirtualMachineInstance is aborting migration."
141
        //VMIMigrating in the reason set when the VMI is migrating
142
        VMIMigrating = "VirtualMachineInstance is migrating."
143
        //VMIMigrationTargetPrepared is the reason set when the migration target has been prepared
144
        VMIMigrationTargetPrepared = "VirtualMachineInstance Migration Target Prepared."
145
        //VMIStopping is the reason set when the VMI is stopping
146
        VMIStopping = "VirtualMachineInstance stopping"
147
        //VMIGracefulShutdown is the reason set when the VMI is gracefully shut down
148
        VMIGracefulShutdown = "Signaled Graceful Shutdown"
149
        //VMISignalDeletion is the reason set when the VMI has signal deletion
150
        VMISignalDeletion = "Signaled Deletion"
151

152
        // MemoryHotplugFailedReason is the reason set when the VM cannot hotplug memory
153
        memoryHotplugFailedReason = "Memory Hotplug Failed"
154
)
155

156
var getCgroupManager = func(vmi *v1.VirtualMachineInstance) (cgroup.Manager, error) {
×
157
        return cgroup.NewManagerFromVM(vmi)
×
158
}
×
159

160
func NewController(
161
        recorder record.EventRecorder,
162
        clientset kubecli.KubevirtClient,
163
        host string,
164
        migrationIpAddress string,
165
        virtShareDir string,
166
        virtPrivateDir string,
167
        kubeletPodsDir string,
168
        vmiSourceInformer cache.SharedIndexInformer,
169
        vmiTargetInformer cache.SharedIndexInformer,
170
        domainInformer cache.SharedInformer,
171
        maxDevices int,
172
        clusterConfig *virtconfig.ClusterConfig,
173
        podIsolationDetector isolation.PodIsolationDetector,
174
        migrationProxy migrationproxy.ProxyManager,
175
        downwardMetricsManager downwardMetricsManager,
176
        capabilities *libvirtxml.Caps,
177
        hostCpuModel string,
178
        netConf netconf,
179
        netStat netstat,
180
        netBindingPluginMemoryCalculator netBindingPluginMemoryCalculator,
181
) (*VirtualMachineController, error) {
1✔
182

1✔
183
        queue := workqueue.NewTypedRateLimitingQueueWithConfig[string](
1✔
184
                workqueue.DefaultTypedControllerRateLimiter[string](),
1✔
185
                workqueue.TypedRateLimitingQueueConfig[string]{Name: "virt-handler-vm"},
1✔
186
        )
1✔
187

1✔
188
        containerDiskState := filepath.Join(virtPrivateDir, "container-disk-mount-state")
1✔
189
        if err := os.MkdirAll(containerDiskState, 0700); err != nil {
1✔
190
                return nil, err
×
191
        }
×
192

193
        hotplugState := filepath.Join(virtPrivateDir, "hotplug-volume-mount-state")
1✔
194
        if err := os.MkdirAll(hotplugState, 0700); err != nil {
1✔
195
                return nil, err
×
196
        }
×
197

198
        c := &VirtualMachineController{
1✔
199
                queue:                            queue,
1✔
200
                recorder:                         recorder,
1✔
201
                clientset:                        clientset,
1✔
202
                host:                             host,
1✔
203
                migrationIpAddress:               migrationIpAddress,
1✔
204
                virtShareDir:                     virtShareDir,
1✔
205
                vmiSourceStore:                   vmiSourceInformer.GetStore(),
1✔
206
                vmiTargetStore:                   vmiTargetInformer.GetStore(),
1✔
207
                domainStore:                      domainInformer.GetStore(),
1✔
208
                heartBeatInterval:                1 * time.Minute,
1✔
209
                migrationProxy:                   migrationProxy,
1✔
210
                podIsolationDetector:             podIsolationDetector,
1✔
211
                containerDiskMounter:             container_disk.NewMounter(podIsolationDetector, containerDiskState, clusterConfig),
1✔
212
                hotplugVolumeMounter:             hotplug_volume.NewVolumeMounter(hotplugState, kubeletPodsDir),
1✔
213
                clusterConfig:                    clusterConfig,
1✔
214
                virtLauncherFSRunDirPattern:      "/proc/%d/root/var/run",
1✔
215
                capabilities:                     capabilities,
1✔
216
                hostCpuModel:                     hostCpuModel,
1✔
217
                vmiExpectations:                  controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()),
1✔
218
                sriovHotplugExecutorPool:         executor.NewRateLimitedExecutorPool(executor.NewExponentialLimitedBackoffCreator()),
1✔
219
                ioErrorRetryManager:              NewFailRetryManager("io-error-retry", 10*time.Second, 3*time.Minute, 30*time.Second),
1✔
220
                netConf:                          netConf,
1✔
221
                netStat:                          netStat,
1✔
222
                netBindingPluginMemoryCalculator: netBindingPluginMemoryCalculator,
1✔
223
        }
1✔
224

1✔
225
        c.hasSynced = func() bool {
1✔
226
                return domainInformer.HasSynced() && vmiSourceInformer.HasSynced() && vmiTargetInformer.HasSynced()
×
227
        }
×
228

229
        _, err := vmiSourceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
230
                AddFunc:    c.addFunc,
1✔
231
                DeleteFunc: c.deleteFunc,
1✔
232
                UpdateFunc: c.updateFunc,
1✔
233
        })
1✔
234
        if err != nil {
1✔
235
                return nil, err
×
236
        }
×
237

238
        _, err = vmiTargetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
239
                AddFunc:    c.addFunc,
1✔
240
                DeleteFunc: c.deleteFunc,
1✔
241
                UpdateFunc: c.updateFunc,
1✔
242
        })
1✔
243
        if err != nil {
1✔
244
                return nil, err
×
245
        }
×
246

247
        _, err = domainInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
248
                AddFunc:    c.addDomainFunc,
1✔
249
                DeleteFunc: c.deleteDomainFunc,
1✔
250
                UpdateFunc: c.updateDomainFunc,
1✔
251
        })
1✔
252
        if err != nil {
1✔
253
                return nil, err
×
254
        }
×
255

256
        c.launcherClients = virtcache.LauncherClientInfoByVMI{}
1✔
257

1✔
258
        c.downwardMetricsManager = downwardMetricsManager
1✔
259

1✔
260
        c.domainNotifyPipes = make(map[string]string)
1✔
261

1✔
262
        permissions := "rw"
1✔
263
        if cgroups.IsCgroup2UnifiedMode() {
1✔
264
                // Need 'rwm' permissions otherwise ebpf filtering program attached by runc
×
265
                // will deny probing the device file with 'access' syscall. That in turn
×
266
                // will lead to virtqemud failure on VM startup.
×
267
                // This has been fixed upstream:
×
268
                //   https://github.com/opencontainers/runc/pull/2796
×
269
                // but the workaround is still needed to support previous versions without
×
270
                // the patch.
×
271
                permissions = "rwm"
×
272
        }
×
273

274
        c.deviceManagerController = device_manager.NewDeviceController(
1✔
275
                c.host,
1✔
276
                maxDevices,
1✔
277
                permissions,
1✔
278
                device_manager.PermanentHostDevicePlugins(maxDevices, permissions),
1✔
279
                clusterConfig,
1✔
280
                clientset.CoreV1())
1✔
281
        c.heartBeat = heartbeat.NewHeartBeat(clientset.CoreV1(), c.deviceManagerController, clusterConfig, host)
1✔
282

1✔
283
        return c, nil
1✔
284
}
285

286
type netBindingPluginMemoryCalculator interface {
287
        Calculate(vmi *v1.VirtualMachineInstance, registeredPlugins map[string]v1.InterfaceBindingPlugin) resource.Quantity
288
}
289

290
type VirtualMachineController struct {
291
        recorder                 record.EventRecorder
292
        clientset                kubecli.KubevirtClient
293
        host                     string
294
        migrationIpAddress       string
295
        virtShareDir             string
296
        virtPrivateDir           string
297
        queue                    workqueue.TypedRateLimitingInterface[string]
298
        vmiSourceStore           cache.Store
299
        vmiTargetStore           cache.Store
300
        domainStore              cache.Store
301
        launcherClients          virtcache.LauncherClientInfoByVMI
302
        heartBeatInterval        time.Duration
303
        deviceManagerController  *device_manager.DeviceController
304
        migrationProxy           migrationproxy.ProxyManager
305
        podIsolationDetector     isolation.PodIsolationDetector
306
        containerDiskMounter     container_disk.Mounter
307
        hotplugVolumeMounter     hotplug_volume.VolumeMounter
308
        clusterConfig            *virtconfig.ClusterConfig
309
        sriovHotplugExecutorPool *executor.RateLimitedExecutorPool
310
        downwardMetricsManager   downwardMetricsManager
311

312
        netConf                          netconf
313
        netStat                          netstat
314
        netBindingPluginMemoryCalculator netBindingPluginMemoryCalculator
315

316
        domainNotifyPipes           map[string]string
317
        virtLauncherFSRunDirPattern string
318
        heartBeat                   *heartbeat.HeartBeat
319
        capabilities                *libvirtxml.Caps
320
        hostCpuModel                string
321
        vmiExpectations             *controller.UIDTrackingControllerExpectations
322
        ioErrorRetryManager         *FailRetryManager
323
        hasSynced                   func() bool
324
}
325

326
type virtLauncherCriticalSecurebootError struct {
327
        msg string
328
}
329

330
func (e *virtLauncherCriticalSecurebootError) Error() string { return e.msg }
×
331

332
type vmiIrrecoverableError struct {
333
        msg string
334
}
335

336
func (e *vmiIrrecoverableError) Error() string { return e.msg }
×
337

338
func formatIrrecoverableErrorMessage(domain *api.Domain) string {
×
339
        msg := "unknown reason"
×
340
        if domainPausedFailedPostCopy(domain) {
×
341
                msg = "VMI is irrecoverable due to failed post-copy migration"
×
342
        }
×
343
        return msg
×
344
}
345

346
func handleDomainNotifyPipe(domainPipeStopChan chan struct{}, ln net.Listener, virtShareDir string, vmi *v1.VirtualMachineInstance) {
1✔
347

1✔
348
        fdChan := make(chan net.Conn, 100)
1✔
349

1✔
350
        // Close listener and exit when stop encountered
1✔
351
        go func() {
2✔
352
                <-domainPipeStopChan
1✔
353
                log.Log.Object(vmi).Infof("closing notify pipe listener for vmi")
1✔
354
                if err := ln.Close(); err != nil {
1✔
355
                        log.Log.Object(vmi).Infof("failed closing notify pipe listener for vmi: %v", err)
×
356
                }
×
357
        }()
358

359
        // Listen for new connections,
360
        go func(vmi *v1.VirtualMachineInstance, ln net.Listener, domainPipeStopChan chan struct{}) {
2✔
361
                for {
2✔
362
                        fd, err := ln.Accept()
1✔
363
                        if err != nil {
2✔
364
                                if goerror.Is(err, net.ErrClosed) {
2✔
365
                                        // As Accept blocks, closing it is our mechanism to exit this loop
1✔
366
                                        return
1✔
367
                                }
1✔
368
                                log.Log.Reason(err).Error("Domain pipe accept error encountered.")
×
369
                                // keep listening until stop invoked
×
370
                                time.Sleep(1 * time.Second)
×
371
                        } else {
1✔
372
                                fdChan <- fd
1✔
373
                        }
1✔
374
                }
375
        }(vmi, ln, domainPipeStopChan)
376

377
        // Process new connections
378
        // exit when stop encountered
379
        go func(vmi *v1.VirtualMachineInstance, fdChan chan net.Conn, domainPipeStopChan chan struct{}) {
2✔
380
                for {
2✔
381
                        select {
1✔
382
                        case <-domainPipeStopChan:
1✔
383
                                return
1✔
384
                        case fd := <-fdChan:
1✔
385
                                go func(vmi *v1.VirtualMachineInstance) {
2✔
386
                                        defer fd.Close()
1✔
387

1✔
388
                                        // pipe the VMI domain-notify.sock to the virt-handler domain-notify.sock
1✔
389
                                        // so virt-handler receives notifications from the VMI
1✔
390
                                        conn, err := net.Dial("unix", filepath.Join(virtShareDir, "domain-notify.sock"))
1✔
391
                                        if err != nil {
2✔
392
                                                log.Log.Reason(err).Error("error connecting to domain-notify.sock for proxy connection")
1✔
393
                                                return
1✔
394
                                        }
1✔
395
                                        defer conn.Close()
1✔
396

1✔
397
                                        log.Log.Object(vmi).Infof("Accepted new notify pipe connection for vmi")
1✔
398
                                        copyErr := make(chan error, 2)
1✔
399
                                        go func() {
2✔
400
                                                _, err := io.Copy(fd, conn)
1✔
401
                                                copyErr <- err
1✔
402
                                        }()
1✔
403
                                        go func() {
2✔
404
                                                _, err := io.Copy(conn, fd)
1✔
405
                                                copyErr <- err
1✔
406
                                        }()
1✔
407

408
                                        // wait until one of the copy routines exit then
409
                                        // let the fd close
410
                                        err = <-copyErr
1✔
411
                                        if err != nil {
2✔
412
                                                log.Log.Object(vmi).Infof("closing notify pipe connection for vmi with error: %v", err)
1✔
413
                                        } else {
2✔
414
                                                log.Log.Object(vmi).Infof("gracefully closed notify pipe connection for vmi")
1✔
415
                                        }
1✔
416

417
                                }(vmi)
418
                        }
419
                }
420
        }(vmi, fdChan, domainPipeStopChan)
421
}
422

423
func (c *VirtualMachineController) startDomainNotifyPipe(domainPipeStopChan chan struct{}, vmi *v1.VirtualMachineInstance) error {
×
424

×
425
        res, err := c.podIsolationDetector.Detect(vmi)
×
426
        if err != nil {
×
427
                return fmt.Errorf("failed to detect isolation for launcher pod when setting up notify pipe: %v", err)
×
428
        }
×
429

430
        // inject the domain-notify.sock into the VMI pod.
431
        root, err := res.MountRoot()
×
432
        if err != nil {
×
433
                return err
×
434
        }
×
435
        socketDir, err := root.AppendAndResolveWithRelativeRoot(c.virtShareDir)
×
436
        if err != nil {
×
437
                return err
×
438
        }
×
439

440
        listener, err := safepath.ListenUnixNoFollow(socketDir, "domain-notify-pipe.sock")
×
441
        if err != nil {
×
442
                log.Log.Reason(err).Error("failed to create unix socket for proxy service")
×
443
                return err
×
444
        }
×
445
        socketPath, err := safepath.JoinNoFollow(socketDir, "domain-notify-pipe.sock")
×
446
        if err != nil {
×
447
                return err
×
448
        }
×
449

450
        if util.IsNonRootVMI(vmi) {
×
451
                err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath)
×
452
                if err != nil {
×
453
                        log.Log.Reason(err).Error("unable to change ownership for domain notify")
×
454
                        return err
×
455
                }
×
456
        }
457

458
        handleDomainNotifyPipe(domainPipeStopChan, listener, c.virtShareDir, vmi)
×
459

×
460
        return nil
×
461
}
462

463
// Determines if a domain's grace period has expired during shutdown.
464
// If the grace period has started but not expired, timeLeft represents
465
// the time in seconds left until the period expires.
466
// If the grace period has not started, timeLeft will be set to -1.
467
func (c *VirtualMachineController) hasGracePeriodExpired(dom *api.Domain) (hasExpired bool, timeLeft int64) {
1✔
468

1✔
469
        hasExpired = false
1✔
470
        timeLeft = 0
1✔
471

1✔
472
        if dom == nil {
1✔
473
                hasExpired = true
×
474
                return
×
475
        }
×
476

477
        startTime := int64(0)
1✔
478
        if dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp != nil {
2✔
479
                startTime = dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp.UTC().Unix()
1✔
480
        }
1✔
481
        gracePeriod := dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds
1✔
482

1✔
483
        // If gracePeriod == 0, then there will be no startTime set, deletion
1✔
484
        // should occur immediately during shutdown.
1✔
485
        if gracePeriod == 0 {
1✔
486
                hasExpired = true
×
487
                return
×
488
        } else if startTime == 0 {
2✔
489
                // If gracePeriod > 0, then the shutdown signal needs to be sent
1✔
490
                // and the gracePeriod start time needs to be set.
1✔
491
                timeLeft = -1
1✔
492
                return
1✔
493
        }
1✔
494

495
        now := time.Now().UTC().Unix()
1✔
496
        diff := now - startTime
1✔
497

1✔
498
        if diff >= gracePeriod {
2✔
499
                hasExpired = true
1✔
500
                return
1✔
501
        }
1✔
502

503
        timeLeft = int64(gracePeriod - diff)
×
504
        if timeLeft < 1 {
×
505
                timeLeft = 1
×
506
        }
×
507
        return
×
508
}
509

510
func (c *VirtualMachineController) hasTargetDetectedReadyDomain(vmi *v1.VirtualMachineInstance) (bool, int64) {
1✔
511
        // give the target node 60 seconds to discover the libvirt domain via the domain informer
1✔
512
        // before allowing the VMI to be processed. This closes the gap between the
1✔
513
        // VMI's status getting updated to reflect the new source node, and the domain
1✔
514
        // informer firing the event to alert the source node of the new domain.
1✔
515
        migrationTargetDelayTimeout := 60
1✔
516

1✔
517
        if vmi.Status.MigrationState != nil &&
1✔
518
                vmi.Status.MigrationState.TargetNodeDomainDetected &&
1✔
519
                vmi.Status.MigrationState.TargetNodeDomainReadyTimestamp != nil {
2✔
520

1✔
521
                return true, 0
1✔
522
        }
1✔
523

524
        nowUnix := time.Now().UTC().Unix()
×
525
        migrationEndUnix := vmi.Status.MigrationState.EndTimestamp.Time.UTC().Unix()
×
526

×
527
        diff := nowUnix - migrationEndUnix
×
528

×
529
        if diff > int64(migrationTargetDelayTimeout) {
×
530
                return false, 0
×
531
        }
×
532

533
        timeLeft := int64(migrationTargetDelayTimeout) - diff
×
534

×
535
        enqueueTime := timeLeft
×
536
        if enqueueTime < 5 {
×
537
                enqueueTime = 5
×
538
        }
×
539

540
        // re-enqueue the key to ensure it gets processed again within the right time.
541
        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Duration(enqueueTime)*time.Second)
×
542

×
543
        return false, timeLeft
×
544
}
545

546
// teardownNetwork performs network cache cleanup for a specific VMI.
547
func (c *VirtualMachineController) teardownNetwork(vmi *v1.VirtualMachineInstance) {
1✔
548
        if string(vmi.UID) == "" {
2✔
549
                return
1✔
550
        }
1✔
551
        if err := c.netConf.Teardown(vmi); err != nil {
1✔
552
                log.Log.Reason(err).Errorf("failed to delete VMI Network cache files: %s", err.Error())
×
553
        }
×
554
        c.netStat.Teardown(vmi)
1✔
555
}
556

557
func domainPausedFailedPostCopy(domain *api.Domain) bool {
1✔
558
        return domain != nil && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedPostcopyFailed
1✔
559
}
1✔
560

561
func domainMigrated(domain *api.Domain) bool {
1✔
562
        return domain != nil && domain.Status.Status == api.Shutoff && domain.Status.Reason == api.ReasonMigrated
1✔
563
}
1✔
564

565
func canUpdateToMounted(currentPhase v1.VolumePhase) bool {
1✔
566
        return currentPhase == v1.VolumeBound || currentPhase == v1.VolumePending || currentPhase == v1.HotplugVolumeAttachedToNode
1✔
567
}
1✔
568

569
func canUpdateToUnmounted(currentPhase v1.VolumePhase) bool {
1✔
570
        return currentPhase == v1.VolumeReady || currentPhase == v1.HotplugVolumeMounted || currentPhase == v1.HotplugVolumeAttachedToNode
1✔
571
}
1✔
572

573
func (c *VirtualMachineController) setMigrationProgressStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
1✔
574
        if domain == nil ||
1✔
575
                domain.Spec.Metadata.KubeVirt.Migration == nil ||
1✔
576
                vmi.Status.MigrationState == nil ||
1✔
577
                !c.isMigrationSource(vmi) {
2✔
578
                return
1✔
579
        }
1✔
580

581
        migrationMetadata := domain.Spec.Metadata.KubeVirt.Migration
1✔
582
        if migrationMetadata.UID != vmi.Status.MigrationState.MigrationUID {
1✔
583
                return
×
584
        }
×
585

586
        if vmi.Status.MigrationState.EndTimestamp == nil && migrationMetadata.EndTimestamp != nil {
2✔
587
                if migrationMetadata.Failed {
1✔
588
                        vmi.Status.MigrationState.FailureReason = migrationMetadata.FailureReason
×
589
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("VirtualMachineInstance migration uid %s failed. reason:%s", string(migrationMetadata.UID), migrationMetadata.FailureReason))
×
590
                }
×
591
        }
592

593
        if vmi.Status.MigrationState.StartTimestamp == nil {
2✔
594
                vmi.Status.MigrationState.StartTimestamp = migrationMetadata.StartTimestamp
1✔
595
        }
1✔
596
        if vmi.Status.MigrationState.EndTimestamp == nil {
2✔
597
                vmi.Status.MigrationState.EndTimestamp = migrationMetadata.EndTimestamp
1✔
598
        }
1✔
599
        vmi.Status.MigrationState.AbortStatus = v1.MigrationAbortStatus(migrationMetadata.AbortStatus)
1✔
600
        vmi.Status.MigrationState.Completed = migrationMetadata.Completed
1✔
601
        vmi.Status.MigrationState.Failed = migrationMetadata.Failed
1✔
602
        vmi.Status.MigrationState.Mode = migrationMetadata.Mode
1✔
603
}
604

605
func (c *VirtualMachineController) migrationSourceUpdateVMIStatus(origVMI *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
606

1✔
607
        vmi := origVMI.DeepCopy()
1✔
608
        oldStatus := vmi.DeepCopy().Status
1✔
609

1✔
610
        // if a migration happens very quickly, it's possible parts of the in
1✔
611
        // progress status wasn't set. We need to make sure we set this even
1✔
612
        // if the migration has completed
1✔
613
        c.setMigrationProgressStatus(vmi, domain)
1✔
614

1✔
615
        // handle migrations differently than normal status updates.
1✔
616
        //
1✔
617
        // When a successful migration is detected, we must transfer ownership of the VMI
1✔
618
        // from the source node (this node) to the target node (node the domain was migrated to).
1✔
619
        //
1✔
620
        // Transfer ownership by...
1✔
621
        // 1. Marking vmi.Status.MigrationState as completed
1✔
622
        // 2. Update the vmi.Status.NodeName to reflect the target node's name
1✔
623
        // 3. Update the VMI's NodeNameLabel annotation to reflect the target node's name
1✔
624
        // 4. Clear the LauncherContainerImageVersion which virt-controller will detect
1✔
625
        //    and accurately based on the version used on the target pod
1✔
626
        //
1✔
627
        // After a migration, the VMI's phase is no longer owned by this node. Only the
1✔
628
        // MigrationState status field is eligible to be mutated.
1✔
629
        migrationHost := ""
1✔
630
        if vmi.Status.MigrationState != nil {
2✔
631
                migrationHost = vmi.Status.MigrationState.TargetNode
1✔
632
        }
1✔
633

634
        if vmi.Status.MigrationState != nil && vmi.Status.MigrationState.EndTimestamp == nil {
1✔
635
                now := metav1.NewTime(time.Now())
×
636
                vmi.Status.MigrationState.EndTimestamp = &now
×
637
        }
×
638

639
        targetNodeDetectedDomain, timeLeft := c.hasTargetDetectedReadyDomain(vmi)
1✔
640
        // If we can't detect where the migration went to, then we have no
1✔
641
        // way of transferring ownership. The only option here is to move the
1✔
642
        // vmi to failed.  The cluster vmi controller will then tear down the
1✔
643
        // resulting pods.
1✔
644
        if migrationHost == "" {
1✔
645
                // migrated to unknown host.
×
646
                vmi.Status.Phase = v1.Failed
×
647
                vmi.Status.MigrationState.Completed = true
×
648
                vmi.Status.MigrationState.Failed = true
×
649

×
650
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("The VirtualMachineInstance migrated to unknown host."))
×
651
        } else if !targetNodeDetectedDomain {
1✔
652
                if timeLeft <= 0 {
×
653
                        vmi.Status.Phase = v1.Failed
×
654
                        vmi.Status.MigrationState.Completed = true
×
655
                        vmi.Status.MigrationState.Failed = true
×
656

×
657
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("The VirtualMachineInstance's domain was never observed on the target after the migration completed within the timeout period."))
×
658
                } else {
×
659
                        log.Log.Object(vmi).Info("Waiting on the target node to observe the migrated domain before performing the handoff")
×
660
                }
×
661
        } else if vmi.Status.MigrationState != nil {
2✔
662
                // this is the migration ACK.
1✔
663
                // At this point we know that the migration has completed and that
1✔
664
                // the target node has seen the domain event.
1✔
665
                vmi.Labels[v1.NodeNameLabel] = migrationHost
1✔
666
                delete(vmi.Labels, v1.OutdatedLauncherImageLabel)
1✔
667
                vmi.Status.LauncherContainerImageVersion = ""
1✔
668
                vmi.Status.NodeName = migrationHost
1✔
669
                // clean the evacuation node name since have already migrated to a new node
1✔
670
                vmi.Status.EvacuationNodeName = ""
1✔
671
                vmi.Status.MigrationState.Completed = true
1✔
672
                // update the vmi migrationTransport to indicate that next migration should use unix URI
1✔
673
                // new workloads will set the migrationTransport on their creation, however, legacy workloads
1✔
674
                // can make the switch only after the first migration
1✔
675
                vmi.Status.MigrationTransport = v1.MigrationTransportUnix
1✔
676
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Migrated.String(), fmt.Sprintf("The VirtualMachineInstance migrated to node %s.", migrationHost))
1✔
677
                log.Log.Object(vmi).Infof("migration completed to node %s", migrationHost)
1✔
678
        }
1✔
679

680
        if !equality.Semantic.DeepEqual(oldStatus, vmi.Status) {
2✔
681
                key := controller.VirtualMachineInstanceKey(vmi)
1✔
682
                c.vmiExpectations.SetExpectations(key, 1, 0)
1✔
683
                _, err := c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmi, metav1.UpdateOptions{})
1✔
684
                if err != nil {
1✔
685
                        c.vmiExpectations.LowerExpectations(key, 1, 0)
×
686
                        return err
×
687
                }
×
688
        }
689
        return nil
1✔
690
}
691

692
func domainIsActiveOnTarget(domain *api.Domain) bool {
1✔
693

1✔
694
        if domain == nil {
1✔
695
                return false
×
696
        }
×
697

698
        // It's possible for the domain to be active on the target node if the domain is
699
        // 1. Running
700
        // 2. User initiated Paused
701
        if domain.Status.Status == api.Running {
2✔
702
                return true
1✔
703
        } else if domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedUser {
1✔
704
                return true
×
705
        }
×
706
        return false
×
707

708
}
709

710
func (c *VirtualMachineController) migrationTargetUpdateVMIStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
711

1✔
712
        vmiCopy := vmi.DeepCopy()
1✔
713

1✔
714
        if migrations.MigrationFailed(vmi) {
2✔
715
                // nothing left to report on the target node if the migration failed
1✔
716
                return nil
1✔
717
        }
1✔
718

719
        domainExists := domain != nil
1✔
720

1✔
721
        // Handle post migration
1✔
722
        if domainExists && vmi.Status.MigrationState != nil && !vmi.Status.MigrationState.TargetNodeDomainDetected {
2✔
723
                // record that we've see the domain populated on the target's node
1✔
724
                log.Log.Object(vmi).Info("The target node received the migrated domain")
1✔
725
                vmiCopy.Status.MigrationState.TargetNodeDomainDetected = true
1✔
726

1✔
727
                // adjust QEMU process memlock limits in order to enable old virt-launcher pod's to
1✔
728
                // perform hotplug host-devices on post migration.
1✔
729
                if err := isolation.AdjustQemuProcessMemoryLimits(c.podIsolationDetector, vmi, c.clusterConfig.GetConfig().AdditionalGuestMemoryOverheadRatio); err != nil {
1✔
730
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), "Failed to update target node qemu memory limits during live migration")
×
731
                }
×
732

733
        }
734

735
        if domainExists &&
1✔
736
                domainIsActiveOnTarget(domain) &&
1✔
737
                vmi.Status.MigrationState != nil &&
1✔
738
                vmi.Status.MigrationState.TargetNodeDomainReadyTimestamp == nil {
2✔
739

1✔
740
                // record the moment we detected the domain is running.
1✔
741
                // This is used as a trigger to help coordinate when CNI drivers
1✔
742
                // fail over the IP to the new pod.
1✔
743
                log.Log.Object(vmi).Info("The target node received the running migrated domain")
1✔
744
                now := metav1.Now()
1✔
745
                vmiCopy.Status.MigrationState.TargetNodeDomainReadyTimestamp = &now
1✔
746
                c.finalizeMigration(vmiCopy)
1✔
747
        }
1✔
748

749
        if !migrations.IsMigrating(vmi) {
2✔
750
                destSrcPortsMap := c.migrationProxy.GetTargetListenerPorts(string(vmi.UID))
1✔
751
                if len(destSrcPortsMap) == 0 {
1✔
752
                        msg := "target migration listener is not up for this vmi"
×
753
                        log.Log.Object(vmi).Error(msg)
×
754
                        return fmt.Errorf(msg)
×
755
                }
×
756

757
                hostAddress := ""
1✔
758
                // advertise the listener address to the source node
1✔
759
                if vmi.Status.MigrationState != nil {
2✔
760
                        hostAddress = vmi.Status.MigrationState.TargetNodeAddress
1✔
761
                }
1✔
762
                if hostAddress != c.migrationIpAddress {
2✔
763
                        portsList := make([]string, 0, len(destSrcPortsMap))
1✔
764

1✔
765
                        for k := range destSrcPortsMap {
2✔
766
                                portsList = append(portsList, k)
1✔
767
                        }
1✔
768
                        portsStrList := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(portsList)), ","), "[]")
1✔
769
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.PreparingTarget.String(), fmt.Sprintf("Migration Target is listening at %s, on ports: %s", c.migrationIpAddress, portsStrList))
1✔
770
                        vmiCopy.Status.MigrationState.TargetNodeAddress = c.migrationIpAddress
1✔
771
                        vmiCopy.Status.MigrationState.TargetDirectMigrationNodePorts = destSrcPortsMap
1✔
772
                }
773

774
                // If the migrated VMI requires dedicated CPUs, report the new pod CPU set to the source node
775
                // via the VMI migration status in order to patch the domain pre migration
776
                if vmi.IsCPUDedicated() {
1✔
777
                        err := c.reportDedicatedCPUSetForMigratingVMI(vmiCopy)
×
778
                        if err != nil {
×
779
                                return err
×
780
                        }
×
781
                        err = c.reportTargetTopologyForMigratingVMI(vmiCopy)
×
782
                        if err != nil {
×
783
                                return err
×
784
                        }
×
785
                }
786
        }
787

788
        // update the VMI if necessary
789
        if !equality.Semantic.DeepEqual(vmi.Status, vmiCopy.Status) {
2✔
790
                key := controller.VirtualMachineInstanceKey(vmi)
1✔
791
                c.vmiExpectations.SetExpectations(key, 1, 0)
1✔
792
                _, err := c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmiCopy, metav1.UpdateOptions{})
1✔
793
                if err != nil {
1✔
794
                        c.vmiExpectations.LowerExpectations(key, 1, 0)
×
795
                        return err
×
796
                }
×
797
        }
798

799
        return nil
1✔
800
}
801

802
func (c *VirtualMachineController) generateEventsForVolumeStatusChange(vmi *v1.VirtualMachineInstance, newStatusMap map[string]v1.VolumeStatus) {
1✔
803
        newStatusMapCopy := make(map[string]v1.VolumeStatus)
1✔
804
        for k, v := range newStatusMap {
2✔
805
                newStatusMapCopy[k] = v
1✔
806
        }
1✔
807
        for _, oldStatus := range vmi.Status.VolumeStatus {
2✔
808
                newStatus, ok := newStatusMap[oldStatus.Name]
1✔
809
                if !ok {
1✔
810
                        // status got removed
×
811
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, VolumeUnplugged, fmt.Sprintf("Volume %s has been unplugged", oldStatus.Name))
×
812
                        continue
×
813
                }
814
                if newStatus.Phase != oldStatus.Phase {
2✔
815
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, newStatus.Reason, newStatus.Message)
1✔
816
                }
1✔
817
                delete(newStatusMapCopy, newStatus.Name)
1✔
818
        }
819
        // Send events for any new statuses.
820
        for _, v := range newStatusMapCopy {
2✔
821
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v.Reason, v.Message)
1✔
822
        }
1✔
823
}
824

825
func (c *VirtualMachineController) updateHotplugVolumeStatus(vmi *v1.VirtualMachineInstance, volumeStatus v1.VolumeStatus, specVolumeMap map[string]v1.Volume) (v1.VolumeStatus, bool) {
1✔
826
        needsRefresh := false
1✔
827
        if volumeStatus.Target == "" {
2✔
828
                needsRefresh = true
1✔
829
                mounted, err := c.hotplugVolumeMounter.IsMounted(vmi, volumeStatus.Name, volumeStatus.HotplugVolume.AttachPodUID)
1✔
830
                if err != nil {
1✔
831
                        log.Log.Object(vmi).Errorf("error occurred while checking if volume is mounted: %v", err)
×
832
                }
×
833
                if mounted {
2✔
834
                        if _, ok := specVolumeMap[volumeStatus.Name]; ok && canUpdateToMounted(volumeStatus.Phase) {
2✔
835
                                log.DefaultLogger().Infof("Marking volume %s as mounted in pod, it can now be attached", volumeStatus.Name)
1✔
836
                                // mounted, and still in spec, and in phase we can change, update status to mounted.
1✔
837
                                volumeStatus.Phase = v1.HotplugVolumeMounted
1✔
838
                                volumeStatus.Message = fmt.Sprintf("Volume %s has been mounted in virt-launcher pod", volumeStatus.Name)
1✔
839
                                volumeStatus.Reason = VolumeMountedToPodReason
1✔
840
                        }
1✔
841
                } else {
1✔
842
                        // Not mounted, check if the volume is in the spec, if not update status
1✔
843
                        if _, ok := specVolumeMap[volumeStatus.Name]; !ok && canUpdateToUnmounted(volumeStatus.Phase) {
2✔
844
                                log.DefaultLogger().Infof("Marking volume %s as unmounted from pod, it can now be detached", volumeStatus.Name)
1✔
845
                                // Not mounted.
1✔
846
                                volumeStatus.Phase = v1.HotplugVolumeUnMounted
1✔
847
                                volumeStatus.Message = fmt.Sprintf("Volume %s has been unmounted from virt-launcher pod", volumeStatus.Name)
1✔
848
                                volumeStatus.Reason = VolumeUnMountedFromPodReason
1✔
849
                        }
1✔
850
                }
851
        } else {
1✔
852
                // Successfully attached to VM.
1✔
853
                volumeStatus.Phase = v1.VolumeReady
1✔
854
                volumeStatus.Message = fmt.Sprintf("Successfully attach hotplugged volume %s to VM", volumeStatus.Name)
1✔
855
                volumeStatus.Reason = VolumeReadyReason
1✔
856
        }
1✔
857
        return volumeStatus, needsRefresh
1✔
858
}
859

860
func needToComputeChecksums(vmi *v1.VirtualMachineInstance) bool {
1✔
861
        containerDisks := map[string]*v1.Volume{}
1✔
862
        for _, volume := range vmi.Spec.Volumes {
2✔
863
                if volume.VolumeSource.ContainerDisk != nil {
2✔
864
                        containerDisks[volume.Name] = &volume
1✔
865
                }
1✔
866
        }
867

868
        for i := range vmi.Status.VolumeStatus {
2✔
869
                _, isContainerDisk := containerDisks[vmi.Status.VolumeStatus[i].Name]
1✔
870
                if !isContainerDisk {
2✔
871
                        continue
1✔
872
                }
873

874
                if vmi.Status.VolumeStatus[i].ContainerDiskVolume == nil ||
1✔
875
                        vmi.Status.VolumeStatus[i].ContainerDiskVolume.Checksum == 0 {
2✔
876
                        return true
1✔
877
                }
1✔
878
        }
879

880
        if util.HasKernelBootContainerImage(vmi) {
1✔
881
                if vmi.Status.KernelBootStatus == nil {
×
882
                        return true
×
883
                }
×
884

885
                kernelBootContainer := vmi.Spec.Domain.Firmware.KernelBoot.Container
×
886

×
887
                if kernelBootContainer.KernelPath != "" &&
×
888
                        (vmi.Status.KernelBootStatus.KernelInfo == nil ||
×
889
                                vmi.Status.KernelBootStatus.KernelInfo.Checksum == 0) {
×
890
                        return true
×
891

×
892
                }
×
893

894
                if kernelBootContainer.InitrdPath != "" &&
×
895
                        (vmi.Status.KernelBootStatus.InitrdInfo == nil ||
×
896
                                vmi.Status.KernelBootStatus.InitrdInfo.Checksum == 0) {
×
897
                        return true
×
898

×
899
                }
×
900
        }
901

902
        return false
1✔
903
}
904

905
func (c *VirtualMachineController) updateChecksumInfo(vmi *v1.VirtualMachineInstance, syncError error) error {
1✔
906

1✔
907
        if syncError != nil || vmi.DeletionTimestamp != nil || !needToComputeChecksums(vmi) {
2✔
908
                return nil
1✔
909
        }
1✔
910

911
        diskChecksums, err := c.containerDiskMounter.ComputeChecksums(vmi)
1✔
912
        if goerror.Is(err, container_disk.ErrDiskContainerGone) {
1✔
913
                log.Log.Errorf("cannot compute checksums as containerdisk/kernelboot containers seem to have been terminated")
×
914
                return nil
×
915
        }
×
916
        if err != nil {
1✔
917
                return err
×
918
        }
×
919

920
        // containerdisks
921
        for i := range vmi.Status.VolumeStatus {
2✔
922
                checksum, exists := diskChecksums.ContainerDiskChecksums[vmi.Status.VolumeStatus[i].Name]
1✔
923
                if !exists {
1✔
924
                        // not a containerdisk
×
925
                        continue
×
926
                }
927

928
                vmi.Status.VolumeStatus[i].ContainerDiskVolume = &v1.ContainerDiskInfo{
1✔
929
                        Checksum: checksum,
1✔
930
                }
1✔
931
        }
932

933
        // kernelboot
934
        if util.HasKernelBootContainerImage(vmi) {
2✔
935
                vmi.Status.KernelBootStatus = &v1.KernelBootStatus{}
1✔
936

1✔
937
                if diskChecksums.KernelBootChecksum.Kernel != nil {
2✔
938
                        vmi.Status.KernelBootStatus.KernelInfo = &v1.KernelInfo{
1✔
939
                                Checksum: *diskChecksums.KernelBootChecksum.Kernel,
1✔
940
                        }
1✔
941
                }
1✔
942

943
                if diskChecksums.KernelBootChecksum.Initrd != nil {
2✔
944
                        vmi.Status.KernelBootStatus.InitrdInfo = &v1.InitrdInfo{
1✔
945
                                Checksum: *diskChecksums.KernelBootChecksum.Initrd,
1✔
946
                        }
1✔
947
                }
1✔
948
        }
949

950
        return nil
1✔
951
}
952

953
func (c *VirtualMachineController) updateVolumeStatusesFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
1✔
954
        // used by unit test
1✔
955
        hasHotplug := false
1✔
956

1✔
957
        if domain == nil {
2✔
958
                return hasHotplug
1✔
959
        }
1✔
960

961
        if len(vmi.Status.VolumeStatus) > 0 {
2✔
962
                diskDeviceMap := make(map[string]string)
1✔
963
                for _, disk := range domain.Spec.Devices.Disks {
2✔
964
                        diskDeviceMap[disk.Alias.GetName()] = disk.Target.Device
1✔
965
                }
1✔
966
                specVolumeMap := make(map[string]v1.Volume)
1✔
967
                for _, volume := range vmi.Spec.Volumes {
2✔
968
                        specVolumeMap[volume.Name] = volume
1✔
969
                }
1✔
970
                newStatusMap := make(map[string]v1.VolumeStatus)
1✔
971
                newStatuses := make([]v1.VolumeStatus, 0)
1✔
972
                needsRefresh := false
1✔
973
                for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
974
                        tmpNeedsRefresh := false
1✔
975
                        if _, ok := diskDeviceMap[volumeStatus.Name]; ok {
2✔
976
                                volumeStatus.Target = diskDeviceMap[volumeStatus.Name]
1✔
977
                        }
1✔
978
                        if volumeStatus.HotplugVolume != nil {
2✔
979
                                hasHotplug = true
1✔
980
                                volumeStatus, tmpNeedsRefresh = c.updateHotplugVolumeStatus(vmi, volumeStatus, specVolumeMap)
1✔
981
                                needsRefresh = needsRefresh || tmpNeedsRefresh
1✔
982
                        }
1✔
983
                        if volumeStatus.MemoryDumpVolume != nil {
2✔
984
                                volumeStatus, tmpNeedsRefresh = c.updateMemoryDumpInfo(vmi, volumeStatus, domain)
1✔
985
                                needsRefresh = needsRefresh || tmpNeedsRefresh
1✔
986
                        }
1✔
987
                        newStatuses = append(newStatuses, volumeStatus)
1✔
988
                        newStatusMap[volumeStatus.Name] = volumeStatus
1✔
989
                }
990
                sort.SliceStable(newStatuses, func(i, j int) bool {
2✔
991
                        return strings.Compare(newStatuses[i].Name, newStatuses[j].Name) == -1
1✔
992
                })
1✔
993
                if needsRefresh {
2✔
994
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second)
1✔
995
                }
1✔
996
                c.generateEventsForVolumeStatusChange(vmi, newStatusMap)
1✔
997
                vmi.Status.VolumeStatus = newStatuses
1✔
998
        }
999
        return hasHotplug
1✔
1000
}
1001

1002
func (c *VirtualMachineController) updateGuestInfoFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
1✔
1003

1✔
1004
        if domain == nil || domain.Status.OSInfo.Name == "" || vmi.Status.GuestOSInfo.Name == domain.Status.OSInfo.Name {
2✔
1005
                return
1✔
1006
        }
1✔
1007

1008
        vmi.Status.GuestOSInfo.Name = domain.Status.OSInfo.Name
1✔
1009
        vmi.Status.GuestOSInfo.Version = domain.Status.OSInfo.Version
1✔
1010
        vmi.Status.GuestOSInfo.KernelRelease = domain.Status.OSInfo.KernelRelease
1✔
1011
        vmi.Status.GuestOSInfo.PrettyName = domain.Status.OSInfo.PrettyName
1✔
1012
        vmi.Status.GuestOSInfo.VersionID = domain.Status.OSInfo.VersionId
1✔
1013
        vmi.Status.GuestOSInfo.KernelVersion = domain.Status.OSInfo.KernelVersion
1✔
1014
        vmi.Status.GuestOSInfo.Machine = domain.Status.OSInfo.Machine
1✔
1015
        vmi.Status.GuestOSInfo.ID = domain.Status.OSInfo.Id
1✔
1016
}
1017

1018
func (c *VirtualMachineController) updateAccessCredentialConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) {
1✔
1019

1✔
1020
        if domain == nil || domain.Spec.Metadata.KubeVirt.AccessCredential == nil {
2✔
1021
                return
1✔
1022
        }
1✔
1023

1024
        message := domain.Spec.Metadata.KubeVirt.AccessCredential.Message
1✔
1025
        status := k8sv1.ConditionFalse
1✔
1026
        if domain.Spec.Metadata.KubeVirt.AccessCredential.Succeeded {
2✔
1027
                status = k8sv1.ConditionTrue
1✔
1028
        }
1✔
1029

1030
        add := false
1✔
1031
        condition := condManager.GetCondition(vmi, v1.VirtualMachineInstanceAccessCredentialsSynchronized)
1✔
1032
        if condition == nil {
2✔
1033
                add = true
1✔
1034
        } else if condition.Status != status || condition.Message != message {
3✔
1035
                // if not as expected, remove, then add.
1✔
1036
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAccessCredentialsSynchronized)
1✔
1037
                add = true
1✔
1038
        }
1✔
1039
        if add {
2✔
1040
                newCondition := v1.VirtualMachineInstanceCondition{
1✔
1041
                        Type:               v1.VirtualMachineInstanceAccessCredentialsSynchronized,
1✔
1042
                        LastTransitionTime: metav1.Now(),
1✔
1043
                        Status:             status,
1✔
1044
                        Message:            message,
1✔
1045
                }
1✔
1046
                vmi.Status.Conditions = append(vmi.Status.Conditions, newCondition)
1✔
1047
                if status == k8sv1.ConditionTrue {
2✔
1048
                        eventMessage := "Access credentials sync successful."
1✔
1049
                        if message != "" {
1✔
NEW
1050
                                eventMessage = fmt.Sprintf("Access credentials sync successful: %s", message)
×
NEW
1051
                        }
×
1052
                        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.AccessCredentialsSyncSuccess.String(), eventMessage)
1✔
1053
                } else {
1✔
1054
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.AccessCredentialsSyncFailed.String(),
1✔
1055
                                fmt.Sprintf("Access credentials sync failed: %s", message),
1✔
1056
                        )
1✔
1057
                }
1✔
1058
        }
1059
}
1060

1061
func (c *VirtualMachineController) updateLiveMigrationConditions(vmi *v1.VirtualMachineInstance, condManager *controller.VirtualMachineInstanceConditionManager) {
1✔
1062
        // Calculate whether the VM is migratable
1✔
1063
        liveMigrationCondition, isBlockMigration := c.calculateLiveMigrationCondition(vmi)
1✔
1064
        if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceIsMigratable) {
2✔
1065
                vmi.Status.Conditions = append(vmi.Status.Conditions, *liveMigrationCondition)
1✔
1066
                // Set VMI Migration Method
1✔
1067
                if isBlockMigration {
2✔
1068
                        vmi.Status.MigrationMethod = v1.BlockMigration
1✔
1069
                } else {
2✔
1070
                        vmi.Status.MigrationMethod = v1.LiveMigration
1✔
1071
                }
1✔
1072
        } else {
1✔
1073
                cond := condManager.GetCondition(vmi, v1.VirtualMachineInstanceIsMigratable)
1✔
1074
                if !equality.Semantic.DeepEqual(cond, liveMigrationCondition) {
1✔
1075
                        condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceIsMigratable)
×
1076
                        vmi.Status.Conditions = append(vmi.Status.Conditions, *liveMigrationCondition)
×
1077
                }
×
1078
        }
1079
        storageLiveMigCond := c.calculateLiveStorageMigrationCondition(vmi)
1✔
1080
        condManager.UpdateCondition(vmi, storageLiveMigCond)
1✔
1081
        evictable := migrations.VMIMigratableOnEviction(c.clusterConfig, vmi)
1✔
1082
        if evictable && liveMigrationCondition.Status == k8sv1.ConditionFalse {
2✔
1083
                c.recorder.Eventf(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), "EvictionStrategy is set but vmi is not migratable; %s", liveMigrationCondition.Message)
1✔
1084
        }
1✔
1085
}
1086

1087
func (c *VirtualMachineController) updateGuestAgentConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) error {
1✔
1088

1✔
1089
        // Update the condition when GA is connected
1✔
1090
        channelConnected := false
1✔
1091
        if domain != nil {
2✔
1092
                for _, channel := range domain.Spec.Devices.Channels {
2✔
1093
                        if channel.Target != nil {
2✔
1094
                                log.Log.V(4).Infof("Channel: %s, %s", channel.Target.Name, channel.Target.State)
1✔
1095
                                if channel.Target.Name == "org.qemu.guest_agent.0" {
2✔
1096
                                        if channel.Target.State == "connected" {
2✔
1097
                                                channelConnected = true
1✔
1098
                                        }
1✔
1099
                                }
1100

1101
                        }
1102
                }
1103
        }
1104

1105
        switch {
1✔
1106
        case channelConnected && !condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected):
1✔
1107
                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
1108
                        Type:          v1.VirtualMachineInstanceAgentConnected,
1✔
1109
                        LastProbeTime: metav1.Now(),
1✔
1110
                        Status:        k8sv1.ConditionTrue,
1✔
1111
                }
1✔
1112
                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
1113
        case !channelConnected:
1✔
1114
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAgentConnected)
1✔
1115
        }
1116

1117
        if condManager.HasCondition(vmi, v1.VirtualMachineInstanceAgentConnected) {
2✔
1118
                client, err := c.getLauncherClient(vmi)
1✔
1119
                if err != nil {
1✔
1120
                        return err
×
1121
                }
×
1122

1123
                guestInfo, err := client.GetGuestInfo()
1✔
1124
                if err != nil {
1✔
1125
                        return err
×
1126
                }
×
1127

1128
                var supported = false
1✔
1129
                var reason = ""
1✔
1130

1✔
1131
                // For current versions, virt-launcher's supported commands will always contain data.
1✔
1132
                // For backwards compatibility: during upgrade from a previous version of KubeVirt,
1✔
1133
                // virt-launcher might not provide any supported commands. If the list of supported
1✔
1134
                // commands is empty, fall back to previous behavior.
1✔
1135
                if len(guestInfo.SupportedCommands) > 0 {
1✔
1136
                        supported, reason = isGuestAgentSupported(vmi, guestInfo.SupportedCommands)
×
1137
                        log.Log.V(3).Object(vmi).Info(reason)
×
1138
                } else {
1✔
1139
                        for _, version := range c.clusterConfig.GetSupportedAgentVersions() {
2✔
1140
                                supported = supported || regexp.MustCompile(version).MatchString(guestInfo.GAVersion)
1✔
1141
                        }
1✔
1142
                        if !supported {
2✔
1143
                                reason = fmt.Sprintf("Guest agent version '%s' is not supported", guestInfo.GAVersion)
1✔
1144
                        }
1✔
1145
                }
1146

1147
                if !supported {
2✔
1148
                        if !condManager.HasCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent) {
2✔
1149
                                agentCondition := v1.VirtualMachineInstanceCondition{
1✔
1150
                                        Type:          v1.VirtualMachineInstanceUnsupportedAgent,
1✔
1151
                                        LastProbeTime: metav1.Now(),
1✔
1152
                                        Status:        k8sv1.ConditionTrue,
1✔
1153
                                        Reason:        reason,
1✔
1154
                                }
1✔
1155
                                vmi.Status.Conditions = append(vmi.Status.Conditions, agentCondition)
1✔
1156
                        }
1✔
1157
                } else {
×
1158
                        condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceUnsupportedAgent)
×
1159
                }
×
1160

1161
        }
1162
        return nil
1✔
1163
}
1164

1165
func (c *VirtualMachineController) updatePausedConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) {
1✔
1166

1✔
1167
        // Update paused condition in case VMI was paused / unpaused
1✔
1168
        if domain != nil && domain.Status.Status == api.Paused {
2✔
1169
                if !condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
2✔
1170
                        reason := domain.Status.Reason
1✔
1171
                        if c.isVMIPausedDuringMigration(vmi) {
1✔
1172
                                reason = api.ReasonPausedMigration
×
1173
                        }
×
1174
                        calculatePausedCondition(vmi, reason)
1✔
1175
                }
1176
        } else if condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) {
2✔
1177
                log.Log.Object(vmi).V(3).Info("Removing paused condition")
1✔
1178
                condManager.RemoveCondition(vmi, v1.VirtualMachineInstancePaused)
1✔
1179
        }
1✔
1180
}
1181

1182
func dumpTargetFile(vmiName, volName string) string {
1✔
1183
        targetFileName := fmt.Sprintf("%s-%s-%s.memory.dump", vmiName, volName, time.Now().Format("20060102-150405"))
1✔
1184
        return targetFileName
1✔
1185
}
1✔
1186

1187
func (c *VirtualMachineController) updateMemoryDumpInfo(vmi *v1.VirtualMachineInstance, volumeStatus v1.VolumeStatus, domain *api.Domain) (v1.VolumeStatus, bool) {
1✔
1188
        needsRefresh := false
1✔
1189
        switch volumeStatus.Phase {
1✔
1190
        case v1.HotplugVolumeMounted:
1✔
1191
                needsRefresh = true
1✔
1192
                log.Log.Object(vmi).V(3).Infof("Memory dump volume %s attached, marking it in progress", volumeStatus.Name)
1✔
1193
                volumeStatus.Phase = v1.MemoryDumpVolumeInProgress
1✔
1194
                volumeStatus.Message = fmt.Sprintf("Memory dump Volume %s is attached, getting memory dump", volumeStatus.Name)
1✔
1195
                volumeStatus.Reason = VolumeMountedToPodReason
1✔
1196
                volumeStatus.MemoryDumpVolume.TargetFileName = dumpTargetFile(vmi.Name, volumeStatus.Name)
1✔
1197
        case v1.MemoryDumpVolumeInProgress:
1✔
1198
                memoryDumpMetadata := domain.Spec.Metadata.KubeVirt.MemoryDump
1✔
1199
                if memoryDumpMetadata == nil || memoryDumpMetadata.FileName != volumeStatus.MemoryDumpVolume.TargetFileName {
2✔
1200
                        // memory dump wasnt triggered yet
1✔
1201
                        return volumeStatus, needsRefresh
1✔
1202
                }
1✔
1203
                needsRefresh = true
1✔
1204
                if memoryDumpMetadata.StartTimestamp != nil {
2✔
1205
                        volumeStatus.MemoryDumpVolume.StartTimestamp = memoryDumpMetadata.StartTimestamp
1✔
1206
                }
1✔
1207
                if memoryDumpMetadata.EndTimestamp != nil && memoryDumpMetadata.Failed {
2✔
1208
                        log.Log.Object(vmi).Errorf("Memory dump to pvc %s failed: %v", volumeStatus.Name, memoryDumpMetadata.FailureReason)
1✔
1209
                        volumeStatus.Message = fmt.Sprintf("Memory dump to pvc %s failed: %v", volumeStatus.Name, memoryDumpMetadata.FailureReason)
1✔
1210
                        volumeStatus.Phase = v1.MemoryDumpVolumeFailed
1✔
1211
                        volumeStatus.MemoryDumpVolume.EndTimestamp = memoryDumpMetadata.EndTimestamp
1✔
1212
                } else if memoryDumpMetadata.Completed {
3✔
1213
                        log.Log.Object(vmi).V(3).Infof("Marking memory dump to volume %s has completed", volumeStatus.Name)
1✔
1214
                        volumeStatus.Phase = v1.MemoryDumpVolumeCompleted
1✔
1215
                        volumeStatus.Message = fmt.Sprintf("Memory dump to Volume %s has completed successfully", volumeStatus.Name)
1✔
1216
                        volumeStatus.Reason = VolumeReadyReason
1✔
1217
                        volumeStatus.MemoryDumpVolume.EndTimestamp = memoryDumpMetadata.EndTimestamp
1✔
1218
                }
1✔
1219
        }
1220

1221
        return volumeStatus, needsRefresh
1✔
1222
}
1223

1224
func (c *VirtualMachineController) updateFSFreezeStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
1✔
1225

1✔
1226
        if domain == nil || domain.Status.FSFreezeStatus.Status == "" {
2✔
1227
                return
1✔
1228
        }
1✔
1229

1230
        if domain.Status.FSFreezeStatus.Status == api.FSThawed {
2✔
1231
                vmi.Status.FSFreezeStatus = ""
1✔
1232
        } else {
2✔
1233
                vmi.Status.FSFreezeStatus = domain.Status.FSFreezeStatus.Status
1✔
1234
        }
1✔
1235

1236
}
1237

1238
func IsoGuestVolumePath(namespace, name string, volume *v1.Volume) string {
×
1239
        const basepath = "/var/run"
×
1240
        switch {
×
1241
        case volume.CloudInitNoCloud != nil:
×
1242
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "noCloud.iso")
×
1243
        case volume.CloudInitConfigDrive != nil:
×
1244
                return filepath.Join(basepath, "kubevirt-ephemeral-disks", "cloud-init-data", namespace, name, "configdrive.iso")
×
1245
        case volume.ConfigMap != nil:
×
1246
                return config.GetConfigMapDiskPath(volume.Name)
×
1247
        case volume.DownwardAPI != nil:
×
1248
                return config.GetDownwardAPIDiskPath(volume.Name)
×
1249
        case volume.Secret != nil:
×
1250
                return config.GetSecretDiskPath(volume.Name)
×
1251
        case volume.ServiceAccount != nil:
×
1252
                return config.GetServiceAccountDiskPath()
×
1253
        case volume.Sysprep != nil:
×
1254
                return config.GetSysprepDiskPath(volume.Name)
×
1255
        default:
×
1256
                return ""
×
1257
        }
1258
}
1259

1260
func (c *VirtualMachineController) updateIsoSizeStatus(vmi *v1.VirtualMachineInstance) {
1✔
1261
        var podUID string
1✔
1262
        if vmi.Status.Phase != v1.Running {
2✔
1263
                return
1✔
1264
        }
1✔
1265

1266
        for k, v := range vmi.Status.ActivePods {
2✔
1267
                if v == vmi.Status.NodeName {
2✔
1268
                        podUID = string(k)
1✔
1269
                        break
1✔
1270
                }
1271
        }
1272
        if podUID == "" {
2✔
1273
                log.DefaultLogger().Warningf("failed to find pod UID for VMI %s", vmi.Name)
1✔
1274
                return
1✔
1275
        }
1✔
1276

1277
        volumes := make(map[string]v1.Volume)
1✔
1278
        for _, volume := range vmi.Spec.Volumes {
1✔
1279
                volumes[volume.Name] = volume
×
1280
        }
×
1281

1282
        for _, disk := range vmi.Spec.Domain.Devices.Disks {
1✔
1283
                volume, ok := volumes[disk.Name]
×
1284
                if !ok {
×
1285
                        log.DefaultLogger().Warningf("No matching volume with name %s found", disk.Name)
×
1286
                        continue
×
1287
                }
1288

1289
                volPath := IsoGuestVolumePath(vmi.Namespace, vmi.Name, &volume)
×
1290
                if volPath == "" {
×
1291
                        continue
×
1292
                }
1293

1294
                res, err := c.podIsolationDetector.Detect(vmi)
×
1295
                if err != nil {
×
1296
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
1297
                        continue
×
1298
                }
1299

1300
                rootPath, err := res.MountRoot()
×
1301
                if err != nil {
×
1302
                        log.DefaultLogger().Reason(err).Warningf("failed to detect VMI %s", vmi.Name)
×
1303
                        continue
×
1304
                }
1305

1306
                safeVolPath, err := rootPath.AppendAndResolveWithRelativeRoot(volPath)
×
1307
                if err != nil {
×
1308
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
1309
                        continue
×
1310
                }
1311
                fileInfo, err := safepath.StatAtNoFollow(safeVolPath)
×
1312
                if err != nil {
×
1313
                        log.DefaultLogger().Warningf("failed to determine file size for volume %s", volPath)
×
1314
                        continue
×
1315
                }
1316

1317
                for i := range vmi.Status.VolumeStatus {
×
1318
                        if vmi.Status.VolumeStatus[i].Name == volume.Name {
×
1319
                                vmi.Status.VolumeStatus[i].Size = fileInfo.Size()
×
1320
                                continue
×
1321
                        }
1322
                }
1323
        }
1324
}
1325

1326
func (c *VirtualMachineController) updateSELinuxContext(vmi *v1.VirtualMachineInstance) error {
1✔
1327
        _, present, err := selinux.NewSELinux()
1✔
1328
        if err != nil {
2✔
1329
                return err
1✔
1330
        }
1✔
1331
        if present {
×
1332
                context, err := selinux.GetVirtLauncherContext(vmi)
×
1333
                if err != nil {
×
1334
                        return err
×
1335
                }
×
1336
                vmi.Status.SelinuxContext = context
×
1337
        } else {
×
1338
                vmi.Status.SelinuxContext = "none"
×
1339
        }
×
1340

1341
        return nil
×
1342
}
1343

1344
func (c *VirtualMachineController) updateVMIStatusFromDomain(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
1345
        c.updateIsoSizeStatus(vmi)
1✔
1346
        err := c.updateSELinuxContext(vmi)
1✔
1347
        if err != nil {
2✔
1348
                log.Log.Reason(err).Errorf("couldn't find the SELinux context for %s", vmi.Name)
1✔
1349
        }
1✔
1350
        c.setMigrationProgressStatus(vmi, domain)
1✔
1351
        c.updateGuestInfoFromDomain(vmi, domain)
1✔
1352
        c.updateVolumeStatusesFromDomain(vmi, domain)
1✔
1353
        c.updateFSFreezeStatus(vmi, domain)
1✔
1354
        c.updateMachineType(vmi, domain)
1✔
1355
        if err = c.updateMemoryInfo(vmi, domain); err != nil {
1✔
1356
                return err
×
1357
        }
×
1358
        err = c.netStat.UpdateStatus(vmi, domain)
1✔
1359
        return err
1✔
1360
}
1361

1362
func (c *VirtualMachineController) updateVMIConditions(vmi *v1.VirtualMachineInstance, domain *api.Domain, condManager *controller.VirtualMachineInstanceConditionManager) error {
1✔
1363
        c.updateAccessCredentialConditions(vmi, domain, condManager)
1✔
1364
        c.updateLiveMigrationConditions(vmi, condManager)
1✔
1365
        err := c.updateGuestAgentConditions(vmi, domain, condManager)
1✔
1366
        if err != nil {
1✔
1367
                return err
×
1368
        }
×
1369
        c.updatePausedConditions(vmi, domain, condManager)
1✔
1370

1✔
1371
        return nil
1✔
1372
}
1373

1374
func (c *VirtualMachineController) updateVMIStatus(origVMI *v1.VirtualMachineInstance, domain *api.Domain, syncError error) (err error) {
1✔
1375
        condManager := controller.NewVirtualMachineInstanceConditionManager()
1✔
1376

1✔
1377
        // Don't update the VirtualMachineInstance if it is already in a final state
1✔
1378
        if origVMI.IsFinal() {
2✔
1379
                return nil
1✔
1380
        } else if origVMI.Status.NodeName != "" && origVMI.Status.NodeName != c.host {
3✔
1381
                // Only update the VMI's phase if this node owns the VMI.
1✔
1382
                // not owned by this host, likely the result of a migration
1✔
1383
                return nil
1✔
1384
        } else if domainMigrated(domain) {
3✔
1385
                return c.migrationSourceUpdateVMIStatus(origVMI, domain)
1✔
1386
        }
1✔
1387

1388
        vmi := origVMI.DeepCopy()
1✔
1389
        oldStatus := *vmi.Status.DeepCopy()
1✔
1390

1✔
1391
        // Update VMI status fields based on what is reported on the domain
1✔
1392
        err = c.updateVMIStatusFromDomain(vmi, domain)
1✔
1393
        if err != nil {
1✔
1394
                return err
×
1395
        }
×
1396

1397
        // Calculate the new VirtualMachineInstance state based on what libvirt reported
1398
        err = c.setVmPhaseForStatusReason(domain, vmi)
1✔
1399
        if err != nil {
1✔
1400
                return err
×
1401
        }
×
1402

1403
        // Update conditions on VMI Status
1404
        err = c.updateVMIConditions(vmi, domain, condManager)
1✔
1405
        if err != nil {
1✔
1406
                return err
×
1407
        }
×
1408

1409
        // Store containerdisks and kernelboot checksums
1410
        if err := c.updateChecksumInfo(vmi, syncError); err != nil {
1✔
1411
                return err
×
1412
        }
×
1413

1414
        // Handle sync error
1415
        handleSyncError(vmi, condManager, syncError)
1✔
1416

1✔
1417
        controller.SetVMIPhaseTransitionTimestamp(origVMI, vmi)
1✔
1418

1✔
1419
        // Only issue vmi update if status has changed
1✔
1420
        if !equality.Semantic.DeepEqual(oldStatus, vmi.Status) {
2✔
1421
                key := controller.VirtualMachineInstanceKey(vmi)
1✔
1422
                c.vmiExpectations.SetExpectations(key, 1, 0)
1✔
1423
                _, err = c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmi, metav1.UpdateOptions{})
1✔
1424
                if err != nil {
2✔
1425
                        c.vmiExpectations.LowerExpectations(key, 1, 0)
1✔
1426
                        return err
1✔
1427
                }
1✔
1428
        }
1429

1430
        // Record an event on the VMI when the VMI's phase changes
1431
        if oldStatus.Phase != vmi.Status.Phase {
2✔
1432
                c.recordPhaseChangeEvent(vmi)
1✔
1433
        }
1✔
1434

1435
        return nil
1✔
1436
}
1437

1438
func handleSyncError(vmi *v1.VirtualMachineInstance, condManager *controller.VirtualMachineInstanceConditionManager, syncError error) {
1✔
1439
        var criticalNetErr *neterrors.CriticalNetworkError
1✔
1440
        if goerror.As(syncError, &criticalNetErr) {
2✔
1441
                log.Log.Errorf("virt-launcher crashed due to a network error. Updating VMI %s status to Failed", vmi.Name)
1✔
1442
                vmi.Status.Phase = v1.Failed
1✔
1443
        }
1✔
1444
        if _, ok := syncError.(*virtLauncherCriticalSecurebootError); ok {
1✔
1445
                log.Log.Errorf("virt-launcher does not support the Secure Boot setting. Updating VMI %s status to Failed", vmi.Name)
×
1446
                vmi.Status.Phase = v1.Failed
×
1447
        }
×
1448

1449
        if _, ok := syncError.(*vmiIrrecoverableError); ok {
1✔
1450
                log.Log.Errorf("virt-launcher reached an irrecoverable error. Updating VMI %s status to Failed", vmi.Name)
×
1451
                vmi.Status.Phase = v1.Failed
×
1452
        }
×
1453
        condManager.CheckFailure(vmi, syncError, "Synchronizing with the Domain failed.")
1✔
1454
}
1455

1456
func (c *VirtualMachineController) recordPhaseChangeEvent(vmi *v1.VirtualMachineInstance) {
1✔
1457
        switch vmi.Status.Phase {
1✔
1458
        case v1.Running:
1✔
1459
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Started.String(), VMIStarted)
1✔
1460
        case v1.Succeeded:
×
1461
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Stopped.String(), VMIShutdown)
×
1462
        case v1.Failed:
1✔
1463
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Stopped.String(), VMICrashed)
1✔
1464
        }
1465
}
1466

1467
func calculatePausedCondition(vmi *v1.VirtualMachineInstance, reason api.StateChangeReason) {
1✔
1468
        now := metav1.NewTime(time.Now())
1✔
1469
        switch reason {
1✔
1470
        case api.ReasonPausedMigration:
×
1471
                log.Log.Object(vmi).V(3).Info("Adding paused condition")
×
1472
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
1473
                        Type:               v1.VirtualMachineInstancePaused,
×
1474
                        Status:             k8sv1.ConditionTrue,
×
1475
                        LastProbeTime:      now,
×
1476
                        LastTransitionTime: now,
×
1477
                        Reason:             "PausedByMigrationMonitor",
×
1478
                        Message:            "VMI was paused by the migration monitor",
×
1479
                })
×
1480
        case api.ReasonPausedUser:
1✔
1481
                log.Log.Object(vmi).V(3).Info("Adding paused condition")
1✔
1482
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
1✔
1483
                        Type:               v1.VirtualMachineInstancePaused,
1✔
1484
                        Status:             k8sv1.ConditionTrue,
1✔
1485
                        LastProbeTime:      now,
1✔
1486
                        LastTransitionTime: now,
1✔
1487
                        Reason:             "PausedByUser",
1✔
1488
                        Message:            "VMI was paused by user",
1✔
1489
                })
1✔
1490
        case api.ReasonPausedIOError:
×
1491
                log.Log.Object(vmi).V(3).Info("Adding paused condition")
×
1492
                vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{
×
1493
                        Type:               v1.VirtualMachineInstancePaused,
×
1494
                        Status:             k8sv1.ConditionTrue,
×
1495
                        LastProbeTime:      now,
×
1496
                        LastTransitionTime: now,
×
1497
                        Reason:             "PausedIOError",
×
1498
                        Message:            "VMI was paused, low-level IO error detected",
×
1499
                })
×
1500
        default:
×
1501
                log.Log.Object(vmi).V(3).Infof("Domain is paused for unknown reason, %s", reason)
×
1502
        }
1503
}
1504

1505
func newNonMigratableCondition(msg string, reason string) *v1.VirtualMachineInstanceCondition {
1✔
1506
        return &v1.VirtualMachineInstanceCondition{
1✔
1507
                Type:    v1.VirtualMachineInstanceIsMigratable,
1✔
1508
                Status:  k8sv1.ConditionFalse,
1✔
1509
                Message: msg,
1✔
1510
                Reason:  reason,
1✔
1511
        }
1✔
1512
}
1✔
1513

1514
func (c *VirtualMachineController) calculateLiveMigrationCondition(vmi *v1.VirtualMachineInstance) (*v1.VirtualMachineInstanceCondition, bool) {
1✔
1515
        isBlockMigration, err := c.checkVolumesForMigration(vmi)
1✔
1516
        if err != nil {
2✔
1517
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonDisksNotMigratable), isBlockMigration
1✔
1518
        }
1✔
1519

1520
        err = c.checkNetworkInterfacesForMigration(vmi)
1✔
1521
        if err != nil {
2✔
1522
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonInterfaceNotMigratable), isBlockMigration
1✔
1523
        }
1✔
1524

1525
        if err := c.isHostModelMigratable(vmi); err != nil {
1✔
1526
                return newNonMigratableCondition(err.Error(), v1.VirtualMachineInstanceReasonCPUModeNotMigratable), isBlockMigration
×
1527
        }
×
1528

1529
        if vmiContainsPCIHostDevice(vmi) {
2✔
1530
                return newNonMigratableCondition("VMI uses a PCI host devices", v1.VirtualMachineInstanceReasonHostDeviceNotMigratable), isBlockMigration
1✔
1531
        }
1✔
1532

1533
        if util.IsSEVVMI(vmi) {
2✔
1534
                return newNonMigratableCondition("VMI uses SEV", v1.VirtualMachineInstanceReasonSEVNotMigratable), isBlockMigration
1✔
1535
        }
1✔
1536

1537
        if reservation.HasVMIPersistentReservation(vmi) {
2✔
1538
                return newNonMigratableCondition("VMI uses SCSI persitent reservation", v1.VirtualMachineInstanceReasonPRNotMigratable), isBlockMigration
1✔
1539
        }
1✔
1540

1541
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
2✔
1542
                return newNonMigratableCondition(tscRequirement.Reason, v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable), isBlockMigration
1✔
1543
        }
1✔
1544

1545
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
2✔
1546
                return newNonMigratableCondition("VMI uses hyperv passthrough", v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable), isBlockMigration
1✔
1547
        }
1✔
1548

1549
        return &v1.VirtualMachineInstanceCondition{
1✔
1550
                Type:   v1.VirtualMachineInstanceIsMigratable,
1✔
1551
                Status: k8sv1.ConditionTrue,
1✔
1552
        }, isBlockMigration
1✔
1553
}
1554

1555
func vmiContainsPCIHostDevice(vmi *v1.VirtualMachineInstance) bool {
1✔
1556
        return len(vmi.Spec.Domain.Devices.HostDevices) > 0 || len(vmi.Spec.Domain.Devices.GPUs) > 0
1✔
1557
}
1✔
1558

1559
type multipleNonMigratableCondition struct {
1560
        reasons []string
1561
        msgs    []string
1562
}
1563

1564
func newMultipleNonMigratableCondition() *multipleNonMigratableCondition {
1✔
1565
        return &multipleNonMigratableCondition{}
1✔
1566
}
1✔
1567

1568
func (cond *multipleNonMigratableCondition) addNonMigratableCondition(reason, msg string) {
1✔
1569
        cond.reasons = append(cond.reasons, reason)
1✔
1570
        cond.msgs = append(cond.msgs, msg)
1✔
1571
}
1✔
1572

1573
func (cond *multipleNonMigratableCondition) String() string {
1✔
1574
        var buffer bytes.Buffer
1✔
1575
        for i, c := range cond.reasons {
2✔
1576
                if i > 0 {
1✔
1577
                        buffer.WriteString(", ")
×
1578
                }
×
1579
                buffer.WriteString(fmt.Sprintf("%s: %s", c, cond.msgs[i]))
1✔
1580
        }
1581
        return buffer.String()
1✔
1582
}
1583

1584
func (cond *multipleNonMigratableCondition) generateStorageLiveMigrationCondition() *v1.VirtualMachineInstanceCondition {
1✔
1585
        switch len(cond.reasons) {
1✔
1586
        case 0:
1✔
1587
                return &v1.VirtualMachineInstanceCondition{
1✔
1588
                        Type:   v1.VirtualMachineInstanceIsStorageLiveMigratable,
1✔
1589
                        Status: k8sv1.ConditionTrue,
1✔
1590
                }
1✔
1591
        default:
1✔
1592
                return &v1.VirtualMachineInstanceCondition{
1✔
1593
                        Type:    v1.VirtualMachineInstanceIsStorageLiveMigratable,
1✔
1594
                        Status:  k8sv1.ConditionFalse,
1✔
1595
                        Message: cond.String(),
1✔
1596
                        Reason:  v1.VirtualMachineInstanceReasonNotMigratable,
1✔
1597
                }
1✔
1598
        }
1599
}
1600

1601
func (c *VirtualMachineController) calculateLiveStorageMigrationCondition(vmi *v1.VirtualMachineInstance) *v1.VirtualMachineInstanceCondition {
1✔
1602
        multiCond := newMultipleNonMigratableCondition()
1✔
1603

1✔
1604
        if err := c.checkNetworkInterfacesForMigration(vmi); err != nil {
2✔
1605
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonInterfaceNotMigratable, err.Error())
1✔
1606
        }
1✔
1607

1608
        if err := c.isHostModelMigratable(vmi); err != nil {
1✔
1609
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonCPUModeNotMigratable, err.Error())
×
1610
        }
×
1611

1612
        if vmiContainsPCIHostDevice(vmi) {
1✔
1613
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHostDeviceNotMigratable, "VMI uses a PCI host devices")
×
1614
        }
×
1615

1616
        if util.IsSEVVMI(vmi) {
1✔
1617
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonSEVNotMigratable, "VMI uses SEV")
×
1618
        }
×
1619

1620
        if reservation.HasVMIPersistentReservation(vmi) {
1✔
1621
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonPRNotMigratable, "VMI uses SCSI persitent reservation")
×
1622
        }
×
1623

1624
        if tscRequirement := topology.GetTscFrequencyRequirement(vmi); !topology.AreTSCFrequencyTopologyHintsDefined(vmi) && tscRequirement.Type == topology.RequiredForMigration {
1✔
1625
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonNoTSCFrequencyMigratable, tscRequirement.Reason)
×
1626
        }
×
1627

1628
        if vmiFeatures := vmi.Spec.Domain.Features; vmiFeatures != nil && vmiFeatures.HypervPassthrough != nil && *vmiFeatures.HypervPassthrough.Enabled {
1✔
1629
                multiCond.addNonMigratableCondition(v1.VirtualMachineInstanceReasonHypervPassthroughNotMigratable, "VMI uses hyperv passthrough")
×
1630
        }
×
1631

1632
        return multiCond.generateStorageLiveMigrationCondition()
1✔
1633
}
1634

1635
func (c *VirtualMachineController) Run(threadiness int, stopCh chan struct{}) {
×
1636
        defer c.queue.ShutDown()
×
1637
        log.Log.Info("Starting virt-handler controller.")
×
1638

×
1639
        go c.deviceManagerController.Run(stopCh)
×
1640

×
1641
        go c.downwardMetricsManager.Run(stopCh)
×
1642

×
1643
        cache.WaitForCacheSync(stopCh, c.hasSynced)
×
1644

×
1645
        // queue keys for previous Domains on the host that no longer exist
×
1646
        // in the cache. This ensures we perform local cleanup of deleted VMs.
×
1647
        for _, domain := range c.domainStore.List() {
×
1648
                d := domain.(*api.Domain)
×
1649
                vmiRef := v1.NewVMIReferenceWithUUID(
×
1650
                        d.ObjectMeta.Namespace,
×
1651
                        d.ObjectMeta.Name,
×
1652
                        d.Spec.Metadata.KubeVirt.UID)
×
1653

×
1654
                key := controller.VirtualMachineInstanceKey(vmiRef)
×
1655

×
1656
                _, exists, _ := c.vmiSourceStore.GetByKey(key)
×
1657
                if !exists {
×
1658
                        c.queue.Add(key)
×
1659
                }
×
1660
        }
1661

1662
        heartBeatDone := c.heartBeat.Run(c.heartBeatInterval, stopCh)
×
1663

×
1664
        go c.ioErrorRetryManager.Run(stopCh)
×
1665

×
1666
        // Start the actual work
×
1667
        for i := 0; i < threadiness; i++ {
×
1668
                go wait.Until(c.runWorker, time.Second, stopCh)
×
1669
        }
×
1670

1671
        <-heartBeatDone
×
1672
        <-stopCh
×
1673
        log.Log.Info("Stopping virt-handler controller.")
×
1674
}
1675

1676
func (c *VirtualMachineController) runWorker() {
×
1677
        for c.Execute() {
×
1678
        }
×
1679
}
1680

1681
func (c *VirtualMachineController) Execute() bool {
1✔
1682
        key, quit := c.queue.Get()
1✔
1683
        if quit {
1✔
1684
                return false
×
1685
        }
×
1686
        defer c.queue.Done(key)
1✔
1687
        if err := c.execute(key); err != nil {
2✔
1688
                log.Log.Reason(err).Infof("re-enqueuing VirtualMachineInstance %v", key)
1✔
1689
                c.queue.AddRateLimited(key)
1✔
1690
        } else {
2✔
1691
                log.Log.V(4).Infof("processed VirtualMachineInstance %v", key)
1✔
1692
                c.queue.Forget(key)
1✔
1693
        }
1✔
1694
        return true
1✔
1695
}
1696

1697
func (c *VirtualMachineController) getVMIFromCache(key string) (vmi *v1.VirtualMachineInstance, exists bool, err error) {
1✔
1698

1✔
1699
        // Fetch the latest Vm state from cache
1✔
1700
        obj, exists, err := c.vmiSourceStore.GetByKey(key)
1✔
1701
        if err != nil {
1✔
1702
                return nil, false, err
×
1703
        }
×
1704

1705
        if !exists {
2✔
1706
                obj, exists, err = c.vmiTargetStore.GetByKey(key)
1✔
1707
                if err != nil {
1✔
1708
                        return nil, false, err
×
1709
                }
×
1710
        }
1711

1712
        // Retrieve the VirtualMachineInstance
1713
        if !exists {
2✔
1714
                namespace, name, err := cache.SplitMetaNamespaceKey(key)
1✔
1715
                if err != nil {
2✔
1716
                        // TODO log and don't retry
1✔
1717
                        return nil, false, err
1✔
1718
                }
1✔
1719
                vmi = v1.NewVMIReferenceFromNameWithNS(namespace, name)
1✔
1720
        } else {
1✔
1721
                vmi = obj.(*v1.VirtualMachineInstance)
1✔
1722
        }
1✔
1723
        return vmi, exists, nil
1✔
1724
}
1725

1726
func (c *VirtualMachineController) getDomainFromCache(key string) (domain *api.Domain, exists bool, cachedUID types.UID, err error) {
1✔
1727

1✔
1728
        obj, exists, err := c.domainStore.GetByKey(key)
1✔
1729

1✔
1730
        if err != nil {
1✔
1731
                return nil, false, "", err
×
1732
        }
×
1733

1734
        if exists {
2✔
1735
                domain = obj.(*api.Domain)
1✔
1736
                cachedUID = domain.Spec.Metadata.KubeVirt.UID
1✔
1737

1✔
1738
                // We're using the DeletionTimestamp to signify that the
1✔
1739
                // Domain is deleted rather than sending the DELETE watch event.
1✔
1740
                if domain.ObjectMeta.DeletionTimestamp != nil {
1✔
1741
                        exists = false
×
1742
                        domain = nil
×
1743
                }
×
1744
        }
1745
        return domain, exists, cachedUID, nil
1✔
1746
}
1747

1748
func (c *VirtualMachineController) migrationOrphanedSourceNodeExecute(vmi *v1.VirtualMachineInstance, domainExists bool) error {
×
1749

×
1750
        if domainExists {
×
1751
                err := c.processVmDelete(vmi)
×
1752
                if err != nil {
×
1753
                        return err
×
1754
                }
×
1755
                // we can perform the cleanup immediately after
1756
                // the successful delete here because we don't have
1757
                // to report the deletion results on the VMI status
1758
                // in this case.
1759
                err = c.processVmCleanup(vmi)
×
1760
                if err != nil {
×
1761
                        return err
×
1762
                }
×
1763
        } else {
×
1764
                err := c.processVmCleanup(vmi)
×
1765
                if err != nil {
×
1766
                        return err
×
1767
                }
×
1768
        }
1769
        return nil
×
1770
}
1771

1772
func (c *VirtualMachineController) migrationTargetExecute(vmi *v1.VirtualMachineInstance, vmiExists bool, domain *api.Domain) error {
1✔
1773

1✔
1774
        // set to true when preparation of migration target should be aborted.
1✔
1775
        shouldAbort := false
1✔
1776
        // set to true when VirtualMachineInstance migration target needs to be prepared
1✔
1777
        shouldUpdate := false
1✔
1778
        // set true when the current migration target has exitted and needs to be cleaned up.
1✔
1779
        shouldCleanUp := false
1✔
1780

1✔
1781
        if vmiExists && vmi.IsRunning() {
2✔
1782
                shouldUpdate = true
1✔
1783
        }
1✔
1784

1785
        if !vmiExists || vmi.DeletionTimestamp != nil {
2✔
1786
                shouldAbort = true
1✔
1787
        } else if vmi.IsFinal() {
2✔
1788
                shouldAbort = true
×
1789
        } else if c.hasStaleClientConnections(vmi) {
2✔
1790
                // if stale client exists, force cleanup.
1✔
1791
                // This can happen as a result of a previously
1✔
1792
                // failed attempt to migrate the vmi to this node.
1✔
1793
                shouldCleanUp = true
1✔
1794
        }
1✔
1795

1796
        domainExists := domain != nil
1✔
1797
        if shouldAbort {
2✔
1798
                if domainExists {
1✔
1799
                        err := c.processVmDelete(vmi)
×
1800
                        if err != nil {
×
1801
                                return err
×
1802
                        }
×
1803
                }
1804

1805
                err := c.processVmCleanup(vmi)
1✔
1806
                if err != nil {
1✔
1807
                        return err
×
1808
                }
×
1809
        } else if shouldCleanUp {
2✔
1810
                log.Log.Object(vmi).Infof("Stale client for migration target found. Cleaning up.")
1✔
1811

1✔
1812
                err := c.processVmCleanup(vmi)
1✔
1813
                if err != nil {
1✔
1814
                        return err
×
1815
                }
×
1816

1817
                // if we're still the migration target, we need to keep trying until the migration fails.
1818
                // it's possible we're simply waiting for another target pod to come online.
1819
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
1820

1821
        } else if shouldUpdate {
2✔
1822
                log.Log.Object(vmi).Info("Processing vmi migration target update")
1✔
1823

1✔
1824
                // prepare the POD for the migration
1✔
1825
                err := c.processVmUpdate(vmi, domain)
1✔
1826
                if err != nil {
1✔
1827
                        return err
×
1828
                }
×
1829

1830
                err = c.migrationTargetUpdateVMIStatus(vmi, domain)
1✔
1831
                if err != nil {
1✔
1832
                        return err
×
1833
                }
×
1834
        }
1835

1836
        return nil
1✔
1837
}
1838

1839
// Determine if gracefulShutdown has been triggered by virt-launcher
1840
func (c *VirtualMachineController) hasGracefulShutdownTrigger(domain *api.Domain) bool {
1✔
1841
        if domain == nil {
2✔
1842
                return false
1✔
1843
        }
1✔
1844
        gracePeriod := domain.Spec.Metadata.KubeVirt.GracePeriod
1✔
1845

1✔
1846
        return gracePeriod != nil &&
1✔
1847
                gracePeriod.MarkedForGracefulShutdown != nil &&
1✔
1848
                *gracePeriod.MarkedForGracefulShutdown
1✔
1849
}
1850

1851
func (c *VirtualMachineController) defaultExecute(key string,
1852
        vmi *v1.VirtualMachineInstance,
1853
        vmiExists bool,
1854
        domain *api.Domain,
1855
        domainExists bool) error {
1✔
1856

1✔
1857
        // set to true when domain needs to be shutdown.
1✔
1858
        shouldShutdown := false
1✔
1859
        // set to true when domain needs to be removed from libvirt.
1✔
1860
        shouldDelete := false
1✔
1861
        // optimization. set to true when processing already deleted domain.
1✔
1862
        shouldCleanUp := false
1✔
1863
        // set to true when VirtualMachineInstance is active or about to become active.
1✔
1864
        shouldUpdate := false
1✔
1865
        // set true to ensure that no updates to the current VirtualMachineInstance state will occur
1✔
1866
        forceIgnoreSync := false
1✔
1867
        // set to true when unrecoverable domain needs to be destroyed non-gracefully.
1✔
1868
        forceShutdownIrrecoverable := false
1✔
1869

1✔
1870
        log.Log.V(3).Infof("Processing event %v", key)
1✔
1871

1✔
1872
        if vmiExists && domainExists {
2✔
1873
                log.Log.Object(vmi).Infof("VMI is in phase: %v | Domain status: %v, reason: %v", vmi.Status.Phase, domain.Status.Status, domain.Status.Reason)
1✔
1874
        } else if vmiExists {
3✔
1875
                log.Log.Object(vmi).Infof("VMI is in phase: %v | Domain does not exist", vmi.Status.Phase)
1✔
1876
        } else if domainExists {
3✔
1877
                vmiRef := v1.NewVMIReferenceWithUUID(domain.ObjectMeta.Namespace, domain.ObjectMeta.Name, domain.Spec.Metadata.KubeVirt.UID)
1✔
1878
                log.Log.Object(vmiRef).Infof("VMI does not exist | Domain status: %v, reason: %v", domain.Status.Status, domain.Status.Reason)
1✔
1879
        } else {
1✔
1880
                log.Log.Info("VMI does not exist | Domain does not exist")
×
1881
        }
×
1882

1883
        domainAlive := domainExists &&
1✔
1884
                domain.Status.Status != api.Shutoff &&
1✔
1885
                domain.Status.Status != api.Crashed &&
1✔
1886
                domain.Status.Status != ""
1✔
1887

1✔
1888
        domainMigrated := domainExists && domainMigrated(domain)
1✔
1889
        forceShutdownIrrecoverable = domainExists && domainPausedFailedPostCopy(domain)
1✔
1890

1✔
1891
        gracefulShutdown := c.hasGracefulShutdownTrigger(domain)
1✔
1892
        if gracefulShutdown && vmi.IsRunning() {
1✔
1893
                if domainAlive {
×
1894
                        log.Log.Object(vmi).V(3).Info("Shutting down due to graceful shutdown signal.")
×
1895
                        shouldShutdown = true
×
1896
                } else {
×
1897
                        shouldDelete = true
×
1898
                }
×
1899
        }
1900

1901
        // Determine removal of VirtualMachineInstance from cache should result in deletion.
1902
        if !vmiExists {
2✔
1903
                switch {
1✔
1904
                case domainAlive:
1✔
1905
                        // The VirtualMachineInstance is deleted on the cluster, and domain is alive,
1✔
1906
                        // then shut down the domain.
1✔
1907
                        log.Log.Object(vmi).V(3).Info("Shutting down domain for deleted VirtualMachineInstance object.")
1✔
1908
                        shouldShutdown = true
1✔
1909
                case domainExists:
1✔
1910
                        // The VirtualMachineInstance is deleted on the cluster, and domain is not alive
1✔
1911
                        // then delete the domain.
1✔
1912
                        log.Log.Object(vmi).V(3).Info("Deleting domain for deleted VirtualMachineInstance object.")
1✔
1913
                        shouldDelete = true
1✔
1914
                default:
×
1915
                        // If neither the domain nor the vmi object exist locally,
×
1916
                        // then ensure any remaining local ephemeral data is cleaned up.
×
1917
                        shouldCleanUp = true
×
1918
                }
1919
        }
1920

1921
        // Determine if VirtualMachineInstance is being deleted.
1922
        if vmiExists && vmi.ObjectMeta.DeletionTimestamp != nil {
2✔
1923
                switch {
1✔
1924
                case domainAlive:
×
1925
                        log.Log.Object(vmi).V(3).Info("Shutting down domain for VirtualMachineInstance with deletion timestamp.")
×
1926
                        shouldShutdown = true
×
1927
                case domainExists:
×
1928
                        log.Log.Object(vmi).V(3).Info("Deleting domain for VirtualMachineInstance with deletion timestamp.")
×
1929
                        shouldDelete = true
×
1930
                default:
1✔
1931
                        if vmi.IsFinal() {
1✔
1932
                                shouldCleanUp = true
×
1933
                        }
×
1934
                }
1935
        }
1936

1937
        // Determine if domain needs to be deleted as a result of VirtualMachineInstance
1938
        // shutting down naturally (guest internal invoked shutdown)
1939
        if domainExists && vmiExists && vmi.IsFinal() {
1✔
1940
                log.Log.Object(vmi).V(3).Info("Removing domain and ephemeral data for finalized vmi.")
×
1941
                shouldDelete = true
×
1942
        } else if !domainExists && vmiExists && vmi.IsFinal() {
2✔
1943
                log.Log.Object(vmi).V(3).Info("Cleaning up local data for finalized vmi.")
1✔
1944
                shouldCleanUp = true
1✔
1945
        }
1✔
1946

1947
        // Determine if an active (or about to be active) VirtualMachineInstance should be updated.
1948
        if vmiExists && !vmi.IsFinal() {
2✔
1949
                // requiring the phase of the domain and VirtualMachineInstance to be in sync is an
1✔
1950
                // optimization that prevents unnecessary re-processing VMIs during the start flow.
1✔
1951
                phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
1✔
1952
                if err != nil {
1✔
1953
                        return err
×
1954
                }
×
1955
                if vmi.Status.Phase == phase {
2✔
1956
                        shouldUpdate = true
1✔
1957
                }
1✔
1958

1959
                if shouldDelay, delay := c.ioErrorRetryManager.ShouldDelay(string(vmi.UID), func() bool {
2✔
1960
                        return isIOError(shouldUpdate, domainExists, domain)
1✔
1961
                }); shouldDelay {
1✔
1962
                        shouldUpdate = false
×
1963
                        log.Log.Object(vmi).Infof("Delay vm update for %f seconds", delay.Seconds())
×
1964
                        c.queue.AddAfter(key, delay)
×
1965
                }
×
1966
        }
1967

1968
        // NOTE: This must be the last check that occurs before checking the sync booleans!
1969
        //
1970
        // Special logic for domains migrated from a source node.
1971
        // Don't delete/destroy domain until the handoff occurs.
1972
        if domainMigrated {
2✔
1973
                // only allow the sync to occur on the domain once we've done
1✔
1974
                // the node handoff. Otherwise we potentially lose the fact that
1✔
1975
                // the domain migrated because we'll attempt to delete the locally
1✔
1976
                // shut off domain during the sync.
1✔
1977
                if vmiExists &&
1✔
1978
                        !vmi.IsFinal() &&
1✔
1979
                        vmi.DeletionTimestamp == nil &&
1✔
1980
                        vmi.Status.NodeName != "" &&
1✔
1981
                        vmi.Status.NodeName == c.host {
2✔
1982

1✔
1983
                        // If the domain migrated but the VMI still thinks this node
1✔
1984
                        // is the host, force ignore the sync until the VMI's status
1✔
1985
                        // is updated to reflect the node the domain migrated to.
1✔
1986
                        forceIgnoreSync = true
1✔
1987
                }
1✔
1988
        }
1989

1990
        var syncErr error
1✔
1991

1✔
1992
        // Process the VirtualMachineInstance update in this order.
1✔
1993
        // * Shutdown and Deletion due to VirtualMachineInstance deletion, process stopping, graceful shutdown trigger, etc...
1✔
1994
        // * Cleanup of already shutdown and Deleted VMIs
1✔
1995
        // * Update due to spec change and initial start flow.
1✔
1996
        switch {
1✔
1997
        case forceIgnoreSync:
1✔
1998
                log.Log.Object(vmi).V(3).Info("No update processing required: forced ignore")
1✔
1999
        case shouldShutdown:
1✔
2000
                log.Log.Object(vmi).V(3).Info("Processing shutdown.")
1✔
2001
                syncErr = c.processVmShutdown(vmi, domain)
1✔
2002
        case forceShutdownIrrecoverable:
×
2003
                msg := formatIrrecoverableErrorMessage(domain)
×
2004
                log.Log.Object(vmi).V(3).Infof("Processing a destruction of an irrecoverable domain - %s.", msg)
×
2005
                syncErr = c.processVmDestroy(vmi, domain)
×
2006
                if syncErr == nil {
×
2007
                        syncErr = &vmiIrrecoverableError{msg}
×
2008
                }
×
2009
        case shouldDelete:
1✔
2010
                log.Log.Object(vmi).V(3).Info("Processing deletion.")
1✔
2011
                syncErr = c.processVmDelete(vmi)
1✔
2012
        case shouldCleanUp:
1✔
2013
                log.Log.Object(vmi).V(3).Info("Processing local ephemeral data cleanup for shutdown domain.")
1✔
2014
                syncErr = c.processVmCleanup(vmi)
1✔
2015
        case shouldUpdate:
1✔
2016
                log.Log.Object(vmi).V(3).Info("Processing vmi update")
1✔
2017
                syncErr = c.processVmUpdate(vmi, domain)
1✔
2018
        default:
1✔
2019
                log.Log.Object(vmi).V(3).Info("No update processing required")
1✔
2020
        }
2021

2022
        if syncErr != nil && !vmi.IsFinal() {
2✔
2023
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.SyncFailed.String(), syncErr.Error())
1✔
2024

1✔
2025
                // `syncErr` will be propagated anyway, and it will be logged in `re-enqueueing`
1✔
2026
                // so there is no need to log it twice in hot path without increased verbosity.
1✔
2027
                log.Log.Object(vmi).Reason(syncErr).Error("Synchronizing the VirtualMachineInstance failed.")
1✔
2028
        }
1✔
2029

2030
        // Update the VirtualMachineInstance status, if the VirtualMachineInstance exists
2031
        if vmiExists {
2✔
2032
                if err := c.updateVMIStatus(vmi, domain, syncErr); err != nil {
2✔
2033
                        log.Log.Object(vmi).Reason(err).Error("Updating the VirtualMachineInstance status failed.")
1✔
2034
                        return err
1✔
2035
                }
1✔
2036
        }
2037

2038
        if syncErr != nil {
2✔
2039
                return syncErr
1✔
2040
        }
1✔
2041

2042
        log.Log.Object(vmi).V(3).Info("Synchronization loop succeeded.")
1✔
2043
        return nil
1✔
2044

2045
}
2046

2047
func (c *VirtualMachineController) execute(key string) error {
1✔
2048
        vmi, vmiExists, err := c.getVMIFromCache(key)
1✔
2049
        if err != nil {
2✔
2050
                return err
1✔
2051
        }
1✔
2052

2053
        if !vmiExists {
2✔
2054
                c.vmiExpectations.DeleteExpectations(key)
1✔
2055
        } else if !c.vmiExpectations.SatisfiedExpectations(key) {
2✔
2056
                return nil
×
2057
        }
×
2058

2059
        domain, domainExists, domainCachedUID, err := c.getDomainFromCache(key)
1✔
2060
        if err != nil {
1✔
2061
                return err
×
2062
        }
×
2063

2064
        if !vmiExists && string(domainCachedUID) != "" {
2✔
2065
                // it's possible to discover the UID from cache even if the domain
1✔
2066
                // doesn't technically exist anymore
1✔
2067
                vmi.UID = domainCachedUID
1✔
2068
                log.Log.Object(vmi).Infof("Using cached UID for vmi found in domain cache")
1✔
2069
        }
1✔
2070

2071
        // As a last effort, if the UID still can't be determined attempt
2072
        // to retrieve it from the ghost record
2073
        if string(vmi.UID) == "" {
2✔
2074
                uid := virtcache.GhostRecordGlobalStore.LastKnownUID(key)
1✔
2075
                if uid != "" {
1✔
2076
                        log.Log.Object(vmi).V(3).Infof("ghost record cache provided %s as UID", uid)
×
2077
                        vmi.UID = uid
×
2078
                }
×
2079
        }
2080

2081
        if vmiExists && domainExists && domain.Spec.Metadata.KubeVirt.UID != vmi.UID {
2✔
2082
                oldVMI := v1.NewVMIReferenceFromNameWithNS(vmi.Namespace, vmi.Name)
1✔
2083
                oldVMI.UID = domain.Spec.Metadata.KubeVirt.UID
1✔
2084
                expired, initialized, err := c.isLauncherClientUnresponsive(oldVMI)
1✔
2085
                if err != nil {
1✔
2086
                        return err
×
2087
                }
×
2088
                // If we found an outdated domain which is also not alive anymore, clean up
2089
                if !initialized {
1✔
2090
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
2091
                        return nil
×
2092
                } else if expired {
1✔
2093
                        log.Log.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)
×
2094
                        err = c.processVmCleanup(oldVMI)
×
2095
                        if err != nil {
×
2096
                                return err
×
2097
                        }
×
2098
                        // Make sure we re-enqueue the key to ensure this new VMI is processed
2099
                        // after the stale domain is removed
2100
                        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*5)
×
2101
                }
2102

2103
                return nil
1✔
2104
        }
2105

2106
        // Take different execution paths depending on the state of the migration and the
2107
        // node this is executed on.
2108

2109
        if vmiExists && c.isPreMigrationTarget(vmi) {
2✔
2110
                // 1. PRE-MIGRATION TARGET PREPARATION PATH
1✔
2111
                //
1✔
2112
                // If this node is the target of the vmi's migration, take
1✔
2113
                // a different execute path. The target execute path prepares
1✔
2114
                // the local environment for the migration, but does not
1✔
2115
                // start the VMI
1✔
2116
                return c.migrationTargetExecute(vmi, vmiExists, domain)
1✔
2117
        } else if vmiExists && c.isOrphanedMigrationSource(vmi) {
2✔
2118
                // 3. POST-MIGRATION SOURCE CLEANUP
×
2119
                //
×
2120
                // After a migration, the migrated domain still exists in the old
×
2121
                // source's domain cache. Ensure that any node that isn't currently
×
2122
                // the target or owner of the VMI handles deleting the domain locally.
×
2123
                return c.migrationOrphanedSourceNodeExecute(vmi, domainExists)
×
2124
        }
×
2125
        return c.defaultExecute(key,
1✔
2126
                vmi,
1✔
2127
                vmiExists,
1✔
2128
                domain,
1✔
2129
                domainExists)
1✔
2130

2131
}
2132

2133
func (c *VirtualMachineController) processVmCleanup(vmi *v1.VirtualMachineInstance) error {
1✔
2134

1✔
2135
        vmiId := string(vmi.UID)
1✔
2136

1✔
2137
        log.Log.Object(vmi).Infof("Performing final local cleanup for vmi with uid %s", vmiId)
1✔
2138

1✔
2139
        c.migrationProxy.StopTargetListener(vmiId)
1✔
2140
        c.migrationProxy.StopSourceListener(vmiId)
1✔
2141

1✔
2142
        c.downwardMetricsManager.StopServer(vmi)
1✔
2143

1✔
2144
        // Unmount container disks and clean up remaining files
1✔
2145
        if err := c.containerDiskMounter.Unmount(vmi); err != nil {
1✔
2146
                return err
×
2147
        }
×
2148

2149
        // UnmountAll does the cleanup on the "best effort" basis: it is
2150
        // safe to pass a nil cgroupManager.
2151
        cgroupManager, _ := getCgroupManager(vmi)
1✔
2152
        if err := c.hotplugVolumeMounter.UnmountAll(vmi, cgroupManager); err != nil {
1✔
2153
                return err
×
2154
        }
×
2155

2156
        c.teardownNetwork(vmi)
1✔
2157

1✔
2158
        c.sriovHotplugExecutorPool.Delete(vmi.UID)
1✔
2159

1✔
2160
        // Watch dog file and command client must be the last things removed here
1✔
2161
        if err := c.closeLauncherClient(vmi); err != nil {
1✔
2162
                return err
×
2163
        }
×
2164

2165
        // Remove the domain from cache in the event that we're performing
2166
        // a final cleanup and never received the "DELETE" event. This is
2167
        // possible if the VMI pod goes away before we receive the final domain
2168
        // "DELETE"
2169
        domain := api.NewDomainReferenceFromName(vmi.Namespace, vmi.Name)
1✔
2170
        log.Log.Object(domain).Infof("Removing domain from cache during final cleanup")
1✔
2171
        return c.domainStore.Delete(domain)
1✔
2172
}
2173

2174
func (c *VirtualMachineController) closeLauncherClient(vmi *v1.VirtualMachineInstance) error {
1✔
2175

1✔
2176
        // UID is required in order to close socket
1✔
2177
        if string(vmi.GetUID()) == "" {
2✔
2178
                return nil
1✔
2179
        }
1✔
2180

2181
        clientInfo, exists := c.launcherClients.Load(vmi.UID)
1✔
2182
        if exists && clientInfo.Client != nil {
2✔
2183
                clientInfo.Client.Close()
1✔
2184
                close(clientInfo.DomainPipeStopChan)
1✔
2185
        }
1✔
2186

2187
        virtcache.GhostRecordGlobalStore.Delete(vmi.Namespace, vmi.Name)
1✔
2188
        c.launcherClients.Delete(vmi.UID)
1✔
2189
        return nil
1✔
2190
}
2191

2192
// used by unit tests to add mock clients
2193
func (c *VirtualMachineController) addLauncherClient(vmUID types.UID, info *virtcache.LauncherClientInfo) error {
1✔
2194
        c.launcherClients.Store(vmUID, info)
1✔
2195
        return nil
1✔
2196
}
1✔
2197

2198
func (c *VirtualMachineController) isLauncherClientUnresponsive(vmi *v1.VirtualMachineInstance) (unresponsive bool, initialized bool, err error) {
1✔
2199
        var socketFile string
1✔
2200

1✔
2201
        clientInfo, exists := c.launcherClients.Load(vmi.UID)
1✔
2202
        if exists {
2✔
2203
                if clientInfo.Ready == true {
2✔
2204
                        // use cached socket if we previously established a connection
1✔
2205
                        socketFile = clientInfo.SocketFile
1✔
2206
                } else {
2✔
2207
                        socketFile, err = cmdclient.FindSocketOnHost(vmi)
1✔
2208
                        if err != nil {
2✔
2209
                                // socket does not exist, but let's see if the pod is still there
1✔
2210
                                if _, err = cmdclient.FindPodDirOnHost(vmi); err != nil {
1✔
2211
                                        // no pod meanst that waiting for it to initialize makes no sense
×
2212
                                        return true, true, nil
×
2213
                                }
×
2214
                                // pod is still there, if there is no socket let's wait for it to become ready
2215
                                if clientInfo.NotInitializedSince.Before(time.Now().Add(-3 * time.Minute)) {
2✔
2216
                                        return true, true, nil
1✔
2217
                                }
1✔
2218
                                return false, false, nil
1✔
2219
                        }
2220
                        clientInfo.Ready = true
×
2221
                        clientInfo.SocketFile = socketFile
×
2222
                }
2223
        } else {
×
2224
                clientInfo := &virtcache.LauncherClientInfo{
×
2225
                        NotInitializedSince: time.Now(),
×
2226
                        Ready:               false,
×
2227
                }
×
2228
                c.launcherClients.Store(vmi.UID, clientInfo)
×
2229
                // attempt to find the socket if the established connection doesn't currently exist.
×
2230
                socketFile, err = cmdclient.FindSocketOnHost(vmi)
×
2231
                // no socket file, no VMI, so it's unresponsive
×
2232
                if err != nil {
×
2233
                        // socket does not exist, but let's see if the pod is still there
×
2234
                        if _, err = cmdclient.FindPodDirOnHost(vmi); err != nil {
×
2235
                                // no pod meanst that waiting for it to initialize makes no sense
×
2236
                                return true, true, nil
×
2237
                        }
×
2238
                        return false, false, nil
×
2239
                }
2240
                clientInfo.Ready = true
×
2241
                clientInfo.SocketFile = socketFile
×
2242
        }
2243
        return cmdclient.IsSocketUnresponsive(socketFile), true, nil
1✔
2244
}
2245

2246
func (c *VirtualMachineController) getLauncherClient(vmi *v1.VirtualMachineInstance) (cmdclient.LauncherClient, error) {
1✔
2247
        var err error
1✔
2248

1✔
2249
        clientInfo, exists := c.launcherClients.Load(vmi.UID)
1✔
2250
        if exists && clientInfo.Client != nil {
2✔
2251
                return clientInfo.Client, nil
1✔
2252
        }
1✔
2253

2254
        socketFile, err := cmdclient.FindSocketOnHost(vmi)
×
2255
        if err != nil {
×
2256
                return nil, err
×
2257
        }
×
2258

2259
        err = virtcache.GhostRecordGlobalStore.Add(vmi.Namespace, vmi.Name, socketFile, vmi.UID)
×
2260
        if err != nil {
×
2261
                return nil, err
×
2262
        }
×
2263

2264
        client, err := cmdclient.NewClient(socketFile)
×
2265
        if err != nil {
×
2266
                return nil, err
×
2267
        }
×
2268

2269
        domainPipeStopChan := make(chan struct{})
×
2270
        //we pipe in the domain socket into the VMI's filesystem
×
2271
        err = c.startDomainNotifyPipe(domainPipeStopChan, vmi)
×
2272
        if err != nil {
×
2273
                client.Close()
×
2274
                close(domainPipeStopChan)
×
2275
                return nil, err
×
2276
        }
×
2277

2278
        c.launcherClients.Store(vmi.UID, &virtcache.LauncherClientInfo{
×
2279
                Client:              client,
×
2280
                SocketFile:          socketFile,
×
2281
                DomainPipeStopChan:  domainPipeStopChan,
×
2282
                NotInitializedSince: time.Now(),
×
2283
                Ready:               true,
×
2284
        })
×
2285

×
2286
        return client, nil
×
2287
}
2288

2289
func (c *VirtualMachineController) processVmDestroy(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
×
2290
        tryGracefully := false
×
2291
        return c.helperVmShutdown(vmi, domain, tryGracefully)
×
2292
}
×
2293

2294
func (c *VirtualMachineController) processVmShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
2295
        tryGracefully := true
1✔
2296
        return c.helperVmShutdown(vmi, domain, tryGracefully)
1✔
2297
}
1✔
2298

2299
func (c *VirtualMachineController) helperVmShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain, tryGracefully bool) error {
1✔
2300

1✔
2301
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
1✔
2302
        client, err := c.getVerifiedLauncherClient(vmi)
1✔
2303
        if err != nil {
1✔
2304
                return err
×
2305
        }
×
2306

2307
        if domainHasGracePeriod(domain) && tryGracefully {
2✔
2308
                if expired, timeLeft := c.hasGracePeriodExpired(domain); !expired {
2✔
2309
                        return c.handleVMIShutdown(vmi, domain, client, timeLeft)
1✔
2310
                }
1✔
2311
                log.Log.Object(vmi).Infof("Grace period expired, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
1✔
2312
        } else {
1✔
2313
                log.Log.Object(vmi).Infof("Graceful shutdown not set, killing deleted VirtualMachineInstance %s", vmi.GetObjectMeta().GetName())
1✔
2314
        }
1✔
2315

2316
        err = client.KillVirtualMachine(vmi)
1✔
2317
        if err != nil && !cmdclient.IsDisconnected(err) {
1✔
2318
                // Only report err if it wasn't the result of a disconnect.
×
2319
                //
×
2320
                // Both virt-launcher and virt-handler are trying to destroy
×
2321
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
2322
                // disconnected during the kill request, which shouldn't be
×
2323
                // considered an error.
×
2324
                return err
×
2325
        }
×
2326

2327
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Deleted.String(), VMIStopping)
1✔
2328

1✔
2329
        return nil
1✔
2330
}
2331

2332
func (c *VirtualMachineController) handleVMIShutdown(vmi *v1.VirtualMachineInstance, domain *api.Domain, client cmdclient.LauncherClient, timeLeft int64) error {
1✔
2333
        if domain.Status.Status != api.Shutdown {
2✔
2334
                return c.shutdownVMI(vmi, client, timeLeft)
1✔
2335
        }
1✔
2336
        log.Log.V(4).Object(vmi).Infof("%s is already shutting down.", vmi.GetObjectMeta().GetName())
×
2337
        return nil
×
2338
}
2339

2340
func (c *VirtualMachineController) shutdownVMI(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient, timeLeft int64) error {
1✔
2341
        err := client.ShutdownVirtualMachine(vmi)
1✔
2342
        if err != nil && !cmdclient.IsDisconnected(err) {
1✔
2343
                // Only report err if it wasn't the result of a disconnect.
×
2344
                //
×
2345
                // Both virt-launcher and virt-handler are trying to destroy
×
2346
                // the VirtualMachineInstance at the same time. It's possible the client may get
×
2347
                // disconnected during the kill request, which shouldn't be
×
2348
                // considered an error.
×
2349
                return err
×
2350
        }
×
2351

2352
        log.Log.Object(vmi).Infof("Signaled graceful shutdown for %s", vmi.GetObjectMeta().GetName())
1✔
2353

1✔
2354
        // Make sure that we don't hot-loop in case we send the first domain notification
1✔
2355
        if timeLeft == -1 {
2✔
2356
                timeLeft = 5
1✔
2357
                if vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds < timeLeft {
1✔
2358
                        timeLeft = *vmi.Spec.TerminationGracePeriodSeconds
×
2359
                }
×
2360
        }
2361
        // In case we have a long grace period, we want to resend the graceful shutdown every 5 seconds
2362
        // That's important since a booting OS can miss ACPI signals
2363
        if timeLeft > 5 {
1✔
2364
                timeLeft = 5
×
2365
        }
×
2366

2367
        // pending graceful shutdown.
2368
        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Duration(timeLeft)*time.Second)
1✔
2369
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.ShuttingDown.String(), VMIGracefulShutdown)
1✔
2370
        return nil
1✔
2371
}
2372

2373
func (c *VirtualMachineController) processVmDelete(vmi *v1.VirtualMachineInstance) error {
1✔
2374

1✔
2375
        // Only attempt to shutdown/destroy if we still have a connection established with the pod.
1✔
2376
        client, err := c.getVerifiedLauncherClient(vmi)
1✔
2377

1✔
2378
        // If the pod has been torn down, we know the VirtualMachineInstance is down.
1✔
2379
        if err == nil {
2✔
2380

1✔
2381
                log.Log.Object(vmi).Infof("Signaled deletion for %s", vmi.GetObjectMeta().GetName())
1✔
2382

1✔
2383
                // pending deletion.
1✔
2384
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Deleted.String(), VMISignalDeletion)
1✔
2385

1✔
2386
                err = client.DeleteDomain(vmi)
1✔
2387
                if err != nil && !cmdclient.IsDisconnected(err) {
1✔
2388
                        // Only report err if it wasn't the result of a disconnect.
×
2389
                        //
×
2390
                        // Both virt-launcher and virt-handler are trying to destroy
×
2391
                        // the VirtualMachineInstance at the same time. It's possible the client may get
×
2392
                        // disconnected during the kill request, which shouldn't be
×
2393
                        // considered an error.
×
2394
                        return err
×
2395
                }
×
2396
        }
2397

2398
        return nil
1✔
2399

2400
}
2401

2402
func (c *VirtualMachineController) hasStaleClientConnections(vmi *v1.VirtualMachineInstance) bool {
1✔
2403
        _, err := c.getVerifiedLauncherClient(vmi)
1✔
2404
        if err == nil {
2✔
2405
                // current client connection is good.
1✔
2406
                return false
1✔
2407
        }
1✔
2408

2409
        // no connection, but ghost file exists.
2410
        if virtcache.GhostRecordGlobalStore.Exists(vmi.Namespace, vmi.Name) {
2✔
2411
                return true
1✔
2412
        }
1✔
2413

2414
        return false
×
2415

2416
}
2417

2418
func (c *VirtualMachineController) getVerifiedLauncherClient(vmi *v1.VirtualMachineInstance) (client cmdclient.LauncherClient, err error) {
1✔
2419
        client, err = c.getLauncherClient(vmi)
1✔
2420
        if err != nil {
1✔
2421
                return
×
2422
        }
×
2423

2424
        // Verify connectivity.
2425
        // It's possible the pod has already been torn down along with the VirtualMachineInstance.
2426
        err = client.Ping()
1✔
2427
        return
1✔
2428
}
2429

2430
func (c *VirtualMachineController) isOrphanedMigrationSource(vmi *v1.VirtualMachineInstance) bool {
1✔
2431
        nodeName, ok := vmi.Labels[v1.NodeNameLabel]
1✔
2432

1✔
2433
        if ok && nodeName != "" && nodeName != c.host {
1✔
2434
                return true
×
2435
        }
×
2436

2437
        return false
1✔
2438
}
2439

2440
func (c *VirtualMachineController) isPreMigrationTarget(vmi *v1.VirtualMachineInstance) bool {
1✔
2441

1✔
2442
        migrationTargetNodeName, ok := vmi.Labels[v1.MigrationTargetNodeNameLabel]
1✔
2443

1✔
2444
        if ok &&
1✔
2445
                migrationTargetNodeName != "" &&
1✔
2446
                migrationTargetNodeName != vmi.Status.NodeName &&
1✔
2447
                migrationTargetNodeName == c.host {
2✔
2448
                return true
1✔
2449
        }
1✔
2450

2451
        return false
1✔
2452
}
2453

2454
func (c *VirtualMachineController) checkNetworkInterfacesForMigration(vmi *v1.VirtualMachineInstance) error {
1✔
2455
        return netvmispec.VerifyVMIMigratable(vmi, c.clusterConfig.GetNetworkBindings())
1✔
2456
}
1✔
2457

2458
func (c *VirtualMachineController) checkVolumesForMigration(vmi *v1.VirtualMachineInstance) (blockMigrate bool, err error) {
1✔
2459
        volumeStatusMap := make(map[string]v1.VolumeStatus)
1✔
2460

1✔
2461
        for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
2462
                volumeStatusMap[volumeStatus.Name] = volumeStatus
1✔
2463
        }
1✔
2464

2465
        if len(vmi.Status.MigratedVolumes) > 0 {
1✔
2466
                blockMigrate = true
×
2467
        }
×
2468

2469
        filesystems := storagetypes.GetFilesystemsFromVolumes(vmi)
1✔
2470

1✔
2471
        // Check if all VMI volumes can be shared between the source and the destination
1✔
2472
        // of a live migration. blockMigrate will be returned as false, only if all volumes
1✔
2473
        // are shared and the VMI has no local disks
1✔
2474
        // Some combinations of disks makes the VMI no suitable for live migration.
1✔
2475
        // A relevant error will be returned in this case.
1✔
2476
        for _, volume := range vmi.Spec.Volumes {
2✔
2477
                volSrc := volume.VolumeSource
1✔
2478
                if volSrc.PersistentVolumeClaim != nil || volSrc.DataVolume != nil {
2✔
2479

1✔
2480
                        var claimName string
1✔
2481
                        if volSrc.PersistentVolumeClaim != nil {
2✔
2482
                                claimName = volSrc.PersistentVolumeClaim.ClaimName
1✔
2483
                        } else {
2✔
2484
                                claimName = volSrc.DataVolume.Name
1✔
2485
                        }
1✔
2486

2487
                        volumeStatus, ok := volumeStatusMap[volume.Name]
1✔
2488

1✔
2489
                        if !ok || volumeStatus.PersistentVolumeClaimInfo == nil {
1✔
2490
                                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)
×
2491
                        } else if !pvctypes.HasSharedAccessMode(volumeStatus.PersistentVolumeClaimInfo.AccessModes) && !pvctypes.IsMigratedVolume(volumeStatus.Name, vmi) {
2✔
2492
                                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)
1✔
2493
                        }
1✔
2494

2495
                } else if volSrc.HostDisk != nil {
2✔
2496
                        shared := volSrc.HostDisk.Shared != nil && *volSrc.HostDisk.Shared
1✔
2497
                        if !shared {
2✔
2498
                                return true, fmt.Errorf("cannot migrate VMI with non-shared HostDisk")
1✔
2499
                        }
1✔
2500
                } else {
1✔
2501
                        if _, ok := filesystems[volume.Name]; ok {
2✔
2502
                                log.Log.Object(vmi).Infof("Volume %s is shared with virtiofs, allow live migration", volume.Name)
1✔
2503
                                continue
1✔
2504
                        }
2505

2506
                        isVolumeUsedByReadOnlyDisk := false
1✔
2507
                        for _, disk := range vmi.Spec.Domain.Devices.Disks {
2✔
2508
                                if isReadOnlyDisk(&disk) && disk.Name == volume.Name {
2✔
2509
                                        isVolumeUsedByReadOnlyDisk = true
1✔
2510
                                        break
1✔
2511
                                }
2512
                        }
2513

2514
                        if isVolumeUsedByReadOnlyDisk {
2✔
2515
                                continue
1✔
2516
                        }
2517

2518
                        if vmi.Status.MigrationMethod == "" || vmi.Status.MigrationMethod == v1.LiveMigration {
2✔
2519
                                log.Log.Object(vmi).Infof("migration is block migration because of %s volume", volume.Name)
1✔
2520
                        }
1✔
2521
                        blockMigrate = true
1✔
2522
                }
2523
        }
2524
        return
1✔
2525
}
2526

2527
func (c *VirtualMachineController) isVMIPausedDuringMigration(vmi *v1.VirtualMachineInstance) bool {
1✔
2528
        return vmi.Status.MigrationState != nil &&
1✔
2529
                vmi.Status.MigrationState.Mode == v1.MigrationPaused &&
1✔
2530
                !vmi.Status.MigrationState.Completed
1✔
2531
}
1✔
2532

2533
func (c *VirtualMachineController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
1✔
2534

1✔
2535
        if vmi.Status.MigrationState != nil &&
1✔
2536
                vmi.Status.MigrationState.SourceNode == c.host &&
1✔
2537
                vmi.Status.MigrationState.TargetNodeAddress != "" &&
1✔
2538
                !vmi.Status.MigrationState.Completed {
2✔
2539

1✔
2540
                return true
1✔
2541
        }
1✔
2542
        return false
1✔
2543

2544
}
2545

2546
func (c *VirtualMachineController) handleTargetMigrationProxy(vmi *v1.VirtualMachineInstance) error {
1✔
2547
        // handle starting/stopping target migration proxy
1✔
2548
        migrationTargetSockets := []string{}
1✔
2549
        res, err := c.podIsolationDetector.Detect(vmi)
1✔
2550
        if err != nil {
1✔
2551
                return err
×
2552
        }
×
2553

2554
        // Get the libvirt connection socket file on the destination pod.
2555
        socketFile := fmt.Sprintf(filepath.Join(c.virtLauncherFSRunDirPattern, "libvirt/virtqemud-sock"), res.Pid())
1✔
2556
        // the migration-proxy is no longer shared via host mount, so we
1✔
2557
        // pass in the virt-launcher's baseDir to reach the unix sockets.
1✔
2558
        baseDir := fmt.Sprintf(filepath.Join(c.virtLauncherFSRunDirPattern, "kubevirt"), res.Pid())
1✔
2559
        migrationTargetSockets = append(migrationTargetSockets, socketFile)
1✔
2560

1✔
2561
        migrationPortsRange := migrationproxy.GetMigrationPortsList(vmi.IsBlockMigration())
1✔
2562
        for _, port := range migrationPortsRange {
2✔
2563
                key := migrationproxy.ConstructProxyKey(string(vmi.UID), port)
1✔
2564
                // a proxy between the target direct qemu channel and the connector in the destination pod
1✔
2565
                destSocketFile := migrationproxy.SourceUnixFile(baseDir, key)
1✔
2566
                migrationTargetSockets = append(migrationTargetSockets, destSocketFile)
1✔
2567
        }
1✔
2568
        err = c.migrationProxy.StartTargetListener(string(vmi.UID), migrationTargetSockets)
1✔
2569
        if err != nil {
1✔
2570
                return err
×
2571
        }
×
2572
        return nil
1✔
2573
}
2574

2575
func (c *VirtualMachineController) handlePostMigrationProxyCleanup(vmi *v1.VirtualMachineInstance) {
1✔
2576
        if vmi.Status.MigrationState == nil || vmi.Status.MigrationState.Completed || vmi.Status.MigrationState.Failed {
2✔
2577
                c.migrationProxy.StopTargetListener(string(vmi.UID))
1✔
2578
                c.migrationProxy.StopSourceListener(string(vmi.UID))
1✔
2579
        }
1✔
2580
}
2581

2582
func (c *VirtualMachineController) handleSourceMigrationProxy(vmi *v1.VirtualMachineInstance) error {
1✔
2583

1✔
2584
        res, err := c.podIsolationDetector.Detect(vmi)
1✔
2585
        if err != nil {
1✔
2586
                return err
×
2587
        }
×
2588
        // the migration-proxy is no longer shared via host mount, so we
2589
        // pass in the virt-launcher's baseDir to reach the unix sockets.
2590
        baseDir := fmt.Sprintf(filepath.Join(c.virtLauncherFSRunDirPattern, "kubevirt"), res.Pid())
1✔
2591
        c.migrationProxy.StopTargetListener(string(vmi.UID))
1✔
2592
        if vmi.Status.MigrationState.TargetDirectMigrationNodePorts == nil {
1✔
2593
                msg := "No migration proxy has been created for this vmi"
×
2594
                return fmt.Errorf("%s", msg)
×
2595
        }
×
2596
        err = c.migrationProxy.StartSourceListener(
1✔
2597
                string(vmi.UID),
1✔
2598
                vmi.Status.MigrationState.TargetNodeAddress,
1✔
2599
                vmi.Status.MigrationState.TargetDirectMigrationNodePorts,
1✔
2600
                baseDir,
1✔
2601
        )
1✔
2602
        if err != nil {
1✔
2603
                return err
×
2604
        }
×
2605

2606
        return nil
1✔
2607
}
2608

2609
func (c *VirtualMachineController) getLauncherClientInfo(vmi *v1.VirtualMachineInstance) *virtcache.LauncherClientInfo {
1✔
2610
        launcherInfo, exists := c.launcherClients.Load(vmi.UID)
1✔
2611
        if !exists {
1✔
2612
                return nil
×
2613
        }
×
2614
        return launcherInfo
1✔
2615
}
2616

2617
func isMigrationInProgress(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
1✔
2618
        var domainMigrationMetadata *api.MigrationMetadata
1✔
2619

1✔
2620
        if domain == nil ||
1✔
2621
                vmi.Status.MigrationState == nil ||
1✔
2622
                domain.Spec.Metadata.KubeVirt.Migration == nil {
2✔
2623
                return false
1✔
2624
        }
1✔
2625
        domainMigrationMetadata = domain.Spec.Metadata.KubeVirt.Migration
1✔
2626

1✔
2627
        if vmi.Status.MigrationState.MigrationUID == domainMigrationMetadata.UID &&
1✔
2628
                domainMigrationMetadata.StartTimestamp != nil {
2✔
2629
                return true
1✔
2630
        }
1✔
2631
        return false
×
2632
}
2633

2634
func (c *VirtualMachineController) vmUpdateHelperMigrationSource(origVMI *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
2635

1✔
2636
        client, err := c.getLauncherClient(origVMI)
1✔
2637
        if err != nil {
1✔
2638
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
2639
        }
×
2640

2641
        if origVMI.Status.MigrationState.AbortRequested {
2✔
2642
                err = c.handleMigrationAbort(origVMI, client)
1✔
2643
                if err != nil {
1✔
2644
                        return err
×
2645
                }
×
2646
        } else {
1✔
2647
                if isMigrationInProgress(origVMI, domain) {
2✔
2648
                        // we already started this migration, no need to rerun this
1✔
2649
                        log.DefaultLogger().Errorf("migration %s has already been started", origVMI.Status.MigrationState.MigrationUID)
1✔
2650
                        return nil
1✔
2651
                }
1✔
2652

2653
                err = c.handleSourceMigrationProxy(origVMI)
1✔
2654
                if err != nil {
1✔
2655
                        return fmt.Errorf("failed to handle migration proxy: %v", err)
×
2656
                }
×
2657

2658
                migrationConfiguration := origVMI.Status.MigrationState.MigrationConfiguration
1✔
2659
                if migrationConfiguration == nil {
2✔
2660
                        migrationConfiguration = c.clusterConfig.GetMigrationConfiguration()
1✔
2661
                }
1✔
2662

2663
                options := &cmdclient.MigrationOptions{
1✔
2664
                        Bandwidth:               *migrationConfiguration.BandwidthPerMigration,
1✔
2665
                        ProgressTimeout:         *migrationConfiguration.ProgressTimeout,
1✔
2666
                        CompletionTimeoutPerGiB: *migrationConfiguration.CompletionTimeoutPerGiB,
1✔
2667
                        UnsafeMigration:         *migrationConfiguration.UnsafeMigrationOverride,
1✔
2668
                        AllowAutoConverge:       *migrationConfiguration.AllowAutoConverge,
1✔
2669
                        AllowPostCopy:           *migrationConfiguration.AllowPostCopy,
1✔
2670
                        AllowWorkloadDisruption: *migrationConfiguration.AllowWorkloadDisruption,
1✔
2671
                }
1✔
2672

1✔
2673
                configureParallelMigrationThreads(options, origVMI)
1✔
2674

1✔
2675
                marshalledOptions, err := json.Marshal(options)
1✔
2676
                if err != nil {
1✔
2677
                        log.Log.Object(origVMI).Warning("failed to marshall matched migration options")
×
2678
                } else {
1✔
2679
                        log.Log.Object(origVMI).Infof("migration options matched for vmi %s: %s", origVMI.Name, string(marshalledOptions))
1✔
2680
                }
1✔
2681

2682
                vmi := origVMI.DeepCopy()
1✔
2683
                err = hostdisk.ReplacePVCByHostDisk(vmi)
1✔
2684
                if err != nil {
1✔
2685
                        return err
×
2686
                }
×
2687

2688
                err = client.MigrateVirtualMachine(vmi, options)
1✔
2689
                if err != nil {
1✔
2690
                        return err
×
2691
                }
×
2692
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Migrating.String(), VMIMigrating)
1✔
2693
        }
2694
        return nil
1✔
2695
}
2696

2697
func replaceMigratedVolumesStatus(vmi *v1.VirtualMachineInstance) {
1✔
2698
        replaceVolsStatus := make(map[string]*v1.PersistentVolumeClaimInfo)
1✔
2699
        for _, v := range vmi.Status.MigratedVolumes {
1✔
2700
                replaceVolsStatus[v.SourcePVCInfo.ClaimName] = v.DestinationPVCInfo
×
2701
        }
×
2702
        for i, v := range vmi.Status.VolumeStatus {
1✔
2703
                if v.PersistentVolumeClaimInfo == nil {
×
2704
                        continue
×
2705
                }
2706
                if status, ok := replaceVolsStatus[v.PersistentVolumeClaimInfo.ClaimName]; ok {
×
2707
                        vmi.Status.VolumeStatus[i].PersistentVolumeClaimInfo = status
×
2708
                }
×
2709
        }
2710

2711
}
2712

2713
func (c *VirtualMachineController) vmUpdateHelperMigrationTarget(origVMI *v1.VirtualMachineInstance) error {
1✔
2714
        client, err := c.getLauncherClient(origVMI)
1✔
2715
        if err != nil {
1✔
2716
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
2717
        }
×
2718

2719
        vmi := origVMI.DeepCopy()
1✔
2720

1✔
2721
        if migrations.MigrationFailed(vmi) {
2✔
2722
                // if the migration failed, signal the target pod it's okay to exit
1✔
2723
                err = client.SignalTargetPodCleanup(vmi)
1✔
2724
                if err != nil {
1✔
2725
                        return err
×
2726
                }
×
2727
                log.Log.Object(vmi).Infof("Signaled target pod for failed migration to clean up")
1✔
2728
                // nothing left to do here if the migration failed.
1✔
2729
                // Re-enqueue to trigger handler final cleanup
1✔
2730
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second)
1✔
2731
                return nil
1✔
2732
        } else if migrations.IsMigrating(vmi) {
2✔
2733
                // If the migration has already started,
1✔
2734
                // then there's nothing left to prepare on the target side
1✔
2735
                return nil
1✔
2736
        }
1✔
2737
        // The VolumeStatus is used to retrive additional information for the volume handling.
2738
        // For example, for filesystem PVC, the information are used to create a right size image.
2739
        // In the case of migrated volumes, we need to replace the original volume information with the
2740
        // destination volume properties.
2741
        replaceMigratedVolumesStatus(vmi)
1✔
2742
        err = hostdisk.ReplacePVCByHostDisk(vmi)
1✔
2743
        if err != nil {
1✔
2744
                return err
×
2745
        }
×
2746

2747
        // give containerDisks some time to become ready before throwing errors on retries
2748
        info := c.getLauncherClientInfo(vmi)
1✔
2749
        if ready, err := c.containerDiskMounter.ContainerDisksReady(vmi, info.NotInitializedSince); !ready {
1✔
2750
                if err != nil {
×
2751
                        return err
×
2752
                }
×
2753
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
2754
                return nil
×
2755
        }
2756

2757
        // Mount container disks
2758
        err = c.containerDiskMounter.MountAndVerify(vmi)
1✔
2759
        if err != nil {
1✔
2760
                return err
×
2761
        }
×
2762

2763
        // Mount hotplug disks
2764
        if attachmentPodUID := vmi.Status.MigrationState.TargetAttachmentPodUID; attachmentPodUID != types.UID("") {
1✔
2765
                cgroupManager, err := getCgroupManager(vmi)
×
2766
                if err != nil {
×
2767
                        return err
×
2768
                }
×
2769
                if err := c.hotplugVolumeMounter.MountFromPod(vmi, attachmentPodUID, cgroupManager); err != nil {
×
2770
                        return fmt.Errorf("failed to mount hotplug volumes: %v", err)
×
2771
                }
×
2772
        }
2773

2774
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
2775
        if err != nil {
1✔
2776
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
2777
        }
×
2778

2779
        if err := c.netConf.Setup(vmi, netsetup.FilterNetsForMigrationTarget(vmi), isolationRes.Pid()); err != nil {
1✔
2780
                return fmt.Errorf("failed to configure vmi network for migration target: %w", err)
×
2781
        }
×
2782

2783
        virtLauncherRootMount, err := isolationRes.MountRoot()
1✔
2784
        if err != nil {
1✔
2785
                return err
×
2786
        }
×
2787

2788
        err = c.claimDeviceOwnership(virtLauncherRootMount, "kvm")
1✔
2789
        if err != nil {
1✔
2790
                return fmt.Errorf("failed to set up file ownership for /dev/kvm: %v", err)
×
2791
        }
×
2792
        if virtutil.IsAutoAttachVSOCK(vmi) {
1✔
2793
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
2794
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
2795
                }
×
2796
        }
2797

2798
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
1✔
2799
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
1✔
2800

1✔
2801
        // initialize disks images for empty PVC
1✔
2802
        hostDiskCreator := hostdisk.NewHostDiskCreator(c.recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
1✔
2803
        err = hostDiskCreator.Create(vmi)
1✔
2804
        if err != nil {
1✔
2805
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
2806
        }
×
2807

2808
        if virtutil.IsNonRootVMI(vmi) {
1✔
2809
                if err := c.nonRootSetup(origVMI); err != nil {
×
2810
                        return err
×
2811
                }
×
2812
        }
2813

2814
        options := virtualMachineOptions(nil, 0, nil, c.capabilities, c.clusterConfig)
1✔
2815
        options.InterfaceDomainAttachment = domainspec.DomainAttachmentByInterfaceName(vmi.Spec.Domain.Devices.Interfaces, c.clusterConfig.GetNetworkBindings())
1✔
2816

1✔
2817
        if err := client.SyncMigrationTarget(vmi, options); err != nil {
1✔
2818
                return fmt.Errorf("syncing migration target failed: %v", err)
×
2819
        }
×
2820
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.PreparingTarget.String(), VMIMigrationTargetPrepared)
1✔
2821

1✔
2822
        err = c.handleTargetMigrationProxy(vmi)
1✔
2823
        if err != nil {
1✔
2824
                return fmt.Errorf("failed to handle post sync migration proxy: %v", err)
×
2825
        }
×
2826
        return nil
1✔
2827
}
2828

2829
func (c *VirtualMachineController) affinePitThread(vmi *v1.VirtualMachineInstance) error {
×
2830
        res, err := c.podIsolationDetector.Detect(vmi)
×
2831
        if err != nil {
×
2832
                return err
×
2833
        }
×
2834
        var Mask unix.CPUSet
×
2835
        Mask.Zero()
×
2836
        qemuprocess, err := res.GetQEMUProcess()
×
2837
        if err != nil {
×
2838
                return err
×
2839
        }
×
2840
        qemupid := qemuprocess.Pid()
×
2841
        if qemupid == -1 {
×
2842
                return nil
×
2843
        }
×
2844

2845
        pitpid, err := res.KvmPitPid()
×
2846
        if err != nil {
×
2847
                return err
×
2848
        }
×
2849
        if pitpid == -1 {
×
2850
                return nil
×
2851
        }
×
2852
        if vmi.IsRealtimeEnabled() {
×
2853
                param := schedParam{priority: 2}
×
2854
                err = schedSetScheduler(pitpid, schedFIFO, param)
×
2855
                if err != nil {
×
2856
                        return fmt.Errorf("failed to set FIFO scheduling and priority 2 for thread %d: %w", pitpid, err)
×
2857
                }
×
2858
        }
2859
        vcpus, err := getVCPUThreadIDs(qemupid)
×
2860
        if err != nil {
×
2861
                return err
×
2862
        }
×
2863
        vpid, ok := vcpus["0"]
×
2864
        if ok == false {
×
2865
                return nil
×
2866
        }
×
2867
        vcpupid, err := strconv.Atoi(vpid)
×
2868
        if err != nil {
×
2869
                return err
×
2870
        }
×
2871
        err = unix.SchedGetaffinity(vcpupid, &Mask)
×
2872
        if err != nil {
×
2873
                return err
×
2874
        }
×
2875
        return unix.SchedSetaffinity(pitpid, &Mask)
×
2876
}
2877

2878
func (c *VirtualMachineController) configureHousekeepingCgroup(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
×
2879
        if err := cgroupManager.CreateChildCgroup("housekeeping", "cpuset"); err != nil {
×
2880
                log.Log.Reason(err).Error("CreateChildCgroup ")
×
2881
                return err
×
2882
        }
×
2883

2884
        key := controller.VirtualMachineInstanceKey(vmi)
×
2885
        domain, domainExists, _, err := c.getDomainFromCache(key)
×
2886
        if err != nil {
×
2887
                return err
×
2888
        }
×
2889
        // bail out if domain does not exist
2890
        if domainExists == false {
×
2891
                return nil
×
2892
        }
×
2893

2894
        if domain.Spec.CPUTune == nil || domain.Spec.CPUTune.EmulatorPin == nil {
×
2895
                return nil
×
2896
        }
×
2897

2898
        hkcpus, err := hardware.ParseCPUSetLine(domain.Spec.CPUTune.EmulatorPin.CPUSet, 100)
×
2899
        if err != nil {
×
2900
                return err
×
2901
        }
×
2902

2903
        log.Log.V(3).Object(vmi).Infof("housekeeping cpu: %v", hkcpus)
×
2904

×
2905
        err = cgroupManager.SetCpuSet("housekeeping", hkcpus)
×
2906
        if err != nil {
×
2907
                return err
×
2908
        }
×
2909

2910
        tids, err := cgroupManager.GetCgroupThreads()
×
2911
        if err != nil {
×
2912
                return err
×
2913
        }
×
2914
        hktids := make([]int, 0, 10)
×
2915

×
2916
        for _, tid := range tids {
×
2917
                proc, err := ps.FindProcess(tid)
×
2918
                if err != nil {
×
2919
                        log.Log.Object(vmi).Errorf("Failure to find process: %s", err.Error())
×
2920
                        return err
×
2921
                }
×
2922
                if proc == nil {
×
2923
                        return fmt.Errorf("failed to find process with tid: %d", tid)
×
2924
                }
×
2925
                comm := proc.Executable()
×
2926
                if strings.Contains(comm, "CPU ") && strings.Contains(comm, "KVM") {
×
2927
                        continue
×
2928
                }
2929
                hktids = append(hktids, tid)
×
2930
        }
2931

2932
        log.Log.V(3).Object(vmi).Infof("hk thread ids: %v", hktids)
×
2933
        for _, tid := range hktids {
×
2934
                err = cgroupManager.AttachTID("cpuset", "housekeeping", tid)
×
2935
                if err != nil {
×
2936
                        log.Log.Object(vmi).Errorf("Error attaching tid %d: %v", tid, err.Error())
×
2937
                        return err
×
2938
                }
×
2939
        }
2940

2941
        return nil
×
2942
}
2943

2944
func (c *VirtualMachineController) vmUpdateHelperDefault(origVMI *v1.VirtualMachineInstance, domainExists bool) error {
1✔
2945
        client, err := c.getLauncherClient(origVMI)
1✔
2946
        if err != nil {
1✔
2947
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
2948
        }
×
2949

2950
        vmi := origVMI.DeepCopy()
1✔
2951
        preallocatedVolumes := c.getPreallocatedVolumes(vmi)
1✔
2952

1✔
2953
        err = hostdisk.ReplacePVCByHostDisk(vmi)
1✔
2954
        if err != nil {
1✔
2955
                return err
×
2956
        }
×
2957

2958
        cgroupManager, err := getCgroupManager(vmi)
1✔
2959
        if err != nil {
1✔
2960
                return err
×
2961
        }
×
2962

2963
        var errorTolerantFeaturesError []error
1✔
2964
        readyToProceed, err := c.handleVMIState(vmi, cgroupManager, &errorTolerantFeaturesError)
1✔
2965
        if err != nil {
2✔
2966
                return err
1✔
2967
        }
1✔
2968

2969
        if !readyToProceed {
2✔
2970
                return nil
1✔
2971
        }
1✔
2972

2973
        // Synchronize the VirtualMachineInstance state
2974
        err = c.syncVirtualMachine(client, vmi, preallocatedVolumes)
1✔
2975
        if err != nil {
1✔
2976
                return err
×
2977
        }
×
2978

2979
        // Post-sync housekeeping
2980
        err = c.handleHousekeeping(vmi, cgroupManager, domainExists)
1✔
2981
        if err != nil {
1✔
2982
                return err
×
2983
        }
×
2984

2985
        return errors.NewAggregate(errorTolerantFeaturesError)
1✔
2986
}
2987

2988
// handleVMIState: Decides whether to call handleRunningVMI or handleStartingVMI based on the VMI's state.
2989
func (c *VirtualMachineController) handleVMIState(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) (bool, error) {
1✔
2990
        if vmi.IsRunning() {
2✔
2991
                return true, c.handleRunningVMI(vmi, cgroupManager, errorTolerantFeaturesError)
1✔
2992
        } else if !vmi.IsFinal() {
3✔
2993
                return c.handleStartingVMI(vmi, cgroupManager)
1✔
2994
        }
1✔
2995
        return true, nil
×
2996
}
2997

2998
// handleRunningVMI contains the logic specifically for running VMs (hotplugging in running state, metrics, network updates)
2999
func (c *VirtualMachineController) handleRunningVMI(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, errorTolerantFeaturesError *[]error) error {
1✔
3000
        if err := c.hotplugSriovInterfaces(vmi); err != nil {
1✔
3001
                log.Log.Object(vmi).Error(err.Error())
×
3002
        }
×
3003

3004
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
2✔
3005
                return err
1✔
3006
        }
1✔
3007

3008
        if err := c.getMemoryDump(vmi); err != nil {
1✔
3009
                return err
×
3010
        }
×
3011

3012
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
3013
        if err != nil {
1✔
3014
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
3015
        }
×
3016

3017
        if err := c.downwardMetricsManager.StartServer(vmi, isolationRes.Pid()); err != nil {
1✔
3018
                return err
×
3019
        }
×
3020

3021
        if err := c.netConf.Setup(vmi, netsetup.FilterNetsForLiveUpdate(vmi), isolationRes.Pid()); err != nil {
1✔
3022
                log.Log.Object(vmi).Error(err.Error())
×
3023
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, "NicHotplug", err.Error())
×
3024
                *errorTolerantFeaturesError = append(*errorTolerantFeaturesError, err)
×
3025
        }
×
3026

3027
        return nil
1✔
3028
}
3029

3030
// handleStartingVMI: Contains the logic for starting VMs (container disks, initial network setup, device ownership).
3031
func (c *VirtualMachineController) handleStartingVMI(
3032
        vmi *v1.VirtualMachineInstance,
3033
        cgroupManager cgroup.Manager,
3034
) (bool, error) {
1✔
3035
        // give containerDisks some time to become ready before throwing errors on retries
1✔
3036
        info := c.getLauncherClientInfo(vmi)
1✔
3037
        if ready, err := c.containerDiskMounter.ContainerDisksReady(vmi, info.NotInitializedSince); !ready {
2✔
3038
                if err != nil {
2✔
3039
                        return false, err
1✔
3040
                }
1✔
3041
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
3042
                return false, nil
1✔
3043
        }
3044

3045
        var err error
1✔
3046
        err = c.containerDiskMounter.MountAndVerify(vmi)
1✔
3047
        if err != nil {
2✔
3048
                return false, err
1✔
3049
        }
1✔
3050

3051
        if err := c.hotplugVolumeMounter.Mount(vmi, cgroupManager); err != nil {
1✔
3052
                return false, err
×
3053
        }
×
3054

3055
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
3056
        if err != nil {
1✔
3057
                return false, fmt.Errorf(failedDetectIsolationFmt, err)
×
3058
        }
×
3059

3060
        if err := c.netConf.Setup(vmi, netsetup.FilterNetsForVMStartup(vmi), isolationRes.Pid()); err != nil {
2✔
3061
                return false, fmt.Errorf("failed to configure vmi network: %w", err)
1✔
3062
        }
1✔
3063

3064
        if err := c.setupDevicesOwnerships(vmi, isolationRes); err != nil {
1✔
3065
                return false, err
×
3066
        }
×
3067

3068
        if err := c.adjustResources(vmi); err != nil {
1✔
3069
                return false, err
×
3070
        }
×
3071

3072
        if c.shouldWaitForSEVAttestation(vmi) {
1✔
3073
                return false, nil
×
3074
        }
×
3075

3076
        return true, nil
1✔
3077
}
3078

3079
func (c *VirtualMachineController) adjustResources(vmi *v1.VirtualMachineInstance) error {
1✔
3080
        err := c.podIsolationDetector.AdjustResources(vmi, c.clusterConfig.GetConfig().AdditionalGuestMemoryOverheadRatio)
1✔
3081
        if err != nil {
1✔
3082
                return fmt.Errorf("failed to adjust resources: %v", err)
×
3083
        }
×
3084
        return nil
1✔
3085
}
3086

3087
func (c *VirtualMachineController) shouldWaitForSEVAttestation(vmi *v1.VirtualMachineInstance) bool {
1✔
3088
        if util.IsSEVAttestationRequested(vmi) {
1✔
3089
                sev := vmi.Spec.Domain.LaunchSecurity.SEV
×
3090
                // Wait for the session parameters to be provided
×
3091
                return sev.Session == "" || sev.DHCert == ""
×
3092
        }
×
3093
        return false
1✔
3094
}
3095

3096
func (c *VirtualMachineController) setupDevicesOwnerships(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
1✔
3097
        virtLauncherRootMount, err := isolationRes.MountRoot()
1✔
3098
        if err != nil {
1✔
3099
                return err
×
3100
        }
×
3101

3102
        err = c.claimDeviceOwnership(virtLauncherRootMount, "kvm")
1✔
3103
        if err != nil {
1✔
3104
                return fmt.Errorf("failed to set up file ownership for /dev/kvm: %v", err)
×
3105
        }
×
3106

3107
        if virtutil.IsAutoAttachVSOCK(vmi) {
1✔
3108
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
3109
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
3110
                }
×
3111
        }
3112

3113
        if err := c.configureHostDisks(vmi, isolationRes, virtLauncherRootMount); err != nil {
1✔
3114
                return err
×
3115
        }
×
3116

3117
        if err := c.configureSEVDeviceOwnership(vmi, isolationRes, virtLauncherRootMount); err != nil {
1✔
3118
                return err
×
3119
        }
×
3120

3121
        if virtutil.IsNonRootVMI(vmi) {
1✔
3122
                if err := c.nonRootSetup(vmi); err != nil {
×
3123
                        return err
×
3124
                }
×
3125
        }
3126

3127
        if err := c.configureVirtioFS(vmi, isolationRes); err != nil {
1✔
3128
                return err
×
3129
        }
×
3130

3131
        return nil
1✔
3132
}
3133

3134
func (c *VirtualMachineController) configureHostDisks(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult, virtLauncherRootMount *safepath.Path) error {
1✔
3135
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
1✔
3136
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
1✔
3137

1✔
3138
        hostDiskCreator := hostdisk.NewHostDiskCreator(c.recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
1✔
3139
        if err := hostDiskCreator.Create(vmi); err != nil {
1✔
3140
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
3141
        }
×
3142
        return nil
1✔
3143
}
3144

3145
func (c *VirtualMachineController) configureSEVDeviceOwnership(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult, virtLauncherRootMount *safepath.Path) error {
1✔
3146
        if virtutil.IsSEVVMI(vmi) {
1✔
3147
                sevDevice, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", "sev"))
×
3148
                if err != nil {
×
3149
                        return err
×
3150
                }
×
3151
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(sevDevice); err != nil {
×
3152
                        return fmt.Errorf("failed to set SEV device owner: %v", err)
×
3153
                }
×
3154
        }
3155
        return nil
1✔
3156
}
3157

3158
func (c *VirtualMachineController) configureVirtioFS(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
1✔
3159
        for _, fs := range vmi.Spec.Domain.Devices.Filesystems {
1✔
3160
                socketPath, err := isolation.SafeJoin(isolationRes, virtiofs.VirtioFSSocketPath(fs.Name))
×
3161
                if err != nil {
×
3162
                        return err
×
3163
                }
×
3164
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath); err != nil {
×
3165
                        return err
×
3166
                }
×
3167
        }
3168
        return nil
1✔
3169
}
3170

3171
func (c *VirtualMachineController) syncVirtualMachine(client cmdclient.LauncherClient, vmi *v1.VirtualMachineInstance, preallocatedVolumes []string) error {
1✔
3172
        smbios := c.clusterConfig.GetSMBIOS()
1✔
3173
        period := c.clusterConfig.GetMemBalloonStatsPeriod()
1✔
3174

1✔
3175
        options := virtualMachineOptions(smbios, period, preallocatedVolumes, c.capabilities, c.clusterConfig)
1✔
3176
        options.InterfaceDomainAttachment = domainspec.DomainAttachmentByInterfaceName(vmi.Spec.Domain.Devices.Interfaces, c.clusterConfig.GetNetworkBindings())
1✔
3177

1✔
3178
        err := client.SyncVirtualMachine(vmi, options)
1✔
3179
        if err != nil {
1✔
3180
                if strings.Contains(err.Error(), "EFI OVMF rom missing") {
×
3181
                        return &virtLauncherCriticalSecurebootError{fmt.Sprintf("mismatch of Secure Boot setting and bootloaders: %v", err)}
×
3182
                }
×
3183
        }
3184

3185
        return err
1✔
3186
}
3187

3188
func (c *VirtualMachineController) handleHousekeeping(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager, domainExists bool) error {
1✔
3189

1✔
3190
        if vmi.IsCPUDedicated() && vmi.Spec.Domain.CPU.IsolateEmulatorThread {
1✔
3191
                err := c.configureHousekeepingCgroup(vmi, cgroupManager)
×
3192
                if err != nil {
×
3193
                        return err
×
3194
                }
×
3195
        }
3196

3197
        // Configure vcpu scheduler for realtime workloads and affine PIT thread for dedicated CPU
3198
        if vmi.IsRealtimeEnabled() && !vmi.IsRunning() && !vmi.IsFinal() {
1✔
3199
                log.Log.Object(vmi).Info("Configuring vcpus for real time workloads")
×
3200
                if err := c.configureVCPUScheduler(vmi); err != nil {
×
3201
                        return err
×
3202
                }
×
3203
        }
3204
        if vmi.IsCPUDedicated() && !vmi.IsRunning() && !vmi.IsFinal() {
1✔
3205
                log.Log.V(3).Object(vmi).Info("Affining PIT thread")
×
3206
                if err := c.affinePitThread(vmi); err != nil {
×
3207
                        return err
×
3208
                }
×
3209
        }
3210
        if !domainExists {
2✔
3211
                c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Created.String(), VMIDefined)
1✔
3212
        }
1✔
3213

3214
        if vmi.IsRunning() {
2✔
3215
                // Umount any disks no longer mounted
1✔
3216
                if err := c.hotplugVolumeMounter.Unmount(vmi, cgroupManager); err != nil {
1✔
3217
                        return err
×
3218
                }
×
3219
        }
3220
        return nil
1✔
3221
}
3222

3223
func (c *VirtualMachineController) getPreallocatedVolumes(vmi *v1.VirtualMachineInstance) []string {
1✔
3224
        preallocatedVolumes := []string{}
1✔
3225
        for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
3226
                if volumeStatus.PersistentVolumeClaimInfo != nil && volumeStatus.PersistentVolumeClaimInfo.Preallocated {
1✔
3227
                        preallocatedVolumes = append(preallocatedVolumes, volumeStatus.Name)
×
3228
                }
×
3229
        }
3230
        return preallocatedVolumes
1✔
3231
}
3232

3233
func (c *VirtualMachineController) hotplugSriovInterfaces(vmi *v1.VirtualMachineInstance) error {
1✔
3234
        sriovSpecInterfaces := netvmispec.FilterSRIOVInterfaces(vmi.Spec.Domain.Devices.Interfaces)
1✔
3235

1✔
3236
        sriovSpecIfacesNames := netvmispec.IndexInterfaceSpecByName(sriovSpecInterfaces)
1✔
3237
        attachedSriovStatusIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
1✔
3238
                _, exist := sriovSpecIfacesNames[iface.Name]
×
3239
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceDomain) &&
×
3240
                        netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
3241
        })
×
3242

3243
        desiredSriovMultusPluggedIfaces := netvmispec.IndexInterfaceStatusByName(vmi.Status.Interfaces, func(iface v1.VirtualMachineInstanceNetworkInterface) bool {
1✔
3244
                _, exist := sriovSpecIfacesNames[iface.Name]
×
3245
                return exist && netvmispec.ContainsInfoSource(iface.InfoSource, netvmispec.InfoSourceMultusStatus)
×
3246
        })
×
3247

3248
        if len(desiredSriovMultusPluggedIfaces) == len(attachedSriovStatusIfaces) {
2✔
3249
                c.sriovHotplugExecutorPool.Delete(vmi.UID)
1✔
3250
                return nil
1✔
3251
        }
1✔
3252

3253
        rateLimitedExecutor := c.sriovHotplugExecutorPool.LoadOrStore(vmi.UID)
×
3254
        return rateLimitedExecutor.Exec(func() error {
×
3255
                return c.hotplugSriovInterfacesCommand(vmi)
×
3256
        })
×
3257
}
3258

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

×
3262
        client, err := c.getVerifiedLauncherClient(vmi)
×
3263
        if err != nil {
×
3264
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
3265
        }
×
3266

3267
        if err := isolation.AdjustQemuProcessMemoryLimits(c.podIsolationDetector, vmi, c.clusterConfig.GetConfig().AdditionalGuestMemoryOverheadRatio); err != nil {
×
3268
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), err.Error())
×
3269
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
3270
        }
×
3271

3272
        log.Log.V(3).Object(vmi).Info("sending hot-plug host-devices command")
×
3273
        if err := client.HotplugHostDevices(vmi); err != nil {
×
3274
                return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
3275
        }
×
3276

3277
        return nil
×
3278
}
3279

3280
func memoryDumpPath(volumeStatus v1.VolumeStatus) string {
×
3281
        target := hotplugdisk.GetVolumeMountDir(volumeStatus.Name)
×
3282
        dumpPath := filepath.Join(target, volumeStatus.MemoryDumpVolume.TargetFileName)
×
3283
        return dumpPath
×
3284
}
×
3285

3286
func (c *VirtualMachineController) getMemoryDump(vmi *v1.VirtualMachineInstance) error {
1✔
3287
        const errMsgPrefix = "failed to getting memory dump"
1✔
3288

1✔
3289
        for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
3290
                if volumeStatus.MemoryDumpVolume == nil || volumeStatus.Phase != v1.MemoryDumpVolumeInProgress {
2✔
3291
                        continue
1✔
3292
                }
3293
                client, err := c.getVerifiedLauncherClient(vmi)
×
3294
                if err != nil {
×
3295
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
3296
                }
×
3297

3298
                log.Log.V(3).Object(vmi).Info("sending memory dump command")
×
3299
                err = client.VirtualMachineMemoryDump(vmi, memoryDumpPath(volumeStatus))
×
3300
                if err != nil {
×
3301
                        return fmt.Errorf("%s: %v", errMsgPrefix, err)
×
3302
                }
×
3303
        }
3304

3305
        return nil
1✔
3306
}
3307

3308
func (c *VirtualMachineController) processVmUpdate(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
3309

1✔
3310
        isUnresponsive, isInitialized, err := c.isLauncherClientUnresponsive(vmi)
1✔
3311
        if err != nil {
1✔
3312
                return err
×
3313
        }
×
3314
        if !isInitialized {
2✔
3315
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
3316
                return nil
1✔
3317
        } else if isUnresponsive {
2✔
3318
                return goerror.New(fmt.Sprintf("Can not update a VirtualMachineInstance with unresponsive command server."))
×
3319
        }
×
3320

3321
        c.handlePostMigrationProxyCleanup(vmi)
1✔
3322

1✔
3323
        if c.isPreMigrationTarget(vmi) {
2✔
3324
                return c.vmUpdateHelperMigrationTarget(vmi)
1✔
3325
        } else if c.isMigrationSource(vmi) {
3✔
3326
                return c.vmUpdateHelperMigrationSource(vmi, domain)
1✔
3327
        } else {
2✔
3328
                return c.vmUpdateHelperDefault(vmi, domain != nil)
1✔
3329
        }
1✔
3330
}
3331

3332
func (c *VirtualMachineController) setVmPhaseForStatusReason(domain *api.Domain, vmi *v1.VirtualMachineInstance) error {
1✔
3333
        phase, err := c.calculateVmPhaseForStatusReason(domain, vmi)
1✔
3334
        if err != nil {
1✔
3335
                return err
×
3336
        }
×
3337
        vmi.Status.Phase = phase
1✔
3338
        return nil
1✔
3339
}
3340
func (c *VirtualMachineController) calculateVmPhaseForStatusReason(domain *api.Domain, vmi *v1.VirtualMachineInstance) (v1.VirtualMachineInstancePhase, error) {
1✔
3341

1✔
3342
        if domain == nil {
2✔
3343
                switch {
1✔
3344
                case vmi.IsScheduled():
1✔
3345
                        isUnresponsive, isInitialized, err := c.isLauncherClientUnresponsive(vmi)
1✔
3346

1✔
3347
                        if err != nil {
1✔
3348
                                return vmi.Status.Phase, err
×
3349
                        }
×
3350
                        if !isInitialized {
2✔
3351
                                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
1✔
3352
                                return vmi.Status.Phase, err
1✔
3353
                        } else if isUnresponsive {
3✔
3354
                                // virt-launcher is gone and VirtualMachineInstance never transitioned
1✔
3355
                                // from scheduled to Running.
1✔
3356
                                return v1.Failed, nil
1✔
3357
                        }
1✔
3358
                        return v1.Scheduled, nil
1✔
3359
                case !vmi.IsRunning() && !vmi.IsFinal():
×
3360
                        return v1.Scheduled, nil
×
3361
                case !vmi.IsFinal():
1✔
3362
                        // That is unexpected. We should not be able to delete a VirtualMachineInstance before we stop it.
1✔
3363
                        // However, if someone directly interacts with libvirt it is possible
1✔
3364
                        return v1.Failed, nil
1✔
3365
                }
3366
        } else {
1✔
3367

1✔
3368
                switch domain.Status.Status {
1✔
3369
                case api.Shutoff, api.Crashed:
1✔
3370
                        switch domain.Status.Reason {
1✔
3371
                        case api.ReasonCrashed, api.ReasonPanicked:
×
3372
                                return v1.Failed, nil
×
3373
                        case api.ReasonDestroyed:
×
3374
                                // When ACPI is available, the domain was tried to be shutdown,
×
3375
                                // and destroyed means that the domain was destroyed after the graceperiod expired.
×
3376
                                // Without ACPI a destroyed domain is ok.
×
3377
                                if isACPIEnabled(vmi, domain) {
×
3378
                                        return v1.Failed, nil
×
3379
                                }
×
3380
                                return v1.Succeeded, nil
×
3381
                        case api.ReasonShutdown, api.ReasonSaved, api.ReasonFromSnapshot:
×
3382
                                return v1.Succeeded, nil
×
3383
                        case api.ReasonMigrated:
1✔
3384
                                // if the domain migrated, we no longer know the phase.
1✔
3385
                                return vmi.Status.Phase, nil
1✔
3386
                        }
3387
                case api.Running, api.Paused, api.Blocked, api.PMSuspended:
1✔
3388
                        return v1.Running, nil
1✔
3389
                }
3390
        }
3391
        return vmi.Status.Phase, nil
×
3392
}
3393

3394
func (c *VirtualMachineController) addFunc(obj interface{}) {
×
3395
        key, err := controller.KeyFunc(obj)
×
3396
        if err == nil {
×
3397
                c.vmiExpectations.LowerExpectations(key, 1, 0)
×
3398
                c.queue.Add(key)
×
3399
        }
×
3400
}
3401
func (c *VirtualMachineController) deleteFunc(obj interface{}) {
×
3402
        key, err := controller.KeyFunc(obj)
×
3403
        if err == nil {
×
3404
                c.vmiExpectations.LowerExpectations(key, 1, 0)
×
3405
                c.queue.Add(key)
×
3406
        }
×
3407
}
3408
func (c *VirtualMachineController) updateFunc(_, new interface{}) {
×
3409
        key, err := controller.KeyFunc(new)
×
3410
        if err == nil {
×
3411
                c.vmiExpectations.LowerExpectations(key, 1, 0)
×
3412
                c.queue.Add(key)
×
3413
        }
×
3414
}
3415

3416
func (c *VirtualMachineController) addDomainFunc(obj interface{}) {
×
3417
        domain := obj.(*api.Domain)
×
3418
        log.Log.Object(domain).Infof("Domain is in state %s reason %s", domain.Status.Status, domain.Status.Reason)
×
3419
        key, err := controller.KeyFunc(obj)
×
3420
        if err == nil {
×
3421
                c.queue.Add(key)
×
3422
        }
×
3423
}
3424
func (c *VirtualMachineController) deleteDomainFunc(obj interface{}) {
×
3425
        domain, ok := obj.(*api.Domain)
×
3426
        if !ok {
×
3427
                tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
×
3428
                if !ok {
×
3429
                        log.Log.Reason(fmt.Errorf("couldn't get object from tombstone %+v", obj)).Error("Failed to process delete notification")
×
3430
                        return
×
3431
                }
×
3432
                domain, ok = tombstone.Obj.(*api.Domain)
×
3433
                if !ok {
×
3434
                        log.Log.Reason(fmt.Errorf("tombstone contained object that is not a domain %#v", obj)).Error("Failed to process delete notification")
×
3435
                        return
×
3436
                }
×
3437
        }
3438
        log.Log.Object(domain).Info("Domain deleted")
×
3439
        key, err := controller.KeyFunc(obj)
×
3440
        if err == nil {
×
3441
                c.queue.Add(key)
×
3442
        }
×
3443
}
3444
func (c *VirtualMachineController) updateDomainFunc(old, new interface{}) {
×
3445
        newDomain := new.(*api.Domain)
×
3446
        oldDomain := old.(*api.Domain)
×
3447
        if oldDomain.Status.Status != newDomain.Status.Status || oldDomain.Status.Reason != newDomain.Status.Reason {
×
3448
                log.Log.Object(newDomain).Infof("Domain is in state %s reason %s", newDomain.Status.Status, newDomain.Status.Reason)
×
3449
        }
×
3450

3451
        if newDomain.ObjectMeta.DeletionTimestamp != nil {
×
3452
                log.Log.Object(newDomain).Info("Domain is marked for deletion")
×
3453
        }
×
3454

3455
        key, err := controller.KeyFunc(new)
×
3456
        if err == nil {
×
3457
                c.queue.Add(key)
×
3458
        }
×
3459
}
3460

3461
func (c *VirtualMachineController) finalizeMigration(vmi *v1.VirtualMachineInstance) error {
1✔
3462
        const errorMessage = "failed to finalize migration"
1✔
3463

1✔
3464
        client, err := c.getVerifiedLauncherClient(vmi)
1✔
3465
        if err != nil {
1✔
3466
                return fmt.Errorf("%s: %v", errorMessage, err)
×
3467
        }
×
3468

3469
        if err := c.hotplugCPU(vmi, client); err != nil {
2✔
3470
                log.Log.Object(vmi).Reason(err).Error(errorMessage)
1✔
3471
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), "failed to change vCPUs")
1✔
3472
        }
1✔
3473

3474
        if err := c.hotplugMemory(vmi, client); err != nil {
1✔
3475
                log.Log.Object(vmi).Reason(err).Error(errorMessage)
×
3476
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, err.Error(), "failed to update guest memory")
×
3477
        }
×
3478
        removeMigratedVolumes(vmi)
1✔
3479

1✔
3480
        options := &cmdv1.VirtualMachineOptions{}
1✔
3481
        options.InterfaceMigration = domainspec.BindingMigrationByInterfaceName(vmi.Spec.Domain.Devices.Interfaces, c.clusterConfig.GetNetworkBindings())
1✔
3482
        if err := client.FinalizeVirtualMachineMigration(vmi, options); err != nil {
1✔
3483
                log.Log.Object(vmi).Reason(err).Error(errorMessage)
×
3484
                return fmt.Errorf("%s: %v", errorMessage, err)
×
3485
        }
×
3486

3487
        return nil
1✔
3488
}
3489

3490
func vmiHasTerminationGracePeriod(vmi *v1.VirtualMachineInstance) bool {
×
3491
        // if not set we use the default graceperiod
×
3492
        return vmi.Spec.TerminationGracePeriodSeconds == nil ||
×
3493
                (vmi.Spec.TerminationGracePeriodSeconds != nil && *vmi.Spec.TerminationGracePeriodSeconds != 0)
×
3494
}
×
3495

3496
func domainHasGracePeriod(domain *api.Domain) bool {
1✔
3497
        return domain != nil &&
1✔
3498
                domain.Spec.Metadata.KubeVirt.GracePeriod != nil &&
1✔
3499
                domain.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds != 0
1✔
3500
}
1✔
3501

3502
func isACPIEnabled(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
×
3503
        return (vmiHasTerminationGracePeriod(vmi) || (vmi.Spec.TerminationGracePeriodSeconds == nil && domainHasGracePeriod(domain))) &&
×
3504
                domain != nil &&
×
3505
                domain.Spec.Features != nil &&
×
3506
                domain.Spec.Features.ACPI != nil
×
3507
}
×
3508

3509
func (c *VirtualMachineController) isHostModelMigratable(vmi *v1.VirtualMachineInstance) error {
1✔
3510
        if cpu := vmi.Spec.Domain.CPU; cpu != nil && cpu.Model == v1.CPUModeHostModel {
2✔
3511
                if c.hostCpuModel == "" {
2✔
3512
                        err := fmt.Errorf("the node \"%s\" does not allow migration with host-model", vmi.Status.NodeName)
1✔
3513
                        log.Log.Object(vmi).Errorf(err.Error())
1✔
3514
                        return err
1✔
3515
                }
1✔
3516
        }
3517
        return nil
1✔
3518
}
3519

3520
func (c *VirtualMachineController) claimDeviceOwnership(virtLauncherRootMount *safepath.Path, deviceName string) error {
1✔
3521
        softwareEmulation := c.clusterConfig.AllowEmulation()
1✔
3522
        devicePath, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", deviceName))
1✔
3523
        if err != nil {
1✔
3524
                if softwareEmulation {
×
3525
                        return nil
×
3526
                }
×
3527
                return err
×
3528
        }
3529

3530
        return diskutils.DefaultOwnershipManager.SetFileOwnership(devicePath)
1✔
3531
}
3532

3533
func (c *VirtualMachineController) reportDedicatedCPUSetForMigratingVMI(vmi *v1.VirtualMachineInstance) error {
×
3534
        cgroupManager, err := getCgroupManager(vmi)
×
3535
        if err != nil {
×
3536
                return err
×
3537
        }
×
3538

3539
        cpusetStr, err := cgroupManager.GetCpuSet()
×
3540
        if err != nil {
×
3541
                return err
×
3542
        }
×
3543

3544
        cpuSet, err := hardware.ParseCPUSetLine(cpusetStr, 50000)
×
3545
        if err != nil {
×
3546
                return fmt.Errorf("failed to parse target VMI cpuset: %v", err)
×
3547
        }
×
3548

3549
        vmi.Status.MigrationState.TargetCPUSet = cpuSet
×
3550

×
3551
        return nil
×
3552
}
3553

3554
func (c *VirtualMachineController) reportTargetTopologyForMigratingVMI(vmi *v1.VirtualMachineInstance) error {
×
3555
        options := virtualMachineOptions(nil, 0, nil, c.capabilities, c.clusterConfig)
×
3556
        topology, err := json.Marshal(options.Topology)
×
3557
        if err != nil {
×
3558
                return err
×
3559
        }
×
3560
        vmi.Status.MigrationState.TargetNodeTopology = string(topology)
×
3561
        return nil
×
3562
}
3563

3564
func (c *VirtualMachineController) handleMigrationAbort(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient) error {
1✔
3565
        if vmi.Status.MigrationState.AbortStatus == v1.MigrationAbortInProgress {
1✔
3566
                return nil
×
3567
        }
×
3568

3569
        err := client.CancelVirtualMachineMigration(vmi)
1✔
3570
        if err != nil && err.Error() == migrations.CancelMigrationFailedVmiNotMigratingErr {
1✔
3571
                // If migration did not even start there is no need to cancel it
×
3572
                log.Log.Object(vmi).Infof("skipping migration cancellation since vmi is not migrating")
×
3573
                return err
×
3574
        }
×
3575

3576
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Migrating.String(), VMIAbortingMigration)
1✔
3577
        return nil
1✔
3578
}
3579

3580
func isIOError(shouldUpdate, domainExists bool, domain *api.Domain) bool {
1✔
3581
        return shouldUpdate && domainExists && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedIOError
1✔
3582
}
1✔
3583

3584
func (c *VirtualMachineController) updateMachineType(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
1✔
3585
        if domain == nil || vmi == nil {
2✔
3586
                return
1✔
3587
        }
1✔
3588
        if domain.Spec.OS.Type.Machine != "" {
2✔
3589
                vmi.Status.Machine = &v1.Machine{Type: domain.Spec.OS.Type.Machine}
1✔
3590
        }
1✔
3591
}
3592

3593
func (c *VirtualMachineController) hotplugCPU(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient) error {
1✔
3594
        vmiConditions := controller.NewVirtualMachineInstanceConditionManager()
1✔
3595

1✔
3596
        removeVMIVCPUChangeConditionAndLabel := func() {
2✔
3597
                delete(vmi.Labels, v1.VirtualMachinePodCPULimitsLabel)
1✔
3598
                vmiConditions.RemoveCondition(vmi, v1.VirtualMachineInstanceVCPUChange)
1✔
3599
        }
1✔
3600
        defer removeVMIVCPUChangeConditionAndLabel()
1✔
3601

1✔
3602
        if !vmiConditions.HasCondition(vmi, v1.VirtualMachineInstanceVCPUChange) {
2✔
3603
                return nil
1✔
3604
        }
1✔
3605

3606
        if vmi.IsCPUDedicated() {
1✔
3607
                cpuLimitStr, ok := vmi.Labels[v1.VirtualMachinePodCPULimitsLabel]
×
3608
                if !ok || len(cpuLimitStr) == 0 {
×
3609
                        return fmt.Errorf("cannot read CPU limit from VMI annotation")
×
3610
                }
×
3611

3612
                cpuLimit, err := strconv.Atoi(cpuLimitStr)
×
3613
                if err != nil {
×
3614
                        return fmt.Errorf("cannot parse CPU limit from VMI annotation: %v", err)
×
3615
                }
×
3616

3617
                vcpus := hardware.GetNumberOfVCPUs(vmi.Spec.Domain.CPU)
×
3618
                if vcpus > int64(cpuLimit) {
×
3619
                        return fmt.Errorf("number of requested VCPUS (%d) exceeds the limit (%d)", vcpus, cpuLimit)
×
3620
                }
×
3621
        }
3622

3623
        options := virtualMachineOptions(
1✔
3624
                nil,
1✔
3625
                0,
1✔
3626
                nil,
1✔
3627
                c.capabilities,
1✔
3628
                c.clusterConfig)
1✔
3629

1✔
3630
        if err := client.SyncVirtualMachineCPUs(vmi, options); err != nil {
2✔
3631
                return err
1✔
3632
        }
1✔
3633

3634
        if vmi.Status.CurrentCPUTopology == nil {
2✔
3635
                vmi.Status.CurrentCPUTopology = &v1.CPUTopology{}
1✔
3636
        }
1✔
3637

3638
        vmi.Status.CurrentCPUTopology.Sockets = vmi.Spec.Domain.CPU.Sockets
1✔
3639
        vmi.Status.CurrentCPUTopology.Cores = vmi.Spec.Domain.CPU.Cores
1✔
3640
        vmi.Status.CurrentCPUTopology.Threads = vmi.Spec.Domain.CPU.Threads
1✔
3641

1✔
3642
        return nil
1✔
3643
}
3644

3645
func (c *VirtualMachineController) hotplugMemory(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient) error {
1✔
3646
        vmiConditions := controller.NewVirtualMachineInstanceConditionManager()
1✔
3647

1✔
3648
        removeVMIMemoryChangeLabel := func() {
2✔
3649
                delete(vmi.Labels, v1.VirtualMachinePodMemoryRequestsLabel)
1✔
3650
                delete(vmi.Labels, v1.MemoryHotplugOverheadRatioLabel)
1✔
3651
        }
1✔
3652
        defer removeVMIMemoryChangeLabel()
1✔
3653

1✔
3654
        if !vmiConditions.HasCondition(vmi, v1.VirtualMachineInstanceMemoryChange) {
2✔
3655
                return nil
1✔
3656
        }
1✔
3657

3658
        podMemReqStr := vmi.Labels[v1.VirtualMachinePodMemoryRequestsLabel]
1✔
3659
        podMemReq, err := resource.ParseQuantity(podMemReqStr)
1✔
3660
        if err != nil {
1✔
3661
                vmiConditions.RemoveCondition(vmi, v1.VirtualMachineInstanceMemoryChange)
×
3662
                return fmt.Errorf("cannot parse Memory requests from VMI label: %v", err)
×
3663
        }
×
3664

3665
        overheadRatio := vmi.Labels[v1.MemoryHotplugOverheadRatioLabel]
1✔
3666
        requiredMemory := services.GetMemoryOverhead(vmi, runtime.GOARCH, &overheadRatio)
1✔
3667
        requiredMemory.Add(
1✔
3668
                c.netBindingPluginMemoryCalculator.Calculate(vmi, c.clusterConfig.GetNetworkBindings()),
1✔
3669
        )
1✔
3670

1✔
3671
        requiredMemory.Add(*vmi.Spec.Domain.Resources.Requests.Memory())
1✔
3672

1✔
3673
        if podMemReq.Cmp(requiredMemory) < 0 {
2✔
3674
                vmiConditions.RemoveCondition(vmi, v1.VirtualMachineInstanceMemoryChange)
1✔
3675
                return fmt.Errorf("amount of requested guest memory (%s) exceeds the launcher memory request (%s)", vmi.Spec.Domain.Memory.Guest.String(), podMemReqStr)
1✔
3676
        }
1✔
3677

3678
        options := virtualMachineOptions(nil, 0, nil, c.capabilities, c.clusterConfig)
1✔
3679

1✔
3680
        if err := client.SyncVirtualMachineMemory(vmi, options); err != nil {
2✔
3681
                // mark hotplug as failed
1✔
3682
                vmiConditions.UpdateCondition(vmi, &v1.VirtualMachineInstanceCondition{
1✔
3683
                        Type:    v1.VirtualMachineInstanceMemoryChange,
1✔
3684
                        Status:  k8sv1.ConditionFalse,
1✔
3685
                        Reason:  memoryHotplugFailedReason,
1✔
3686
                        Message: "memory hotplug failed, the VM configuration is not supported",
1✔
3687
                })
1✔
3688
                return err
1✔
3689
        }
1✔
3690

3691
        vmiConditions.RemoveCondition(vmi, v1.VirtualMachineInstanceMemoryChange)
1✔
3692
        vmi.Status.Memory.GuestRequested = vmi.Spec.Domain.Memory.Guest
1✔
3693
        return nil
1✔
3694
}
3695

3696
func removeMigratedVolumes(vmi *v1.VirtualMachineInstance) {
1✔
3697
        vmiConditions := controller.NewVirtualMachineInstanceConditionManager()
1✔
3698
        vmiConditions.RemoveCondition(vmi, v1.VirtualMachineInstanceVolumesChange)
1✔
3699
        vmi.Status.MigratedVolumes = nil
1✔
3700
}
1✔
3701

3702
func parseLibvirtQuantity(value int64, unit string) *resource.Quantity {
1✔
3703
        switch unit {
1✔
3704
        case "b", "bytes":
1✔
3705
                return resource.NewQuantity(value, resource.BinarySI)
1✔
3706
        case "KB":
1✔
3707
                return resource.NewQuantity(value*1000, resource.DecimalSI)
1✔
3708
        case "MB":
1✔
3709
                return resource.NewQuantity(value*1000*1000, resource.DecimalSI)
1✔
3710
        case "GB":
1✔
3711
                return resource.NewQuantity(value*1000*1000*1000, resource.DecimalSI)
1✔
3712
        case "TB":
1✔
3713
                return resource.NewQuantity(value*1000*1000*1000*1000, resource.DecimalSI)
1✔
3714
        case "k", "KiB":
1✔
3715
                return resource.NewQuantity(value*1024, resource.BinarySI)
1✔
3716
        case "M", "MiB":
1✔
3717
                return resource.NewQuantity(value*1024*1024, resource.BinarySI)
1✔
3718
        case "G", "GiB":
1✔
3719
                return resource.NewQuantity(value*1024*1024*1024, resource.BinarySI)
1✔
3720
        case "T", "TiB":
1✔
3721
                return resource.NewQuantity(value*1024*1024*1024*1024, resource.BinarySI)
1✔
3722
        }
3723
        return nil
×
3724
}
3725

3726
func (c *VirtualMachineController) updateMemoryInfo(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
3727
        if domain == nil || vmi == nil || domain.Spec.CurrentMemory == nil {
2✔
3728
                return nil
1✔
3729
        }
1✔
3730
        if vmi.Status.Memory == nil {
1✔
3731
                vmi.Status.Memory = &v1.MemoryStatus{}
×
3732
        }
×
3733
        currentGuest := parseLibvirtQuantity(int64(domain.Spec.CurrentMemory.Value), domain.Spec.CurrentMemory.Unit)
1✔
3734
        vmi.Status.Memory.GuestCurrent = currentGuest
1✔
3735
        return nil
1✔
3736
}
3737

3738
func configureParallelMigrationThreads(options *cmdclient.MigrationOptions, vm *v1.VirtualMachineInstance) {
1✔
3739
        // When the CPU is limited, there's a risk of the migration threads choking the CPU resources on the compute container.
1✔
3740
        // For this reason, we will avoid configuring migration threads in such scenarios.
1✔
3741
        if cpuLimit, cpuLimitExists := vm.Spec.Domain.Resources.Limits[k8sv1.ResourceCPU]; cpuLimitExists && !cpuLimit.IsZero() {
2✔
3742
                return
1✔
3743
        }
1✔
3744

3745
        options.ParallelMigrationThreads = pointer.P(parallelMultifdMigrationThreads)
1✔
3746
}
3747

3748
func isReadOnlyDisk(disk *v1.Disk) bool {
1✔
3749
        isReadOnlyCDRom := disk.CDRom != nil && (disk.CDRom.ReadOnly == nil || *disk.CDRom.ReadOnly)
1✔
3750

1✔
3751
        return isReadOnlyCDRom
1✔
3752
}
1✔
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