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

kubevirt / kubevirt / 6b174c3e-3b7f-431b-8df6-c5ae93e097dd

23 Jun 2026 05:49AM UTC coverage: 71.846% (-0.002%) from 71.848%
6b174c3e-3b7f-431b-8df6-c5ae93e097dd

push

prow

web-flow
Merge pull request #18105 from keinsword/nebius/keinsword/hotplug-mount-pod-uid-disambiguation

virt-handler: Fix hotplug mount resolution for multiple attachment pods

21 of 22 new or added lines in 2 files covered. (95.45%)

6 existing lines in 1 file now uncovered.

80401 of 111908 relevant lines covered (71.85%)

569.79 hits per line

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

57.14
/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/cgroups"
45

46
        devices "github.com/opencontainers/cgroups/devices/config"
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
var ErrWaitingForHotplugMount = errors.New("waiting for hotplug mount")
56

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

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

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

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

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

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

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

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

129
        isMounted = func(path *safepath.Path) (bool, error) {
8✔
130
                return isolation.IsMounted(path)
8✔
131
        }
8✔
132

133
        isBlockDevice = func(path *safepath.Path) (bool, error) {
5✔
134
                return isolation.IsBlockDevice(path)
5✔
135
        }
5✔
136

137
        isolationDetector = func() isolation.PodIsolationDetector {
×
138
                return isolation.NewSocketBasedIsolationDetector()
×
139
        }
×
140

141
        parentPathForMount = func(
142
                parent isolation.IsolationResult,
143
                child isolation.IsolationResult,
144
                findmntInfo FindmntInfo,
145
                podUID string,
146
                kubeletPodsDir string,
147
        ) (*safepath.Path, error) {
×
NEW
148
                return isolation.ParentPathForMount(parent, child, findmntInfo.Source, findmntInfo.Target, podUID, kubeletPodsDir)
×
149
        }
×
150
)
151

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

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

176
type vmiMountTargetEntry struct {
177
        TargetFile string `json:"targetFile"`
178
}
179

180
type vmiMountTargetRecord struct {
181
        MountTargetEntries []vmiMountTargetEntry `json:"mountTargetEntries"`
182
        UsesSafePaths      bool                  `json:"usesSafePaths"`
183
}
184

185
func (r *vmiMountTargetRecord) appendPath(path string) bool {
12✔
186
        for _, entry := range r.MountTargetEntries {
16✔
187
                if entry.TargetFile == path {
5✔
188
                        return false // skip appending if already present
1✔
189
                }
1✔
190
        }
191
        r.MountTargetEntries = append(r.MountTargetEntries, vmiMountTargetEntry{TargetFile: path})
11✔
192
        return true
11✔
193
}
194

195
// NewVolumeMounter creates a new VolumeMounter
196
func NewVolumeMounter(mountStateDir string, kubeletPodsDir string, host string) VolumeMounter {
150✔
197
        return &volumeMounter{
150✔
198
                mountRecords:       make(map[types.UID]*vmiMountTargetRecord),
150✔
199
                checkpointManager:  checkpoint.NewSimpleCheckpointManager(mountStateDir),
150✔
200
                hotplugDiskManager: hotplugdisk.NewHotplugDiskManager(kubeletPodsDir),
150✔
201
                ownershipManager:   diskutils.DefaultOwnershipManager,
150✔
202
                kubeletPodsDir:     kubeletPodsDir,
150✔
203
                host:               host,
150✔
204
        }
150✔
205
}
150✔
206

207
func (m *volumeMounter) deleteMountTargetRecord(vmi *v1.VirtualMachineInstance) error {
4✔
208
        if string(vmi.UID) == "" {
4✔
209
                return fmt.Errorf(unableFindHotplugMountedDir)
×
210
        }
×
211

212
        record := vmiMountTargetRecord{}
4✔
213
        err := m.checkpointManager.Get(string(vmi.UID), &record)
4✔
214
        if err != nil && !errors.Is(err, os.ErrNotExist) {
4✔
215
                return fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
216
        }
×
217

218
        if err == nil {
8✔
219
                for _, target := range record.MountTargetEntries {
9✔
220
                        os.Remove(target.TargetFile)
5✔
221
                }
5✔
222

223
                if err := m.checkpointManager.Delete(string(vmi.UID)); err != nil {
4✔
224
                        return fmt.Errorf("failed to delete checkpoint %s, %w", vmi.UID, err)
×
225
                }
×
226
        }
227

228
        m.mountRecordsLock.Lock()
4✔
229
        defer m.mountRecordsLock.Unlock()
4✔
230
        delete(m.mountRecords, vmi.UID)
4✔
231

4✔
232
        return nil
4✔
233
}
234

