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

kubevirt / kubevirt / 0aa7703b-246c-4326-97d5-7b05c597ed36

08 Apr 2026 12:01PM UTC coverage: 71.704% (+0.002%) from 71.702%
0aa7703b-246c-4326-97d5-7b05c597ed36

push

prow

web-flow
Merge pull request #17294 from lyarwood/forward-gomaxprocs

hack/dockerized: forward GOMAXPROCS to build container

77361 of 107890 relevant lines covered (71.7%)

589.54 hits per line

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

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

20
package hotplug_volume
21

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

181
func (r *vmiMountTargetRecord) appendPath(path string) bool {
8✔
182
        for _, entry := range r.MountTargetEntries {
11✔
183
                if entry.TargetFile == path {
4✔
184
                        return false // skip appending if already present
1✔
185
                }
1✔
186
        }
187
        r.MountTargetEntries = append(r.MountTargetEntries, vmiMountTargetEntry{TargetFile: path})
7✔
188
        return true
7✔
189
}
190

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

203
func (m *volumeMounter) deleteMountTargetRecord(vmi *v1.VirtualMachineInstance) error {
4✔
204
        if string(vmi.UID) == "" {
4✔
205
                return fmt.Errorf(unableFindHotplugMountedDir)
×
206
        }
×
207

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

214
        if err == nil {
8✔
215
                for _, target := range record.MountTargetEntries {
9✔
216
                        os.Remove(target.TargetFile)
5✔
217
                }
5✔
218

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

224
        m.mountRecordsLock.Lock()
4✔
225
        defer m.mountRecordsLock.Unlock()
4✔
226
        delete(m.mountRecords, vmi.UID)
4✔
227

4✔
228
        return nil
4✔
229
}
230

231
func (m *volumeMounter) getMountTargetRecord(vmi *v1.VirtualMachineInstance) (*vmiMountTargetRecord, error) {
13✔
232
        var ok bool
13✔
233
        var existingRecord *vmiMountTargetRecord
13✔
234

13✔
235
        if string(vmi.UID) == "" {
14✔
236
                return nil, fmt.Errorf(unableFindHotplugMountedDir)
1✔
237
        }
1✔
238

239
        m.mountRecordsLock.Lock()
12✔
240
        defer m.mountRecordsLock.Unlock()
12✔
241
        existingRecord, ok = m.mountRecords[vmi.UID]
12✔
242

12✔
243
        // first check memory cache
12✔
244
        if ok {
18✔
245
                return existingRecord, nil
6✔
246
        }
6✔
247

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

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

269
                m.mountRecords[vmi.UID] = &record
×
270
                return &record, nil
×
271
        }
272

273
        // not found
274
        return &vmiMountTargetRecord{UsesSafePaths: true}, nil
6✔
275
}
276

277
func (m *volumeMounter) setMountTargetRecord(vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
16✔
278
        if string(vmi.UID) == "" {
17✔
279
                return fmt.Errorf(unableFindHotplugMountedDir)
1✔
280
        }
1✔
281

282
        // XXX: backward compatibility for old unresolved paths, can be removed in July 2023
283
        // After a one-time convert and persist, old records are safe too.
284
        record.UsesSafePaths = true
15✔
285

15✔
286
        m.mountRecordsLock.Lock()
15✔
287
        defer m.mountRecordsLock.Unlock()
15✔
288

15✔
289
        if err := m.checkpointManager.Store(string(vmi.UID), record); err != nil {
15✔
290
                return fmt.Errorf("failed to checkpoint %s, %w", vmi.UID, err)
×
291
        }
×
292
        m.mountRecords[vmi.UID] = record
15✔
293
        return nil
15✔
294
}
295

296
func (m *volumeMounter) writePathToMountRecord(path string, vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
6✔
297
        if !record.appendPath(path) {
7✔
298
                return nil
1✔
299
        }
1✔
300
        if err := m.setMountTargetRecord(vmi, record); err != nil {
5✔
301
                return err
×
302
        }
×
303
        return nil
5✔
304
}
305

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

332
func (m *volumeMounter) Mount(vmi *v1.VirtualMachineInstance, cgroupManager cgroup.Manager) error {
3✔
333
        return m.mountFromPod(vmi, "", cgroupManager)
3✔
334
}
3✔
335

