Files
nomad/client/pluginmanager/csimanager/testing.go
Tim Gross 65ae61249c CSI: include volume namespace in staging path (#20532)
CSI volumes are namespaced. But the client does not include the namespace in the
staging mount path. This causes CSI volumes with the same volume ID but
different namespace to collide if they happen to be placed on the same host. The
per-allocation paths don't need to be namespaced, because an allocation can only
mount volumes from its job's own namespace.

Rework the CSI hook tests to have more fine-grained control over the mock
on-disk state. Add tests covering upgrades from staging paths missing
namespaces.

Fixes: https://github.com/hashicorp/nomad/issues/18741
2024-05-13 11:24:09 -04:00

127 lines
3.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package csimanager
import (
"context"
"path/filepath"
"github.com/hashicorp/nomad/client/pluginmanager"
nstructs "github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/csi"
"github.com/hashicorp/nomad/testutil"
)
var _ Manager = &MockCSIManager{}
type MockCSIManager struct {
VM *MockVolumeManager
NextWaitForPluginErr error
NextManagerForPluginErr error
}
func (m *MockCSIManager) PluginManager() pluginmanager.PluginManager {
panic("implement me")
}
func (m *MockCSIManager) WaitForPlugin(_ context.Context, pluginType, pluginID string) error {
return m.NextWaitForPluginErr
}
func (m *MockCSIManager) ManagerForPlugin(_ context.Context, pluginID string) (VolumeManager, error) {
if m.VM == nil {
m.VM = &MockVolumeManager{}
}
return m.VM, m.NextManagerForPluginErr
}
func (m *MockCSIManager) Shutdown() {
panic("implement me")
}
var _ VolumeManager = &MockVolumeManager{}
type MockVolumeManager struct {
CallCounter *testutil.CallCounter
Mounts map[string]bool // lazy set
NextMountVolumeErr error
NextUnmountVolumeErr error
NextExpandVolumeErr error
LastExpandVolumeCall *MockExpandVolumeCall
}
func (m *MockVolumeManager) mountName(volNS, volID, allocID string, usageOpts *UsageOptions) string {
return filepath.Join("test-alloc-dir", allocID, volNS, volID, usageOpts.ToFS())
}
func (m *MockVolumeManager) MountVolume(_ context.Context, vol *nstructs.CSIVolume, alloc *nstructs.Allocation, usageOpts *UsageOptions, publishContext map[string]string) (*MountInfo, error) {
if m.CallCounter != nil {
m.CallCounter.Inc("MountVolume")
}
if m.NextMountVolumeErr != nil {
err := m.NextMountVolumeErr
m.NextMountVolumeErr = nil // reset it
return nil, err
}
// "mount" it
if m.Mounts == nil {
m.Mounts = make(map[string]bool)
}
source := m.mountName(vol.Namespace, vol.ID, alloc.ID, usageOpts)
m.Mounts[source] = true
return &MountInfo{
Source: source,
}, nil
}
func (m *MockVolumeManager) UnmountVolume(_ context.Context, volNS, volID, remoteID, allocID string, usageOpts *UsageOptions) error {
if m.CallCounter != nil {
m.CallCounter.Inc("UnmountVolume")
}
if m.NextUnmountVolumeErr != nil {
err := m.NextUnmountVolumeErr
m.NextUnmountVolumeErr = nil // reset it
return err
}
// "unmount" it
delete(m.Mounts, m.mountName(volNS, volID, allocID, usageOpts))
return nil
}
func (m *MockVolumeManager) HasMount(_ context.Context, mountInfo *MountInfo) (bool, error) {
if m.CallCounter != nil {
m.CallCounter.Inc("HasMount")
}
if m.Mounts == nil {
return false, nil
}
return m.Mounts[mountInfo.Source], nil
}
func (m *MockVolumeManager) ExpandVolume(_ context.Context, volNS, volID, remoteID, allocID string, usageOpts *UsageOptions, capacity *csi.CapacityRange) (int64, error) {
m.LastExpandVolumeCall = &MockExpandVolumeCall{
volNS, volID, remoteID, allocID, usageOpts, capacity,
}
return capacity.RequiredBytes, m.NextExpandVolumeErr
}
type MockExpandVolumeCall struct {
VolNS, VolID, RemoteID, AllocID string
UsageOpts *UsageOptions
Capacity *csi.CapacityRange
}
func (m *MockVolumeManager) ExternalID() string {
return "mock-volume-manager"
}