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

kubevirt / kubevirt / d81f3799-2d0d-4f91-b833-b0d10a101bab

16 Nov 2025 03:02PM UTC coverage: 70.444% (+0.05%) from 70.392%
d81f3799-2d0d-4f91-b833-b0d10a101bab

push

prow

web-flow
Merge pull request #15922 from ShellyKa13/utility-volumes

VEP 90: Add new Utility volumes type in VMI spec

384 of 440 new or added lines in 14 files covered. (87.27%)

23 existing lines in 5 files now uncovered.

70115 of 99533 relevant lines covered (70.44%)

434.66 hits per line

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

55.16
/pkg/virt-handler/hotplug-disk/mount.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_volume
21

22
import (
23
        "errors"
24
        "fmt"
25
        "os"
26
        "path/filepath"
27
        "sync"
28
        "syscall"
29

30
        "kubevirt.io/kubevirt/pkg/checkpoint"
31
        "kubevirt.io/kubevirt/pkg/unsafepath"
32

33
        "golang.org/x/sys/unix"
34

35
        "kubevirt.io/kubevirt/pkg/safepath"
36
        virt_chroot "kubevirt.io/kubevirt/pkg/virt-handler/virt-chroot"
37

38
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
39
        hotplugdisk "kubevirt.io/kubevirt/pkg/hotplug-disk"
40
        storagetypes "kubevirt.io/kubevirt/pkg/storage/types"
41
        "kubevirt.io/kubevirt/pkg/virt-handler/cgroup"
42
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
43

44
        "github.com/opencontainers/runc/libcontainer/configs"
45

46
        "github.com/opencontainers/runc/libcontainer/devices"
47
        "k8s.io/apimachinery/pkg/types"
48

49
        v1 "kubevirt.io/api/core/v1"
50
        "kubevirt.io/client-go/log"
51
)
52

53
//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_$GOFILE
54

55
const (
56
        unableFindHotplugMountedDir = "unable to find hotplug mounted directories for vmi without uid"
57
)
58

59
var (
60
        nodeIsolationResult = func() isolation.IsolationResult {
×
61
                return isolation.NodeIsolationResult()
×
62
        }
×
63
        deviceBasePath = func(podUID types.UID, kubeletPodsDir string) (*safepath.Path, error) {
×
64
                return safepath.JoinAndResolveWithRelativeRoot("/proc/1/root", kubeletPodsDir, fmt.Sprintf("/%s/volumes/kubernetes.io~empty-dir/hotplug-disks", string(podUID)))
×
65
        }
×
66

67
        socketPath = func(podUID types.UID) string {
9✔
68
                return fmt.Sprintf("/pods/%s/volumes/kubernetes.io~empty-dir/hotplug-disks/hp.sock", string(podUID))
9✔
69
        }
9✔
70

71
        statDevice = func(fileName *safepath.Path) (os.FileInfo, error) {
×
72
                info, err := safepath.StatAtNoFollow(fileName)
×
73
                if err != nil {
×
74
                        return nil, err
×
75
                }
×
76
                if info.Mode()&os.ModeDevice == 0 {
×
77
                        return info, fmt.Errorf("%v is not a block device", fileName)
×
78
                }
×
79
                return info, nil
×
80
        }
81

82
        statSourceDevice = func(fileName *safepath.Path) (os.FileInfo, error) {
×
83
                // we don't know the device name, we only know that it is the only
×
84
                // device in a specific directory, let's look it up
×
85
                var devName string
×
86
                err := fileName.ExecuteNoFollow(func(safePath string) error {
×
87
                        entries, err := os.ReadDir(safePath)
×
88
                        if err != nil {
×
89
                                return err
×
90
                        }
×
91
                        for _, entry := range entries {
×
92
                                info, err := entry.Info()
×
93
                                if err != nil {
×
94
                                        return err
×
95
                                }
×
96
                                if info.Mode()&os.ModeDevice == 0 {
×
97
                                        // not a device
×
98
                                        continue
×
99
                                }
100
                                devName = entry.Name()
×
101
                                return nil
×
102
                        }
103
                        return fmt.Errorf("no device in %v", fileName)
×
104
                })
105
                if err != nil {
×
106
                        return nil, err
×
107
                }
×
108
                devPath, err := safepath.JoinNoFollow(fileName, devName)
×
109
                if err != nil {
×
110
                        return nil, err
×
111
                }
×
112
                return statDevice(devPath)
×
113
        }
114

115
        mknodCommand = func(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
×
116
                return safepath.MknodAtNoFollow(basePath, deviceName, blockDevicePermissions|syscall.S_IFBLK, dev)
×
117
        }
×
118

119
        mountCommand = func(sourcePath, targetPath *safepath.Path) ([]byte, error) {
×
120
                return virt_chroot.MountChroot(sourcePath, targetPath, false).CombinedOutput()
×
121
        }
×
122

123
        unmountCommand = func(diskPath *safepath.Path) ([]byte, error) {
×
124
                return virt_chroot.UmountChroot(diskPath).CombinedOutput()
×
125
        }
×
126

127
        isMounted = func(path *safepath.Path) (bool, error) {
5✔
128
                return isolation.IsMounted(path)
5✔
129
        }
5✔
130

131
        isBlockDevice = func(path *safepath.Path) (bool, error) {
3✔
132
                return isolation.IsBlockDevice(path)
3✔
133
        }
3✔
134

135
        isolationDetector = func() isolation.PodIsolationDetector {
×
136
                return isolation.NewSocketBasedIsolationDetector()
×
137
        }
×
138

139
        parentPathForMount = func(
140
                parent isolation.IsolationResult,
141
                child isolation.IsolationResult,
142
                findmntInfo FindmntInfo,
143
        ) (*safepath.Path, error) {
×
144
                return isolation.ParentPathForMount(parent, child, findmntInfo.Source, findmntInfo.Target)
×
145
        }
×
146
)
147