336
func (m *volumeMounter) MountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
×
337
        return m.mountFromPod(vmi, sourceUID, cgroupManager)
×
338
}
×
339

340
func (m *volumeMounter) mountFromPod(vmi *v1.VirtualMachineInstance, sourceUID types.UID, cgroupManager cgroup.Manager) error {
4✔
341
        record, err := m.getMountTargetRecord(vmi)
4✔
342
        if err != nil {
4✔
343
                return err
×
344
        }
×
345

346
        for _, volumeStatus := range vmi.Status.VolumeStatus {
12✔
347
                if volumeStatus.HotplugVolume == nil {
11✔
348
                        // Skip non hotplug volumes
3✔
349
                        continue
3✔
350
                }
351

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

357
                mountDirectory := m.isDirectoryMounted(vmi, volumeStatus.Name)
4✔
358
                if sourceUID == "" {
6✔
359
                        sourceUID = volumeStatus.HotplugVolume.AttachPodUID
2✔
360
                }
2✔
361
                if err := m.mountHotplugVolume(vmi, volumeStatus.Name, sourceUID, record, mountDirectory, cgroupManager); err != nil {
4✔
362
                        return err
×
363
                }
×
364
        }
365
        return nil
4✔
366
}
367

368
func (m *volumeMounter) isDirectoryMounted(vmi *v1.VirtualMachineInstance, volumeName string) bool {
5✔
369
        for _, utilityVolume := range vmi.Spec.UtilityVolumes {
5✔
370
                if utilityVolume.Name == volumeName {
×
371
                        return true
×
372
                }
×
373
        }
374
        for _, volume := range vmi.Spec.Volumes {
7✔
375
                if volume.Name == volumeName {
3✔
376
                        return volume.MemoryDump != nil
1✔
377
                }
1✔
378
        }
379
        return false
4✔
380
}
381

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

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

427
        if _, err := safepath.JoinNoFollow(targetPath, volume); errors.Is(err, os.ErrNotExist) {
5✔
428
                dev, permissions, err := m.getSourceMajorMinor(sourceUID, volume)
2✔
429
                if err != nil {
2✔
430
                        return err
×
431
                }
×
432

433
                if err := m.writePathToMountRecord(filepath.Join(unsafepath.UnsafeAbsolute(targetPath.Raw()), volume), vmi, record); err != nil {
2✔
434
                        return err
×
435
                }
×
436

437
                if err := m.createBlockDeviceFile(targetPath, volume, dev, permissions); err != nil && !os.IsExist(err) {
2✔
438
                        return err
×
439
                }
×
440
                log.DefaultLogger().V(1).Infof("successfully created block device %v", volume)
2✔
441
        } else if err != nil {
1✔
442
                return err
×
443
        }
×
444

445
        devicePath, err := safepath.JoinNoFollow(targetPath, volume)
3✔
446
        if err != nil {
3✔
447
                return err
×
448
        }
×
449
        if isBlockExists, err := isBlockDevice(devicePath); err != nil {
3✔
450
                return err
×
451
        } else if !isBlockExists {
3✔
452
                return fmt.Errorf("target device %v exists but it is not a block device", devicePath)
×
453
        }
×
454

455
        dev, _, err := m.getBlockFileMajorMinor(devicePath, statDevice)
3✔
456
        if err != nil {
3✔
457
                return err
×
458
        }
×
459
        // allow block devices
460
        if err := m.allowBlockMajorMinor(dev, cgroupManager); err != nil {
3✔
461
                return err
×
462
        }
×
463

464
        return m.ownershipManager.SetFileOwnership(devicePath)
3✔
465
}
466

467
func (m *volumeMounter) getSourceMajorMinor(sourceUID types.UID, volumeName string) (uint64, os.FileMode, error) {
5✔
468
        basePath, err := deviceBasePath(sourceUID, m.kubeletPodsDir)
5✔
469
        if err != nil {
5✔
470
                return 0, 0, err
×
471
        }
×
472
        devicePath, err := basePath.AppendAndResolveWithRelativeRoot(volumeName)
5✔
473
        if err != nil {
7✔
474
                return 0, 0, err
2✔
475
        }
2✔
476
        return m.getBlockFileMajorMinor(devicePath, statSourceDevice)
3✔
477
}
478