235
func (m *volumeMounter) getMountTargetRecord(vmi *v1.VirtualMachineInstance) (*vmiMountTargetRecord, error) {
14✔
236
        var ok bool
14✔
237
        var existingRecord *vmiMountTargetRecord
14✔
238

14✔
239
        if string(vmi.UID) == "" {
15✔
240
                return nil, fmt.Errorf(unableFindHotplugMountedDir)
1✔
241
        }
1✔
242

243
        m.mountRecordsLock.Lock()
13✔
244
        defer m.mountRecordsLock.Unlock()
13✔
245
        existingRecord, ok = m.mountRecords[vmi.UID]
13✔
246

13✔
247
        // first check memory cache
13✔
248
        if ok {
19✔
249
                return existingRecord, nil
6✔
250
        }
6✔
251

252
        // if not there, see if record is on disk, this can happen if virt-handler restarts
253
        record := vmiMountTargetRecord{}
7✔
254
        err := m.checkpointManager.Get(string(vmi.UID), &record)
7✔
255
        if err != nil && !errors.Is(err, os.ErrNotExist) {
7✔
256
                return nil, fmt.Errorf("failed to get checkpoint %s, %w", vmi.UID, err)
×
257
        }
×
258

259
        if err == nil {
7✔
260
                // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
×
261
                // After a one-time convert and persist, old records are safe too.
×
262
                if !record.UsesSafePaths {
×
263
                        for i, path := range record.MountTargetEntries {
×
264
                                record.UsesSafePaths = true
×
265
                                safePath, err := safepath.JoinAndResolveWithRelativeRoot("/", path.TargetFile)
×
266
                                if err != nil {
×
267
                                        return nil, fmt.Errorf("failed converting legacy path to safepath: %v", err)
×
268
                                }
×
269
                                record.MountTargetEntries[i].TargetFile = unsafepath.UnsafeAbsolute(safePath.Raw())
×
270
                        }
271
                }
272

273
                m.mountRecords[vmi.UID] = &record
×
274
                return &record, nil
×
275
        }
276

277
        // not found
278
        return &vmiMountTargetRecord{UsesSafePaths: true}, nil
7✔
279
}
280

281
func (m *volumeMounter) setMountTargetRecord(vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
20✔
282
        if string(vmi.UID) == "" {
21✔
283
                return fmt.Errorf(unableFindHotplugMountedDir)
1✔
284
        }
1✔
285

286
        // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
287
        // After a one-time convert and persist, old records are safe too.
288
        record.UsesSafePaths = true
19✔
289

19✔
290
        m.mountRecordsLock.Lock()
19✔
291
        defer m.mountRecordsLock.Unlock()
19✔
292

19✔
293
        if err := m.checkpointManager.Store(string(vmi.UID), record); err != nil {
19✔
294
                return fmt.Errorf("failed to checkpoint %s, %w", vmi.UID, err)
×
295
        }
×
296
        m.mountRecords[vmi.UID] = record
19✔
297
        return nil
19✔
298
}
299

300
func (m *volumeMounter) writePathToMountRecord(path string, vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
10✔
301
        if !record.appendPath(path) {
11✔
302
                return nil
1✔
303
        }
1✔
304
        if err := m.setMountTargetRecord(vmi, record); err != nil {
9✔
305
                return err
×
306
        }
×
307
        return nil
9✔
308
}
309

