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

kubevirt / kubevirt / 9429f286-be9a-458e-899b-6091de073df0

14 Mar 2025 12:12AM UTC coverage: 71.412% (-0.03%) from 71.441%
9429f286-be9a-458e-899b-6091de073df0

push

prow

web-flow
Merge pull request #13931 from alicefr/vol-mig-hp

volume-migration: fix several bugs: volume mode evaluation, hotplug paths and cgroup allow list generation

32 of 73 new or added lines in 6 files covered. (43.84%)

11 existing lines in 4 files now uncovered.

61928 of 86719 relevant lines covered (71.41%)

0.8 hits per line

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

52.36
/pkg/virt-handler/hotplug-disk/mount.go
1
package hotplug_volume
2

3
import (
4
        "errors"
5
        "fmt"
6
        "os"
7
        "path/filepath"
8
        "sync"
9
        "syscall"
10

11
        "kubevirt.io/kubevirt/pkg/checkpoint"
12
        "kubevirt.io/kubevirt/pkg/unsafepath"
13

14
        "golang.org/x/sys/unix"
15

16
        "kubevirt.io/kubevirt/pkg/safepath"
17
        virt_chroot "kubevirt.io/kubevirt/pkg/virt-handler/virt-chroot"
18

19
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
20
        hotplugdisk "kubevirt.io/kubevirt/pkg/hotplug-disk"
21
        storagetypes "kubevirt.io/kubevirt/pkg/storage/types"
22
        "kubevirt.io/kubevirt/pkg/virt-handler/cgroup"
23
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
24

25
        "github.com/opencontainers/runc/libcontainer/configs"
26

27
        "github.com/opencontainers/runc/libcontainer/devices"
28
        "k8s.io/apimachinery/pkg/types"
29

30
        v1 "kubevirt.io/api/core/v1"
31
        "kubevirt.io/client-go/log"
32
)
33

34
//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_$GOFILE
35

36
const (
37
        unableFindHotplugMountedDir = "unable to find hotplug mounted directories for vmi without uid"
38
)
39

40
var (
41
        nodeIsolationResult = func() isolation.IsolationResult {
×
42
                return isolation.NodeIsolationResult()
×
43
        }
×
44
        deviceBasePath = func(podUID types.UID, kubeletPodsDir string) (*safepath.Path, error) {
×
45
                return safepath.JoinAndResolveWithRelativeRoot("/proc/1/root", kubeletPodsDir, fmt.Sprintf("/%s/volumes/kubernetes.io~empty-dir/hotplug-disks", string(podUID)))
×
46
        }
×
47

48
        socketPath = func(podUID types.UID) string {
1✔
49
                return fmt.Sprintf("pods/%s/volumes/kubernetes.io~empty-dir/hotplug-disks/hp.sock", string(podUID))
1✔
50
        }
1✔
51

52
        statDevice = func(fileName *safepath.Path) (os.FileInfo, error) {
×
53
                info, err := safepath.StatAtNoFollow(fileName)
×
54
                if err != nil {
×
55
                        return nil, err
×
56
                }
×
57
                if info.Mode()&os.ModeDevice == 0 {
×
58
                        return info, fmt.Errorf("%v is not a block device", fileName)
×
59
                }
×
60
                return info, nil
×
61
        }
62

63
        statSourceDevice = func(fileName *safepath.Path) (os.FileInfo, error) {
×
64
                // we don't know the device name, we only know that it is the only
×
65
                // device in a specific directory, let's look it up
×
66
                var devName string
×
67
                err := fileName.ExecuteNoFollow(func(safePath string) error {
×
68
                        entries, err := os.ReadDir(safePath)
×
69
                        if err != nil {
×
70
                                return err
×
71
                        }
×
72
                        for _, entry := range entries {
×
73
                                info, err := entry.Info()
×
74
                                if err != nil {
×
75
                                        return err
×
76
                                }
×
77
                                if info.Mode()&os.ModeDevice == 0 {
×
78
                                        // not a device
×
79
                                        continue
×
80
                                }
81
                                devName = entry.Name()
×
82
                                return nil
×
83
                        }
84
                        return fmt.Errorf("no device in %v", fileName)
×
85
                })
86
                if err != nil {
×
87
                        return nil, err
×
88
                }
×
89
                devPath, err := safepath.JoinNoFollow(fileName, devName)
×
90
                if err != nil {
×
91
                        return nil, err
×
92
                }
×
93
                return statDevice(devPath)
×
94
        }
95

96
        mknodCommand = func(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
×
97
                return safepath.MknodAtNoFollow(basePath, deviceName, blockDevicePermissions|syscall.S_IFBLK, dev)
×
98
        }
×
99

100
        mountCommand = func(sourcePath, targetPath *safepath.Path) ([]byte, error) {
×
101
                return virt_chroot.MountChroot(sourcePath, targetPath, false).CombinedOutput()
×
102
        }
×
103

104
        unmountCommand = func(diskPath *safepath.Path) ([]byte, error) {
×
105
                return virt_chroot.UmountChroot(diskPath).CombinedOutput()
×
106
        }
×
107

108
        isMounted = func(path *safepath.Path) (bool, error) {
1✔
109
                return isolation.IsMounted(path)
1✔
110
        }
1✔
111

112
        isBlockDevice = func(path *safepath.Path) (bool, error) {
1✔
113
                return isolation.IsBlockDevice(path)
1✔
114
        }
1✔
115

116
        isolationDetector = func(path string) isolation.PodIsolationDetector {
×
117
                return isolation.NewSocketBasedIsolationDetector(path)
×
118
        }
×
119

120
        parentPathForMount = func(
121
                parent isolation.IsolationResult,
122
                child isolation.IsolationResult,
123
                findmntInfo FindmntInfo,
124
        ) (*safepath.Path, error) {
×
125
                return isolation.ParentPathForMount(parent, child, findmntInfo.Source, findmntInfo.Target)
×
126
        }
×
127
)
128

