diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index 57bb70426d..a8f21a53eb 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -152,7 +152,12 @@ func main() { controllerNamespace := os.Getenv("POD_NAMESPACE") transportServerValidator := cr_validation.NewTransportServerValidator(*enableTLSPassthrough, *enableSnippets, *nginxPlus) - virtualServerValidator := cr_validation.NewVirtualServerValidator(cr_validation.IsPlus(*nginxPlus), cr_validation.IsDosEnabled(*appProtectDos), cr_validation.IsCertManagerEnabled(*enableCertManager), cr_validation.IsExternalDNSEnabled(*enableExternalDNS)) + virtualServerValidator := cr_validation.NewVirtualServerValidator( + cr_validation.IsPlus(*nginxPlus), + cr_validation.IsDosEnabled(*appProtectDos), + cr_validation.IsCertManagerEnabled(*enableCertManager), + cr_validation.IsExternalDNSEnabled(*enableExternalDNS), + ) if *enableServiceInsight { createHealthProbeEndpoint(kubeClient, plusClient, cnf) diff --git a/go.mod b/go.mod index 5989811189..c1b2e56007 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/prometheus/common v0.47.0 github.com/spiffe/go-spiffe/v2 v2.1.7 github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/otel v1.21.0 golang.org/x/exp v0.0.0-20231226003508-02704c960a9b k8s.io/api v0.29.2 k8s.io/apimachinery v0.29.2 @@ -98,7 +99,6 @@ require ( go.etcd.io/etcd/client/v3 v3.5.11 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 8574589ae4..f060eb9039 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -962,6 +962,7 @@ func (cnf *Configurator) deleteTransportServer(key string) error { name := getFileNameForTransportServerFromKey(key) cnf.nginxManager.DeleteStreamConfig(name) + delete(cnf.transportServers, name) // update TLS Passthrough Hosts config in case we have a TLS Passthrough TransportServer if _, exists := cnf.tlsPassthroughPairs[key]; exists { delete(cnf.tlsPassthroughPairs, key) @@ -1468,23 +1469,29 @@ func (cnf *Configurator) GetIngressCounts() map[string]int { } } - for _, min := range cnf.minions { - counters["minion"] += len(min) + for _, minion := range cnf.minions { + counters["minion"] += len(minion) } return counters } -// GetVirtualServerCounts returns the total count of VS/VSR resources that are handled by the Ingress Controller +// GetVirtualServerCounts returns the total count of +// VirtualServer and VirtualServerRoute resources that are handled by the Ingress Controller func (cnf *Configurator) GetVirtualServerCounts() (vsCount int, vsrCount int) { vsCount = len(cnf.virtualServers) for _, vs := range cnf.virtualServers { vsrCount += len(vs.VirtualServerRoutes) } - return vsCount, vsrCount } +// GetTransportServerCounts returns the total count of +// TransportServer resources that are handled by the Ingress Controller +func (cnf *Configurator) GetTransportServerCounts() (tsCount int) { + return len(cnf.transportServers) +} + // AddOrUpdateSpiffeCerts writes Spiffe certs and keys to disk and reloads NGINX func (cnf *Configurator) AddOrUpdateSpiffeCerts(svidResponse *workloadapi.X509Context) error { svid := svidResponse.DefaultSVID() diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 2e333135c2..2c3d9bbc86 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -245,7 +245,6 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc isLatencyMetricsEnabled: input.IsLatencyMetricsEnabled, isIPV6Disabled: input.IsIPV6Disabled, } - eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&core_v1.EventSinkImpl{ @@ -277,18 +276,6 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc lbc.externalDNSController = ed_controller.NewController(ed_controller.BuildOpts(context.TODO(), lbc.namespaceList, lbc.recorder, lbc.confClient, input.ResyncPeriod, isDynamicNs)) } - // NIC Telemetry Reporting - if input.EnableTelemetryReporting { - lbc.telemetryChan = make(chan struct{}) - collector, err := telemetry.NewCollector( - telemetry.WithTimePeriod(input.TelemetryReportingPeriod), - ) - if err != nil { - glog.Fatalf("failed to initialize telemetry collector: %v", err) - } - lbc.telemetryCollector = collector - } - glog.V(3).Infof("Nginx Ingress Controller has class: %v", input.IngressClass) lbc.namespacedInformers = make(map[string]*namespacedInformer) @@ -357,6 +344,24 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc lbc.secretStore = secrets.NewLocalSecretStore(lbc.configurator) + // NIC Telemetry Reporting + if input.EnableTelemetryReporting { + collectorConfig := telemetry.CollectorConfig{ + K8sClientReader: input.KubeClient, + CustomK8sClientReader: input.ConfClient, + Period: 5 * time.Second, + Configurator: lbc.configurator, + } + lbc.telemetryChan = make(chan struct{}) + collector, err := telemetry.NewCollector( + collectorConfig, + ) + if err != nil { + glog.Fatalf("failed to initialize telemetry collector: %v", err) + } + lbc.telemetryCollector = collector + } + return lbc } diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index d8de01b8a5..489700f708 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -3757,6 +3757,7 @@ func TestNewTelemetryCollector(t *testing.T) { testCases := []struct { testCase string input NewLoadBalancerControllerInput + collectorConfig telemetry.CollectorConfig expectedCollector telemetry.Collector }{ { @@ -3767,8 +3768,10 @@ func TestNewTelemetryCollector(t *testing.T) { TelemetryReportingPeriod: "24h", }, expectedCollector: telemetry.Collector{ - Period: 24 * time.Hour, - Exporter: telemetry.DiscardExporter, + Config: telemetry.CollectorConfig{ + Period: 24 * time.Hour, + }, + Exporter: &telemetry.StdoutExporter{}, }, }, { @@ -3784,7 +3787,7 @@ func TestNewTelemetryCollector(t *testing.T) { for _, tc := range testCases { lbc := NewLoadBalancerController(tc.input) if reflect.DeepEqual(tc.expectedCollector, lbc.telemetryCollector) { - t.Fatalf("Expected %x, but got %x", tc.expectedCollector, lbc.telemetryCollector) + t.Fatalf("Expected %v, but got %v", tc.expectedCollector, lbc.telemetryCollector) } } } diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go new file mode 100644 index 0000000000..ffd8b047ad --- /dev/null +++ b/internal/telemetry/collector.go @@ -0,0 +1,104 @@ +// Package telemetry provides functionality for collecting and exporting NIC telemetry data. +package telemetry + +import ( + "context" + "io" + "time" + + "github.com/nginxinc/kubernetes-ingress/internal/configs" + + k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + + "github.com/golang/glog" +) + +// Option is a functional option used for configuring TraceReporter. +type Option func(*Collector) error + +// WithExporter configures telemetry collector to use given exporter. +// +// This may change in the future when we use exporter implemented +// in the external module. +func WithExporter(e Exporter) Option { + return func(c *Collector) error { + c.Exporter = e + return nil + } +} + +// Collector is NIC telemetry data collector. +type Collector struct { + // Exporter is a temp exporter for exporting telemetry data. + // The concrete implementation will be implemented in a separate module. + Exporter Exporter + + // Configuration for the collector. + Config CollectorConfig +} + +// CollectorConfig contains configuration options for a Collector +type CollectorConfig struct { + // K8sClientReader is a kubernetes client. + K8sClientReader kubernetes.Interface + + // CustomK8sClientReader is a kubernetes client for our CRDs. + // Note: May not need this client. + CustomK8sClientReader k8s_nginx.Interface + + // Period to collect telemetry + Period time.Duration + + Configurator *configs.Configurator +} + +// NewCollector takes 0 or more options and creates a new TraceReporter. +// If no options are provided, NewReporter returns TraceReporter +// configured to gather data every 24h. +func NewCollector(cfg CollectorConfig, opts ...Option) (*Collector, error) { + c := Collector{ + Exporter: &StdoutExporter{Endpoint: io.Discard}, + Config: cfg, + } + for _, o := range opts { + if err := o(&c); err != nil { + return nil, err + } + } + return &c, nil +} + +// Start starts running NIC Telemetry Collector. +func (c *Collector) Start(ctx context.Context) { + wait.JitterUntilWithContext(ctx, c.Collect, c.Config.Period, 0.1, true) +} + +// Collect collects and exports telemetry data. +// 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() + if err != nil { + glog.Errorf("Error collecting telemetry data: %v", err) + } + err = c.Exporter.Export(ctx, data) + if err != nil { + glog.Errorf("Error exporting telemetry data: %v", err) + } + glog.V(3).Infof("Exported telemetry data: %+v", data) +} + +// BuildReport takes context and builds report from gathered telemetry data. +func (c *Collector) BuildReport() (Data, error) { + d := Data{} + var err error + + if c.Config.Configurator != nil { + d.NICResourceCounts.VirtualServers, d.NICResourceCounts.VirtualServerRoutes = c.Config.Configurator.GetVirtualServerCounts() + d.NICResourceCounts.TransportServers = c.Config.Configurator.GetTransportServerCounts() + } + return d, err +} diff --git a/internal/telemetry/collector_test.go b/internal/telemetry/collector_test.go new file mode 100644 index 0000000000..d53be0e76d --- /dev/null +++ b/internal/telemetry/collector_test.go @@ -0,0 +1,434 @@ +package telemetry_test + +import ( + "bytes" + "context" + "fmt" + "testing" + "time" + + "github.com/nginxinc/kubernetes-ingress/internal/configs" + "github.com/nginxinc/kubernetes-ingress/internal/configs/version1" + "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" + "github.com/nginxinc/kubernetes-ingress/internal/nginx" + + "github.com/google/go-cmp/cmp" + "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" +) + +func TestCreateNewCollectorWithCustomReportingPeriod(t *testing.T) { + t.Parallel() + + cfg := telemetry.CollectorConfig{ + Period: 24 * time.Hour, + } + + c, err := telemetry.NewCollector(cfg) + if err != nil { + t.Fatal(err) + } + + want := 24.0 + got := c.Config.Period.Hours() + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestCreateNewCollectorWithCustomExporter(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + td := telemetry.Data{} + + cfg := telemetry.CollectorConfig{ + Configurator: newConfigurator(t), + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + 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() + + testCases := []struct { + testName string + expectedTraceDataOnAdd telemetry.Data + expectedTraceDataOnDelete telemetry.Data + virtualServers []*configs.VirtualServerEx + deleteCount int + }{ + { + testName: "Create and delete 1 VirtualServer", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 1, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 0, + }, + }, + virtualServers: []*configs.VirtualServerEx{ + { + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.VirtualServerSpec{}, + }, + }, + }, + deleteCount: 1, + }, + { + testName: "Create 2 VirtualServers and delete 2", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 2, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 0, + }, + }, + virtualServers: []*configs.VirtualServerEx{ + { + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.VirtualServerSpec{}, + }, + }, + { + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "tea", + }, + Spec: conf_v1.VirtualServerSpec{}, + }, + }, + }, + deleteCount: 2, + }, + { + testName: "Create 2 VirtualServers and delete 1", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 2, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + VirtualServers: 1, + }, + }, + virtualServers: []*configs.VirtualServerEx{ + { + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.VirtualServerSpec{}, + }, + }, + { + VirtualServer: &conf_v1.VirtualServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "tea", + }, + Spec: conf_v1.VirtualServerSpec{}, + }, + }, + }, + deleteCount: 1, + }, + } + + for _, test := range testCases { + configurator := newConfigurator(t) + + c, err := telemetry.NewCollector(telemetry.CollectorConfig{ + Configurator: configurator, + }) + if err != nil { + t.Fatal(err) + } + + for _, vs := range test.virtualServers { + _, err := configurator.AddOrUpdateVirtualServer(vs) + if err != nil { + t.Fatal(err) + } + } + + gotTraceDataOnAdd, err := c.BuildReport() + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(test.expectedTraceDataOnAdd, gotTraceDataOnAdd) { + t.Error(cmp.Diff(test.expectedTraceDataOnAdd, gotTraceDataOnAdd)) + } + + for i := 0; i < test.deleteCount; i++ { + vs := test.virtualServers[i] + key := getResourceKey(vs.VirtualServer.Namespace, vs.VirtualServer.Name) + err := configurator.DeleteVirtualServer(key, false) + if err != nil { + t.Fatal(err) + } + } + + gotTraceDataOnDelete, err := c.BuildReport() + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(test.expectedTraceDataOnDelete, gotTraceDataOnDelete) { + t.Error(cmp.Diff(test.expectedTraceDataOnDelete, gotTraceDataOnDelete)) + } + } +} + +func TestCountTransportServers(t *testing.T) { + t.Parallel() + + testCases := []struct { + testName string + expectedTraceDataOnAdd telemetry.Data + expectedTraceDataOnDelete telemetry.Data + transportServers []*configs.TransportServerEx + deleteCount int + }{ + { + testName: "Create and delete 1 TransportServer", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 1, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 0, + }, + }, + transportServers: []*configs.TransportServerEx{ + { + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.TransportServerSpec{ + Action: &conf_v1.TransportServerAction{ + Pass: "coffee", + }, + }, + }, + }, + }, + deleteCount: 1, + }, + { + testName: "Create 2 and delete 2 TransportServer", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 2, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 0, + }, + }, + transportServers: []*configs.TransportServerEx{ + { + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.TransportServerSpec{ + Action: &conf_v1.TransportServerAction{ + Pass: "coffee", + }, + }, + }, + }, + { + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "tea", + }, + Spec: conf_v1.TransportServerSpec{ + Action: &conf_v1.TransportServerAction{ + Pass: "tea", + }, + }, + }, + }, + }, + deleteCount: 2, + }, + { + testName: "Create 2 and delete 1 TransportServer", + expectedTraceDataOnAdd: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 2, + }, + }, + expectedTraceDataOnDelete: telemetry.Data{ + NICResourceCounts: telemetry.NICResourceCounts{ + TransportServers: 1, + }, + }, + transportServers: []*configs.TransportServerEx{ + { + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "coffee", + }, + Spec: conf_v1.TransportServerSpec{ + Action: &conf_v1.TransportServerAction{ + Pass: "coffee", + }, + }, + }, + }, + { + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns-1", + Name: "tea", + }, + Spec: conf_v1.TransportServerSpec{ + Action: &conf_v1.TransportServerAction{ + Pass: "tea", + }, + }, + }, + }, + }, + deleteCount: 1, + }, + } + + for _, test := range testCases { + configurator := newConfigurator(t) + + c, err := telemetry.NewCollector(telemetry.CollectorConfig{ + Configurator: configurator, + }) + if err != nil { + t.Fatal(err) + } + + for _, ts := range test.transportServers { + _, err := configurator.AddOrUpdateTransportServer(ts) + if err != nil { + t.Fatal(err) + } + } + + gotTraceDataOnAdd, err := c.BuildReport() + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(test.expectedTraceDataOnAdd, gotTraceDataOnAdd) { + t.Error(cmp.Diff(test.expectedTraceDataOnAdd, gotTraceDataOnAdd)) + } + + for i := 0; i < test.deleteCount; i++ { + ts := test.transportServers[i] + key := getResourceKey(ts.TransportServer.Namespace, ts.TransportServer.Name) + err := configurator.DeleteTransportServer(key) + if err != nil { + t.Fatal(err) + } + } + + gotTraceDataOnDelete, err := c.BuildReport() + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(test.expectedTraceDataOnDelete, gotTraceDataOnDelete) { + t.Error(cmp.Diff(test.expectedTraceDataOnDelete, gotTraceDataOnDelete)) + } + } +} + +func getResourceKey(namespace, name string) string { + return fmt.Sprintf("%s_%s", namespace, name) +} + +func newConfigurator(t *testing.T) *configs.Configurator { + t.Helper() + + templateExecutor, err := version1.NewTemplateExecutor(mainTemplatePath, ingressTemplatePath) + if err != nil { + t.Fatal(err) + } + + templateExecutorV2, err := version2.NewTemplateExecutor(virtualServerTemplatePath, transportServerTemplatePath) + if err != nil { + t.Fatal(err) + } + + manager := nginx.NewFakeManager("/etc/nginx") + cnf := configs.NewConfigurator(configs.ConfiguratorParams{ + NginxManager: manager, + StaticCfgParams: &configs.StaticConfigParams{ + HealthStatus: true, + HealthStatusURI: "/nginx-health", + NginxStatus: true, + NginxStatusAllowCIDRs: []string{"127.0.0.1"}, + NginxStatusPort: 8080, + StubStatusOverUnixSocketForOSS: false, + NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"), + }, + Config: configs.NewDefaultConfigParams(false), + TemplateExecutor: templateExecutor, + TemplateExecutorV2: templateExecutorV2, + LatencyCollector: nil, + LabelUpdater: nil, + IsPlus: false, + IsWildcardEnabled: false, + IsPrometheusEnabled: false, + IsLatencyMetricsEnabled: false, + }) + return cnf +} + +const ( + mainTemplatePath = "../configs/version1/nginx-plus.tmpl" + ingressTemplatePath = "../configs/version1/nginx-plus.ingress.tmpl" + virtualServerTemplatePath = "../configs/version2/nginx-plus.virtualserver.tmpl" + transportServerTemplatePath = "../configs/version2/nginx-plus.transportserver.tmpl" +) diff --git a/internal/telemetry/exporter.go b/internal/telemetry/exporter.go new file mode 100644 index 0000000000..6ec60802c4 --- /dev/null +++ b/internal/telemetry/exporter.go @@ -0,0 +1,51 @@ +package telemetry + +import ( + "context" + "fmt" + "io" + + "go.opentelemetry.io/otel/attribute" +) + +// Exporter interface for exporters. +type Exporter interface { + // TODO Change Data to Exportable. + Export(ctx context.Context, data Data) error +} + +// StdoutExporter represents a temporary telemetry data exporter. +type StdoutExporter struct { + Endpoint io.Writer +} + +// Export takes context and trace data and writes to the endpoint. +func (e *StdoutExporter) Export(_ context.Context, data Data) error { + fmt.Fprintf(e.Endpoint, "%+v", data) + return nil +} + +// Data holds collected telemetry data. +type Data struct { + ProjectMeta ProjectMeta + NICResourceCounts NICResourceCounts +} + +// ProjectMeta holds metadata for the project. +type ProjectMeta struct { + Name string + Version string +} + +// NICResourceCounts holds a count of NIC specific resource. +type NICResourceCounts struct { + VirtualServers int + VirtualServerRoutes int + TransportServers int +} + +// Attributes is a placeholder function. +// This ensures that Data is of type Exportable +func (d *Data) Attributes() []attribute.KeyValue { + return nil +} diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go deleted file mode 100644 index a1c1a5272e..0000000000 --- a/internal/telemetry/telemetry.go +++ /dev/null @@ -1,135 +0,0 @@ -// Package telemetry provides functionality for collecting and exporting NIC telemetry data. -package telemetry - -import ( - "context" - "fmt" - "io" - "time" - - "github.com/golang/glog" - "k8s.io/apimachinery/pkg/util/wait" -) - -// DiscardExporter is a temporary exporter -// for discarding collected telemetry data. -var DiscardExporter = Exporter{Endpoint: io.Discard} - -// Exporter represents a temporary telemetry data exporter. -type Exporter struct { - Endpoint io.Writer -} - -// Export takes context and trace data and writes to the endpoint. -func (e *Exporter) Export(_ context.Context, td TraceData) error { - // Note: exporting functionality will be implemented in a separate module. - fmt.Fprintf(e.Endpoint, "%+v", td) - return nil -} - -// TraceData holds collected NIC telemetry data. -type TraceData struct { - // Count of VirtualServers - VSCount int - // Count of TransportServers - TSCount int - - // TODO - // Add more fields for NIC data points -} - -// Option is a functional option used for configuring TraceReporter. -type Option func(*Collector) error - -// WithTimePeriod configures reporting time on TraceReporter. -func WithTimePeriod(period string) Option { - return func(c *Collector) error { - d, err := time.ParseDuration(period) - if err != nil { - return err - } - c.Period = d - return nil - } -} - -// WithExporter configures telemetry collector to use given exporter. -// -// This may change in the future when we use exporter implemented -// in the external module. -func WithExporter(e Exporter) Option { - return func(c *Collector) error { - c.Exporter = e - return nil - } -} - -// Collector is NIC telemetry data collector. -type Collector struct { - Period time.Duration - - // Exporter is a temp exporter for exporting telemetry data. - // The concrete implementation will be implemented in a separate module. - Exporter Exporter -} - -// NewCollector takes 0 or more options and creates a new TraceReporter. -// If no options are provided, NewReporter returns TraceReporter -// configured to gather data every 24h. -func NewCollector(opts ...Option) (*Collector, error) { - c := Collector{ - Period: 24 * time.Hour, - Exporter: DiscardExporter, // Use DiscardExporter until the real exporter is available. - } - for _, o := range opts { - if err := o(&c); err != nil { - return nil, err - } - } - return &c, nil -} - -// BuildReport takes context and builds report from gathered telemetry data. -func (c *Collector) BuildReport(context.Context) (TraceData, error) { - dt := TraceData{} - - // TODO: Implement handling and logging errors for each collected data point - - return dt, nil -} - -// Collect collects and exports telemetry data. -// It exports data using provided exporter. -func (c *Collector) Collect(ctx context.Context) { - glog.V(3).Info("Collecting telemetry data") - traceData, err := c.BuildReport(ctx) - if err != nil { - glog.Errorf("Error collecting telemetry data: %v", err) - } - err = c.Exporter.Export(ctx, traceData) - if err != nil { - glog.Errorf("Error exporting telemetry data: %v", err) - } - glog.V(3).Infof("Exported telemetry data: %x", traceData) -} - -// Start starts running NIC Telemetry Collector. -func (c *Collector) Start(ctx context.Context) { - wait.JitterUntilWithContext(ctx, c.Collect, c.Period, 0.1, true) -} - -// GetVSCount returns number of VirtualServers in watched namespaces. -// -// Note: this is a placeholder function. -func (c *Collector) GetVSCount() int { - // Placeholder function - return 0 -} - -// GetTSCount returns number of TransportServers in watched namespaces. -// -// Note: this is a placeholder function. -func (c *Collector) GetTSCount() int { - // Placeholder function - return 0 -} diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go deleted file mode 100644 index 99ca921ae7..0000000000 --- a/internal/telemetry/telemetry_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package telemetry_test - -import ( - "bytes" - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/nginxinc/kubernetes-ingress/internal/telemetry" -) - -func TestCreateNewDefaultCollector(t *testing.T) { - t.Parallel() - - c, err := telemetry.NewCollector() - if err != nil { - t.Fatal(err) - } - - want := 24.0 - got := c.Period.Hours() - - if !cmp.Equal(want, got) { - t.Error(cmp.Diff(want, got)) - } -} - -func TestCreateNewCollectorWithCustomReportingPeriod(t *testing.T) { - t.Parallel() - - c, err := telemetry.NewCollector(telemetry.WithTimePeriod("4h")) - if err != nil { - t.Fatal(err) - } - - want := 4.0 - got := c.Period.Hours() - - if !cmp.Equal(want, got) { - t.Error(cmp.Diff(want, got)) - } -} - -func TestCreateNewCollectorWithCustomExporter(t *testing.T) { - t.Parallel() - - buf := &bytes.Buffer{} - exp := telemetry.Exporter{Endpoint: buf} - - c, err := telemetry.NewCollector(telemetry.WithExporter(exp)) - if err != nil { - t.Fatal(err) - } - c.Collect(context.Background()) - - want := "{VSCount:0 TSCount:0}" - got := buf.String() - if !cmp.Equal(want, got) { - t.Error(cmp.Diff(want, got)) - } -}