148
type volumeMounter struct {
149
        checkpointManager  checkpoint.CheckpointManager
150
        mountRecords       map[types.UID]*vmiMountTargetRecord
151
        mountRecordsLock   sync.Mutex
152
        skipSafetyCheck    bool
153
        hotplugDiskManager hotplugdisk.HotplugDiskManagerInterface
154
        ownershipManager   diskutils.OwnershipManagerInterface
155
        kubeletPodsDir     string
156
        host               string
157
}
158

159
// VolumeMounter is the interface used to mount and unmount volumes to/from a running virtlauncher pod.
160
type VolumeMounter interface {
161
        // Mount any new volumes defined in the VMI
162
        Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
163
        MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error
164
        // Unmount any volumes no longer defined in the VMI
165
        Unmount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
166
        //UnmountAll cleans up all hotplug volumes
167
        UnmountAll(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
168
        //IsMounted returns if the volume is mounted or not.
169
        IsMounted(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID) (bool, error)
170
}
171

172
type vmiMountTargetEntry struct {
173
        TargetFile string `json:"targetFile"`
174
}
175

176
type vmiMountTargetRecord struct {
177
        MountTargetEntries []vmiMountTargetEntry `json:"mountTargetEntries"`
178
        UsesSafePaths      bool                  `json:"usesSafePaths"`
179
}
180

181
// NewVolumeMounter creates a new VolumeMounter
182
func NewVolumeMounter(mountStateDir string, kubeletPodsDir string, host string) VolumeMounter {
124✔
183
        return &volumeMounter{
124✔
184
                mountRecords:       make(map[types.UID]*vmiMountTargetRecord),
124✔
185
                checkpointManager:  checkpoint.NewSimpleCheckpointManager(mountStateDir),
124✔
186
                hotplugDiskManager: hotplugdisk.NewHotplugDiskManager(kubeletPodsDir),
124✔
187
                ownershipManager:   diskutils.DefaultOwnershipManager,
124✔
188
                kubeletPodsDir:     kubeletPodsDir,
124✔
189
                host:               host,
124✔
190
        }
124✔
191
}
124✔
192

193
func (m *volumeMounter) deleteMountTargetRecord(vmi *v1.VirtualMachineInstance) error {
4✔
194
        if string(vmi.UID) == "" {
4✔
195
                return fmt.Errorf(unableFindHotplugMountedDir)
×
196
        }
×
197

198
        record := vmiMountTargetRecord{}
4✔
199
        err := m.checkpointManager.Get(string(vmi.UID), &record)
4✔
200
        if err != nil && !errors.Is(err, os.ErrNotExist) {
4✔
201
                return fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
202
        }
×
203

204
        if err == nil {
8✔
205
                for _, target := range record.MountTargetEntries {
9✔
206
                        os.Remove(target.TargetFile)
5✔
207
                }
5✔
208

209
                if err := m.checkpointManager.Delete(string(vmi.UID)); err != nil {
4✔
210
                        return fmt.Errorf("failed to delete checkpoint %s, %w", vmi.UID, err)
×
211
                }
×
212
        }
213

214
        m.mountRecordsLock.Lock()
4✔
215
        defer m.mountRecordsLock.Unlock()
4✔
216
        delete(m.mountRecords, vmi.UID)
4✔
217

4✔
218
        return nil
4✔
219
}
220

221
func (m *volumeMounter) getMountTargetRecord(vmi *v1.VirtualMachineInstance) (*vmiMountTargetRecord, error) {
12✔
222
        var ok bool
12✔
223
        var existingRecord *vmiMountTargetRecord
12✔
224

12✔
225
        if string(vmi.UID) == "" {
13✔
226
                return nil, fmt.Errorf(unableFindHotplugMountedDir)
1✔
227
        }
1✔
228

229
        m.mountRecordsLock.Lock()
11✔
230
        defer m.mountRecordsLock.Unlock()
11✔
231
        existingRecord, ok = m.mountRecords[vmi.UID]
11✔
232

11✔
233
        // first check memory cache
11✔
234
        if ok {
16✔
235
                return existingRecord, nil
5✔
236
        }
5✔
237

238
        // if not there, see if record is on disk, this can happen if virt-handler restarts
239
        record := vmiMountTargetRecord{}
6✔
240
        err := m.checkpointManager.Get(string(vmi.UID), &record)
6✔
241
        if err != nil && !errors.Is(err, os.ErrNotExist) {
6✔
242
                return nil, fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
243
        }
×
244

245
        if err == nil {
6✔
246
                // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
×
247
                // After a one-time convert and persist, old records are safe too.
×
248
                if !record.UsesSafePaths {
×
249
                        for i, path := range record.MountTargetEntries {
×
250
                                record.UsesSafePaths = true
×
251
                                safePath, err := safepath.JoinAndResolveWithRelativeRoot("/", path.TargetFile)
×
252
                                if err != nil {
×
253
                                        return nil, fmt.Errorf("failed converting legacy path to safepath: %v", err)
×
254
                                }
×
255
                                record.MountTargetEntries[i].TargetFile = unsafepath.UnsafeAbsolute(safePath.Raw())
×
256
                        }
257
                }
258

259
                m.mountRecords[vmi.UID] = &record
×
260
                return &record, nil
×
261
        }
262

263
        // not found
264
        return &vmiMountTargetRecord{UsesSafePaths: true}, nil
6✔
265
}
266

267
func (m *volumeMounter) setMountTargetRecord(vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
13✔
268
        if string(vmi.UID) == "" {
14✔
269
                return fmt.Errorf(unableFindHotplugMountedDir)
1✔
270
        }
1✔
271

272
        // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
273
        // After a one-time convert and persist, old records are safe too.
274
        record.UsesSafePaths = true
12✔
275

12✔
276
        m.mountRecordsLock.Lock()
12✔
277
        defer m.mountRecordsLock.Unlock()
12✔
278

12✔
279
        if err := m.checkpointManager.Store(string(vmi.UID), record); err != nil {
12✔
280
                return fmt.Errorf("failed to checkpoint %s, %w", vmi.UID, err)
×
281
        }
×
282
        m.mountRecords[vmi.UID] = record
12✔
283
        return nil
12✔
284
}
285

286
func (m *volumeMounter) writePathToMountRecord(path string, vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
5✔
287
        record.MountTargetEntries = append(record.MountTargetEntries, vmiMountTargetEntry{
5✔
288
                TargetFile: path,
5✔
289
        })
5✔
290
        if err := m.setMountTargetRecord(vmi, record); err != nil {
5✔
291
                return err
×
292
        }
×
293
        return nil
5✔
294
}
295

296
func (m *volumeMounter) mountHotplugVolume(
297
        vmi *v1.VirtualMachineInstance,
298
        volumeName string,
299
        sourceUID types.UID,
300
        record *vmiMountTargetRecord,
301
        mountDirectory bool,
302
        cgroupManager cgroup.Manager,
303
) error {
4✔
304
        logger := log.DefaultLogger()
4✔
305
        logger.V(4).Infof("Hotplug check volume name: %s", volumeName)
4✔
306
        if sourceUID != "" {
8✔
307
                if m.isBlockVolume(&vmi.Status, volumeName) {
6✔
308
                        logger.V(3).Infof("Mounting block volume: %s", volumeName)
2✔
309
                        if err := m.mountBlockHotplugVolume(vmi, volumeName, sourceUID, record, cgroupManager); err != nil {
2✔
310
                                return fmt.Errorf("failed to mount block hotplug volume %s: %w", volumeName, err)
×
311
                        }
×
312
                } else {
2✔
313
                        logger.V(3).Infof("Mounting file system volume: %s", volumeName)
2✔
314
                        if err := m.mountFileSystemHotplugVolume(vmi, volumeName, sourceUID, record, mountDirectory); err != nil {
2✔
315
                                return fmt.Errorf("failed to mount filesystem hotplug volume %s: %w", volumeName, err)
×
316
                        }
×
317
                }
318
        }
319
        return nil
4✔
320
}
321

322
func (m *volumeMounter) Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
3✔
323
        return m.mountFromPod(vmi, "", cgroupManager)
3✔
324
}
3✔
325