129
type volumeMounter struct {
130
        checkpointManager  checkpoint.CheckpointManager
131
        mountRecords       map[types.UID]*vmiMountTargetRecord
132
        mountRecordsLock   sync.Mutex
133
        skipSafetyCheck    bool
134
        hotplugDiskManager hotplugdisk.HotplugDiskManagerInterface
135
        ownershipManager   diskutils.OwnershipManagerInterface
136
        kubeletPodsDir     string
137
        host               string
138
}
139

140
// VolumeMounter is the interface used to mount and unmount volumes to/from a running virtlauncher pod.
141
type VolumeMounter interface {
142
        // Mount any new volumes defined in the VMI
143
        Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
144
        MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error
145
        // Unmount any volumes no longer defined in the VMI
146
        Unmount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
147
        //UnmountAll cleans up all hotplug volumes
148
        UnmountAll(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error
149
        //IsMounted returns if the volume is mounted or not.
150
        IsMounted(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID) (bool, error)
151
}
152

153
type vmiMountTargetEntry struct {
154
        TargetFile string `json:"targetFile"`
155
}
156

157
type vmiMountTargetRecord struct {
158
        MountTargetEntries []vmiMountTargetEntry `json:"mountTargetEntries"`
159
        UsesSafePaths      bool                  `json:"usesSafePaths"`
160
}
161

162
// NewVolumeMounter creates a new VolumeMounter
163
func NewVolumeMounter(mountStateDir string, kubeletPodsDir string, host string) VolumeMounter {
1✔
164
        return &volumeMounter{
1✔
165
                mountRecords:       make(map[types.UID]*vmiMountTargetRecord),
1✔
166
                checkpointManager:  checkpoint.NewSimpleCheckpointManager(mountStateDir),
1✔
167
                hotplugDiskManager: hotplugdisk.NewHotplugDiskManager(kubeletPodsDir),
1✔
168
                ownershipManager:   diskutils.DefaultOwnershipManager,
1✔
169
                kubeletPodsDir:     kubeletPodsDir,
1✔
170
                host:               host,
1✔
171
        }
1✔
172
}
1✔
173

174
func (m *volumeMounter) deleteMountTargetRecord(vmi *v1.VirtualMachineInstance) error {
1✔
175
        if string(vmi.UID) == "" {
1✔
176
                return fmt.Errorf(unableFindHotplugMountedDir)
×
177
        }
×
178

179
        record := vmiMountTargetRecord{}
1✔
180
        err := m.checkpointManager.Get(string(vmi.UID), &record)
1✔
181
        if err != nil && !errors.Is(err, os.ErrNotExist) {
1✔
182
                return fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
183
        }
×
184

185
        if err == nil {
2✔
186
                for _, target := range record.MountTargetEntries {
2✔
187
                        os.Remove(target.TargetFile)
1✔
188
                }
1✔
189

190
                if err := m.checkpointManager.Delete(string(vmi.UID)); err != nil {
1✔
191
                        return fmt.Errorf("failed to delete checkpoint %s, %w", vmi.UID, err)
×
192
                }
×
193
        }
194

195
        m.mountRecordsLock.Lock()
1✔
196
        defer m.mountRecordsLock.Unlock()
1✔
197
        delete(m.mountRecords, vmi.UID)
1✔
198

1✔
199
        return nil
1✔
200
}
201

202
func (m *volumeMounter) getMountTargetRecord(vmi *v1.VirtualMachineInstance) (*vmiMountTargetRecord, error) {
1✔
203
        var ok bool
1✔
204
        var existingRecord *vmiMountTargetRecord
1✔
205

1✔
206
        if string(vmi.UID) == "" {
2✔
207
                return nil, fmt.Errorf(unableFindHotplugMountedDir)
1✔
208
        }
1✔
209

210
        m.mountRecordsLock.Lock()
1✔
211
        defer m.mountRecordsLock.Unlock()
1✔
212
        existingRecord, ok = m.mountRecords[vmi.UID]
1✔
213

1✔
214
        // first check memory cache
1✔
215
        if ok {
2✔
216
                return existingRecord, nil
1✔
217
        }
1✔
218

219
        // if not there, see if record is on disk, this can happen if virt-handler restarts
220
        record := vmiMountTargetRecord{}
1✔
221
        err := m.checkpointManager.Get(string(vmi.UID), &record)
1✔
222
        if err != nil && !errors.Is(err, os.ErrNotExist) {
1✔
223
                return nil, fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
224
        }
×
225

226
        if err == nil {
1✔
227
                // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
×
228
                // After a one-time convert and persist, old records are safe too.
×
229
                if !record.UsesSafePaths {
×
230
                        for i, path := range record.MountTargetEntries {
×
231
                                record.UsesSafePaths = true
×
232
                                safePath, err := safepath.JoinAndResolveWithRelativeRoot("/", path.TargetFile)
×
233
                                if err != nil {
×
234
                                        return nil, fmt.Errorf("failed converting legacy path to safepath: %v", err)
×
235
                                }
×
236
                                record.MountTargetEntries[i].TargetFile = unsafepath.UnsafeAbsolute(safePath.Raw())
×
237
                        }
238
                }
239

240
                m.mountRecords[vmi.UID] = &record
×
241
                return &record, nil
×
242
        }
243

244
        // not found
245
        return &vmiMountTargetRecord{UsesSafePaths: true}, nil
1✔
246
}
247

248
func (m *volumeMounter) setMountTargetRecord(vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
1✔
249
        if string(vmi.UID) == "" {
2✔
250
                return fmt.Errorf(unableFindHotplugMountedDir)
1✔
251
        }
1✔
252

253
        // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
254
        // After a one-time convert and persist, old records are safe too.
255
        record.UsesSafePaths = true
1✔
256

1✔
257
        m.mountRecordsLock.Lock()
1✔
258
        defer m.mountRecordsLock.Unlock()
1✔
259

1✔
260
        if err := m.checkpointManager.Store(string(vmi.UID), record); err != nil {
1✔
261
                return fmt.Errorf("failed to checkpoint %s, %w", vmi.UID, err)
×
262
        }
×
263
        m.mountRecords[vmi.UID] = record
1✔
264
        return nil
1✔
265
}
266

267
func (m *volumeMounter) writePathToMountRecord(path string, vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
1✔
268
        record.MountTargetEntries = append(record.MountTargetEntries, vmiMountTargetEntry{
1✔
269
                TargetFile: path,
1✔
270
        })
1✔
271
        if err := m.setMountTargetRecord(vmi, record); err != nil {
1✔
272
                return err
×
273
        }
×
274
        return nil
1✔
275
}
276

277
func (m *volumeMounter) mountHotplugVolume(
278
        vmi *v1.VirtualMachineInstance,
279
        volumeName string,
280
        sourceUID types.UID,
281
        record *vmiMountTargetRecord,
282
        mountDirectory bool,
283
        cgroupManager cgroup.Manager,
284
) error {
1✔
285
        logger := log.DefaultLogger()
1✔
286
        logger.V(4).Infof("Hotplug check volume name: %s", volumeName)
1✔
287
        if sourceUID != "" {
2✔
288
                if m.isBlockVolume(&vmi.Status, volumeName) {
2✔
289
                        logger.V(4).Infof("Mounting block volume: %s", volumeName)
1✔
290
                        if err := m.mountBlockHotplugVolume(vmi, volumeName, sourceUID, record, cgroupManager); err != nil {
1✔
291
                                if !errors.Is(err, os.ErrNotExist) {
×
292
                                        return fmt.Errorf("failed to mount block hotplug volume %s: %v", volumeName, err)
×
293
                                }
×
294
                        }
295
                } else {
1✔
296
                        logger.V(4).Infof("Mounting file system volume: %s", volumeName)
1✔
297
                        if err := m.mountFileSystemHotplugVolume(vmi, volumeName, sourceUID, record, mountDirectory); err != nil {
1✔
298
                                if !errors.Is(err, os.ErrNotExist) {
×
299
                                        return fmt.Errorf("failed to mount filesystem hotplug volume %s: %v", volumeName, err)
×
300
                                }
×
301
                        }
302
                }
303
        }
304
        return nil
1✔
305
}
306

307
func (m *volumeMounter) Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
308
        return m.mountFromPod(vmi, "", cgroupManager)
1✔
309
}
1✔
310

