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

kubevirt / kubevirt / bff5c267-c90e-48aa-b810-bc814df4d7dd

13 Jun 2025 01:58AM UTC coverage: 71.125% (+0.05%) from 71.074%
bff5c267-c90e-48aa-b810-bc814df4d7dd

push

prow

web-flow
Merge pull request #13847 from mhenriks/decl-hotplug

Declarative Volume Hotplug with Inject/Eject CD-ROM Support for VirtualMachines

326 of 376 new or added lines in 11 files covered. (86.7%)

4 existing lines in 2 files now uncovered.

66092 of 92924 relevant lines covered (71.12%)

0.79 hits per line

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

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

20
package hotplug
21

22
import (
23
        "context"
24

25
        "k8s.io/apimachinery/pkg/api/equality"
26
        v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
        "k8s.io/apimachinery/pkg/types"
28

29
        virtv1 "kubevirt.io/api/core/v1"
30
        "kubevirt.io/client-go/kubecli"
31
        "kubevirt.io/client-go/log"
32

33
        "kubevirt.io/kubevirt/pkg/apimachinery/patch"
34
        storagetypes "kubevirt.io/kubevirt/pkg/storage/types"
35
)
36

37
func HandleDeclarativeVolumes(client kubecli.KubevirtClient, vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance) error {
1✔
38
        if vm.Spec.UpdateVolumesStrategy != nil && *vm.Spec.UpdateVolumesStrategy == virtv1.UpdateVolumesStrategyMigration {
2✔
39
                // Are there some cases we can proceed?
1✔
40
                return nil
1✔
41
        }
1✔
42

43
        if err := patchHotplugVolumes(client, vm, vmi); err != nil {
1✔
NEW
44
                log.Log.Object(vm).Errorf("failed to update hotplug volumes for vmi:%v", err)
×
NEW
45
                return err
×
NEW
46
        }
×
47

48
        return nil
1✔
49
}
50

51
func patchHotplugVolumes(client kubecli.KubevirtClient, vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance) error {
1✔
52
        if vmi == nil || !vmi.IsRunning() {
2✔
53
                return nil
1✔
54
        }
1✔
55

56
        newVmiVolumes := append(filterHotplugVMIVolumes(vm, vmi), getNewHotplugVMVolumes(vm, vmi)...)
1✔
57
        newVmiDisks := append(filterHotplugVMIDisks(vm, vmi, newVmiVolumes), getNewHotplugVMDisks(vm, vmi, newVmiVolumes)...)
1✔
58

1✔
59
        if equality.Semantic.DeepEqual(vmi.Spec.Volumes, newVmiVolumes) &&
1✔
60
                equality.Semantic.DeepEqual(vmi.Spec.Domain.Devices.Disks, newVmiDisks) {
2✔
61
                log.Log.Object(vm).V(3).Info("No hotplug volumes to patch")
1✔
62
                return nil
1✔
63
        }
1✔
64

65
        patchSet := patch.New(
1✔
66
                patch.WithTest("/spec/volumes", vmi.Spec.Volumes),
1✔
67
                patch.WithTest("/spec/domain/devices/disks", vmi.Spec.Domain.Devices.Disks),
1✔
68
        )
1✔
69

1✔
70
        if len(vmi.Spec.Volumes) > 0 {
2✔
71
                patchSet.AddOption(patch.WithReplace("/spec/volumes", newVmiVolumes))
1✔
72
        } else {
1✔
NEW
73
                patchSet.AddOption(patch.WithAdd("/spec/volumes", newVmiVolumes))
×
NEW
74
        }
×
75

76
        if len(vmi.Spec.Domain.Devices.Disks) > 0 {
2✔
77
                patchSet.AddOption(patch.WithReplace("/spec/domain/devices/disks", newVmiDisks))
1✔
78
        } else {
1✔
NEW
79
                patchSet.AddOption(patch.WithAdd("/spec/domain/devices/disks", newVmiDisks))
×
NEW
80
        }
×
81

82
        patchBytes, err := patchSet.GeneratePayload()
1✔
83
        if err != nil {
1✔
NEW
84
                return err
×
NEW
85
        }
×
86

87
        _, err = client.VirtualMachineInstance(vmi.Namespace).Patch(context.Background(), vmi.Name, types.JSONPatchType, patchBytes, v1.PatchOptions{})
1✔
88
        if err != nil {
1✔
NEW
89
                return err
×
NEW
90
        }
×
91

92
        return nil
1✔
93
}
94

95
func filterHotplugVMIVolumes(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance) []virtv1.Volume {
1✔
96
        var volumes []virtv1.Volume
1✔
97
        vmVolumesByName := storagetypes.GetVolumesByName(&vm.Spec.Template.Spec)
1✔
98

1✔
99
        // remove any volumes missing/changed in the VM spec
1✔
100
        for _, vmiVolume := range vmi.Spec.Volumes {
2✔
101
                if storagetypes.IsDeclarativeHotplugVolume(&vmiVolume) {
2✔
102
                        vmVolume, exists := vmVolumesByName[vmiVolume.Name]
1✔
103
                        if !exists {
2✔
104
                                // volume not in VM spec, remove it
1✔
105
                                log.Log.Object(vm).Infof("Removing hotplug volume %s from VMI, no longer in VM", vmiVolume.Name)
1✔
106
                                continue
1✔
107
                        }
108

109
                        // volume changed in VM spec - remove it to be re-added with new values later
110
                        if storagetypes.IsDeclarativeHotplugVolume(vmVolume) && !equality.Semantic.DeepEqual(vmVolume, &vmiVolume) {
2✔
111
                                log.Log.Object(vm).Infof("Removing hotplug volume %s from VMI, volume changed", vmiVolume.Name)
1✔
112
                                continue
1✔
113
                        }
114
                }
115

116
                volumes = append(volumes, *vmiVolume.DeepCopy())
1✔
117
        }
118

119
        return volumes
1✔
120
}
121

