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

kubevirt / kubevirt / 439f3882-b2a9-4f75-8aca-8b8715be636b

19 Jun 2025 03:58AM UTC coverage: 70.705% (-0.5%) from 71.186%
439f3882-b2a9-4f75-8aca-8b8715be636b

push

prow

web-flow
Merge pull request #14705 from jean-edouard/update_13792

Fix migration completion logic (along with other bugs)

1113 of 1994 new or added lines in 13 files covered. (55.82%)

251 existing lines in 10 files now uncovered.

66217 of 93652 relevant lines covered (70.71%)

0.79 hits per line

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

66.45
/pkg/virt-handler/controller.go
1
/*
2
 * This file is part of the KubeVirt project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 *
16
 * Copyright 2025 The KubeVirt Authors.
17
 *
18
 */
19

20
package virthandler
21

22
import (
23
        "fmt"
24
        "path/filepath"
25

26
        "k8s.io/apimachinery/pkg/types"
27
        "k8s.io/client-go/tools/cache"
28
        "k8s.io/client-go/tools/record"
29

30
        v1 "kubevirt.io/api/core/v1"
31

32
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
33
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
34
        "kubevirt.io/kubevirt/pkg/safepath"
35
        "kubevirt.io/kubevirt/pkg/util"
36
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
37
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
38
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
39
        "kubevirt.io/kubevirt/pkg/virtiofs"
40
)
41

42
const (
43
        failedDetectIsolationFmt              = "failed to detect isolation for launcher pod: %v"
44
        unableCreateVirtLauncherConnectionFmt = "unable to create virt-launcher client connection: %v"
45
        // This value was determined after consulting with libvirt developers and performing extensive testing.
46
        parallelMultifdMigrationThreads = uint(8)
47
)
48

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

79
        // MemoryHotplugFailedReason is the reason set when the VM cannot hotplug memory
80
        memoryHotplugFailedReason = "Memory Hotplug Failed"
81
)
82

83
type netconf interface {
84
        Setup(vmi *v1.VirtualMachineInstance, networks []v1.Network, launcherPid int) error
85
        Teardown(vmi *v1.VirtualMachineInstance) error
86
}
87

88
type BaseController struct {
89
        host                 string
90
        vmiStore             cache.Store
91
        domainStore          cache.Store
92
        clusterConfig        *virtconfig.ClusterConfig
93
        podIsolationDetector isolation.PodIsolationDetector
94
        hasSynced            func() bool
95
}
96

97
func NewBaseController(
98
        host string,
99
        vmiInformer cache.SharedIndexInformer,
100
        domainInformer cache.SharedInformer,
101
        clusterConfig *virtconfig.ClusterConfig,
102
        podIsolationDetector isolation.PodIsolationDetector,
103
) (*BaseController, error) {
1✔
104

1✔
105
        c := &BaseController{
1✔
106
                host:                 host,
1✔
107
                vmiStore:             vmiInformer.GetStore(),
1✔
108
                domainStore:          domainInformer.GetStore(),
1✔
109
                clusterConfig:        clusterConfig,
1✔
110
                podIsolationDetector: podIsolationDetector,
1✔
111
                hasSynced:            func() bool { return domainInformer.HasSynced() && vmiInformer.HasSynced() },
1✔
112
        }
113

114
        return c, nil
1✔
115
}
116

117
func (c *BaseController) getVMIFromCache(key string) (vmi *v1.VirtualMachineInstance, exists bool, err error) {
1✔
118
        obj, exists, err := c.vmiStore.GetByKey(key)
1✔
119
        if err != nil {
1✔
NEW
120
                return nil, false, err
×
NEW
121
        }
×
122

123
        if !exists {
2✔
124
                namespace, name, err := cache.SplitMetaNamespaceKey(key)
1✔
125
                if err != nil {
2✔
126
                        // Invalid keys will be retried forever, but the queue has no reason to contain any
1✔
127
                        return nil, false, err
1✔
128
                }
1✔
129
                vmi = v1.NewVMIReferenceFromNameWithNS(namespace, name)
1✔
130
        } else {
1✔
131
                vmi = obj.(*v1.VirtualMachineInstance)
1✔
132
        }
1✔
133
        return vmi, exists, nil
1✔
134
}
135