311
func (m *volumeMounter) MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
×
312
        return m.mountFromPod(vmi, sourceUID, cgroupManager)
×
313
}
×
314

315
func (m *volumeMounter) mountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
1✔
316
        record, err := m.getMountTargetRecord(vmi)
1✔
317
        if err != nil {
1✔
318
                return err
×
319
        }
×
320
        for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
321
                if volumeStatus.HotplugVolume == nil {
2✔
322
                        // Skip non hotplug volumes
1✔
323
                        continue
1✔
324
                }
325
                mountDirectory := false
1✔
326
                if volumeStatus.MemoryDumpVolume != nil {
1✔
327
                        mountDirectory = true
×
328
                }
×
329
                if sourceUID == "" {
2✔
330
                        sourceUID = volumeStatus.HotplugVolume.AttachPodUID
1✔
331
                }
1✔
332
                if err := m.mountHotplugVolume(vmi, volumeStatus.Name, sourceUID, record, mountDirectory, cgroupManager); err != nil {
1✔
333
                        return err
×
334
                }
×
335
        }
336
        return nil
1✔
337
}
338

339
func (m *volumeMounter) isDirectoryMounted(vmiStatus *v1.VirtualMachineInstanceStatus, volumeName string) bool {
×
340
        for _, status := range vmiStatus.VolumeStatus {
×
341
                if status.Name == volumeName {
×
342
                        return status.MemoryDumpVolume != nil
×
343
                }
×
344
        }
345
        return false
×
346
}
347