326
func (m *volumeMounter) MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
×
327
        return m.mountFromPod(vmi, sourceUID, cgroupManager)
×
328
}
×
329

330
func (m *volumeMounter) mountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
4✔
331
        record, err := m.getMountTargetRecord(vmi)
4✔
332
        if err != nil {
4✔
333
                return err
×
334
        }
×
335

336
        for _, volumeStatus := range vmi.Status.VolumeStatus {
12✔
337
                if volumeStatus.HotplugVolume == nil {
11✔
338
                        // Skip non hotplug volumes
3✔
339
                        continue
3✔
340
                }
341

342
                if storagetypes.IsUtilityVolume(vmi, volumeStatus.Name) && m.isBlockVolume(&vmi.Status, volumeStatus.Name) {
6✔
343
                        log.Log.Object(vmi).Warningf("Skipping mount for utility volume %s: configured with block mode PVC, utility volumes require filesystem mode", volumeStatus.Name)
1✔
344
                        continue
1✔
345
                }
346

347
                mountDirectory := m.isDirectoryMounted(vmi, volumeStatus.Name)
4✔
348
                if sourceUID == "" {
6✔
349
                        sourceUID = volumeStatus.HotplugVolume.AttachPodUID
2✔
350
                }
2✔
351
                if err := m.mountHotplugVolume(vmi, volumeStatus.Name, sourceUID, record, mountDirectory, cgroupManager); err != nil {
4✔
352
                        return err
×
353
                }
×
354
        }