136
func (c *BaseController) getDomainFromCache(key string) (domain *api.Domain, exists bool, cachedUID types.UID, err error) {
1✔
137

1✔
138
        obj, exists, err := c.domainStore.GetByKey(key)
1✔
139

1✔
140
        if err != nil {
1✔
NEW
141
                return nil, false, "", err
×
NEW
142
        }
×
143

144
        if exists {
2✔
145
                domain = obj.(*api.Domain)
1✔
146
                cachedUID = domain.Spec.Metadata.KubeVirt.UID
1✔
147

1✔
148
                // We're using the DeletionTimestamp to signify that the
1✔
149
                // Domain is deleted rather than sending the DELETE watch event.
1✔
150
                if domain.ObjectMeta.DeletionTimestamp != nil {
1✔
NEW
151
                        exists = false
×
NEW
152
                        domain = nil
×
NEW
153
                }
×
154
        }
155
        return domain, exists, cachedUID, nil
1✔
156
}
157

NEW
158
func (c *BaseController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
×
NEW
159
        return vmi.Status.MigrationState != nil &&
×
NEW
160
                vmi.Status.MigrationState.SourceNode == c.host &&
×
NEW
161
                vmi.Status.MigrationState.TargetNodeAddress != "" &&
×
NEW
162
                !vmi.Status.MigrationState.Completed
×
NEW
163
}
×
164

165
func (c *BaseController) claimDeviceOwnership(virtLauncherRootMount *safepath.Path, deviceName string) error {
1✔
166
        softwareEmulation := c.clusterConfig.AllowEmulation()
1✔
167
        devicePath, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", deviceName))
1✔
168
        if err != nil {
2✔
169
                if softwareEmulation && deviceName == "kvm" {
2✔
170
                        return nil
1✔
171
                }
1✔
172
                return err
1✔
173
        }
174

175
        return diskutils.DefaultOwnershipManager.SetFileOwnership(devicePath)
1✔
176
}
177

178
func (c *BaseController) configureHostDisks(
179
        vmi *v1.VirtualMachineInstance,
180
        virtLauncherRootMount *safepath.Path,
181
        recorder record.EventRecorder) error {
1✔
182
        lessPVCSpaceToleration := c.clusterConfig.GetLessPVCSpaceToleration()
1✔
183
        minimumPVCReserveBytes := c.clusterConfig.GetMinimumReservePVCBytes()
1✔
184

1✔
185
        hostDiskCreator := hostdisk.NewHostDiskCreator(recorder, lessPVCSpaceToleration, minimumPVCReserveBytes, virtLauncherRootMount)
1✔
186
        if err := hostDiskCreator.Create(vmi); err != nil {
1✔
NEW
187
                return fmt.Errorf("preparing host-disks failed: %v", err)
×
NEW
188
        }
×
189
        return nil
1✔
190
}
191

192
func (c *BaseController) configureSEVDeviceOwnership(vmi *v1.VirtualMachineInstance, virtLauncherRootMount *safepath.Path) error {
1✔
193
        if util.IsSEVVMI(vmi) {
1✔
NEW
194
                sevDevice, err := safepath.JoinNoFollow(virtLauncherRootMount, filepath.Join("dev", "sev"))
×
NEW
195
                if err != nil {
×
NEW
196
                        return err
×
NEW
197
                }
×
NEW
198
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(sevDevice); err != nil {
×
NEW
199
                        return fmt.Errorf("failed to set SEV device owner: %v", err)
×
NEW
200
                }
×
201
        }
202
        return nil
1✔
203
}
204

205
func (c *BaseController) configureVirtioFS(vmi *v1.VirtualMachineInstance, isolationRes isolation.IsolationResult) error {
1✔
206
        for _, fs := range vmi.Spec.Domain.Devices.Filesystems {
1✔
NEW
207
                socketPath, err := isolation.SafeJoin(isolationRes, virtiofs.VirtioFSSocketPath(fs.Name))
×
NEW
208
                if err != nil {
×
NEW
209
                        return err
×
NEW
210
                }
×
NEW
211
                if err := diskutils.DefaultOwnershipManager.SetFileOwnership(socketPath); err != nil {
×
NEW
212
                        return err
×
NEW
213
                }
×
214
        }
215
        return nil
1✔
216
}
217