348
// isBlockVolume checks if the volumeDevices directory exists in the pod path, we assume there is a single volume associated with
349
// each pod, we use this knowledge to determine if we have a block volume or not.
350
func (m *volumeMounter) isBlockVolume(vmiStatus *v1.VirtualMachineInstanceStatus, volumeName string) bool {
1✔
351
        // First evaluate the migrated volumed. In the case of a migrated volume, virt-handler needs to understand if it needs to consider the
1✔
352
        // 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.
1✔
353
        isDstHost := false
1✔
354
        if vmiStatus.MigrationState != nil {
1✔
NEW
355
                isDstHost = vmiStatus.MigrationState.TargetNode == m.host
×
NEW
356
        }
×
357
        for _, migVol := range vmiStatus.MigratedVolumes {
1✔
NEW
358
                if migVol.VolumeName == volumeName {
×
NEW
359
                        if isDstHost && migVol.DestinationPVCInfo != nil {
×
NEW
360
                                return storagetypes.IsPVCBlock(migVol.DestinationPVCInfo.VolumeMode)
×
NEW
361
                        }
×
NEW
362
                        if !isDstHost && migVol.SourcePVCInfo != nil {
×
NEW
363
                                return storagetypes.IsPVCBlock(migVol.SourcePVCInfo.VolumeMode)
×
NEW
364
                        }
×
365
                }
366
        }
367
        // Check if the volumeDevices directory exists in the attachment pod, if so, its a block device, otherwise its file system.
368
        for _, status := range vmiStatus.VolumeStatus {
2✔
369
                if status.Name == volumeName {
2✔
370
                        return status.PersistentVolumeClaimInfo != nil && storagetypes.IsPVCBlock(status.PersistentVolumeClaimInfo.VolumeMode)
1✔
371
                }
1✔
372
        }
373
        return false
1✔
374
}
375

376
func (m *volumeMounter) mountBlockHotplugVolume(
377
        vmi *v1.VirtualMachineInstance,
378
        volume string,
379
        sourceUID types.UID,
380
        record *vmiMountTargetRecord,
381
        cgroupManager cgroup.Manager,
382
) error {
1✔
383
        virtlauncherUID := m.findVirtlauncherUID(vmi)
1✔
384
        if virtlauncherUID == "" {
1✔
385
                // This is not the node the pod is running on.
×
386
                return nil
×
387
        }
×
388
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
1✔
389
        if err != nil {
1✔
390
                return err
×
391
        }
×
392

393
        if _, err := safepath.JoinNoFollow(targetPath, volume); errors.Is(err, os.ErrNotExist) {
2✔
394
                dev, permissions, err := m.getSourceMajorMinor(sourceUID, volume)
1✔
395
                if err != nil {
1✔
396
                        return err
×
397
                }
×
398

399
                if err := m.writePathToMountRecord(filepath.Join(unsafepath.UnsafeAbsolute(targetPath.Raw()), volume), vmi, record); err != nil {
1✔
400
                        return err
×
401
                }
×
402

403
                if err := m.createBlockDeviceFile(targetPath, volume, dev, permissions); err != nil && !os.IsExist(err) {
1✔
404
                        return err
×
405
                }
×
406
                log.DefaultLogger().V(1).Infof("successfully created block device %v", volume)
1✔
407
        } else if err != nil {
1✔
408
                return err
×
409
        }
×
410

411
        devicePath, err := safepath.JoinNoFollow(targetPath, volume)
1✔
412
        if err != nil {
1✔
413
                return err
×
414
        }
×
415
        if isBlockExists, err := isBlockDevice(devicePath); err != nil {
1✔
416
                return err
×
417
        } else if !isBlockExists {
1✔
418
                return fmt.Errorf("target device %v exists but it is not a block device", devicePath)
×
419
        }
×
420

421
        dev, _, err := m.getBlockFileMajorMinor(devicePath, statDevice)
1✔
422
        if err != nil {
1✔
423
                return err
×
424
        }
×
425
        // allow block devices
426
        if err := m.allowBlockMajorMinor(dev, cgroupManager); err != nil {
1✔
427
                return err
×
428
        }
×
429

430
        return m.ownershipManager.SetFileOwnership(devicePath)
1✔
431
}
432

