diff --git a/.changelog/17939.txt b/.changelog/17939.txt new file mode 100644 index 000000000000..069ac53ee7be --- /dev/null +++ b/.changelog/17939.txt @@ -0,0 +1,4 @@ +```release-note:improvement +http: GET API `operator/usage` endpoint now returns node count +cli: `consul operator usage` command now returns node count +``` \ No newline at end of file diff --git a/api/operator_usage.go b/api/operator_usage.go index e47d4b53e037..8977449ddd36 100644 --- a/api/operator_usage.go +++ b/api/operator_usage.go @@ -10,6 +10,7 @@ type Usage struct { // ServiceUsage contains information about the number of services and service instances for a datacenter. type ServiceUsage struct { + Nodes int Services int ServiceInstances int ConnectServiceInstances map[string]int diff --git a/command/operator/usage/instances/usage_instances.go b/command/operator/usage/instances/usage_instances.go index 5e8dba201ce8..1ac90dce8d36 100644 --- a/command/operator/usage/instances/usage_instances.go +++ b/command/operator/usage/instances/usage_instances.go @@ -99,6 +99,14 @@ func (c *cmd) Run(args []string) int { return 1 } c.UI.Output(billableOutput + "\n") + + c.UI.Output("\nNodes") + nodesOutput, err := formatNodesCounts(usage.Usage) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + c.UI.Output(nodesOutput + "\n\n") } // Output Connect service counts @@ -115,6 +123,34 @@ func (c *cmd) Run(args []string) int { return 0 } +func formatNodesCounts(usageStats map[string]api.ServiceUsage) (string, error) { + var output bytes.Buffer + tw := tabwriter.NewWriter(&output, 0, 2, 6, ' ', 0) + + nodesTotal := 0 + + fmt.Fprintf(tw, "Datacenter\t") + + fmt.Fprintf(tw, "Count\t") + + fmt.Fprint(tw, "\t\n") + + for dc, usage := range usageStats { + nodesTotal += usage.Nodes + fmt.Fprintf(tw, "%s\t%d\n", dc, usage.Nodes) + } + + fmt.Fprint(tw, "\t\n") + fmt.Fprintf(tw, "Total") + + fmt.Fprintf(tw, "\t%d", nodesTotal) + + if err := tw.Flush(); err != nil { + return "", fmt.Errorf("Error flushing tabwriter: %s", err) + } + return strings.TrimSpace(output.String()), nil +} + func formatServiceCounts(usageStats map[string]api.ServiceUsage, billable, showDatacenter bool) (string, error) { var output bytes.Buffer tw := tabwriter.NewWriter(&output, 0, 2, 6, ' ', 0) diff --git a/command/operator/usage/instances/usage_instances_oss_test.go b/command/operator/usage/instances/usage_instances_oss_test.go index 4f0686c47952..478b9f42118b 100644 --- a/command/operator/usage/instances/usage_instances_oss_test.go +++ b/command/operator/usage/instances/usage_instances_oss_test.go @@ -117,3 +117,54 @@ Total 45`, }) } } + +func TestUsageInstances_formatNodesCounts(t *testing.T) { + usageBasic := map[string]api.ServiceUsage{ + "dc1": { + Nodes: 10, + }, + } + + usageMultiDC := map[string]api.ServiceUsage{ + "dc1": { + Nodes: 10, + }, + "dc2": { + Nodes: 11, + }, + } + + cases := []struct { + name string + usageStats map[string]api.ServiceUsage + expectedNodes string + }{ + { + name: "basic", + usageStats: usageBasic, + expectedNodes: ` +Datacenter Count +dc1 10 + +Total 10`, + }, + { + name: "multi-datacenter", + usageStats: usageMultiDC, + expectedNodes: ` +Datacenter Count +dc1 10 +dc2 11 + +Total 21`, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + nodesOutput, err := formatNodesCounts(tc.usageStats) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(tc.expectedNodes), nodesOutput) + }) + } +} diff --git a/website/content/commands/operator/usage.mdx b/website/content/commands/operator/usage.mdx index 56d364862690..83ae028f6c95 100644 --- a/website/content/commands/operator/usage.mdx +++ b/website/content/commands/operator/usage.mdx @@ -50,6 +50,12 @@ Billable Services Services Service instances 2 3 +Nodes +Datacenter Count +dc1 1 + +Total 1 + Connect Services Type Service instances connect-native 0 @@ -74,6 +80,13 @@ dc2 1 1 Total 3 4 +Nodes +Datacenter Count +dc1 1 +dc2 2 + +Total 3 + Connect Services Datacenter Type Service instances dc1 connect-native 0