diff --git a/cmd/main.go b/cmd/main.go index d7e9bbdfd..dfb3c22e4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -30,30 +30,17 @@ import ( const etcAmazonEfs = "/etc/amazon/efs" func main() { - var ( - endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI Endpoint") - version = flag.Bool("version", false, "Print the version and exit") - efsUtilsCfgDirPath = flag.String("efs-utils-config-dir-path", "/var/amazon/efs", "The preferred path for the efs-utils config directory. efs-utils-config-legacy-dir-path will be used if it is not empty, otherwise efs-utils-config-dir-path will be used.") - efsUtilsCfgLegacyDirPath = flag.String("efs-utils-config-legacy-dir-path", "/etc/amazon/efs-legacy", "The path to the legacy efs-utils config directory mounted from the host path /etc/amazon/efs") - efsUtilsStaticFilesPath = flag.String("efs-utils-static-files-path", "/etc/amazon/efs-static-files/", "The path to efs-utils static files directory") - volMetricsOptIn = flag.Bool("vol-metrics-opt-in", false, "Opt in to emit volume metrics") - volMetricsRefreshPeriod = flag.Float64("vol-metrics-refresh-period", 240, "Refresh period for volume metrics in minutes") - volMetricsFsRateLimit = flag.Int("vol-metrics-fs-rate-limit", 5, "Volume metrics routines rate limiter per file system") - deleteAccessPointRootDir = flag.Bool("delete-access-point-root-dir", false, - "Opt in to delete access point root directory by DeleteVolume. By default, DeleteVolume will delete the access point behind Persistent Volume and deleting access point will not delete the access point root directory or its contents.") - adaptiveRetryMode = flag.Bool("adaptive-retry-mode", true, "Opt out to use standard sdk retry configuration. By default, adaptive retry mode will be used to more heavily client side rate limit EFS API requests.") - tags = flag.String("tags", "", "Space separated key:value pairs which will be added as tags for EFS resources. For example, 'environment:prod region:us-east-1'") - maxInflightMountCallsOptIn = flag.Bool("max-inflight-mount-calls-opt-in", false, "Opt in to use max inflight mount calls limit.") - maxInflightMountCalls = flag.Int64("max-inflight-mount-calls", driver.UnsetMaxInflightMountCounts, "New NodePublishVolume operation will be blocked if maximum number of inflight calls is reached. If maxInflightMountCallsOptIn is true, it has to be set to a positive value.") - volumeAttachLimitOptIn = flag.Bool("volume-attach-limit-opt-in", false, "Opt in to use volume attach limit.") - volumeAttachLimit = flag.Int64("volume-attach-limit", driver.UnsetVolumeAttachLimit, "Maximum number of volumes that can be attached to a node. If volumeAttachLimitOptIn is true, it has to be set to a positive value.") - forceUnmountAfterTimeout = flag.Bool("force-unmount-after-timeout", false, "Enable force unmount if normal unmount times out during NodeUnpublishVolume.") - unmountTimeout = flag.Duration("unmount-timeout", driver.DefaultUnmountTimeout, "Timeout for unmounting a volume during NodePublishVolume when forceUnmountAfterTimeout is true. If the timeout is reached, the volume will be forcibly unmounted. The default value is 30 seconds.") - ) klog.InitFlags(nil) + + options := driver.NewOptions() flag.Parse() - if *version { + if err := options.Validate(); err != nil { + klog.ErrorS(err, "Invalid options") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + if *options.Version { info, err := driver.GetVersionJSON() if err != nil { klog.Fatalln(err) @@ -63,11 +50,11 @@ func main() { } // chose which configuration directory we will use and create a symlink to it - err := driver.InitConfigDir(*efsUtilsCfgLegacyDirPath, *efsUtilsCfgDirPath, etcAmazonEfs) + err := driver.InitConfigDir(*options.EfsUtilsCfgLegacyDirPath, *options.EfsUtilsCfgDirPath, etcAmazonEfs) if err != nil { klog.Fatalln(err) } - drv := driver.NewDriver(*endpoint, etcAmazonEfs, *efsUtilsStaticFilesPath, *tags, *volMetricsOptIn, *volMetricsRefreshPeriod, *volMetricsFsRateLimit, *deleteAccessPointRootDir, *adaptiveRetryMode, *maxInflightMountCallsOptIn, *maxInflightMountCalls, *volumeAttachLimitOptIn, *volumeAttachLimit, *forceUnmountAfterTimeout, *unmountTimeout) + drv := driver.NewDriver(options, etcAmazonEfs) if err := drv.Run(); err != nil { klog.Fatalln(err) } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 3763f9394..21c30478a 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -33,10 +33,7 @@ const ( driverName = "efs.csi.aws.com" // AgentNotReadyTaintKey contains the key of taints to be removed on driver startup - AgentNotReadyNodeTaintKey = "efs.csi.aws.com/agent-not-ready" - UnsetMaxInflightMountCounts = -1 - UnsetVolumeAttachLimit = -1 - DefaultUnmountTimeout = 30 * time.Second + AgentNotReadyNodeTaintKey = "efs.csi.aws.com/agent-not-ready" ) type Driver struct { @@ -62,34 +59,34 @@ type Driver struct { unmountTimeout time.Duration } -func NewDriver(endpoint, efsUtilsCfgPath, efsUtilsStaticFilesPath, tags string, volMetricsOptIn bool, volMetricsRefreshPeriod float64, volMetricsFsRateLimit int, deleteAccessPointRootDir bool, adaptiveRetryMode bool, maxInflightMountCallsOptIn bool, maxInflightMountCalls int64, volumeAttachLimitOptIn bool, volumeAttachLimit int64, forceUnmountAfterTimeout bool, unmountTimeout time.Duration) *Driver { - cloud, err := cloud.NewCloud(adaptiveRetryMode) +func NewDriver(options *Options, efsUtilsCfgPath string) *Driver { + cloud, err := cloud.NewCloud(*options.AdaptiveRetryMode) if err != nil { klog.Fatalln(err) } - nodeCaps := SetNodeCapOptInFeatures(volMetricsOptIn) - watchdog := newExecWatchdog(efsUtilsCfgPath, efsUtilsStaticFilesPath, "amazon-efs-mount-watchdog") + nodeCaps := SetNodeCapOptInFeatures(*options.VolMetricsOptIn) + watchdog := newExecWatchdog(efsUtilsCfgPath, *options.EfsUtilsStaticFilesPath, "amazon-efs-mount-watchdog") return &Driver{ - endpoint: endpoint, + endpoint: *options.Endpoint, nodeID: cloud.GetMetadata().GetInstanceID(), mounter: newNodeMounter(), efsWatchdog: watchdog, cloud: cloud, nodeCaps: nodeCaps, volStatter: NewVolStatter(), - volMetricsOptIn: volMetricsOptIn, - volMetricsRefreshPeriod: volMetricsRefreshPeriod, - volMetricsFsRateLimit: volMetricsFsRateLimit, + volMetricsOptIn: *options.VolMetricsOptIn, + volMetricsRefreshPeriod: *options.VolMetricsRefreshPeriod, + volMetricsFsRateLimit: *options.VolMetricsFsRateLimit, gidAllocator: NewGidAllocator(), - deleteAccessPointRootDir: deleteAccessPointRootDir, - adaptiveRetryMode: adaptiveRetryMode, - tags: parseTagsFromStr(strings.TrimSpace(tags)), + deleteAccessPointRootDir: *options.DeleteAccessPointRootDir, + adaptiveRetryMode: *options.AdaptiveRetryMode, + tags: parseTagsFromStr(strings.TrimSpace(*options.Tags)), lockManager: NewLockManagerMap(), - inFlightMountTracker: NewInFlightMountTracker(getMaxInflightMountCalls(maxInflightMountCallsOptIn, maxInflightMountCalls)), - volumeAttachLimit: getVolumeAttachLimit(volumeAttachLimitOptIn, volumeAttachLimit), - forceUnmountAfterTimeout: forceUnmountAfterTimeout, - unmountTimeout: unmountTimeout, + inFlightMountTracker: NewInFlightMountTracker(getMaxInflightMountCalls(*options.MaxInflightMountCallsOptIn, *options.MaxInflightMountCalls)), + volumeAttachLimit: getVolumeAttachLimit(*options.VolumeAttachLimitOptIn, *options.VolumeAttachLimit), + forceUnmountAfterTimeout: *options.ForceUnmountAfterTimeout, + unmountTimeout: *options.UnmountTimeout, } } diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 6f5bd5475..87ff17f13 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -568,11 +568,6 @@ func tryRemoveNotReadyTaintUntilSucceed(interval time.Duration, removeFn func() } func getMaxInflightMountCalls(maxInflightMountCallsOptIn bool, maxInflightMountCalls int64) int64 { - if maxInflightMountCallsOptIn && maxInflightMountCalls <= 0 { - klog.Errorf("Fatal error: maxInflightMountCalls must be greater than 0 when maxInflightMountCallsOptIn is true!") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - if !maxInflightMountCallsOptIn { klog.V(4).Infof("MaxInflightMountCallsOptIn is false, setting maxInflightMountCalls to %d and inflight check is disabled", UnsetMaxInflightMountCounts) return UnsetMaxInflightMountCounts @@ -583,11 +578,6 @@ func getMaxInflightMountCalls(maxInflightMountCallsOptIn bool, maxInflightMountC } func getVolumeAttachLimit(volumeAttachLimitOptIn bool, volumeAttachLimit int64) int64 { - if volumeAttachLimitOptIn && volumeAttachLimit <= 0 { - klog.Errorf("Fatal error: volumeAttachLimit must be greater than 0 when volumeAttachLimitOptIn is true!") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - if !volumeAttachLimitOptIn { klog.V(4).Infof("VolumeAttachLimitOptIn is false, setting maxVolumesPerNode to zero so that container orchestrator will decide the value") return 0 diff --git a/pkg/driver/node_test.go b/pkg/driver/node_test.go index bc2bd9aea..964ef101b 100644 --- a/pkg/driver/node_test.go +++ b/pkg/driver/node_test.go @@ -1140,7 +1140,6 @@ func TestGetMaxInflightMountCalls(t *testing.T) { maxInflightMountCallsOptIn bool maxInflightMountCalls int64 expected int64 - expectFatal bool }{ { name: "opt-in false returns unset", @@ -1154,37 +1153,13 @@ func TestGetMaxInflightMountCalls(t *testing.T) { maxInflightMountCalls: 5, expected: 5, }, - { - name: "opt-in true with zero value should fatal", - maxInflightMountCallsOptIn: true, - maxInflightMountCalls: 0, - expectFatal: true, - }, - { - name: "opt-in true with negative value should fatal", - maxInflightMountCallsOptIn: true, - maxInflightMountCalls: UnsetMaxInflightMountCounts, - expectFatal: true, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if tc.expectFatal { - if os.Getenv("FORK") == "1" { - // If it is in forked process, run the fatal code directly and let klog.Fatal exit - getMaxInflightMountCalls(tc.maxInflightMountCallsOptIn, tc.maxInflightMountCalls) - return - } - err := runForkFatalTest("TestGetMaxInflightMountCalls/" + tc.name) - if err == nil { - t.Fatal("expected process to exit with error") - } - } else { - result := getMaxInflightMountCalls(tc.maxInflightMountCallsOptIn, tc.maxInflightMountCalls) - if result != tc.expected { - t.Errorf("Expected %d, got %d", tc.expected, result) - } + result := getMaxInflightMountCalls(tc.maxInflightMountCallsOptIn, tc.maxInflightMountCalls) + if result != tc.expected { + t.Errorf("Expected %d, got %d", tc.expected, result) } }) } @@ -1210,37 +1185,13 @@ func TestGetVolumeAttachLimit(t *testing.T) { volumeAttachLimit: 50, expected: 50, }, - { - name: "opt-in true with zero value should fatal", - volumeAttachLimitOptIn: true, - volumeAttachLimit: 0, - expectFatal: true, - }, - { - name: "opt-in true with negative value should fatal", - volumeAttachLimitOptIn: true, - volumeAttachLimit: -1, - expectFatal: true, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if tc.expectFatal { - // If it is in forked process, run the fatal code directly and let klog.Fatal exit - if os.Getenv("FORK") == "1" { - getVolumeAttachLimit(tc.volumeAttachLimitOptIn, tc.volumeAttachLimit) - return - } - err := runForkFatalTest("TestGetVolumeAttachLimit/" + tc.name) - if err == nil { - t.Fatal("expected process to exit with error") - } - } else { - result := getVolumeAttachLimit(tc.volumeAttachLimitOptIn, tc.volumeAttachLimit) - if result != tc.expected { - t.Errorf("Expected %d, got %d", tc.expected, result) - } + result := getVolumeAttachLimit(tc.volumeAttachLimitOptIn, tc.volumeAttachLimit) + if result != tc.expected { + t.Errorf("Expected %d, got %d", tc.expected, result) } }) } diff --git a/pkg/driver/options.go b/pkg/driver/options.go new file mode 100644 index 000000000..4a9705167 --- /dev/null +++ b/pkg/driver/options.go @@ -0,0 +1,68 @@ +package driver + +import ( + "errors" + "flag" + "time" +) + +const ( + UnsetMaxInflightMountCounts = -1 + UnsetVolumeAttachLimit = -1 + DefaultUnmountTimeout = 30 * time.Second +) + +type Options struct { + Endpoint *string + Version *bool + EfsUtilsCfgDirPath *string + EfsUtilsCfgLegacyDirPath *string + EfsUtilsStaticFilesPath *string + VolMetricsOptIn *bool + VolMetricsRefreshPeriod *float64 + VolMetricsFsRateLimit *int + DeleteAccessPointRootDir *bool + AdaptiveRetryMode *bool + Tags *string + MaxInflightMountCallsOptIn *bool + MaxInflightMountCalls *int64 + VolumeAttachLimitOptIn *bool + VolumeAttachLimit *int64 + ForceUnmountAfterTimeout *bool + UnmountTimeout *time.Duration +} + +func NewOptions() *Options { + return &Options{ + Endpoint: flag.String("endpoint", "unix://tmp/csi.sock", "CSI Endpoint"), + Version: flag.Bool("version", false, "Print the version and exit"), + EfsUtilsCfgDirPath: flag.String("efs-utils-config-dir-path", "/var/amazon/efs", "The preferred path for the efs-utils config directory. efs-utils-config-legacy-dir-path will be used if it is not empty, otherwise efs-utils-config-dir-path will be used."), + EfsUtilsCfgLegacyDirPath: flag.String("efs-utils-config-legacy-dir-path", "/etc/amazon/efs-legacy", "The path to the legacy efs-utils config directory mounted from the host path /etc/amazon/efs"), + EfsUtilsStaticFilesPath: flag.String("efs-utils-static-files-path", "/etc/amazon/efs-static-files/", "The path to efs-utils static files directory"), + VolMetricsOptIn: flag.Bool("vol-metrics-opt-in", false, "Opt in to emit volume metrics"), + VolMetricsRefreshPeriod: flag.Float64("vol-metrics-refresh-period", 240, "Refresh period for volume metrics in minutes"), + VolMetricsFsRateLimit: flag.Int("vol-metrics-fs-rate-limit", 5, "Volume metrics routines rate limiter per file system"), + DeleteAccessPointRootDir: flag.Bool("delete-access-point-root-dir", false, + "Opt in to delete access point root directory by DeleteVolume. By default, DeleteVolume will delete the access point behind Persistent Volume and deleting access point will not delete the access point root directory or its contents."), + AdaptiveRetryMode: flag.Bool("adaptive-retry-mode", true, "Opt out to use standard sdk retry configuration. By default, adaptive retry mode will be used to more heavily client side rate limit EFS API requests."), + Tags: flag.String("tags", "", "Space separated key:value pairs which will be added as tags for EFS resources. For example, 'environment:prod region:us-east-1'"), + MaxInflightMountCallsOptIn: flag.Bool("max-inflight-mount-calls-opt-in", false, "Opt in to use max inflight mount calls limit."), + MaxInflightMountCalls: flag.Int64("max-inflight-mount-calls", UnsetMaxInflightMountCounts, "New NodePublishVolume operation will be blocked if maximum number of inflight calls is reached. If maxInflightMountCallsOptIn is true, it has to be set to a positive value."), + VolumeAttachLimitOptIn: flag.Bool("volume-attach-limit-opt-in", false, "Opt in to use volume attach limit."), + VolumeAttachLimit: flag.Int64("volume-attach-limit", UnsetVolumeAttachLimit, "Maximum number of volumes that can be attached to a node. If volumeAttachLimitOptIn is true, it has to be set to a positive value."), + ForceUnmountAfterTimeout: flag.Bool("force-unmount-after-timeout", false, "Enable force unmount if normal unmount times out during NodeUnpublishVolume."), + UnmountTimeout: flag.Duration("unmount-timeout", DefaultUnmountTimeout, "Timeout for unmounting a volume during NodePublishVolume when forceUnmountAfterTimeout is true. If the timeout is reached, the volume will be forcibly unmounted. The default value is 30 seconds."), + } +} + +func (o *Options) Validate() error { + if *o.MaxInflightMountCallsOptIn && *o.MaxInflightMountCalls <= 0 { + return errors.New("maxInflightMountCallsOptIn is true, but maxInflightMountCalls is not set to a positive value") + } + + if *o.VolumeAttachLimitOptIn && *o.VolumeAttachLimit <= 0 { + return errors.New("volumeAttachLimitOptIn is true, but volumeAttachLimit is not set to a positive value") + } + + return nil +} diff --git a/pkg/driver/options_test.go b/pkg/driver/options_test.go new file mode 100644 index 000000000..dbf025f2d --- /dev/null +++ b/pkg/driver/options_test.go @@ -0,0 +1,225 @@ +package driver + +import ( + "flag" + "os" + "testing" + "time" +) + +func TestParseFlagsWithDefaultValue(t *testing.T) { + // Reset flag package for clean test + flag.CommandLine = flag.NewFlagSet("test", flag.ExitOnError) + + // Test with default values + os.Args = []string{"test"} + opts := NewOptions() + + if opts == nil { + t.Fatal("NewOptions() returned nil") + } + + if *opts.Endpoint != "unix://tmp/csi.sock" { + t.Errorf("Expected endpoint 'unix://tmp/csi.sock', got '%s'", *opts.Endpoint) + } + if *opts.Version != false { + t.Errorf("Expected version false, got %v", *opts.Version) + } + if *opts.EfsUtilsCfgDirPath != "/var/amazon/efs" { + t.Errorf("Expected efs-utils-config-dir-path '/var/amazon/efs', got '%s'", *opts.EfsUtilsCfgDirPath) + } + if *opts.EfsUtilsCfgLegacyDirPath != "/etc/amazon/efs-legacy" { + t.Errorf("Expected efs-utils-config-legacy-dir-path '/etc/amazon/efs-legacy', got '%s'", *opts.EfsUtilsCfgLegacyDirPath) + } + if *opts.EfsUtilsStaticFilesPath != "/etc/amazon/efs-static-files/" { + t.Errorf("Expected efs-utils-static-files-path '/etc/amazon/efs-static-files/', got '%s'", *opts.EfsUtilsStaticFilesPath) + } + if *opts.VolMetricsOptIn != false { + t.Errorf("Expected vol-metrics-opt-in false, got %v", *opts.VolMetricsOptIn) + } + if *opts.VolMetricsRefreshPeriod != 240 { + t.Errorf("Expected vol-metrics-refresh-period 240, got %f", *opts.VolMetricsRefreshPeriod) + } + if *opts.VolMetricsFsRateLimit != 5 { + t.Errorf("Expected vol-metrics-fs-rate-limit 5, got %d", *opts.VolMetricsFsRateLimit) + } + if *opts.DeleteAccessPointRootDir != false { + t.Errorf("Expected delete-access-point-root-dir false, got %v", *opts.DeleteAccessPointRootDir) + } + if *opts.AdaptiveRetryMode != true { + t.Errorf("Expected adaptive-retry-mode true, got %v", *opts.AdaptiveRetryMode) + } + if *opts.Tags != "" { + t.Errorf("Expected tags empty string, got '%s'", *opts.Tags) + } + if *opts.MaxInflightMountCallsOptIn != false { + t.Errorf("Expected max-inflight-mount-calls-opt-in false, got %v", *opts.MaxInflightMountCallsOptIn) + } + if *opts.MaxInflightMountCalls != UnsetMaxInflightMountCounts { + t.Errorf("Expected max-inflight-mount-calls %d, got %d", UnsetMaxInflightMountCounts, *opts.MaxInflightMountCalls) + } + if *opts.VolumeAttachLimitOptIn != false { + t.Errorf("Expected volume-attach-limit-opt-in false, got %v", *opts.VolumeAttachLimitOptIn) + } + if *opts.VolumeAttachLimit != UnsetVolumeAttachLimit { + t.Errorf("Expected volume-attach-limit %d, got %d", UnsetVolumeAttachLimit, *opts.VolumeAttachLimit) + } + + if *opts.ForceUnmountAfterTimeout != false { + t.Errorf("Expected force-unmount-after-timeout false, got %v", *opts.ForceUnmountAfterTimeout) + } + + if *opts.UnmountTimeout != DefaultUnmountTimeout { + t.Errorf("Expected unmount-timeout %d, got %d", DefaultUnmountTimeout, *opts.UnmountTimeout) + } +} + +func TestParseFlagsWithCustomValues(t *testing.T) { + // Reset flag package for clean test + flag.CommandLine = flag.NewFlagSet("test", flag.ExitOnError) + + // Test with custom values + os.Args = []string{ + "test", + "--endpoint=tcp://localhost:9000", + "--version=true", + "--efs-utils-config-dir-path=/custom/efs", + "--efs-utils-config-legacy-dir-path=/custom/efs-legacy", + "--efs-utils-static-files-path=/custom/efs-static/", + "--vol-metrics-opt-in=true", + "--vol-metrics-refresh-period=120", + "--vol-metrics-fs-rate-limit=10", + "--delete-access-point-root-dir=true", + "--adaptive-retry-mode=false", + "--tags=env:test region:us-west-2", + "--max-inflight-mount-calls-opt-in=true", + "--max-inflight-mount-calls=20", + "--volume-attach-limit-opt-in=true", + "--volume-attach-limit=15", + "--force-unmount-after-timeout=true", + "--unmount-timeout=60s", + } + + opts := NewOptions() + flag.Parse() + + if *opts.Endpoint != "tcp://localhost:9000" { + t.Errorf("Expected endpoint 'tcp://localhost:9000', got '%s'", *opts.Endpoint) + } + if *opts.Version != true { + t.Errorf("Expected version true, got %v", *opts.Version) + } + if *opts.EfsUtilsCfgDirPath != "/custom/efs" { + t.Errorf("Expected efs-utils-config-dir-path '/custom/efs', got '%s'", *opts.EfsUtilsCfgDirPath) + } + if *opts.EfsUtilsCfgLegacyDirPath != "/custom/efs-legacy" { + t.Errorf("Expected efs-utils-config-legacy-dir-path '/custom/efs-legacy', got '%s'", *opts.EfsUtilsCfgLegacyDirPath) + } + if *opts.EfsUtilsStaticFilesPath != "/custom/efs-static/" { + t.Errorf("Expected efs-utils-static-files-path '/custom/efs-static/', got '%s'", *opts.EfsUtilsStaticFilesPath) + } + if *opts.VolMetricsOptIn != true { + t.Errorf("Expected vol-metrics-opt-in true, got %v", *opts.VolMetricsOptIn) + } + if *opts.VolMetricsRefreshPeriod != 120 { + t.Errorf("Expected vol-metrics-refresh-period 120, got %f", *opts.VolMetricsRefreshPeriod) + } + if *opts.VolMetricsFsRateLimit != 10 { + t.Errorf("Expected vol-metrics-fs-rate-limit 10, got %d", *opts.VolMetricsFsRateLimit) + } + if *opts.DeleteAccessPointRootDir != true { + t.Errorf("Expected delete-access-point-root-dir true, got %v", *opts.DeleteAccessPointRootDir) + } + if *opts.AdaptiveRetryMode != false { + t.Errorf("Expected adaptive-retry-mode false, got %v", *opts.AdaptiveRetryMode) + } + if *opts.Tags != "env:test region:us-west-2" { + t.Errorf("Expected tags 'env:test region:us-west-2', got '%s'", *opts.Tags) + } + if *opts.MaxInflightMountCallsOptIn != true { + t.Errorf("Expected max-inflight-mount-calls-opt-in true, got %v", *opts.MaxInflightMountCallsOptIn) + } + if *opts.MaxInflightMountCalls != 20 { + t.Errorf("Expected max-inflight-mount-calls 20, got %d", *opts.MaxInflightMountCalls) + } + if *opts.VolumeAttachLimitOptIn != true { + t.Errorf("Expected volume-attach-limit-opt-in true, got %v", *opts.VolumeAttachLimitOptIn) + } + if *opts.VolumeAttachLimit != 15 { + t.Errorf("Expected volume-attach-limit 15, got %d", *opts.VolumeAttachLimit) + } + + if *opts.ForceUnmountAfterTimeout != true { + t.Errorf("Expected force-unmount-after-timeout true, got %v", *opts.ForceUnmountAfterTimeout) + } + + if *opts.UnmountTimeout != 60*time.Second { + t.Errorf("Expected unmount-timeout 60 seconds, got %v", *opts.UnmountTimeout) + } +} + +func TestValidate(t *testing.T) { + tests := []struct { + name string + opts *Options + expectError bool + }{ + { + name: "valid default options", + opts: &Options{ + MaxInflightMountCallsOptIn: boolPtr(false), + MaxInflightMountCalls: int64Ptr(UnsetMaxInflightMountCounts), + VolumeAttachLimitOptIn: boolPtr(false), + VolumeAttachLimit: int64Ptr(UnsetVolumeAttachLimit), + }, + expectError: false, + }, + { + name: "invalid max inflight mount calls while opt in", + opts: &Options{ + MaxInflightMountCallsOptIn: boolPtr(true), + MaxInflightMountCalls: int64Ptr(0), + VolumeAttachLimitOptIn: boolPtr(false), + VolumeAttachLimit: int64Ptr(UnsetVolumeAttachLimit), + }, + expectError: true, + }, + { + name: "invalid negative volume attach limit while opt in", + opts: &Options{ + MaxInflightMountCallsOptIn: boolPtr(false), + MaxInflightMountCalls: int64Ptr(UnsetMaxInflightMountCounts), + VolumeAttachLimitOptIn: boolPtr(true), + VolumeAttachLimit: int64Ptr(-1), + }, + expectError: true, + }, + { + name: "valid positive values while opt in", + opts: &Options{ + MaxInflightMountCallsOptIn: boolPtr(true), + MaxInflightMountCalls: int64Ptr(10), + VolumeAttachLimitOptIn: boolPtr(true), + VolumeAttachLimit: int64Ptr(5), + }, + expectError: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Validate() + if (err != nil) != tc.expectError { + t.Errorf("Validate() error = %v, expectError %v", err, tc.expectError) + } + }) + } +} + +func boolPtr(b bool) *bool { + return &b +} + +func int64Ptr(i int64) *int64 { + return &i +}