355
        return nil
4✔
356
}
357

358
func (m *volumeMounter) isDirectoryMounted(vmi *v1.VirtualMachineInstance, volumeName string) bool {
5✔
359
        for _, utilityVolume := range vmi.Spec.UtilityVolumes {
5✔
NEW
360
                if utilityVolume.Name == volumeName {
×
NEW
361
                        return true
×
NEW
362
                }
×
363
        }
364
        for _, volume := range vmi.Spec.Volumes {
7✔
365
                if volume.Name == volumeName {
3✔
366
                        return volume.MemoryDump != nil
1✔
367
                }
1✔
368
        }
369
        return false
4✔
370
}
371

372
// isBlockVolume checks if the volumeDevices directory exists in the pod path, we assume there is a single volume associated with
373
// each pod, we use this knowledge to determine if we have a block volume or not.
374
func (m *volumeMounter) isBlockVolume(vmiStatus *v1.VirtualMachineInstanceStatus, volumeName string) bool {
11✔
375
        // First evaluate the migrated volumed. In the case of a migrated volume, virt-handler needs to understand if it needs to consider the
11✔
376
        // volume mode of the source or the destination. Therefore, it evaluates if its is running on the destiation host or otherwise, it is the source.
11✔
377
        isDstHost := false
11✔
378
        if vmiStatus.MigrationState != nil {
11✔
379
                isDstHost = vmiStatus.MigrationState.TargetNode == m.host
×
380
        }
×
381
        for _, migVol := range vmiStatus.MigratedVolumes {
11✔
382
                if migVol.VolumeName == volumeName {
×
383
                        if isDstHost && migVol.DestinationPVCInfo != nil {
×
384
                                return storagetypes.IsPVCBlock(migVol.DestinationPVCInfo.VolumeMode)
×
385
                        }
×
386
                        if !isDstHost && migVol.SourcePVCInfo != nil {
×
387
                                return storagetypes.IsPVCBlock(migVol.SourcePVCInfo.VolumeMode)
×
388
                        }
×
389
                }
390
        }
391
        // Check if the volumeDevices directory exists in the attachment pod, if so, its a block device, otherwise its file system.
392
        for _, status := range vmiStatus.VolumeStatus {
28✔
393
                if status.Name == volumeName {
27✔
394
                        return status.PersistentVolumeClaimInfo != nil && storagetypes.IsPVCBlock(status.PersistentVolumeClaimInfo.VolumeMode)
10✔
395
                }
10✔
396
        }
397
        return false
1✔
398
}
399

400
func (m *volumeMounter) mountBlockHotplugVolume(
401
        vmi *v1.VirtualMachineInstance,
402
        volume string,
403
        sourceUID types.UID,
404
        record *vmiMountTargetRecord,
405
        cgroupManager cgroup.Manager,
406
) error {
3✔
407
        virtlauncherUID := m.findVirtlauncherUID(vmi)
3✔
408
        if virtlauncherUID == "" {
3✔
409
                // This is not the node the pod is running on.
×
410
                return nil
×
411
        }
×
412
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
3✔
413
        if err != nil {
3✔
414
                return err
×
415
        }
×
416

417
        if _, err := safepath.JoinNoFollow(targetPath, volume); errors.Is(err, os.ErrNotExist) {
5✔
418
                dev, permissions, err := m.getSourceMajorMinor(sourceUID, volume)
2✔
419
                if err != nil {
2✔
420
                        return err
×
421
                }
×
422

423
                if err := m.writePathToMountRecord(filepath.Join(unsafepath.UnsafeAbsolute(targetPath.Raw()), volume), vmi, record); err != nil {
2✔
424
                        return err
×
425
                }
×
426

427
                if err := m.createBlockDeviceFile(targetPath, volume, dev, permissions); err != nil && !os.IsExist(err) {
2✔
428
                        return err
×
429
                }
×
430
                log.DefaultLogger().V(1).Infof("successfully created block device %v", volume)
2✔
431
        } else if err != nil {
1✔
432
                return err
×
433
        }
×
434

435
        devicePath, err := safepath.JoinNoFollow(targetPath, volume)
3✔
436
        if err != nil {
3✔
437
                return err
×
438
        }
×
439
        if isBlockExists, err := isBlockDevice(devicePath); err != nil {
3✔
440
                return err
×
441
        } else if !isBlockExists {
3✔
442
                return fmt.Errorf("target device %v exists but it is not a block device", devicePath)
×
443
        }
×
444

445
        dev, _, err := m.getBlockFileMajorMinor(devicePath, statDevice)
3✔
446
        if err != nil {
3✔
447
                return err
×
448
        }
×
449
        // allow block devices
450
        if err := m.allowBlockMajorMinor(dev, cgroupManager); err != nil {
3✔
451
                return err
×
452
        }
×
453

454
        return m.ownershipManager.SetFileOwnership(devicePath)
3✔
455
}
456

