Files
nomad/client/pluginmanager/csimanager/usage_tracker.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

74 lines
1.8 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package csimanager
import (
"sync"
)
// volumeUsageTracker tracks the allocations that depend on a given volume
type volumeUsageTracker struct {
// state is a map of volumeUsageKey to a slice of allocation ids
state map[volumeUsageKey][]string
stateMu sync.Mutex
}
func newVolumeUsageTracker() *volumeUsageTracker {
return &volumeUsageTracker{
state: make(map[volumeUsageKey][]string),
}
}
type volumeUsageKey struct {
id string
ns string
usageOpts UsageOptions
}
func (v *volumeUsageTracker) allocsForKey(key volumeUsageKey) []string {
return v.state[key]
}
func (v *volumeUsageTracker) appendAlloc(key volumeUsageKey, allocID string) {
allocs := v.allocsForKey(key)
allocs = append(allocs, allocID)
v.state[key] = allocs
}
func (v *volumeUsageTracker) removeAlloc(key volumeUsageKey, needle string) {
allocs := v.allocsForKey(key)
var newAllocs []string
for _, allocID := range allocs {
if allocID != needle {
newAllocs = append(newAllocs, allocID)
}
}
if len(newAllocs) == 0 {
delete(v.state, key)
} else {
v.state[key] = newAllocs
}
}
func (v *volumeUsageTracker) Claim(allocID, volID, volNS string, usage *UsageOptions) {
v.stateMu.Lock()
defer v.stateMu.Unlock()
key := volumeUsageKey{id: volID, ns: volNS, usageOpts: *usage}
v.appendAlloc(key, allocID)
}
// Free removes the allocation from the state list for the given alloc. If the
// alloc is the last allocation for the volume then it returns true.
func (v *volumeUsageTracker) Free(allocID, volID, volNS string, usage *UsageOptions) bool {
v.stateMu.Lock()
defer v.stateMu.Unlock()
key := volumeUsageKey{id: volID, ns: volNS, usageOpts: *usage}
v.removeAlloc(key, allocID)
allocs := v.allocsForKey(key)
return len(allocs) == 0
}