310
func (m *volumeMounter) mountHotplugVolume(
311
        vmi *v1.VirtualMachineInstance,
312
        volumeName string,
313
        sourceUID types.UID,
314
        record *vmiMountTargetRecord,
315
        mountDirectory bool,
316
        cgroupManager cgroup.Manager,
317
) error {
7✔
318
        logger := log.Log.Object(vmi)
7✔
319
        logger.V(4).Infof("Hotplug check volume name: %s", volumeName)
7✔
320
        if sourceUID != "" {
13✔
321
                if m.isBlockVolume(&vmi.Status, volumeName) {
10✔
322
                        logger.V(3).Infof("Mounting block volume: %s", volumeName)
4✔
323
                        if err := m.mountBlockHotplugVolume(vmi, volumeName, sourceUID, record, cgroupManager); err != nil {
4✔
324
                                return fmt.Errorf("failed to mount block hotplug volume %s: %w", volumeName, err)
×
325
                        }
×
326
                } else {
2✔
327
                        logger.V(3).Infof("Mounting file system volume: %s", volumeName)
2✔
328
                        if err := m.mountFileSystemHotplugVolume(vmi, volumeName, sourceUID, record, mountDirectory); err != nil {
2✔
329
                                return fmt.Errorf("failed to mount filesystem hotplug volume %s: %w", volumeName, err)
×
330
                        }
×
331
                }
332
        }
333
        return nil
7✔
334
}
335

336
func (m *volumeMounter) Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
4✔
337
        return m.mountFromPod(vmi, "", cgroupManager)
4✔
338
}
4✔
339

340
func (m *volumeMounter) MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
×
341
        return m.mountFromPod(vmi, sourceUID, cgroupManager)
×
342
}
×
343

344
func (m *volumeMounter) mountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
5✔
345
        record, err := m.getMountTargetRecord(vmi)
5✔
346
        if err != nil {
5✔
347
                return err
×
348
        }
×
349

350
        for _, volumeStatus := range vmi.Status.VolumeStatus {
16✔
351
                if volumeStatus.HotplugVolume == nil {
14✔
352
                        // Skip non hotplug volumes
3✔
353
                        continue
3✔
354
                }
355

356
                if storagetypes.IsUtilityVolume(vmi, volumeStatus.Name) && m.isBlockVolume(&vmi.Status, volumeStatus.Name) {
9✔
357
                        log.Log.Object(vmi).Warningf("Skipping mount for utility volume %s: configured with block mode PVC, utility volumes require filesystem mode", volumeStatus.Name)
1✔
358
                        continue
1✔
359
                }
360

361
                mountDirectory := m.isDirectoryMounted(vmi, volumeStatus.Name)
7✔
362
                volumeSourceUID := sourceUID
7✔
363
                if volumeSourceUID == "" {
14✔
364
                        volumeSourceUID = volumeStatus.HotplugVolume.AttachPodUID
7✔
365
                }
7✔
366
                if err := m.mountHotplugVolume(vmi, volumeStatus.Name, volumeSourceUID, record, mountDirectory, cgroupManager); err != nil {
7✔
367
                        return err
×
368
                }
×
369
        }
370
        return nil
5✔
371
}
372

373
func (m *volumeMounter) isDirectoryMounted(vmi *v1.VirtualMachineInstance, volumeName string) bool {
8✔
374
        for _, utilityVolume := range vmi.Spec.UtilityVolumes {
8✔
375
                if utilityVolume.Name == volumeName {
×
376
                        return true
×
377
                }
×
378
        }
379
        for _, volume := range vmi.Spec.Volumes {
10✔
380
                if volume.Name == volumeName {
3✔
381
                        return volume.MemoryDump != nil
1✔
382
                }
1✔
383
        }
384
        return false
7✔
385
}
386

387
// isBlockVolume checks if the volumeDevices directory exists in the pod path, we assume there is a single volume associated with
388
// each pod, we use this knowledge to determine if we have a block volume or not.
389
func (m *volumeMounter) isBlockVolume(vmiStatus *v1.VirtualMachineInstanceStatus, volumeName string) bool {
13✔
390
        // First evaluate the migrated volumed. In the case of a migrated volume, virt-handler needs to understand if it needs to consider the
13✔
391
        // 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.
13✔
392
        isDstHost := false
13✔
393
        if vmiStatus.MigrationState != nil {
13✔
394
                isDstHost = vmiStatus.MigrationState.TargetNode == m.host
×
395
        }
×
396
        for _, migVol := range vmiStatus.MigratedVolumes {
13✔
397
                if migVol.VolumeName == volumeName {
×
398
                        if isDstHost && migVol.DestinationPVCInfo != nil {
×
399
                                return storagetypes.IsPVCBlock(migVol.DestinationPVCInfo.VolumeMode)
×
400
                        }
×
401
                        if !isDstHost && migVol.SourcePVCInfo != nil {
×
402
                                return storagetypes.IsPVCBlock(migVol.SourcePVCInfo.VolumeMode)
×
403
                        }
×
404
                }
405
        }
406
        // Check if the volumeDevices directory exists in the attachment pod, if so, its a block device, otherwise its file system.
407
        for _, status := range vmiStatus.VolumeStatus {
34✔
408
                if status.Name == volumeName {
33✔
409
                        return status.PersistentVolumeClaimInfo != nil && storagetypes.IsPVCBlock(status.PersistentVolumeClaimInfo.VolumeMode)
12✔
410
                }
12✔
411
        }
412
        return false
1✔
413
}
414