457
func (m *volumeMounter) getSourceMajorMinor(sourceUID types.UID, volumeName string) (uint64, os.FileMode, error) {
5✔
458
        basePath, err := deviceBasePath(sourceUID, m.kubeletPodsDir)
5✔
459
        if err != nil {
5✔
460
                return 0, 0, err
×
461
        }
×
462
        devicePath, err := basePath.AppendAndResolveWithRelativeRoot(volumeName)
5✔
463
        if err != nil {
7✔
464
                return 0, 0, err
2✔
465
        }
2✔
466
        return m.getBlockFileMajorMinor(devicePath, statSourceDevice)
3✔
467
}
468

469
func (m *volumeMounter) getBlockFileMajorMinor(devicePath *safepath.Path, getter func(fileName *safepath.Path) (os.FileInfo, error)) (uint64, os.FileMode, error) {
13✔
470
        fileInfo, err := getter(devicePath)
13✔
471
        if err != nil {
14✔
472
                return 0, 0, err
1✔
473
        }
1✔
474
        info := fileInfo.Sys().(*syscall.Stat_t)
12✔
475
        return info.Rdev, fileInfo.Mode(), nil
12✔
476
}
477

478
func (m *volumeMounter) removeBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
5✔
479
        return m.updateBlockMajorMinor(dev, false, cgroupManager)
5✔
480
}
5✔
481

482
func (m *volumeMounter) allowBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
4✔
483
        return m.updateBlockMajorMinor(dev, true, cgroupManager)
4✔
484
}
4✔
485

486
func (m *volumeMounter) updateBlockMajorMinor(dev uint64, allow bool, cgroupManager cgroup.Manager) error {
9✔
487
        deviceRule := &devices.Rule{
9✔
488
                Type:        devices.BlockDevice,
9✔
489
                Major:       int64(unix.Major(dev)),
9✔
490
                Minor:       int64(unix.Minor(dev)),
9✔
491
                Permissions: "rwm",
9✔
492
                Allow:       allow,
9✔
493
        }
9✔
494

9✔
495
        if cgroupManager == nil {
9✔
496
                return fmt.Errorf("failed to apply device rule %+v: cgroup manager is nil", *deviceRule)
×
497
        }
×
498

499
        err := cgroupManager.Set(&configs.Resources{
9✔
500
                Devices: []*devices.Rule{deviceRule},
9✔
501
        })
9✔
502

9✔
503
        if err != nil {
9✔
504
                log.Log.Errorf("cgroup %s had failed to set device rule. error: %v. rule: %+v", cgroupManager.GetCgroupVersion(), err, *deviceRule)
×
505
        } else {
9✔
506
                log.Log.Infof("cgroup %s device rule is set successfully. rule: %+v", cgroupManager.GetCgroupVersion(), *deviceRule)
9✔
507
        }
9✔
508

509
        return err
9✔
510
}
511

512
func (m *volumeMounter) createBlockDeviceFile(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
5✔
513
        if _, err := safepath.JoinNoFollow(basePath, deviceName); errors.Is(err, os.ErrNotExist) {
9✔
514
                return mknodCommand(basePath, deviceName, dev, blockDevicePermissions)
4✔
515
        } else {
5✔
516
                return err
1✔
517
        }
1✔
518
}
519

520
func (m *volumeMounter) mountFileSystemHotplugVolume(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID, record *vmiMountTargetRecord, mountDirectory bool) error {
3✔
521
        virtlauncherUID := m.findVirtlauncherUID(vmi)
3✔
522
        if virtlauncherUID == "" {
3✔
523
                // This is not the node the pod is running on.
×
524
                return nil
×
525
        }
×
526
        var target *safepath.Path
3✔
527
        var err error
3✔
528
        if mountDirectory {
3✔
529
                target, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, volume, true)
×
530
        } else {
3✔
531
                target, err = m.hotplugDiskManager.GetFileSystemDiskTargetPathFromHostView(virtlauncherUID, volume, true)
3✔
532
        }
3✔
533
        if err != nil {
3✔
534
                return err
×
535
        }
×
536

537
        isMounted, err := isMounted(target)
3✔
538
        if err != nil {
3✔
539
                return fmt.Errorf("failed to determine if %s is already mounted: %v", target, err)
×
540
        }
