diff --git a/deployments/rbac/rbac.yaml b/deployments/rbac/rbac.yaml index 4a95414b83..b7c83e9667 100644 --- a/deployments/rbac/rbac.yaml +++ b/deployments/rbac/rbac.yaml @@ -54,6 +54,12 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list - apiGroups: - "" resources: diff --git a/internal/telemetry/cluster.go b/internal/telemetry/cluster.go new file mode 100644 index 0000000000..f343cf90e8 --- /dev/null +++ b/internal/telemetry/cluster.go @@ -0,0 +1,17 @@ +package telemetry + +import ( + "context" + + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NodeCount returns the total number of nodes in the cluster. +// It returns an error if the underlying k8s API client errors. +func (c *Collector) NodeCount(ctx context.Context) (int, error) { + nodes, err := c.Config.K8sClientReader.CoreV1().Nodes().List(ctx, metaV1.ListOptions{}) + if err != nil { + return 0, err + } + return len(nodes.Items), nil +} diff --git a/internal/telemetry/cluster_test.go b/internal/telemetry/cluster_test.go new file mode 100644 index 0000000000..391a2afce4 --- /dev/null +++ b/internal/telemetry/cluster_test.go @@ -0,0 +1,94 @@ +package telemetry_test + +import ( + "context" + "testing" + + "github.com/nginxinc/kubernetes-ingress/internal/telemetry" + apiCoreV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + testClient "k8s.io/client-go/kubernetes/fake" +) + +func TestNodeCountInAClusterWithThreeNodes(t *testing.T) { + t.Parallel() + + c := newTestCollectorForCluserWithNodes(t, node1, node2, node3) + + got, err := c.NodeCount(context.Background()) + if err != nil { + t.Fatal(err) + } + want := 3 + if want != got { + t.Errorf("want %v, got %v", want, got) + } +} + +func TestNodeCountInAClusterWithOneNode(t *testing.T) { + t.Parallel() + + c := newTestCollectorForCluserWithNodes(t, node1) + got, err := c.NodeCount(context.Background()) + if err != nil { + t.Fatal(err) + } + want := 1 + if want != got { + t.Errorf("want %v, got %v", want, got) + } +} + +// newTestCollectorForClusterWithNodes returns a telemetry collector configured +// to simulate collecting data on a cluser with provided nodes. +func newTestCollectorForCluserWithNodes(t *testing.T, nodes ...runtime.Object) *telemetry.Collector { + t.Helper() + + c, err := telemetry.NewCollector( + telemetry.CollectorConfig{}, + ) + if err != nil { + t.Fatal(err) + } + c.Config.K8sClientReader = testClient.NewSimpleClientset(nodes...) + return c +} + +var ( + node1 = &apiCoreV1.Node{ + TypeMeta: metaV1.TypeMeta{ + Kind: "Node", + APIVersion: "v1", + }, + ObjectMeta: metaV1.ObjectMeta{ + Name: "test-node-1", + Namespace: "default", + }, + Spec: apiCoreV1.NodeSpec{}, + } + + node2 = &apiCoreV1.Node{ + TypeMeta: metaV1.TypeMeta{ + Kind: "Node", + APIVersion: "v1", + }, + ObjectMeta: metaV1.ObjectMeta{ + Name: "test-node-2", + Namespace: "default", + }, + Spec: apiCoreV1.NodeSpec{}, + } + + node3 = &apiCoreV1.Node{ + TypeMeta: metaV1.TypeMeta{ + Kind: "Node", + APIVersion: "v1", + }, + ObjectMeta: metaV1.ObjectMeta{ + Name: "test-node-3", + Namespace: "default", + }, + Spec: apiCoreV1.NodeSpec{}, + } +) diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go index ffd8b047ad..301de1e115 100644 --- a/internal/telemetry/collector.go +++ b/internal/telemetry/collector.go @@ -79,8 +79,7 @@ func (c *Collector) Start(ctx context.Context) { // It exports data using provided exporter. func (c *Collector) Collect(ctx context.Context) { glog.V(3).Info("Collecting telemetry data") - // TODO: Re-add ctx to BuildReport when collecting Node Count. - data, err := c.BuildReport() + data, err := c.BuildReport(ctx) if err != nil { glog.Errorf("Error collecting telemetry data: %v", err) } @@ -92,7 +91,7 @@ func (c *Collector) Collect(ctx context.Context) { } // BuildReport takes context and builds report from gathered telemetry data. -func (c *Collector) BuildReport() (Data, error) { +func (c *Collector) BuildReport(ctx context.Context) (Data, error) { d := Data{} var err error @@ -100,5 +99,10 @@ func (c *Collector) BuildReport() (Data, error) { d.NICResourceCounts.VirtualServers, d.NICResourceCounts.VirtualServerRoutes = c.Config.Configurator.GetVirtualServerCounts() d.NICResourceCounts.TransportServers = c.Config.Configurator.GetTransportServerCounts() } + nc, err := c.NodeCount(ctx) + if err != nil { + glog.Errorf("Error collecting telemetry data: Nodes: %v", err) + } + d.NodeCount = nc return d, err } diff --git a/internal/telemetry/collector_test.go b/internal/telemetry/collector_test.go index d53be0e76d..9974a06329 100644 --- a/internal/telemetry/collector_test.go +++ b/internal/telemetry/collector_test.go @@ -16,6 +16,7 @@ import ( "github.com/nginxinc/kubernetes-ingress/internal/telemetry" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testClient "k8s.io/client-go/kubernetes/fake" ) func TestCreateNewCollectorWithCustomReportingPeriod(t *testing.T) { @@ -46,7 +47,8 @@ func TestCreateNewCollectorWithCustomExporter(t *testing.T) { td := telemetry.Data{} cfg := telemetry.CollectorConfig{ - Configurator: newConfigurator(t), + K8sClientReader: testClient.NewSimpleClientset(), + Configurator: newConfigurator(t), } c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) @@ -62,6 +64,76 @@ func TestCreateNewCollectorWithCustomExporter(t *testing.T) { } } +func TestCollectNodeCountInClusterWithOneNode(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + cfg := telemetry.CollectorConfig{ + Configurator: newConfigurator(t), + K8sClientReader: testClient.NewSimpleClientset(node1), + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + td := telemetry.Data{ + ProjectMeta: telemetry.ProjectMeta{ + Name: "", + Version: "", + }, + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 0, + VirtualServerRoutes: 0, + TransportServers: 0, + }, + NodeCount: 1, + } + want := fmt.Sprintf("%+v", td) + got := buf.String() + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestCollectNodeCountInClusterWithThreeNodes(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + cfg := telemetry.CollectorConfig{ + Configurator: newConfigurator(t), + K8sClientReader: testClient.NewSimpleClientset(node1, node2, node3), + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + td := telemetry.Data{ + ProjectMeta: telemetry.ProjectMeta{ + Name: "", + Version: "", + }, + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 0, + VirtualServerRoutes: 0, + TransportServers: 0, + }, + NodeCount: 3, + } + want := fmt.Sprintf("%+v", td) + got := buf.String() + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + func TestCountVirtualServers(t *testing.T) { t.Parallel() @@ -171,7 +243,8 @@ func TestCountVirtualServers(t *testing.T) { configurator := newConfigurator(t) c, err := telemetry.NewCollector(telemetry.CollectorConfig{ - Configurator: configurator, + K8sClientReader: testClient.NewSimpleClientset(), + Configurator: configurator, }) if err != nil { t.Fatal(err) @@ -184,7 +257,7 @@ func TestCountVirtualServers(t *testing.T) { } } - gotTraceDataOnAdd, err := c.BuildReport() + gotTraceDataOnAdd, err := c.BuildReport(context.Background()) if err != nil { t.Fatal(err) } @@ -202,7 +275,7 @@ func TestCountVirtualServers(t *testing.T) { } } - gotTraceDataOnDelete, err := c.BuildReport() + gotTraceDataOnDelete, err := c.BuildReport(context.Background()) if err != nil { t.Fatal(err) } @@ -342,7 +415,8 @@ func TestCountTransportServers(t *testing.T) { configurator := newConfigurator(t) c, err := telemetry.NewCollector(telemetry.CollectorConfig{ - Configurator: configurator, + K8sClientReader: testClient.NewSimpleClientset(), + Configurator: configurator, }) if err != nil { t.Fatal(err) @@ -355,7 +429,7 @@ func TestCountTransportServers(t *testing.T) { } } - gotTraceDataOnAdd, err := c.BuildReport() + gotTraceDataOnAdd, err := c.BuildReport(context.Background()) if err != nil { t.Fatal(err) } @@ -373,7 +447,7 @@ func TestCountTransportServers(t *testing.T) { } } - gotTraceDataOnDelete, err := c.BuildReport() + gotTraceDataOnDelete, err := c.BuildReport(context.Background()) if err != nil { t.Fatal(err) } diff --git a/internal/telemetry/exporter.go b/internal/telemetry/exporter.go index 6ec60802c4..e3349505e1 100644 --- a/internal/telemetry/exporter.go +++ b/internal/telemetry/exporter.go @@ -29,6 +29,7 @@ func (e *StdoutExporter) Export(_ context.Context, data Data) error { type Data struct { ProjectMeta ProjectMeta NICResourceCounts NICResourceCounts + NodeCount int } // ProjectMeta holds metadata for the project.