433
func (m *volumeMounter) getSourceMajorMinor(sourceUID types.UID, volumeName string) (uint64, os.FileMode, error) {
1✔
434
        basePath, err := deviceBasePath(sourceUID, m.kubeletPodsDir)
1✔
435
        if err != nil {
1✔
436
                return 0, 0, err
×
437
        }
×
438
        devicePath, err := basePath.AppendAndResolveWithRelativeRoot(volumeName)
1✔
439
        if err != nil {
2✔
440
                return 0, 0, err
1✔
441
        }
1✔
442
        return m.getBlockFileMajorMinor(devicePath, statSourceDevice)
1✔
443
}
444

445
func (m *volumeMounter) getBlockFileMajorMinor(devicePath *safepath.Path, getter func(fileName *safepath.Path) (os.FileInfo, error)) (uint64, os.FileMode, error) {
1✔
446
        fileInfo, err := getter(devicePath)
1✔
447
        if err != nil {
2✔
448
                return 0, 0, err
1✔
449
        }
1✔
450
        info := fileInfo.Sys().(*syscall.Stat_t)
1✔
451
        return info.Rdev, fileInfo.Mode(), nil
1✔
452
}
453

454
func (m *volumeMounter) removeBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
1✔
455
        return m.updateBlockMajorMinor(dev, false, cgroupManager)
1✔
456
}
1✔
457

458
func (m *volumeMounter) allowBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
1✔
459
        return m.updateBlockMajorMinor(dev, true, cgroupManager)
1✔
460
}
1✔
461

462
func (m *volumeMounter) updateBlockMajorMinor(dev uint64, allow bool, cgroupManager cgroup.Manager) error {
1✔
463
        deviceRule := &devices.Rule{
1✔
464
                Type:        devices.BlockDevice,
1✔
465
                Major:       int64(unix.Major(dev)),
1✔
466
                Minor:       int64(unix.Minor(dev)),
1✔
467
                Permissions: "rwm",
1✔
468
                Allow:       allow,
1✔
469
        }
1✔
470

1✔
471
        if cgroupManager == nil {
1✔
472
                return fmt.Errorf("failed to apply device rule %+v: cgroup manager is nil", *deviceRule)
×
473
        }
×
474

475
        err := cgroupManager.Set(&configs.Resources{
1✔
476
                Devices: []*devices.Rule{deviceRule},
1✔
477
        })
1✔
478

1✔
479
        if err != nil {
1✔
480
                log.Log.Errorf("cgroup %s had failed to set device rule. error: %v. rule: %+v", cgroupManager.GetCgroupVersion(), err, *deviceRule)
×
481
        } else {
1✔
482
                log.Log.Infof("cgroup %s device rule is set successfully. rule: %+v", cgroupManager.GetCgroupVersion(), *deviceRule)
1✔
483
        }
1✔
484

485
        return err
1✔
486
}
487

488
func (m *volumeMounter) createBlockDeviceFile(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
1✔
489
        if _, err := safepath.JoinNoFollow(basePath, deviceName); errors.Is(err, os.ErrNotExist) {
2✔
490
                return mknodCommand(basePath, deviceName, dev, blockDevicePermissions)
1✔
491
        } else {
2✔
492
                return err
1✔
493
        }
1✔
494
}
495

496
func (m *volumeMounter) mountFileSystemHotplugVolume(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID, record *vmiMountTargetRecord, mountDirectory bool) error {
1✔
497
        virtlauncherUID := m.findVirtlauncherUID(vmi)
1✔
498
        if virtlauncherUID == "" {
1✔
499
                // This is not the node the pod is running on.
×
500
                return nil
×
501
        }
×
502
        var target *safepath.Path
1✔
503
        var err error
1✔
504
        if mountDirectory {
1✔
505
                target, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, volume, true)
×
506
        } else {
1✔
507
                target, err = m.hotplugDiskManager.GetFileSystemDiskTargetPathFromHostView(virtlauncherUID, volume, true)
1✔
508
        }
1✔
509
        if err != nil {
1✔
510
                return err
×
511
        }
×
512

513
        isMounted, err := isMounted(target)