×
541
        if !isMounted {
6✔
542
                sourcePath, err := m.getSourcePodFilePath(sourceUID, vmi, volume)
3✔
543
                if err != nil {
3✔
544
                        log.DefaultLogger().V(3).Infof("Error getting source path: %v", err)
×
545
                        // We are eating the error to avoid spamming the log with errors, it might take a while for the volume
×
546
                        // to get mounted on the node, and this will error until the volume is mounted.
×
547
                        return nil
×
548
                }
×
549
                if err := m.writePathToMountRecord(unsafepath.UnsafeAbsolute(target.Raw()), vmi, record); err != nil {
3✔
550
                        return err
×
551
                }
×
552
                if !mountDirectory {
6✔
553
                        sourcePath, err = sourcePath.AppendAndResolveWithRelativeRoot("disk.img")
3✔
554
                        if err != nil {
3✔
555
                                return err
×
556
                        }
×
557
                }
558
                if out, err := mountCommand(sourcePath, target); err != nil {
3✔
559
                        return fmt.Errorf("failed to bindmount hotplug volume source from %v to %v: %v : %v", sourcePath, target, string(out), err)
×
560
                }
×
561
                log.DefaultLogger().V(1).Infof("successfully mounted %v", volume)
3✔
562
        }
563

564
        return m.ownershipManager.SetFileOwnership(target)
3✔
565
}
566

567
func (m *volumeMounter) findVirtlauncherUID(vmi *v1.VirtualMachineInstance) (uid types.UID) {
12✔
568
        cnt := 0
12✔
569
        for podUID := range vmi.Status.ActivePods {
27✔
570
                _, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(podUID)
15✔
571
                if err == nil {
28✔
572
                        uid = podUID
13✔
573
                        cnt++
13✔
574
                }
13✔
575
        }
576
        if cnt == 1 {
23✔
577
                return
11✔
578
        }
11✔
579
        // Either no pods, or multiple pods, skip.
580
        return ""
1✔
581
}
582

583
func (m *volumeMounter) getSourcePodFilePath(sourceUID types.UID, vmi *v1.VirtualMachineInstance, volume string) (*safepath.Path, error) {
9✔
584
        iso := isolationDetector()
9✔
585
        isoRes, err := iso.DetectForSocket(socketPath(sourceUID))
9✔
586
        if err != nil {
10✔
587
                return nil, err
1✔
588
        }
1✔
589
        findmounts, err := LookupFindmntInfoByVolume(volume, isoRes.Pid())
8✔
590
        if err != nil {
9✔
591
                return nil, err
1✔
592
        }
1✔
593
        nodeIsoRes := nodeIsolationResult()
7✔
594
        mountRoot, err := nodeIsoRes.MountRoot()
7✔
595
        if err != nil {
7✔
596
                return nil, err
×
597
        }
×
598

599
        for _, findmnt := range findmounts {
14✔
600
                if filepath.Base(findmnt.Target) == volume {
12✔
601
                        source := findmnt.GetSourcePath()
5✔
602
                        path, err := parentPathForMount(nodeIsoRes, isoRes, findmnt)
5✔
603
                        exists := !errors.Is(err, os.ErrNotExist)
5✔
604
                        if err != nil && !errors.Is(err, os.ErrNotExist) {
5✔
605
                                return nil, err
×
606
                        }
×
607

608
                        isBlock := false
5✔
609
                        if exists {
10✔
610
                                isBlock, _ = isBlockDevice(path)
5✔
611
                        }
5✔
612

613
                        if !exists || isBlock {
5✔
614
                                // file not found, or block device, or directory check if we can find the mount.
×
615
                                deviceFindMnt, err := LookupFindmntInfoByDevice(source)
×
616
                                if err != nil {
×
617
                                        // Try the device found from the source
×
618
                                        deviceFindMnt, err = LookupFindmntInfoByDevice(findmnt.GetSourceDevice())
×
619
                                        if err != nil {
×
620
                                                return nil, err
×
621
                                        }
×
622
                                        // Check if the path was relative to the device.
623
                                        if !exists {
×
624
                                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target, source)
×
625
                                        }
×
626
                                        return nil, err
×
627
                                }
628
                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target)
×
629
                        } else {
5✔
630
                                return path, nil
5✔
631
                        }
5✔
632
                }
633
        }
634
        // Did not find the disk image file, return error
635
        return nil, fmt.Errorf("unable to find source disk image path for pod %s", sourceUID)
2✔
636
}
637