415
func (m *volumeMounter) mountBlockHotplugVolume(
416
        vmi *v1.VirtualMachineInstance,
417
        volume string,
418
        sourceUID types.UID,
419
        record *vmiMountTargetRecord,
420
        cgroupManager cgroup.Manager,
421
) error {
5✔
422
        virtlauncherUID := m.findVirtlauncherUID(vmi)
5✔
423
        if virtlauncherUID == "" {
5✔
424
                // This is not the node the pod is running on.
×
425
                return nil
×
426
        }
×
427
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
5✔
428
        if err != nil {
5✔
429
                return err
×
430
        }
×
431

432
        if _, err := safepath.JoinNoFollow(targetPath, volume); errors.Is(err, os.ErrNotExist) {
9✔
433
                dev, permissions, err := m.getSourceMajorMinor(sourceUID, volume)
4✔
434
                if err != nil {
4✔
435
                        return err
×
436
                }
×
437

438
                if err := m.writePathToMountRecord(filepath.Join(unsafepath.UnsafeAbsolute(targetPath.Raw()), volume), vmi, record); err != nil {
4✔
439
                        return err
×
440
                }
×
441

442
                if err := m.createBlockDeviceFile(targetPath, volume, dev, permissions); err != nil && !os.IsExist(err) {
4✔
443
                        return err
×
444
                }
×
445
                log.Log.Object(vmi).V(1).Infof("successfully created block device %s", volume)
4✔
446
        } else if err != nil {
1✔
447
                return err
×
448
        }
×
449

450
        devicePath, err := safepath.JoinNoFollow(targetPath, volume)
5✔
451
        if err != nil {
5✔
452
                return err
×
453
        }
×
454
        if isBlockExists, err := isBlockDevice(devicePath); err != nil {
5✔
455
                return err
×
456
        } else if !isBlockExists {
5✔
457
                return fmt.Errorf("target device %v exists but it is not a block device", devicePath)
×
458
        }
×
459

460
        dev, _, err := m.getBlockFileMajorMinor(devicePath, statDevice)
5✔
461
        if err != nil {
5✔
462
                return err
×
463
        }
×
464
        // allow block devices
465
        if err := m.allowBlockMajorMinor(dev, cgroupManager); err != nil {
5✔
466
                return err
×
467
        }
×
468

469
        return m.ownershipManager.SetFileOwnership(devicePath)
5✔
470
}
471

472
func (m *volumeMounter) getSourceMajorMinor(sourceUID types.UID, volumeName string) (uint64, os.FileMode, error) {
7✔
473
        basePath, err := deviceBasePath(sourceUID, m.kubeletPodsDir)
7✔
474
        if err != nil {
7✔
475
                return 0, 0, err
×
476
        }
×
477
        devicePath, err := basePath.AppendAndResolveWithRelativeRoot(volumeName)
7✔
478
        if err != nil {
9✔
479
                return 0, 0, err
2✔
480
        }
2✔
481
        return m.getBlockFileMajorMinor(devicePath, statSourceDevice)
5✔
482
}
483

484
func (m *volumeMounter) getBlockFileMajorMinor(devicePath *safepath.Path, getter func(fileName *safepath.Path) (os.FileInfo, error)) (uint64, os.FileMode, error) {
17✔
485
        fileInfo, err := getter(devicePath)
17✔
486
        if err != nil {
18✔
487
                return 0, 0, err
1✔
488
        }
1✔
489
        info := fileInfo.Sys().(*syscall.Stat_t)
16✔
490
        return info.Rdev, fileInfo.Mode(), nil
16✔
491
}
492

493
func (m *volumeMounter) removeBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
5✔
494
        return m.updateBlockMajorMinor(dev, false, cgroupManager)
5✔
495
}
5✔
496

497
func (m *volumeMounter) allowBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
6✔
498
        return m.updateBlockMajorMinor(dev, true, cgroupManager)
6✔
499
}
6✔
500

