Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions charts/latest/templates/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ spec:
- --enable-lvm-orphan-cleanup={{ .Values.cleanup.lvmOrphanCleanup.enabled }}
- --lvm-orphan-cleanup-interval={{ .Values.cleanup.lvmOrphanCleanup.interval }}
- --run-alongside-webhook={{ .Values.webhook.hyperconverged.enabled | default false }}

- --disk-path-prefixes={{ join "," (.Values.diskSelection.extraPathPrefixes | default list) }}
- --disk-models={{ join "," (.Values.diskSelection.extraModels | default list) }}
- --disk-types={{ join "," (.Values.diskSelection.extraTypes | default list) }}
command:
- /local-csi-driver
env:
Expand Down
16 changes: 16 additions & 0 deletions charts/latest/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ raid:
# StorageClass in the `VolumeGroup` parameter.
volumeGroup: containerstorage


# Disk selection configuration.
# Controls which block devices the driver picks up as LVM physical volumes.
# A device must match ALL of the criteria below (path prefix AND model AND
# type). Within each list, a device matches if it satisfies ANY entry.
# Leave a list empty to disable filtering on that attribute. The defaults
# target NVMe disks found on Azure VMs.
diskSelection:
# Additional device path prefixes to select, appended to the built-in defaults.
extraPathPrefixes: []
# Additional disk models to select, appended to the built-in defaults.
# Add new SKUs here as they are introduced.
extraModels: []
# Additional device types to select, appended to the built-in defaults.
extraTypes: []

