diff --git a/.changelog/17939.txt b/.changelog/17939.txt new file mode 100644 index 0000000000..069ac53ee7 --- /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/agent/consul/state/usage.go b/agent/consul/state/usage.go index b379410550..0893d25288 100644 --- a/agent/consul/state/usage.go +++ b/agent/consul/state/usage.go @@ -424,6 +424,11 @@ func (s *Store) ServiceUsage(ws memdb.WatchSet) (uint64, structs.ServiceUsage, e return 0, structs.ServiceUsage{}, fmt.Errorf("failed services lookup: %s", err) } + nodes, err := firstUsageEntry(ws, tx, tableNodes) + if err != nil { + return 0, structs.ServiceUsage{}, fmt.Errorf("failed nodes lookup: %s", err) + } + serviceKindInstances := make(map[string]int) for _, kind := range allConnectKind { usage, err := firstUsageEntry(ws, tx, connectUsageTableName(kind)) @@ -443,6 +448,7 @@ func (s *Store) ServiceUsage(ws memdb.WatchSet) (uint64, structs.ServiceUsage, e Services: services.Count, ConnectServiceInstances: serviceKindInstances, BillableServiceInstances: billableServiceInstances.Count, + Nodes: nodes.Count, } results, err := compileEnterpriseServiceUsage(ws, tx, usage) if err != nil { diff --git a/agent/operator_endpoint_oss_test.go b/agent/operator_endpoint_oss_test.go index 90f0e7d0cd..f4de46f905 100644 --- a/agent/operator_endpoint_oss_test.go +++ b/agent/operator_endpoint_oss_test.go @@ -65,6 +65,7 @@ func TestOperator_Usage(t *testing.T) { }, // 4 = 6 total service instances - 1 connect proxy - 1 consul service BillableServiceInstances: 4, + Nodes: 2, }, } require.Equal(t, expected, raw.(structs.Usage).Usage) diff --git a/agent/structs/structs.go b/agent/structs/structs.go index c1f6d07606..59385fa5ba 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -2324,6 +2324,7 @@ type ServiceUsage struct { ServiceInstances int ConnectServiceInstances map[string]int BillableServiceInstances int + Nodes int EnterpriseServiceUsage } diff --git a/api/operator_usage.go b/api/operator_usage.go index e47d4b53e0..8977449ddd 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 5e8dba201c..1ac90dce8d 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 4f0686c479..478b9f4211 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/api-docs/operator/usage.mdx b/website/content/api-docs/operator/usage.mdx index b1dc75c39e..093ccf7362 100644 --- a/website/content/api-docs/operator/usage.mdx +++ b/website/content/api-docs/operator/usage.mdx @@ -64,7 +64,8 @@ $ curl \ "mesh-gateway": 0, "terminating-gateway": 0 }, - "BillableServiceInstances": 0 + "BillableServiceInstances": 0, + "Nodes": 1 } }, "Index": 13, diff --git a/website/content/commands/operator/usage.mdx b/website/content/commands/operator/usage.mdx index 56d3648626..83ae028f6c 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