501
func (m *volumeMounter) updateBlockMajorMinor(dev uint64, allow bool, cgroupManager cgroup.Manager) error {
11✔
502
        deviceRule := &devices.Rule{
11✔
503
                Type:        devices.BlockDevice,
11✔
504
                Major:       int64(unix.Major(dev)),
11✔
505
                Minor:       int64(unix.Minor(dev)),
11✔
506
                Permissions: "rwm",
11✔
507
                Allow:       allow,
11✔
508
        }
11✔
509

11✔
510
        if cgroupManager == nil {
11✔
511
                return fmt.Errorf("failed to apply device rule %+v: cgroup manager is nil", *deviceRule)
×
512
        }
×
513

514
        err := cgroupManager.Set(&cgroups.Resources{
11✔
515
                Devices: []*devices.Rule{deviceRule},
11✔
516
        })
11✔
517

11✔
518
        if err != nil {
11✔
519
                log.Log.Errorf("cgroup %s had failed to set device rule. error: %v. rule: %+v", cgroupManager.GetCgroupVersion(), err, *deviceRule)
×
520
        } else {
11✔
521
                log.Log.Infof("cgroup %s device rule is set successfully. rule: %+v", cgroupManager.GetCgroupVersion(), *deviceRule)
11✔
522
        }
11✔
523

524
        return err
11✔
525
}
526

527
func (m *volumeMounter) createBlockDeviceFile(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
7✔
528
        if _, err := safepath.JoinNoFollow(basePath, deviceName); errors.Is(err, os.ErrNotExist) {
13✔
529
                return mknodCommand(basePath, deviceName, dev, blockDevicePermissions)
6✔
530
        } else {
7✔
531
                return err
1✔
532
        }
1✔
533
}
534

535
func (m *volumeMounter) mountFileSystemHotplugVolume(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID, record *vmiMountTargetRecord, mountDirectory bool) error {
6✔
536
        virtlauncherUID := m.findVirtlauncherUID(vmi)
6✔
537
        if virtlauncherUID == "" {
6✔
538
                // This is not the node the pod is running on.
×
539
                return nil
×
540
        }
×
541
        var target *safepath.Path
6✔
542
        var err error
6✔
543
        if mountDirectory {
6✔
544
                target, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, volume, true)
×
545
        } else {
6✔
546
                target, err = m.hotplugDiskManager.GetFileSystemDiskTargetPathFromHostView(virtlauncherUID, volume, true)
6✔
547
        }
6✔
548
        if err != nil {
6✔
549
                return err
×
550
        }
×
551

552
        isMounted, err := isMounted(target)
6✔
553
        if err != nil {
6✔
554
                return fmt.Errorf("failed to determine if %s is already mounted: %v", target, err)
×
555
        }
×
556
        if !isMounted {
12✔
557
                sourcePath, err := m.getSourcePodFilePath(sourceUID, vmi, volume)
6✔
558
                if err != nil {
7✔
559
                        return fmt.Errorf("failed to get source path for volume %s from source pod %s: %v: %w", volume, sourceUID, err, ErrWaitingForHotplugMount)
1✔
560
                }
1✔
561
                if err := m.writePathToMountRecord(unsafepath.UnsafeAbsolute(target.Raw()), vmi, record); err != nil {
5✔
562
                        return err
×
563
                }
×
564
                if !mountDirectory {
10✔
565
                        sourcePath, err = sourcePath.AppendAndResolveWithRelativeRoot("disk.img")
5✔
566
                        if err != nil {
6✔
567
                                return err
1✔
568
                        }
1✔
569
                }
570
                if out, err := mountCommand(sourcePath, target); err != nil {
4✔
571
                        return fmt.Errorf("failed to bindmount hotplug volume source from %v to %v: %v : %v", sourcePath, target, string(out), err)
×
572
                }
×
573
                log.Log.Object(vmi).V(1).Infof("successfully mounted hotplug volume %s", volume)
4✔
574
        }
575

576
        return m.ownershipManager.SetFileOwnership(target)
4✔
577
}
578

579
func (m *volumeMounter) findVirtlauncherUID(vmi *v1.VirtualMachineInstance) (uid types.UID) {
18✔
580
        cnt := 0
18✔
581
        for podUID := range vmi.Status.ActivePods {
39✔
582
                _, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(podUID)
21✔
583
                if err == nil {
40✔
584
                        uid = podUID
19✔
585
                        cnt++
19✔
586
                }
19✔
587
        }
588
        if cnt == 1 {
35✔
589
                return
17✔
590
        }
17✔
591
        // Either no pods, or multiple pods, skip.
592
        return ""
1✔
593
}
594