218
func (c *BaseController) setupDevicesOwnerships(vmi *v1.VirtualMachineInstance, recorder record.EventRecorder) error {
1✔
219
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
220
        if err != nil {
1✔
NEW
221
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
NEW
222
        }
×
223

224
        virtLauncherRootMount, err := isolationRes.MountRoot()
1✔
225
        if err != nil {
1✔
NEW
226
                return err
×
NEW
227
        }
×
228

229
        err = c.claimDeviceOwnership(virtLauncherRootMount, "kvm")
1✔
230
        if err != nil {
1✔
NEW
231
                return fmt.Errorf("failed to set up file ownership for /dev/kvm: %v", err)
×
NEW
232
        }
×
233

234
        if util.IsAutoAttachVSOCK(vmi) {
1✔
NEW
235
                if err := c.claimDeviceOwnership(virtLauncherRootMount, "vhost-vsock"); err != nil {
×
NEW
236
                        return fmt.Errorf("failed to set up file ownership for /dev/vhost-vsock: %v", err)
×
NEW
237
                }
×
238
        }
239

240
        if err := c.configureHostDisks(vmi, virtLauncherRootMount, recorder); err != nil {
1✔
NEW
241
                return err
×
NEW
242
        }
×
243

244
        if err := c.configureSEVDeviceOwnership(vmi, virtLauncherRootMount); err != nil {
1✔
NEW
245
                return err
×
NEW
246
        }
×
247

248
        if util.IsNonRootVMI(vmi) {
1✔
NEW
249
                if err := c.nonRootSetup(vmi); err != nil {
×
NEW
250
                        return err
×
NEW
251
                }
×
252
        }
253

254
        if err := c.configureVirtioFS(vmi, isolationRes); err != nil {
1✔
NEW
255
                return err
×
NEW
256
        }
×
257

258
        return nil
1✔
259
}
260

261
func (c *BaseController) setupNetwork(vmi *v1.VirtualMachineInstance, networks []v1.Network, netConf netconf) error {
1✔
262
        if len(networks) == 0 {
2✔
263
                return nil
1✔
264
        }
1✔
265

266
        isolationRes, err := c.podIsolationDetector.Detect(vmi)
1✔
267
        if err != nil {
1✔
NEW
268
                return fmt.Errorf(failedDetectIsolationFmt, err)
×
NEW
269
        }
×
270

271
        return netConf.Setup(vmi, networks, isolationRes.Pid())
1✔
272
}
273

274
func isMigrationInProgress(vmi *v1.VirtualMachineInstance, domain *api.Domain) bool {
1✔
275
        var domainMigrationMetadata *api.MigrationMetadata
1✔
276

1✔
277
        if vmi != nil &&
1✔
278
                vmi.Status.MigrationState != nil &&
1✔
279
                vmi.Status.MigrationState.StartTimestamp != nil &&
1✔
280
                vmi.Status.MigrationState.EndTimestamp == nil {
2✔
281
                return true
1✔
282
        }
1✔
283

284
        if domain != nil {
2✔
285
                domainMigrationMetadata = domain.Spec.Metadata.KubeVirt.Migration
1✔
286

1✔
287
                if domainMigrationMetadata != nil &&
1✔
288
                        domainMigrationMetadata.StartTimestamp != nil &&
1✔
289
                        domainMigrationMetadata.EndTimestamp == nil {
1✔
NEW
290
                        return true
×
NEW
291
                }
×
292

293
                if domain.Status.Status == api.Paused &&
1✔
294
                        (domain.Status.Reason == api.ReasonPausedMigration ||
1✔
295
                                domain.Status.Reason == api.ReasonPausedStartingUp ||
1✔
296
                                domain.Status.Reason == api.ReasonPausedPostcopy) {
2✔
297
                        return true
1✔
298
                }
1✔
299
        }
300

301
        return false
1✔
302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc