diff --git a/internal/telemetry/cluster.go b/internal/telemetry/cluster.go index 076c0be4e1..e3f16dd4c9 100644 --- a/internal/telemetry/cluster.go +++ b/internal/telemetry/cluster.go @@ -26,9 +26,9 @@ func (c *Collector) ClusterID(ctx context.Context) (string, error) { return string(cluster.UID), nil } -// K8sVersion returns a string respresenting the K8s version. +// ClusterVersion returns a string respresenting the K8s version. // It returns an error if the underlying k8s API client errors. -func (c *Collector) K8sVersion() (string, error) { +func (c *Collector) ClusterVersion() (string, error) { sv, err := c.Config.K8sClientReader.Discovery().ServerVersion() if err != nil { return "", err diff --git a/internal/telemetry/cluster_test.go b/internal/telemetry/cluster_test.go index 6671536456..162e576641 100644 --- a/internal/telemetry/cluster_test.go +++ b/internal/telemetry/cluster_test.go @@ -69,7 +69,7 @@ func TestK8sVersionRetrievesClusterVersion(t *testing.T) { t.Parallel() c := newTestCollectorForClusterWithNodes(t, node1) - got, err := c.K8sVersion() + got, err := c.ClusterVersion() if err != nil { t.Fatal(err) } diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go index 6c12223426..a98fea9c72 100644 --- a/internal/telemetry/collector.go +++ b/internal/telemetry/collector.go @@ -7,6 +7,8 @@ import ( "runtime" "time" + telemetry "github.com/nginxinc/telemetry-exporter/pkg/telemetry" + "github.com/nginxinc/kubernetes-ingress/internal/configs" k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned" @@ -95,14 +97,13 @@ func (c *Collector) Collect(ctx context.Context) { } // BuildReport takes context and builds report from gathered telemetry data. -func (c *Collector) BuildReport(ctx context.Context) (Data, error) { - pm := ProjectMeta{ - Name: "NIC", - Version: c.Config.Version, - } +func (c *Collector) BuildReport(ctx context.Context) (telemetry.Exportable, error) { d := Data{ - ProjectMeta: pm, - Arch: runtime.GOARCH, + Data: telemetry.Data{ + ProjectName: "NIC", + ProjectVersion: c.Config.Version, + ProjectArchitecture: runtime.GOARCH, + }, } var err error @@ -113,16 +114,21 @@ func (c *Collector) BuildReport(ctx context.Context) (Data, error) { d.TransportServers = int64(c.Config.Configurator.GetTransportServerCounts()) } - if d.NodeCount, err = c.NodeCount(ctx); err != nil { - glog.Errorf("Error collecting telemetry data: Nodes: %v", err) - } - if d.ClusterID, err = c.ClusterID(ctx); err != nil { glog.Errorf("Error collecting telemetry data: ClusterID: %v", err) } - if d.K8sVersion, err = c.K8sVersion(); err != nil { + if d.ClusterNodeCount, err = c.NodeCount(ctx); err != nil { + glog.Errorf("Error collecting telemetry data: Nodes: %v", err) + } + + if d.ClusterVersion, err = c.ClusterVersion(); err != nil { glog.Errorf("Error collecting telemetry data: K8s Version: %v", err) } - return d, err + + // TODO: Get Cluster (k8s) platform. e.g. EKS, AWS, Openshift, etc... + + // TODO: Get InstallationID + // example of how NGF gets this ID https://github.com/nginxinc/nginx-gateway-fabric/blob/f33db51fc9e05ccf98fc8cdae100772a5cc6775e/internal/mode/static/telemetry/collector.go#L244-L248 + return &d, err } diff --git a/internal/telemetry/collector_test.go b/internal/telemetry/collector_test.go index 0c0bb2d5bb..c797dff1ae 100644 --- a/internal/telemetry/collector_test.go +++ b/internal/telemetry/collector_test.go @@ -16,7 +16,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nginxinc/kubernetes-ingress/internal/telemetry" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" - _ "github.com/nginxinc/telemetry-exporter/pkg/telemetry" + exporter "github.com/nginxinc/telemetry-exporter/pkg/telemetry" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/version" @@ -24,6 +24,13 @@ import ( testClient "k8s.io/client-go/kubernetes/fake" ) +var commonData = exporter.Data{ + ProjectName: "NIC", + ProjectVersion: "3.5.0", + ClusterVersion: "v1.29.2", + ProjectArchitecture: runtime.GOARCH, +} + func TestCreateNewCollectorWithCustomReportingPeriod(t *testing.T) { t.Parallel() @@ -62,14 +69,14 @@ func TestCreateNewCollectorWithCustomExporter(t *testing.T) { c.Collect(context.Background()) td := telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", + Data: exporter.Data{ + ProjectName: "NIC", + ProjectVersion: "3.5.0", + ClusterVersion: "v1.29.2", + ProjectArchitecture: runtime.GOARCH, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, } - want := fmt.Sprintf("%+v", td) + want := fmt.Sprintf("%+v", &td) got := buf.String() if !cmp.Equal(want, got) { t.Error(cmp.Diff(want, got)) @@ -94,20 +101,21 @@ func TestCollectNodeCountInClusterWithOneNode(t *testing.T) { c.Collect(context.Background()) td := telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", + Data: exporter.Data{ + ProjectName: "NIC", + ProjectVersion: "3.5.0", + ClusterVersion: "v1.29.2", + ProjectArchitecture: runtime.GOARCH, + ClusterNodeCount: 1, }, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 0, VirtualServerRoutes: 0, TransportServers: 0, }, - NodeCount: 1, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, } - want := fmt.Sprintf("%+v", td) + + want := fmt.Sprintf("%+v", &td) got := buf.String() if !cmp.Equal(want, got) { t.Error(cmp.Diff(want, got)) @@ -132,20 +140,20 @@ func TestCollectNodeCountInClusterWithThreeNodes(t *testing.T) { c.Collect(context.Background()) td := telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", + Data: exporter.Data{ + ProjectName: "NIC", + ProjectVersion: "3.5.0", + ClusterVersion: "v1.29.2", + ProjectArchitecture: runtime.GOARCH, + ClusterNodeCount: 3, }, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 0, VirtualServerRoutes: 0, TransportServers: 0, }, - NodeCount: 3, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, } - want := fmt.Sprintf("%+v", td) + want := fmt.Sprintf("%+v", &td) got := buf.String() if !cmp.Equal(want, got) { t.Error(cmp.Diff(want, got)) @@ -170,60 +178,21 @@ func TestCollectClusterIDInClusterWithOneNode(t *testing.T) { c.Collect(context.Background()) td := telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", + Data: exporter.Data{ + ProjectName: "NIC", + ProjectVersion: "3.5.0", + ClusterVersion: "v1.29.2", + ProjectArchitecture: runtime.GOARCH, + ClusterNodeCount: 1, + ClusterID: "329766ff-5d78-4c9e-8736-7faad1f2e937", }, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 0, VirtualServerRoutes: 0, TransportServers: 0, }, - NodeCount: 1, - ClusterID: "329766ff-5d78-4c9e-8736-7faad1f2e937", - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, } - want := fmt.Sprintf("%+v", td) - got := buf.String() - if !cmp.Equal(want, got) { - t.Error(cmp.Diff(want, got)) - } -} - -func TestCollectK8sVersion(t *testing.T) { - t.Parallel() - - buf := &bytes.Buffer{} - exp := &telemetry.StdoutExporter{Endpoint: buf} - cfg := telemetry.CollectorConfig{ - Configurator: newConfigurator(t), - K8sClientReader: newTestClientset(node1, kubeNS), - Version: "3.5.0", - } - - 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: "NIC", - Version: "3.5.0", - }, - NICResourceCounts: telemetry.NICResourceCounts{ - VirtualServers: 0, - VirtualServerRoutes: 0, - TransportServers: 0, - }, - NodeCount: 1, - ClusterID: "329766ff-5d78-4c9e-8736-7faad1f2e937", - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, - } - want := fmt.Sprintf("%+v", td) + want := fmt.Sprintf("%+v", &td) got := buf.String() if !cmp.Equal(want, got) { t.Error(cmp.Diff(want, got)) @@ -235,34 +204,24 @@ func TestCountVirtualServers(t *testing.T) { testCases := []struct { testName string - expectedTraceDataOnAdd telemetry.Data - expectedTraceDataOnDelete telemetry.Data + expectedTraceDataOnAdd *telemetry.Data + expectedTraceDataOnDelete *telemetry.Data virtualServers []*configs.VirtualServerEx deleteCount int }{ { testName: "Create and delete 1 VirtualServer", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 1, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 0, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, virtualServers: []*configs.VirtualServerEx{ { @@ -279,27 +238,17 @@ func TestCountVirtualServers(t *testing.T) { }, { testName: "Create 2 VirtualServers and delete 2", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 2, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 0, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, virtualServers: []*configs.VirtualServerEx{ { @@ -325,27 +274,17 @@ func TestCountVirtualServers(t *testing.T) { }, { testName: "Create 2 VirtualServers and delete 1", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 2, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ VirtualServers: 1, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, virtualServers: []*configs.VirtualServerEx{ { @@ -424,34 +363,24 @@ func TestCountTransportServers(t *testing.T) { testCases := []struct { testName string - expectedTraceDataOnAdd telemetry.Data - expectedTraceDataOnDelete telemetry.Data + expectedTraceDataOnAdd *telemetry.Data + expectedTraceDataOnDelete *telemetry.Data transportServers []*configs.TransportServerEx deleteCount int }{ { testName: "Create and delete 1 TransportServer", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 1, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 0, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, transportServers: []*configs.TransportServerEx{ { @@ -472,27 +401,17 @@ func TestCountTransportServers(t *testing.T) { }, { testName: "Create 2 and delete 2 TransportServer", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 2, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 0, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, transportServers: []*configs.TransportServerEx{ { @@ -526,27 +445,17 @@ func TestCountTransportServers(t *testing.T) { }, { testName: "Create 2 and delete 1 TransportServer", - expectedTraceDataOnAdd: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnAdd: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 2, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, - expectedTraceDataOnDelete: telemetry.Data{ - ProjectMeta: telemetry.ProjectMeta{ - Name: "NIC", - Version: "3.5.0", - }, + expectedTraceDataOnDelete: &telemetry.Data{ + Data: commonData, NICResourceCounts: telemetry.NICResourceCounts{ TransportServers: 1, }, - K8sVersion: "v1.29.2", - Arch: runtime.GOARCH, }, transportServers: []*configs.TransportServerEx{ { @@ -688,11 +597,11 @@ func newConfigurator(t *testing.T) *configs.Configurator { // Platform string // } func newTestClientset(objects ...k8sruntime.Object) *testClient.Clientset { - testClient := testClient.NewSimpleClientset(objects...) - testClient.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = &version.Info{ + client := testClient.NewSimpleClientset(objects...) + client.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = &version.Info{ GitVersion: "v1.29.2", } - return testClient + return client } const ( diff --git a/internal/telemetry/data.avdl b/internal/telemetry/data.avdl new file mode 100644 index 0000000000..8ce86fdee4 --- /dev/null +++ b/internal/telemetry/data.avdl @@ -0,0 +1,46 @@ +@namespace("ingress.nginx.com") protocol NICProductTelemetry { + @df_datatype("nic-product-telemetry") record Data { + /** The field that identifies what type of data this is. */ + string dataType; + /** The time the event occurred */ + long eventTime; + /** The time our edge ingested the event */ + long ingestTime; + + + /** ProjectName is the name of the project. */ + string? ProjectName = null; + + /** ProjectVersion is the version of the project. */ + string? ProjectVersion = null; + + /** ProjectArchitecture is the architecture of the project. For example, "amd64". */ + string? ProjectArchitecture = null; + + /** ClusterID is the unique id of the Kubernetes cluster where the project is installed. +It is the UID of the `kube-system` Namespace. */ + string? ClusterID = null; + + /** ClusterVersion is the Kubernetes version of the cluster. */ + string? ClusterVersion = null; + + /** ClusterPlatform is the Kubernetes platform of the cluster. */ + string? ClusterPlatform = null; + + /** InstallationID is the unique id of the project installation in the cluster. */ + string? InstallationID = null; + + /** ClusterNodeCount is the number of nodes in the cluster. */ + long? ClusterNodeCount = null; + + /** VirtualServer is the number of VirtualServer managed by the Ingress Controller. */ + long? VirtualServers = null; + + /** VirtualServerRoutes is the number of VirtualServerRoutes managed by the Ingress Controller. */ + long? VirtualServerRoutes = null; + + /** TransportServers is the number of TransportServers managed by the Ingress Controller. */ + long? TransportServers = null; + + } +} diff --git a/internal/telemetry/data_attributes_generated.go b/internal/telemetry/data_attributes_generated.go new file mode 100644 index 0000000000..9fbc1610e6 --- /dev/null +++ b/internal/telemetry/data_attributes_generated.go @@ -0,0 +1,22 @@ +package telemetry + +/* +This is a generated file. DO NOT EDIT. +*/ + +import ( + "go.opentelemetry.io/otel/attribute" + + ngxTelemetry "github.com/nginxinc/telemetry-exporter/pkg/telemetry" +) + +func (d *Data) Attributes() []attribute.KeyValue { + var attrs []attribute.KeyValue + + attrs = append(attrs, d.Data.Attributes()...) + attrs = append(attrs, d.NICResourceCounts.Attributes()...) + + return attrs +} + +var _ ngxTelemetry.Exportable = (*Data)(nil) diff --git a/internal/telemetry/exporter.go b/internal/telemetry/exporter.go index 19793d22e2..ff84584164 100644 --- a/internal/telemetry/exporter.go +++ b/internal/telemetry/exporter.go @@ -5,13 +5,12 @@ import ( "fmt" "io" - "go.opentelemetry.io/otel/attribute" + "github.com/nginxinc/telemetry-exporter/pkg/telemetry" ) // Exporter interface for exporters. type Exporter interface { - // TODO Change Data to Exportable. - Export(ctx context.Context, data Data) error + Export(ctx context.Context, data telemetry.Exportable) error } // StdoutExporter represents a temporary telemetry data exporter. @@ -20,36 +19,27 @@ type StdoutExporter struct { } // Export takes context and trace data and writes to the endpoint. -func (e *StdoutExporter) Export(_ context.Context, data Data) error { +func (e *StdoutExporter) Export(_ context.Context, data telemetry.Exportable) error { fmt.Fprintf(e.Endpoint, "%+v", data) return nil } // Data holds collected telemetry data. +// +//go:generate go run -tags=generator github.com/nginxinc/telemetry-exporter/cmd/generator -type Data -scheme -scheme-protocol=NICProductTelemetry -scheme-df-datatype=nic-product-telemetry -scheme-namespace=ingress.nginx.com type Data struct { - ProjectMeta + telemetry.Data NICResourceCounts - NodeCount int64 - ClusterID string - K8sVersion string - Arch string -} - -// ProjectMeta holds metadata for the project. -type ProjectMeta struct { - Name string - Version string } // NICResourceCounts holds a count of NIC specific resource. +// +//go:generate go run -tags=generator github.com/nginxinc/telemetry-exporter/cmd/generator -type NICResourceCounts type NICResourceCounts struct { - VirtualServers int64 + // VirtualServer is the number of VirtualServer managed by the Ingress Controller. + VirtualServers int64 + // VirtualServerRoutes is the number of VirtualServerRoutes managed by the Ingress Controller. VirtualServerRoutes int64 - TransportServers int64 -} - -// Attributes is a placeholder function. -// This ensures that Data is of type Exportable -func (d *Data) Attributes() []attribute.KeyValue { - return nil + // TransportServers is the number of TransportServers managed by the Ingress Controller. + TransportServers int64 } diff --git a/internal/telemetry/nicresourcecounts_attributes_generated.go b/internal/telemetry/nicresourcecounts_attributes_generated.go new file mode 100644 index 0000000000..1c85cd5542 --- /dev/null +++ b/internal/telemetry/nicresourcecounts_attributes_generated.go @@ -0,0 +1,23 @@ +package telemetry + +/* +This is a generated file. DO NOT EDIT. +*/ + +import ( + "go.opentelemetry.io/otel/attribute" + + ngxTelemetry "github.com/nginxinc/telemetry-exporter/pkg/telemetry" +) + +func (d *NICResourceCounts) Attributes() []attribute.KeyValue { + var attrs []attribute.KeyValue + + attrs = append(attrs, attribute.Int64("VirtualServers", d.VirtualServers)) + attrs = append(attrs, attribute.Int64("VirtualServerRoutes", d.VirtualServerRoutes)) + attrs = append(attrs, attribute.Int64("TransportServers", d.TransportServers)) + + return attrs +} + +var _ ngxTelemetry.Exportable = (*NICResourceCounts)(nil)