638
// Unmount unmounts all hotplug disk that are no longer part of the VMI
639
func (m *volumeMounter) Unmount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
2✔
640
        if vmi.UID != "" {
4✔
641
                record, err := m.getMountTargetRecord(vmi)
2✔
642
                if err != nil {
2✔
643
                        return err
×
644
                } else if record == nil {
2✔
645
                        // no entries to unmount
×
646
                        return nil
×
647
                }
×
648
                if len(record.MountTargetEntries) == 0 {
2✔
649
                        return nil
×
650
                }
×
651

652
                currentHotplugPaths := make(map[string]types.UID)
2✔
653
                virtlauncherUID := m.findVirtlauncherUID(vmi)
2✔
654

2✔
655
                basePath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
2✔
656
                if err != nil {
2✔
657
                        if errors.Is(err, os.ErrNotExist) {
×
658
                                // no mounts left, the base path does not even exist anymore
×
659
                                if err := m.deleteMountTargetRecord(vmi); err != nil {
×
660
                                        return fmt.Errorf("failed to delete mount target records: %v", err)
×
661
                                }
×
662
                                return nil
×
663
                        }
664
                        return err
×
665
                }
666
                // Ideally we would be looking at the actual disks in the domain but:
667
                // 1. The domain is built from the VMI spec
668
                // 2. The domain syncs before unmount is called
669
                // 3. Unmount will not get called if VMI sync fails
670
                // we should be good
671
                for _, volume := range vmi.Spec.Volumes {
5✔
672
                        if !storagetypes.IsHotplugVolume(&volume) {
5✔
673
                                continue
2✔
674
                        }
675
                        var path *safepath.Path
1✔
676
                        var err error
1✔
677
                        if m.isBlockVolume(&vmi.Status, volume.Name) {
1✔
678
                                path, err = safepath.JoinNoFollow(basePath, volume.Name)
×
679
                                if errors.Is(err, os.ErrNotExist) {
×
680
                                        // already unmounted or never mounted
×
681
                                        continue
×
682
                                }
683
                        } else if m.isDirectoryMounted(vmi, volume.Name) {
1✔
684
                                path, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, volume.Name, false)
×
685
                                if errors.Is(err, os.ErrNotExist) {
×
686
                                        // already unmounted or never mounted
×
687
                                        continue
×
688
                                }
689
                        } else {
1✔
690
                                path, err = m.hotplugDiskManager.GetFileSystemDiskTargetPathFromHostView(virtlauncherUID, volume.Name, false)
1✔
691
                                if errors.Is(err, os.ErrNotExist) {
1✔
692
                                        // already unmounted or never mounted
×
693
                                        continue
×
694
                                }
695
                        }
696
                        if err != nil {
1✔
697
                                return err
×
698
                        }
×
699
                        currentHotplugPaths[unsafepath.UnsafeAbsolute(path.Raw())] = virtlauncherUID
1✔
700
                }
701
                for _, utilityVolume := range vmi.Spec.UtilityVolumes {
2✔
NEW
702
                        if m.isBlockVolume(&vmi.Status, utilityVolume.Name) {
×
NEW
703
                                log.Log.Object(vmi).Warningf("Skipping unmount cleanup for utility volume %s: configured with block mode PVC", utilityVolume.Name)
×
NEW
704
                                continue
×
705
                        }
706

NEW
707
                        var path *safepath.Path
×
NEW
708
                        var err error
×
NEW
709
                        path, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, utilityVolume.Name, false)
×
NEW
710
                        if errors.Is(err, os.ErrNotExist) {
×
NEW
711
                                // already unmounted or never mounted
×
NEW
712
                                continue
×
713
                        }
NEW
714
                        if err != nil {
×
NEW
715
                                return err
×
NEW
716
                        }
×
NEW
717
                        currentHotplugPaths[unsafepath.UnsafeAbsolute(path.Raw())] = virtlauncherUID
×
718
                }
719
                newRecord := vmiMountTargetRecord{
2✔
720
                        MountTargetEntries: make([]vmiMountTargetEntry, 0),
2✔
721
                }
2✔
722
                for _, entry := range record.MountTargetEntries {
5✔
723
                        fd, err := safepath.NewFileNoFollow(entry.TargetFile)
3✔
724
                        if err != nil {
3✔
725
                                return err
×
726
                        }
×
727
                        fd.Close()
3✔
728
                        diskPath := fd.Path()
3✔
729

3✔
730
                        if _, ok := currentHotplugPaths[unsafepath.UnsafeAbsolute(diskPath.Raw())]; !ok {
5✔
731
                                if blockDevice, err := isBlockDevice(diskPath); err != nil {
2✔
732
                                        return err
×
733
                                } else if blockDevice {
3✔
734
                                        if err := m.unmountBlockHotplugVolumes(diskPath, cgroupManager); err != nil {
1✔
735
                                                return err
×
736
                                        }
×
737
                                } else if err := m.unmountFileSystemHotplugVolumes(diskPath); err != nil {
1✔
738
                                        return err
×
739
                                }
×
740
                                log.Log.Object(vmi).V(3).Infof("Unmounted hotplug volume path %s", diskPath)
2✔
741
                        } else {
1✔
742
                                newRecord.MountTargetEntries = append(newRecord.MountTargetEntries, vmiMountTargetEntry{
1✔
743
                                        TargetFile: unsafepath.UnsafeAbsolute(diskPath.Raw()),
1✔
744
                                })
1✔
745
                        }
1✔
746
                }
747
                if len(newRecord.MountTargetEntries) > 0 {
3✔
748
                        err = m.setMountTargetRecord(vmi, &newRecord)
1✔
749
                } else {
2✔
750
                        err = m.deleteMountTargetRecord(vmi)
1✔
751
                }
1✔
752
                if err != nil {
2✔
753
                        return err
×
754
                }
×
755
        }
756
        return nil
2✔
757
}
758