595
func (m *volumeMounter) getSourcePodFilePath(sourceUID types.UID, vmi *v1.VirtualMachineInstance, volume string) (*safepath.Path, error) {
12✔
596
        iso := isolationDetector()
12✔
597
        isoRes, err := iso.DetectForSocket(socketPath(sourceUID))
12✔
598
        if err != nil {
14✔
599
                return nil, err
2✔
600
        }
2✔
601
        findmounts, err := LookupFindmntInfoByVolume(volume, isoRes.Pid())
10✔
602
        if err != nil {
11✔
603
                return nil, err
1✔
604
        }
1✔
605
        nodeIsoRes := nodeIsolationResult()
9✔
606
        mountRoot, err := nodeIsoRes.MountRoot()
9✔
607
        if err != nil {
9✔
608
                return nil, err
×
609
        }
×
610

611
        for _, findmnt := range findmounts {
18✔
612
                if filepath.Base(findmnt.Target) == volume {
16✔
613
                        source := findmnt.GetSourcePath()
7✔
614
                        path, err := parentPathForMount(nodeIsoRes, isoRes, findmnt, string(sourceUID), m.kubeletPodsDir)
7✔
615
                        exists := !errors.Is(err, os.ErrNotExist)
7✔
616
                        if err != nil && !errors.Is(err, os.ErrNotExist) {
7✔
617
                                return nil, err
×
618
                        }
×
619

620
                        isBlock := false
7✔
621
                        if exists {
14✔
622
                                isBlock, _ = isBlockDevice(path)
7✔
623
                        }
7✔
624

625
                        if !exists || isBlock {
7✔
626
                                // file not found, or block device, or directory check if we can find the mount.
×
627
                                deviceFindMnt, err := LookupFindmntInfoByDevice(source)
×
628
                                if err != nil {
×
629
                                        // Try the device found from the source
×
630
                                        deviceFindMnt, err = LookupFindmntInfoByDevice(findmnt.GetSourceDevice())
×
631
                                        if err != nil {
×
632
                                                return nil, err
×
633
                                        }
×
634
                                        // Check if the path was relative to the device.
635
                                        if !exists {
×
636
                                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target, source)
×
637
                                        }
×
638
                                        return nil, err
×
639
                                }
640
                                return mountRoot.AppendAndResolveWithRelativeRoot(deviceFindMnt[0].Target)
×
641
                        } else {
7✔
642
                                return path, nil
7✔
643
                        }
7✔
644
                }
645
        }
646
        // Did not find the disk image file, return error
647
        return nil, fmt.Errorf("unable to find source disk image path for pod %s", sourceUID)
2✔
648
}
649

650
// Unmount unmounts all hotplug disk that are no longer part of the VMI
651
func (m *volumeMounter) Unmount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
3✔
652
        if vmi.UID != "" {
6✔
653
                record, err := m.getMountTargetRecord(vmi)
3✔
654
                if err != nil {
3✔
655
                        return err
×
656
                } else if record == nil {
3✔
657
                        // no entries to unmount
×
658
                        return nil
×
659
                }
×
660
                if len(record.MountTargetEntries) == 0 {
3✔
661
                        return nil
×
662
                }
×
663

664
                currentHotplugPaths := make(map[string]types.UID)
3✔
665
                virtlauncherUID := m.findVirtlauncherUID(vmi)
3✔
666

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

719
                        var path *safepath.Path
×
720
                        var err error
×
721
                        path, err = m.hotplugDiskManager.GetFileSystemDirectoryTargetPathFromHostView(virtlauncherUID, utilityVolume.Name, false)
×
722
                        if errors.Is(err, os.ErrNotExist) {
×
723
                                // already unmounted or never mounted
×
724
                                continue
×
725
                        }
726
                        if err != nil {
×
727
                                return err
×
728
                        }
×
729
                        currentHotplugPaths[unsafepath.UnsafeAbsolute(path.Raw())] = virtlauncherUID
×
730
                }
731
                newRecord := vmiMountTargetRecord{
3✔
732
                        MountTargetEntries: make([]vmiMountTargetEntry, 0),
3✔
733
                }
3✔
734
                var errs []error