122
func getNewHotplugVMVolumes(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance) []virtv1.Volume {
1✔
123
        var volumes []virtv1.Volume
1✔
124
        vmiVolumesByName := storagetypes.GetVolumesByName(&vmi.Spec)
1✔
125

1✔
126
        var volumesWithStatus = make(map[string]struct{})
1✔
127
        for _, vs := range vmi.Status.VolumeStatus {
2✔
128
                volumesWithStatus[vs.Name] = struct{}{}
1✔
129
        }
1✔
130

131
        for _, vmVolume := range vm.Spec.Template.Spec.Volumes {
2✔
132
                if storagetypes.IsDeclarativeHotplugVolume(&vmVolume) {
2✔
133
                        _, vmiVolumeExists := vmiVolumesByName[vmVolume.Name]
1✔
134

1✔
135
                        // vmi will report status on volume after removed from spec
1✔
136
                        // if in process of hot unplugging
1✔
137
                        _, vmiVolumeHasStatus := volumesWithStatus[vmVolume.Name]
1✔
138

1✔
139
                        if !vmiVolumeExists && !vmiVolumeHasStatus {
2✔
140
                                log.Log.Object(vm).Infof("Adding hotplug volume %s to VMI", vmVolume.Name)
1✔
141
                                volumes = append(volumes, *vmVolume.DeepCopy())
1✔
142
                        }
1✔
143
                }
144
        }
145

146
        return volumes
1✔
147
}
148

149
func volumesByName(volumes []virtv1.Volume) map[string]*virtv1.Volume {
1✔
150
        volumeMap := make(map[string]*virtv1.Volume)
1✔
151
        for _, v := range volumes {
2✔
152
                volumeMap[v.Name] = v.DeepCopy()
1✔
153
        }
1✔
154
        return volumeMap
1✔
155
}
156

157
func filterHotplugVMIDisks(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance, vmiNewVolumes []virtv1.Volume) []virtv1.Disk {
1✔
158
        var disks []virtv1.Disk
1✔
159
        vmiNewVolumesByName := volumesByName(vmiNewVolumes)
1✔
160
        vmDisksByName := storagetypes.GetDisksByName(&vm.Spec.Template.Spec)
1✔
161
        vmVolumesByName := storagetypes.GetVolumesByName(&vm.Spec.Template.Spec)
1✔
162

1✔
163
        for _, vmiDisk := range vmi.Spec.Domain.Devices.Disks {
2✔
164
                _, vmiVolumeExists := vmiNewVolumesByName[vmiDisk.Name]
1✔
165

1✔
166
                if !vmiVolumeExists {
2✔
167
                        vmDisk, vmDiskExists := vmDisksByName[vmiDisk.Name]
1✔
168
                        _, vmVolumeExists := vmVolumesByName[vmiDisk.Name]
1✔
169
                        vmiIsCDRom := vmiDisk.CDRom != nil
1✔
170
                        vmIsCDRom := vmDiskExists && vmDisk.CDRom != nil
1✔
171

1✔
172
                        // disk and volume are gone
1✔
173
                        if !vmDiskExists {
2✔
174
                                continue
1✔
175
                        }
176

177
                        // volume changed, remove if not CD-ROM
178
                        if vmVolumeExists && (!vmIsCDRom || !vmiIsCDRom) {
2✔
179
                                continue
1✔
180
                        }
181
                }
182

183
                disks = append(disks, *vmiDisk.DeepCopy())
1✔
184
        }
185

186
        return disks
1✔
187
}
188

189
func getNewHotplugVMDisks(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance, vmiNewVolumes []virtv1.Volume) []virtv1.Disk {
1✔
190
        var disks []virtv1.Disk
1✔
191
        vmiNewVolumesByName := volumesByName(vmiNewVolumes)
1✔
192
        vmiDisksByName := storagetypes.GetDisksByName(&vmi.Spec)
1✔
193

1✔
194
        for _, vmDisk := range vm.Spec.Template.Spec.Domain.Devices.Disks {
2✔
195
                vmVolume, vmVolumeExists := vmiNewVolumesByName[vmDisk.Name]
1✔
196
                _, vmiDiskExists := vmiDisksByName[vmDisk.Name]
1✔
197

1✔
198
                if vmVolumeExists && storagetypes.IsDeclarativeHotplugVolume(vmVolume) && !vmiDiskExists {
2✔
199
                        log.Log.Object(vm).Infof("Adding hotplug disk %s to VMI", vmDisk.Name)
1✔
200
                        disks = append(disks, *vmDisk.DeepCopy())
1✔
201
                }
1✔
202
        }
203

204
        return disks
1✔
205
}
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