759
func (m *volumeMounter) unmountFileSystemHotplugVolumes(diskPath *safepath.Path) error {
6✔
760
        if mounted, err := isMounted(diskPath); err != nil {
7✔
761
                return fmt.Errorf("failed to check mount point for hotplug disk %v: %v", diskPath, err)
1✔
762
        } else if mounted {
8✔
763
                out, err := unmountCommand(diskPath)
2✔
764
                if err != nil {
3✔
765
                        return fmt.Errorf("failed to unmount hotplug disk %v: %v : %v", diskPath, string(out), err)
1✔
766
                }
1✔
767
                err = safepath.UnlinkAtNoFollow(diskPath)
1✔
768
                if err != nil {
1✔
769
                        return fmt.Errorf("failed to remove hotplug disk directory %v: %v : %v", diskPath, string(out), err)
×
770
                }
×
771

772
        }
773
        return nil
4✔
774
}
775

776
func (m *volumeMounter) unmountBlockHotplugVolumes(diskPath *safepath.Path, cgroupManager cgroup.Manager) error {
5✔
777
        // Get major and minor so we can deny the container.
5✔
778
        dev, _, err := m.getBlockFileMajorMinor(diskPath, statDevice)
5✔
779
        if err != nil {
5✔
780
                return err
×
781
        }
×
782
        // Delete block device file
783
        if err := safepath.UnlinkAtNoFollow(diskPath); err != nil {
6✔
784
                return err
1✔
785
        }
1✔
786
        return m.removeBlockMajorMinor(dev, cgroupManager)
4✔
787
}
788

789
// UnmountAll unmounts all hotplug disks of a given VMI.
790
func (m *volumeMounter) UnmountAll(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
791
        if vmi.UID != "" {
2✔
792
                logger := log.DefaultLogger().Object(vmi)
1✔
793
                logger.Info("Cleaning up remaining hotplug volumes")
1✔
794
                record, err := m.getMountTargetRecord(vmi)
1✔
795
                if err != nil {
1✔
796
                        return err
×
797
                } else if record == nil {
1✔
798
                        // no entries to unmount
×
799
                        logger.Info("No hotplug volumes found to unmount")
×
800
                        return nil
×
801
                }
×
802

803
                for _, entry := range record.MountTargetEntries {
3✔
804
                        diskPath, err := safepath.NewFileNoFollow(entry.TargetFile)
2✔
805
                        if err != nil {
2✔
806
                                if errors.Is(err, os.ErrNotExist) {
×
807
                                        logger.Infof("Device %v is not mounted anymore, continuing.", entry.TargetFile)
×
808
                                        continue
×
809
                                }
810
                                logger.Warningf("Unable to unmount volume at path %s: %v", entry.TargetFile, err)
×
811
                                continue
×
812
                        }
813
                        diskPath.Close()
2✔
814
                        if isBlock, err := isBlockDevice(diskPath.Path()); err != nil {
2✔
815
                                logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
816
                        } else if isBlock {
3✔
817
                                if err := m.unmountBlockHotplugVolumes(diskPath.Path(), cgroupManager); err != nil {
1✔
818
                                        logger.Warningf("Unable to remove block device at path %s: %v", diskPath, err)
×
819
                                        // Don't return error, try next.
×
820
                                }
×
821
                        } else {
1✔
822
                                if err := m.unmountFileSystemHotplugVolumes(diskPath.Path()); err != nil {
1✔
823
                                        logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
824
                                        // Don't return error, try next.
×
825
                                }
×
826
                        }
827
                }
828
                err = m.deleteMountTargetRecord(vmi)
1✔
829
                if err != nil {
1✔
830
                        return err
×
831
                }
×
832
        }
833
        return nil
1✔
834
}
835

836
func (m *volumeMounter) IsMounted(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID) (bool, error) {
×
837
        virtlauncherUID := m.findVirtlauncherUID(vmi)
×
838
        if virtlauncherUID == "" {
×
839
                // This is not the node the pod is running on.
×
840
                return false, fmt.Errorf("Unable to determine virt-launcher UID")
×
841
        }
×
842
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
×
843
        if err != nil {
×
844
                if errors.Is(err, os.ErrNotExist) {
×
845
                        return false, nil
×
846
                }
×
847
                return false, err
×
848
        }
849
        if m.isBlockVolume(&vmi.Status, volume) {
×
850
                deviceName, err := safepath.JoinNoFollow(targetPath, volume)
×
851
                if err != nil {
×
852
                        if errors.Is(err, os.ErrNotExist) {
×
853
                                return false, nil
×
854
                        }
×
855
                        return false, err
×
856
                }
857
                isBlockExists, _ := isBlockDevice(deviceName)
×
858
                return isBlockExists, nil
×
859
        }
NEW
860
        if m.isDirectoryMounted(vmi, volume) {
×
861
                path, err := safepath.JoinNoFollow(targetPath, volume)
×
862
                if err != nil {
×
863
                        if errors.Is(err, os.ErrNotExist) {
×
864
                                return false, nil
×
865
                        }
×
866
                        return false, err
×
867
                }
868
                return isMounted(path)
×
869
        }
870
        path, err := safepath.JoinNoFollow(targetPath, fmt.Sprintf("%s.img", volume))
×
871
        if err != nil {
×
872
                if errors.Is(err, os.ErrNotExist) {
×
873
                        return false, nil
×
874
                }
×
875
                return false, err
×
876
        }
877
        return isMounted(path)
×
878
}
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