3✔
735
                logErrAndKeepMount := func(path string, format string, args ...any) {
4✔
736
                        err := fmt.Errorf(format, args...)
1✔
737
                        log.Log.Object(vmi).Error(err.Error())
1✔
738
                        newRecord.appendPath(path)
1✔
739
                        errs = append(errs, err)
1✔
740
                }
1✔
741
                for _, entry := range record.MountTargetEntries {
8✔
742
                        fd, err := safepath.NewFileNoFollow(entry.TargetFile)
5✔
743
                        if err != nil {
5✔
744
                                if errors.Is(err, os.ErrNotExist) {
×
745
                                        log.Log.Object(vmi).Infof("Volume %v is not mounted anymore, continuing", entry.TargetFile)
×
746
                                } else {
×
747
                                        logErrAndKeepMount(entry.TargetFile, "Unable to unmount volume at path %s: %v", entry.TargetFile, err)
×
748
                                }
×
749
                                continue
×
750
                        }
751
                        fd.Close()
5✔
752
                        diskPath := fd.Path()
5✔
753
                        diskPathAbs := unsafepath.UnsafeAbsolute(diskPath.Raw())
5✔
754

5✔
755
                        if _, ok := currentHotplugPaths[diskPathAbs]; !ok {
9✔
756
                                if blockDevice, err := isBlockDevice(diskPath); err != nil {
5✔
757
                                        logErrAndKeepMount(diskPathAbs, "Unable to unmount volume at path %s: %v", diskPath, err)
1✔
758
                                        continue
1✔
759
                                } else if blockDevice {
4✔
760
                                        if err := m.unmountBlockHotplugVolumes(diskPath, cgroupManager); err != nil {
1✔
761
                                                logErrAndKeepMount(diskPathAbs, "Unable to remove block device at path %s: %v", diskPath, err)
×
762
                                                continue
×
763
                                        }
764
                                } else if err := m.unmountFileSystemHotplugVolumes(diskPath); err != nil {
2✔
765
                                        logErrAndKeepMount(diskPathAbs, "Unable to unmount filesystem volume at path %s: %v", diskPath, err)
×
766
                                        continue
×
767
                                }
768
                                log.Log.Object(vmi).V(3).Infof("Unmounted hotplug volume path %s", diskPath)
3✔
769
                        } else {
1✔
770
                                _ = newRecord.appendPath(diskPathAbs)
1✔
771
                        }
1✔
772
                }
773
                if len(newRecord.MountTargetEntries) > 0 {
5✔
774
                        err = m.setMountTargetRecord(vmi, &newRecord)
2✔
775
                } else {
3✔
776
                        err = m.deleteMountTargetRecord(vmi)
1✔
777
                }
1✔
778
                if err != nil {
3✔
779
                        return err
×
780
                }
×
781
                if len(errs) > 0 {
4✔
782
                        return fmt.Errorf("failed to cleanup hotplug mounts for VMI %s: %w", vmi.Name, errors.Join(errs...))
1✔
783
                }
1✔
784
        }
785
        return nil
2✔
786
}
787

788
func (m *volumeMounter) unmountFileSystemHotplugVolumes(diskPath *safepath.Path) error {
7✔
789
        if mounted, err := isMounted(diskPath); err != nil {
8✔
790
                return fmt.Errorf("failed to check mount point for hotplug disk %v: %v", diskPath, err)
1✔
791
        } else if mounted {
10✔
792
                out, err := unmountCommand(diskPath)
3✔
793
                if err != nil {
4✔
794
                        return fmt.Errorf("failed to unmount hotplug disk %v: %v : %v", diskPath, string(out), err)
1✔
795
                }
1✔
796
                err = safepath.UnlinkAtNoFollow(diskPath)
2✔
797
                if err != nil {
2✔
798
                        return fmt.Errorf("failed to remove hotplug disk directory %v: %v : %v", diskPath, string(out), err)
×
799
                }
×
800

801
        }
802
        return nil
5✔
803
}
804

805
func (m *volumeMounter) unmountBlockHotplugVolumes(diskPath *safepath.Path, cgroupManager cgroup.Manager) error {
5✔
806
        // Get major and minor so we can deny the container.
5✔
807
        dev, _, err := m.getBlockFileMajorMinor(diskPath, statDevice)
5✔
808
        if err != nil {
5✔
809
                return err
×
810
        }
×
811
        // Delete block device file
812
        if err := safepath.UnlinkAtNoFollow(diskPath); err != nil {
6✔
813
                return err
1✔
814
        }
1✔
815
        return m.removeBlockMajorMinor(dev, cgroupManager)
4✔
816
}
817