1✔
514
        if err != nil {
1✔
515
                return fmt.Errorf("failed to determine if %s is already mounted: %v", target, err)
×
516
        }
×
517
        if !isMounted {
2✔
518
                sourcePath, err := m.getSourcePodFilePath(sourceUID, vmi, volume)
1✔
519
                if err != nil {
1✔
520
                        log.DefaultLogger().V(3).Infof("Error getting source path: %v", err)
×
521
                        // We are eating the error to avoid spamming the log with errors, it might take a while for the volume
×
522
                        // to get mounted on the node, and this will error until the volume is mounted.
×
523
                        return nil
×
524
                }
×
525
                if err := m.writePathToMountRecord(unsafepath.UnsafeAbsolute(target.Raw()), vmi, record); err != nil {
1✔
526
                        return err
×
527
                }
×
528
                if !mountDirectory {
2✔
529
                        sourcePath, err = sourcePath.AppendAndResolveWithRelativeRoot("disk.img")
1✔
530
                        if err != nil {
1✔
531
                                return err
×
532
                        }
×
533
                }
534
                if out, err := mountCommand(sourcePath, target); err != nil {
1✔
535
                        return fmt.Errorf("failed to bindmount hotplug volume source from %v to %v: %v : %v", sourcePath, target, string(out), err)
×
536
                }
×
537
                log.DefaultLogger().V(1).Infof("successfully mounted %v", volume)
1✔
538
        }
539

540
        return m.ownershipManager.SetFileOwnership(target)
1✔
541
}
542

543
func (m *volumeMounter) findVirtlauncherUID(vmi *v1.VirtualMachineInstance) (uid types.UID) {
1✔
544
        cnt := 0
1✔
545
        for podUID := range vmi.Status.ActivePods {
2✔
546
                _, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(podUID)
1✔
547
                if err == nil {
2✔
548
                        uid = podUID
1✔
549
                        cnt++
1✔
550
                }
1✔
551
        }
552
        if cnt == 1 {
2✔
553
                return
1✔
554
        }
1✔
555
        // Either no pods, or multiple pods, skip.
556
        return ""
1✔
557
}
558

559
func (m *volumeMounter) getSourcePodFilePath(sourceUID types.UID, vmi *v1.VirtualMachineInstance, volume string) (*safepath.Path, error) {
1✔
560
        iso := isolationDetector("/path")
1✔
561
        isoRes, err := iso.DetectForSocket(vmi, socketPath(sourceUID))
1✔
562
        if err != nil {
2✔
563
                return nil, err
1✔
564
        }
1✔
565
        findmounts, err := LookupFindmntInfoByVolume(volume, isoRes.Pid())
1✔
566
        if err != nil {
2✔
567
                return nil, err
1✔
568
        }
1✔
569
        nodeIsoRes := nodeIsolationResult()
1✔
570
        mountRoot, err := nodeIsoRes.MountRoot()
1✔
571
        if err != nil {
1✔
572
                return nil, err
×
573
        }
×
574

575
        for _, findmnt := range findmounts {
2✔
576
                if filepath.Base(findmnt.Target) == volume {
2✔
577
                        source := findmnt.GetSourcePath()
1✔
578
                        path, err := parentPathForMount(nodeIsoRes, isoRes, findmnt)
1✔
579
                        exists := !errors.Is(err, os.ErrNotExist)
1✔
580
                        if err != nil && !errors.Is(err, os.ErrNotExist) {
1✔
581
                                return nil, err
×
582
                        }
×
583

584
                        isBlock := false
1✔
585
                        if exists {
2✔
586
                                isBlock, _ = isBlockDevice(path)
1✔
587
                        }
1✔
588

589
                        if !exists || isBlock {
1✔
590
                                // file not found, or block device, or directory check if we can find the mount.
×
591
                                deviceFindMnt, err := LookupFindmntInfoByDevice(source)
×
592
                                if err != nil {
×
593
                                        // Try the device found from the source
×
594
                                        deviceFindMnt, err = LookupFindmntInfoByDevice(findmnt.GetSourceDevice())
×
595
                                        if err != nil {
×
596
                                                return nil, err
×
597
                                        }
×
598
                                        // Check if the path was relative to the device.
599
                                        if !exists {
×
600
                                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target, source)
×
601
                                        }
×
602
                                        return nil, err
×
603
                                }
604
                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target)
×
605
                        } else {
1✔
606
                                return path, nil
1✔
607
                        }
1✔
608
                }
609
        }
610
        // Did not find the disk image file, return error
611
        return nil, fmt.Errorf("unable to find source disk image path for pod %s", sourceUID)
1✔
612
}
613