# Garbage collection and cleanup configuration.
cleanup:
# Cleanup volume groups and physical volumes on pod termination if logical volume are not in use.
Expand Down
25 changes: 22 additions & 3 deletions cmd/driver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
Expand Down Expand Up @@ -88,6 +89,9 @@ func main() {
var enableLVMOrphanCleanup bool
var lvmOrphanCleanupInterval time.Duration
var runAlongsideWebhook bool
var diskPathPrefixes string
var diskModels string
var diskTypes string
flag.StringVar(&nodeName, "node-name", "",
"The name of the node this agent is running on.")
flag.StringVar(&podName, "pod-name", "",
Expand Down Expand Up @@ -129,7 +133,12 @@ func main() {
"Interval for the LVM orphan cleanup controller to scan and clean up orphaned volumes.")
flag.BoolVar(&runAlongsideWebhook, "run-alongside-webhook", false,
"If set, indicates that the driver is running alongside a separate webhook deployment. This affects PV node affinity behavior.")
// Initialize logger flagsconfig.
flag.StringVar(&diskPathPrefixes, "disk-path-prefixes", "",
"Comma-separated list of additional device path prefixes to select (e.g. /dev/sda), appended to the built-in defaults.")
flag.StringVar(&diskModels, "disk-models", "",
"Comma-separated list of additional device models to select, appended to the built-in defaults.")
flag.StringVar(&diskTypes, "disk-types", "",
"Comma-separated list of additional device types to select (e.g. loop), appended to the built-in defaults.") // Initialize logger flagsconfig.
logConfig := textlogger.NewConfig(textlogger.VerbosityFlagName("v"))
logConfig.AddFlags(flag.CommandLine)

Expand Down Expand Up @@ -239,8 +248,18 @@ func main() {

// TODO(sc): move filter to controller so we can read filters from
// storageclass params. Hardcoded for now.

// Build the disk selection filter by appending any CLI-provided entries to
// the built-in defaults. This preserves the default NVMe selection while
// letting operators add extra path prefixes, models, or types via Helm.
diskFilter := probe.NewEphemeralDiskFilter(
strings.Split(diskPathPrefixes, ","),
strings.Split(diskModels, ","),
strings.Split(diskTypes, ","),
)

blockDevUtils := block.New()
deviceProbe := probe.New(blockDevUtils, probe.EphemeralDiskFilter)
deviceProbe := probe.New(blockDevUtils, diskFilter)

// Create the LVM manager.
// LVM manager is an abstraction that understands how to create and
Expand Down Expand Up @@ -276,7 +295,7 @@ func main() {

// Run startup diagnostic to check disk availability and emit a Warning
// event on the pod if no disks are available for volume group creation.
startupDiag := lvm.NewStartupDiagnostic(deviceProbe, blockDevUtils, probe.EphemeralDiskFilter, recorder, selfPod)
startupDiag := lvm.NewStartupDiagnostic(deviceProbe, blockDevUtils, diskFilter, recorder, selfPod)
if err := mgr.Add(startupDiag); err != nil {
logAndExit(err, "unable to add startup diagnostic to manager")
}
Expand Down
18 changes: 9 additions & 9 deletions internal/csi/core/lvm/startup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestStartupDiagnostic_DisksAvailable(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -108,7 +108,7 @@ func TestStartupDiagnostic_DisksAvailable_NoInUse(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -147,7 +147,7 @@ func TestStartupDiagnostic_NoDisks_AllFormattedNVMe(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -189,7 +189,7 @@ func TestStartupDiagnostic_NoDisks_NoNVMe(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -222,7 +222,7 @@ func TestStartupDiagnostic_ScanError(t *testing.T) {
mockBlock := block.NewMock(ctrl)
recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -257,7 +257,7 @@ func TestStartupDiagnostic_MultipleNVMe_SomeFormatted(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -300,7 +300,7 @@ func TestStartupDiagnostic_GetDevicesError(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -335,7 +335,7 @@ func TestStartupDiagnostic_IsFormattedError(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -370,7 +370,7 @@ func TestStartupDiagnostic_IsLVM2Error(t *testing.T) {

recorder := kevents.NewFakeRecorder(10)

diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.EphemeralDiskFilter, recorder, newTestPod())
diag := lvm.NewStartupDiagnostic(mockProbe, mockBlock, probe.NewEphemeralDiskFilter(nil, nil, nil), recorder, newTestPod())

if err := diag.Start(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down
82 changes: 75 additions & 7 deletions internal/pkg/probe/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,81 @@ import (
"local-csi-driver/internal/pkg/block"
)

// EphemeralDiskFilter is a filter for ephemeral disks.
var EphemeralDiskFilter = &Filter{
Filters: []FilterPredicate{
&PathFilter{Path: "/dev/nvme"},
NewModelFilter("Microsoft NVMe Direct Disk", "Microsoft NVMe Direct Disk v2"),
&TypeFilter{Type: "disk"},
},
// Default disk selection values. These preserve the historical hardcoded
// behavior when no overrides are provided via CLI flags.
var (
DefaultDiskPathPrefixes = []string{"/dev/nvme"}
DefaultDiskModels = []string{
"Microsoft NVMe Direct Disk",
"Microsoft NVMe Direct Disk v2",
"Amazon EC2 NVMe Instance Storage",
}
DefaultDiskTypes = []string{"disk"}
)

// NewEphemeralDiskFilter builds a Filter from the built-in defaults plus any
// extra path prefixes, models, and types (for example, sourced from CLI flags).
// A device must match all three categories (path AND model AND type); within a
// category it matches if it satisfies any entry. Extra entries are appended to
// the defaults; empty/whitespace entries and case-insensitive duplicates are
// ignored.
func NewEphemeralDiskFilter(extraPathPrefixes, extraModels, extraTypes []string) *Filter {
pathPrefixes := appendUnique(DefaultDiskPathPrefixes, extraPathPrefixes)
models := appendUnique(DefaultDiskModels, extraModels)
types := appendUnique(DefaultDiskTypes, extraTypes)

anyPath := make([]FilterPredicate, 0, len(pathPrefixes))
for _, p := range pathPrefixes {
anyPath = append(anyPath, &PathFilter{Path: p})
}

anyType := make([]FilterPredicate, 0, len(types))
for _, t := range types {
anyType = append(anyType, &TypeFilter{Type: t})
}

return &Filter{Filters: []FilterPredicate{
&anyFilter{filters: anyPath},
NewModelFilter(models...),
&anyFilter{filters: anyType},
}}
}

// appendUnique returns base followed by the entries of extra that are not
// already present. Entries are trimmed, empty entries are dropped, and
// de-duplication is case-insensitive.
func appendUnique(base, extra []string) []string {
seen := make(map[string]struct{})
out := make([]string, 0, len(base)+len(extra))
for _, list := range [][]string{base, extra} {
for _, s := range list {
s = strings.TrimSpace(s)
if s == "" {
continue
}
key := strings.ToLower(s)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, s)
}
}
return out
}

// anyFilter matches if any of its contained predicates match (OR).
type anyFilter struct {
filters []FilterPredicate
}

func (f *anyFilter) Match(device block.Device) bool {
for _, filter := range f.filters {
if filter.Match(device) {
return true
}
}
return false
}

// FilterPredicate defines a predicate for filtering devices.
Expand Down
Loading
Loading