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

kubevirt / kubevirt / b2197dcb-9a7c-402e-bb7a-f2cc0633cab0

05 May 2025 05:39PM UTC coverage: 71.626% (-0.001%) from 71.627%
b2197dcb-9a7c-402e-bb7a-f2cc0633cab0

push

prow

web-flow
Merge pull request #14491 from Acedus/log-hotplug-failure-unpopulated-file-pvc

virt-handler: emit VMI event on non-fatal hotplug failure

3 of 10 new or added lines in 2 files covered. (30.0%)

62418 of 87144 relevant lines covered (71.63%)

0.8 hits per line

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

52.53
/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✔
NEW
291
                                return fmt.Errorf("failed to mount block hotplug volume %s: %w", volumeName, err)
×
292
                        }
×
293
                } else {
1✔
294
                        logger.V(4).Infof("Mounting file system volume: %s", volumeName)
1✔
295
                        if err := m.mountFileSystemHotplugVolume(vmi, volumeName, sourceUID, record, mountDirectory); err != nil {
1✔
NEW
296
                                return fmt.Errorf("failed to mount filesystem hotplug volume %s: %w", volumeName, err)
×
297
                        }
×
298
                }
299
        }
300
        return nil
1✔
301
}
302

303
func (m *volumeMounter) Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
304
        return m.mountFromPod(vmi, "", cgroupManager)
1✔
305
}
1✔
306

307
func (m *volumeMounter) MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
×
308
        return m.mountFromPod(vmi, sourceUID, cgroupManager)
×
309
}
×
310

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

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

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

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

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

395
                if err := m.writePathToMountRecord(filepath.Join(unsafepath.UnsafeAbsolute(targetPath.Raw()), volume), vmi, record); err != nil {
1✔
396
                        return err
×
397
                }
×
398

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

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

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

426
        return m.ownershipManager.SetFileOwnership(devicePath)
1✔
427
}
428

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

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

450
func (m *volumeMounter) removeBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
1✔
451
        return m.updateBlockMajorMinor(dev, false, cgroupManager)
1✔
452
}
1✔
453

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

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

1✔
467
        if cgroupManager == nil {
1✔
468
                return fmt.Errorf("failed to apply device rule %+v: cgroup manager is nil", *deviceRule)
×
469
        }
×
470

471
        err := cgroupManager.Set(&configs.Resources{
1✔
472
                Devices: []*devices.Rule{deviceRule},
1✔
473
        })
1✔
474

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

481
        return err
1✔
482
}
483

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

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

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

536
        return m.ownershipManager.SetFileOwnership(target)
1✔
537
}
538

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

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

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

580
                        isBlock := false
1✔
581
                        if exists {
2✔
582
                                isBlock, _ = isBlockDevice(path)
1✔
583
                        }
1✔
584

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

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

624
                currentHotplugPaths := make(map[string]types.UID)
1✔
625
                virtlauncherUID := m.findVirtlauncherUID(vmi)
1✔
626

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

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

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

720
        }
721
        return nil
1✔
722
}
723

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

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

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

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