614
// Unmount unmounts all hotplug disk that are no longer part of the VMI
615
func (m *volumeMounter) Unmount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
616
        if vmi.UID != "" {
2✔
617
                record, err := m.getMountTargetRecord(vmi)
1✔
618
                if err != nil {
1✔
619
                        return err
×
620
                } else if record == nil {
1✔
621
                        // no entries to unmount
×
622
                        return nil
×
623
                }
×
624
                if len(record.MountTargetEntries) == 0 {
1✔
625
                        return nil
×
626
                }
×
627

628
                currentHotplugPaths := make(map[string]types.UID, 0)
1✔
629
                virtlauncherUID := m.findVirtlauncherUID(vmi)
1✔
630

1✔
631
                basePath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
1✔
632
                if err != nil {
1✔
633
                        if errors.Is(err, os.ErrNotExist) {
×
634
                                // no mounts left, the base path does not even exist anymore
×
635
                                if err := m.deleteMountTargetRecord(vmi); err != nil {
×
636
                                        return fmt.Errorf("failed to delete mount target records: %v", err)
×
637
                                }
×
638
                                return nil
×
639
                        }
640
                        return err
×
641
                }
642
                for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
643
                        if volumeStatus.HotplugVolume == nil {
2✔
644
                                continue
1✔
645
                        }
646
                        var path *safepath.Path
×
647
                        var err error
×
648
                        if m.isBlockVolume(&vmi.Status, volumeStatus.Name) {
×
649
                                path, err = safepath.JoinNoFollow(basePath, volumeStatus.Name)
×
650
                                if errors.Is(err, os.ErrNotExist) {
×
651
                                        // already unmounted or never mounted
×
652
                                        continue
×
653
                                }
654
                        } else if m.isDirectoryMounted(&vmi.Status, volumeStatus.Name) {
×
655
                                path, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, volumeStatus.Name, false)
×
656
                                if errors.Is(err, os.ErrNotExist) {
×
657
                                        // already unmounted or never mounted
×
658
                                        continue
×
659
                                }
660
                        } else {
×
661
                                path, err = m.hotplugDiskManager.GetFileSystemDiskTargetPathFromHostView(virtlauncherUID, volumeStatus.Name, false)
×
662
                                if errors.Is(err, os.ErrNotExist) {
×
663
                                        // already unmounted or never mounted
×
664
                                        continue
×
665
                                }
666
                        }
667
                        if err != nil {
×
668
                                return err
×
669
                        }
×
670
                        currentHotplugPaths[unsafepath.UnsafeAbsolute(path.Raw())] = virtlauncherUID
×
671
                }
672
                newRecord := vmiMountTargetRecord{
1✔
673
                        MountTargetEntries: make([]vmiMountTargetEntry, 0),
1✔
674
                }
1✔
675
                for _, entry := range record.MountTargetEntries {
2✔
676
                        fd, err := safepath.NewFileNoFollow(entry.TargetFile)
1✔
677
                        if err != nil {
1✔
678
                                return err
×
679
                        }
×
680
                        fd.Close()
1✔
681
                        diskPath := fd.Path()
1✔
682

1✔
683
                        if _, ok := currentHotplugPaths[unsafepath.UnsafeAbsolute(diskPath.Raw())]; !ok {
2✔
684
                                if blockDevice, err := isBlockDevice(diskPath); err != nil {
1✔
685
                                        return err
×
686
                                } else if blockDevice {
2✔
687
                                        if err := m.unmountBlockHotplugVolumes(diskPath, cgroupManager); err != nil {
1✔
688
                                                return err
×
689
                                        }
×
690
                                } else if err := m.unmountFileSystemHotplugVolumes(diskPath); err != nil {
1✔
691
                                        return err
×
692
                                }
×
693
                        } else {
×
694
                                newRecord.MountTargetEntries = append(newRecord.MountTargetEntries, vmiMountTargetEntry{
×
695
                                        TargetFile: unsafepath.UnsafeAbsolute(diskPath.Raw()),
×
696
                                })
×
697
                        }
×
698
                }
699
                if len(newRecord.MountTargetEntries) > 0 {
1✔
700
                        err = m.setMountTargetRecord(vmi, &newRecord)
×
701
                } else {
1✔
702
                        err = m.deleteMountTargetRecord(vmi)
1✔
703
                }
1✔
704
                if err != nil {
1✔
705
                        return err
×
706
                }
×
707
        }
708
        return nil
1✔
709
}
710

711
func (m *volumeMounter) unmountFileSystemHotplugVolumes(diskPath *safepath.Path) error {
1✔
712
        if mounted, err := isMounted(diskPath); err != nil {
2✔
713
                return fmt.Errorf("failed to check mount point for hotplug disk %v: %v", diskPath, err)
1✔
714
        } else if mounted {
3✔
715
                out, err := unmountCommand(diskPath)
1✔
716
                if err != nil {
2✔
717
                        return fmt.Errorf("failed to unmount hotplug disk %v: %v : %v", diskPath, string(out), err)
1✔
718
                }
1✔
719
                err = safepath.UnlinkAtNoFollow(diskPath)
1✔
720
                if err != nil {
1✔
721
                        return fmt.Errorf("failed to remove hotplug disk directory %v: %v : %v", diskPath, string(out), err)
×
722
                }
×
723

724
        }
725
        return nil
1✔
726
}
727