818
// UnmountAll unmounts all hotplug disks of a given VMI.
819
func (m *volumeMounter) UnmountAll(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
1✔
820
        if vmi.UID != "" {
2✔
821
                logger := log.DefaultLogger().Object(vmi)
1✔
822
                logger.Info("Cleaning up remaining hotplug volumes")
1✔
823
                record, err := m.getMountTargetRecord(vmi)
1✔
824
                if err != nil {
1✔
825
                        return err
×
826
                } else if record == nil {
1✔
827
                        // no entries to unmount
×
828
                        logger.Info("No hotplug volumes found to unmount")
×
829
                        return nil
×
830
                }
×
831

832
                for _, entry := range record.MountTargetEntries {
3✔
833
                        diskPath, err := safepath.NewFileNoFollow(entry.TargetFile)
2✔
834
                        if err != nil {
2✔
835
                                if errors.Is(err, os.ErrNotExist) {
×
836
                                        logger.Infof("Device %v is not mounted anymore, continuing.", entry.TargetFile)
×
837
                                        continue
×
838
                                }
839
                                logger.Warningf("Unable to unmount volume at path %s: %v", entry.TargetFile, err)
×
840
                                continue
×
841
                        }
842
                        diskPath.Close()
2✔
843
                        if isBlock, err := isBlockDevice(diskPath.Path()); err != nil {
2✔
844
                                logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
845
                        } else if isBlock {
3✔
846
                                if err := m.unmountBlockHotplugVolumes(diskPath.Path(), cgroupManager); err != nil {
1✔
847
                                        logger.Warningf("Unable to remove block device at path %s: %v", diskPath, err)
×
848
                                        // Don't return error, try next.
×
849
                                }
×
850
                        } else {
1✔
851
                                if err := m.unmountFileSystemHotplugVolumes(diskPath.Path()); err != nil {
1✔
852
                                        logger.Warningf("Unable to unmount volume at path %s: %v", diskPath, err)
×
853
                                        // Don't return error, try next.
×
854
                                }
×
855
                        }
856
                }
857
                err = m.deleteMountTargetRecord(vmi)
1✔
858
                if err != nil {
1✔
859
                        return err
×
860
                }
×
861
        }
862
        return nil
1✔
863
}
864

865
func (m *volumeMounter) IsMounted(vmi *v1.VirtualMachineInstance, volume string, sourceUID types.UID) (bool, error) {
×
866
        virtlauncherUID := m.findVirtlauncherUID(vmi)
×
867
        if virtlauncherUID == "" {
×
868
                // This is not the node the pod is running on.
×
869
                return false, fmt.Errorf("Unable to determine virt-launcher UID")
×
870
        }
×
871
        targetPath, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(virtlauncherUID)
×
872
        if err != nil {
×
873
                if errors.Is(err, os.ErrNotExist) {
×
874
                        return false, nil
×
875
                }
×
876
                return false, err
×
877
        }
878
        if m.isBlockVolume(&vmi.Status, volume) {
×
879
                deviceName, err := safepath.JoinNoFollow(targetPath, volume)
×
880
                if err != nil {
×
881
                        if errors.Is(err, os.ErrNotExist) {
×
882
                                return false, nil
×
883
                        }
×
884
                        return false, err
×
885
                }
886
                isBlockExists, _ := isBlockDevice(deviceName)
×
887
                return isBlockExists, nil
×
888
        }
889
        if m.isDirectoryMounted(vmi, volume) {
×
890
                path, err := safepath.JoinNoFollow(targetPath, volume)
×
891
                if err != nil {
×
892
                        if errors.Is(err, os.ErrNotExist) {
×
893
                                return false, nil
×
894
                        }
×
895
                        return false, err
×
896
                }
897
                return isMounted(path)
×
898
        }
899
        path, err := safepath.JoinNoFollow(targetPath, fmt.Sprintf("%s.img", volume))
×
900
        if err != nil {
×
901
                if errors.Is(err, os.ErrNotExist) {
×
902
                        return false, nil
×
903
                }
×
904
                return false, err
×
905
        }
906
        return isMounted(path)
×
907
}
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