479
func (m *volumeMounter) getBlockFileMajorMinor(devicePath *safepath.Path, getter func(fileName *safepath.Path) (os.FileInfo, error)) (uint64, os.FileMode, error) {
13✔
480
        fileInfo, err := getter(devicePath)
13✔
481
        if err != nil {
14✔
482
                return 0, 0, err
1✔
483
        }
1✔
484
        info := fileInfo.Sys().(*syscall.Stat_t)
12✔
485
        return info.Rdev, fileInfo.Mode(), nil
12✔
486
}
487

488
func (m *volumeMounter) removeBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
5✔
489
        return m.updateBlockMajorMinor(dev, false, cgroupManager)
5✔
490
}
5✔
491

492
func (m *volumeMounter) allowBlockMajorMinor(dev uint64, cgroupManager cgroup.Manager) error {
4✔
493
        return m.updateBlockMajorMinor(dev, true, cgroupManager)
4✔
494
}
4✔
495

496
func (m *volumeMounter) updateBlockMajorMinor(dev uint64, allow bool, cgroupManager cgroup.Manager) error {
9✔
497
        deviceRule := &devices.Rule{
9✔
498
                Type:        devices.BlockDevice,
9✔
499
                Major:       int64(unix.Major(dev)),
9✔
500
                Minor:       int64(unix.Minor(dev)),
9✔
501
                Permissions: "rwm",
9✔
502
                Allow:       allow,
9✔
503
        }
9✔
504

9✔
505
        if cgroupManager == nil {
9✔
506
                return fmt.Errorf("failed to apply device rule %+v: cgroup manager is nil", *deviceRule)
×
507
        }
×
508

509
        err := cgroupManager.Set(&configs.Resources{
9✔
510
                Devices: []*devices.Rule{deviceRule},
9✔
511
        })
9✔
512

9✔
513
        if err != nil {
9✔
514
                log.Log.Errorf("cgroup %s had failed to set device rule. error: %v. rule: %+v", cgroupManager.GetCgroupVersion(), err, *deviceRule)
×
515
        } else {
9✔
516
                log.Log.Infof("cgroup %s device rule is set successfully. rule: %+v", cgroupManager.GetCgroupVersion(), *deviceRule)
9✔
517
        }
9✔
518

519
        return err
9✔
520
}
521

522
func (m *volumeMounter) createBlockDeviceFile(basePath *safepath.Path, deviceName string, dev uint64, blockDevicePermissions os.FileMode) error {
5✔
523
        if _, err := safepath.JoinNoFollow(basePath, deviceName); errors.Is(err, os.ErrNotExist) {
9✔
524
                return mknodCommand(basePath, deviceName, dev, blockDevicePermissions)
4✔
525
        } else {
5✔
526
                return err
1✔
527
        }
1✔
528
}
529

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

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

574
        return m.ownershipManager.SetFileOwnership(target)
3✔
575
}
576

577
func (m *volumeMounter) findVirtlauncherUID(vmi *v1.VirtualMachineInstance) (uid types.UID) {
13✔
578
        cnt := 0
13✔
579
        for podUID := range vmi.Status.ActivePods {
29✔
580
                _, err := m.hotplugDiskManager.GetHotplugTargetPodPathOnHost(podUID)
16✔
581
                if err == nil {
30✔
582
                        uid = podUID
14✔
583
                        cnt++
14✔
584
                }
14✔
585
        }
586
        if cnt == 1 {
25✔
587
                return
12✔
588
        }
12✔
589
        // Either no pods, or multiple pods, skip.
590
        return ""
1✔
591
}
592

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

609
        for _, findmnt := range findmounts {
14✔
610
                if filepath.Base(findmnt.Target) == volume {
12✔
611
                        source := findmnt.GetSourcePath()
5✔
612
                        path, err := parentPathForMount(nodeIsoRes, isoRes, findmnt)
5✔
613
                        exists := !errors.Is(err, os.ErrNotExist)
5✔
614
                        if err != nil && !errors.Is(err, os.ErrNotExist) {
5✔
615
                                return nil, err
×
616
                        }
×
617

618
                        isBlock := false
5✔
619
                        if exists {
10✔
620
                                isBlock, _ = isBlockDevice(path)
5✔
621
                        }
5✔
622

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

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

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

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

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

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

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

799
        }
800
        return nil
5✔
801
}
802

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

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

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

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