728
func (m *volumeMounter) unmountBlockHotplugVolumes(diskPath *safepath.Path, cgroupManager cgroup.Manager) error {
1✔
729
        // Get major and minor so we can deny the container.
1✔
730
        dev, _, err := m.getBlockFileMajorMinor(diskPath, statDevice)
1✔
731
        if err != nil {
1✔
732
                return err
×
733
        }
×
734
        // Delete block device file
735
        if err := safepath.UnlinkAtNoFollow(diskPath); err != nil {
2✔
736
                return err
1✔
737
        }
1✔
738
        return m.removeBlockMajorMinor(dev, cgroupManager)
1✔
739
}
740

741
// UnmountAll unmounts all hotplug disks of a given VMI.
742
func (m *volumeMounter) UnmountAll(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
743
        if vmi.UID != "" {
2✔
744
                logger := log.DefaultLogger().Object(vmi)
1✔
745
                logger.Info("Cleaning up remaining hotplug volumes")
1✔
746
                record, err := m.getMountTargetRecord(vmi)
1✔
747
                if err != nil {
1✔
748
                        return err
×
749
                } else if record == nil {
1✔
750
                        // no entries to unmount
×
751
                        logger.Info("No hotplug volumes found to unmount")
×
752
                        return nil
×
753
                }
×
754

755
                for _, entry := range record.MountTargetEntries {
2✔
756
                        diskPath, err := safepath.NewFileNoFollow(entry.TargetFile)
1✔
757
                        if err != nil {
1✔
758
                                if errors.Is(err, os.ErrNotExist) {
×
759
                                        logger.Infof("Device %v is not mounted anymore, continuing.", entry.TargetFile)
×
760
                                        continue
×
761
                                }
762
                                logger.Warningf("Unable to unmount volume at path %s: %v", entry.TargetFile, err)
×
763
                                continue
×
764
                        }
765
                        diskPath.Close()
1✔
766
                        if isBlock, err := isBlockDevice(diskPath.Path()); err != nil {
1✔
767
                                logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
768
                        } else if isBlock {
2✔
769
                                if err := m.unmountBlockHotplugVolumes(diskPath.Path(), cgroupManager); err != nil {
1✔
770
                                        logger.Warningf("Unable to remove block device at path %s: %v", diskPath, err)
×
771
                                        // Don't return error, try next.
×
772
                                }
×
773
                        } else {
1✔
774
                                if err := m.unmountFileSystemHotplugVolumes(diskPath.Path()); err != nil {
1✔
775
                                        logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
776
                                        // Don't return error, try next.
×
777
                                }
×
778
                        }
779
                }
780
                err = m.deleteMountTargetRecord(vmi)
1✔
781
                if err != nil {
1✔
782
                        return err
×
783
                }
×
784
        }
785
        return nil
1✔
786
}
787

788
func (m *volumeMounter) IsMounted(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID) (bool, error) {
×
789
        virtlauncherUID := m.findVirtlauncherUID(vmi)
×
790
        if virtlauncherUID == "" {
×
791
                // This is not the node the pod is running on.
×
792
                return false, fmt.Errorf("Unable to determine virt-launcher UID")
×
793
        }
×
794
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
×
795
        if err != nil {
×
796
                if errors.Is(err, os.ErrNotExist) {
×
797
                        return false, nil
×
798
                }
×
799
                return false, err
×
800
        }
801
        if m.isBlockVolume(&vmi.Status, volume) {
×
802
                deviceName, err := safepath.JoinNoFollow(targetPath, volume)
×
803
                if err != nil {
×
804
                        if errors.Is(err, os.ErrNotExist) {
×
805
                                return false, nil
×
806
                        }
×
807
                        return false, err
×
808
                }
809
                isBlockExists, _ := isBlockDevice(deviceName)
×
810
                return isBlockExists, nil
×
811
        }
812
        if m.isDirectoryMounted(&vmi.Status, volume) {
×
813
                path, err := safepath.JoinNoFollow(targetPath, volume)
×
814
                if err != nil {
×
815
                        if errors.Is(err, os.ErrNotExist) {
×
816
                                return false, nil
×
817
                        }
×
818
                        return false, err
×
819
                }
820
                return isMounted(path)
×
821
        }
822
        path, err := safepath.JoinNoFollow(targetPath, fmt.Sprintf("%s.img", volume))
×
823
        if err != nil {
×
824
                if errors.Is(err, os.ErrNotExist) {
×
825
                        return false, nil
×
826
                }
×
827
                return false, err
×
828
        }
829
        return isMounted(path)
×
830
}
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