diff --git a/cli/cmd/cluster_watch.go b/cli/cmd/cluster_watch.go index 9c1a2d44..c1a47159 100644 --- a/cli/cmd/cluster_watch.go +++ b/cli/cmd/cluster_watch.go @@ -41,6 +41,9 @@ var clusterWatchCmd = &cobra.Command{ Use: "watch", Short: "Live-watch Kafka cluster health (refreshing dashboard)", RunE: func(cmd *cobra.Command, args []string) error { + if err := checkInterval(clusterWatchInterval); err != nil { + return err + } tick := 0 hist := &healthHistory{} diff --git a/cli/cmd/dashboard.go b/cli/cmd/dashboard.go index 69b11448..dfe7c9d8 100644 --- a/cli/cmd/dashboard.go +++ b/cli/cmd/dashboard.go @@ -18,6 +18,9 @@ var dashboardCmd = &cobra.Command{ Aliases: []string{"dash"}, Short: "Full-screen monitoring dashboard with live metrics", RunE: func(cmd *cobra.Command, args []string) error { + if err := checkInterval(dashInterval); err != nil { + return err + } tick := 0 throughputHistory := make([]float64, 0, 30) latencyHistory := make([]float64, 0, 30) diff --git a/cli/cmd/interval_test.go b/cli/cmd/interval_test.go new file mode 100644 index 00000000..f6715f75 --- /dev/null +++ b/cli/cmd/interval_test.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "errors" + "strings" + "testing" + "time" + + "github.com/bmscomp/kates/cli/client" + "github.com/spf13/cobra" +) + +// At --interval 0 the refresh loops never slept: top sent 20,347 requests in +// 3 s to a stub API. +func TestRefreshCommandsRefuseIntervalUnderOneSecond(t *testing.T) { + origClient, origTop, origDash, origWatch := apiClient, topInterval, dashInterval, clusterWatchInterval + t.Cleanup(func() { + apiClient, topInterval, dashInterval, clusterWatchInterval = origClient, origTop, origDash, origWatch + }) + apiClient = client.Failing(errors.New("no API in this test")) + + for _, tc := range []struct { + name string + cmd *cobra.Command + interval *int + }{ + {"top", topCmd, &topInterval}, + {"dashboard", dashboardCmd, &dashInterval}, + {"cluster watch", clusterWatchCmd, &clusterWatchInterval}, + } { + for _, seconds := range []int{0, -1} { + *tc.interval = seconds + done := make(chan error, 1) + go func() { done <- tc.cmd.RunE(tc.cmd, nil) }() + select { + case err := <-done: + if err == nil || !strings.Contains(err.Error(), "--interval must be at least 1 second") { + t.Errorf("%s --interval %d: err = %v, want the interval refused", tc.name, seconds, err) + } + case <-time.After(5 * time.Second): + t.Fatalf("%s --interval %d is still refreshing instead of refusing", tc.name, seconds) + } + } + } + + if err := checkInterval(1); err != nil { + t.Errorf("checkInterval(1) = %v, want nil", err) + } +} diff --git a/cli/cmd/top.go b/cli/cmd/top.go index cef104c9..417b2892 100644 --- a/cli/cmd/top.go +++ b/cli/cmd/top.go @@ -16,6 +16,9 @@ var topCmd = &cobra.Command{ Use: "top", Short: "Live view of running tests (like kubectl top)", RunE: func(cmd *cobra.Command, args []string) error { + if err := checkInterval(topInterval); err != nil { + return err + } tick := 0 for { @@ -120,3 +123,12 @@ func init() { topCmd.Flags().IntVar(&topInterval, "interval", 2, "Refresh interval in seconds") rootCmd.AddCommand(topCmd) } + +// checkInterval refuses a refresh interval under a second: at 0 or below the +// refresh loops never slept, and sent thousands of requests a second. +func checkInterval(seconds int) error { + if seconds < 1 { + return fmt.Errorf("--interval must be at least 1 second, got %d", seconds) + } + return nil +}