diff --git a/terraform/eks/daemon/otel/main.tf b/terraform/eks/daemon/otel/main.tf index 22b3d5de..195986b7 100644 --- a/terraform/eks/daemon/otel/main.tf +++ b/terraform/eks/daemon/otel/main.tf @@ -119,6 +119,131 @@ resource "null_resource" "kubectl" { } } +# --- KEDA Helm install --- + +resource "helm_release" "keda" { + name = "keda" + repository = "https://kedacore.github.io/charts" + chart = "keda" + version = var.keda_version + namespace = "keda" + create_namespace = true + + set = [ + { name = "resources.operator.requests.cpu", value = "100m" }, + { name = "resources.operator.requests.memory", value = "128Mi" }, + { name = "prometheus.metricServer.enabled", value = "true" }, + { name = "prometheus.operator.enabled", value = "true" }, + ] + + depends_on = [ + aws_eks_node_group.this, + null_resource.kubectl, + ] +} + +# --- KEDA test workload: nginx-keda with cron ScaledObject --- + +resource "kubernetes_deployment_v1" "nginx_keda" { + depends_on = [aws_eks_node_group.this] + metadata { + name = "nginx-keda" + namespace = "default" + } + spec { + replicas = 1 + selector { match_labels = { app = "nginx-keda" } } + template { + metadata { labels = { app = "nginx-keda" } } + spec { + node_selector = { "ci-test.example.com/node-color" = "blue" } + container { + name = "nginx" + image = "public.ecr.aws/nginx/nginx:latest" + port { container_port = 80 } + resources { requests = { cpu = "10m", memory = "32Mi" } } + } + } + } + } +} + +resource "null_resource" "keda_scaled_object" { + depends_on = [helm_release.keda, kubernetes_deployment_v1.nginx_keda] + provisioner "local-exec" { + command = <<-EOT + cat <<'EOF' | kubectl apply -f - + apiVersion: keda.sh/v1alpha1 + kind: ScaledObject + metadata: + name: nginx-keda-scaledobject + namespace: default + spec: + scaleTargetRef: + name: nginx-keda + minReplicaCount: 1 + maxReplicaCount: 3 + pollingInterval: 15 + triggers: + - type: cron + metadata: + timezone: "UTC" + start: "0 0 * * *" + end: "59 23 * * *" + desiredReplicas: "1" + EOF + EOT + } + provisioner "local-exec" { + when = destroy + command = "kubectl delete scaledobject nginx-keda-scaledobject -n default --timeout=60s 2>/dev/null || true" + } +} + +resource "null_resource" "keda_scaled_job" { + depends_on = [helm_release.keda, null_resource.kubectl] + provisioner "local-exec" { + command = <<-EOT + cat <<'EOF' | kubectl apply -f - + apiVersion: keda.sh/v1alpha1 + kind: ScaledJob + metadata: + name: keda-test-scaledjob + namespace: default + spec: + jobTargetRef: + template: + spec: + nodeSelector: + ci-test.example.com/node-color: blue + containers: + - name: echo + image: busybox:1.36 + command: ["echo", "keda-scaled-job-test"] + resources: + requests: + cpu: 10m + memory: 16Mi + restartPolicy: Never + backoffLimit: 1 + pollingInterval: 30 + maxReplicaCount: 1 + triggers: + - type: cron + metadata: + timezone: "UTC" + start: "0 0 * * *" + end: "59 23 * * *" + desiredReplicas: "1" + EOF + EOT + } + provisioner "local-exec" { + when = destroy + command = "kubectl delete scaledjob keda-test-scaledjob -n default --timeout=60s 2>/dev/null || true" + } +} + # --- Helm chart install --- data "external" "clone_helm_chart" { @@ -140,12 +265,14 @@ resource "helm_release" "aws_observability" { { name = "clusterName", value = aws_eks_cluster.this.name }, { name = "region", value = var.region }, { name = "otelContainerInsights.enabled", value = "true" }, + { name = "otelContainerInsights.solutions.keda.enabled", value = "true" }, ] depends_on = [ aws_eks_addon.pod_identity_agent, null_resource.kubectl, data.external.clone_helm_chart, + helm_release.keda, ] } @@ -351,6 +478,8 @@ resource "null_resource" "validator" { depends_on = [ null_resource.restart_pods, kubernetes_deployment_v1.nginx_test, + null_resource.keda_scaled_object, + null_resource.keda_scaled_job, ] triggers = { always_run = timestamp() } @@ -368,6 +497,15 @@ resource "null_resource" "validator" { -computeType=EKS \ -eksDeploymentStrategy=DAEMON \ -region=${var.region} + + echo "Running OTEL solutions tests (KEDA)..." + echo "Waiting 2 additional minutes for KEDA metrics to propagate..." + sleep 120 + go test -tags integration -timeout 1h -v ./test/otel/solutions/keda/... \ + -eksClusterName=${aws_eks_cluster.this.name} \ + -computeType=EKS \ + -eksDeploymentStrategy=DAEMON \ + -region=${var.region} EOT } } diff --git a/terraform/eks/daemon/otel/variables.tf b/terraform/eks/daemon/otel/variables.tf index 9149462f..cf2c508e 100644 --- a/terraform/eks/daemon/otel/variables.tf +++ b/terraform/eks/daemon/otel/variables.tf @@ -40,3 +40,8 @@ variable "instance_type" { type = string default = "t3.medium" } + +variable "keda_version" { + type = string + default = "2.16.1" +} diff --git a/test/otel/solutions/keda/keda_test.go b/test/otel/solutions/keda/keda_test.go new file mode 100644 index 00000000..146e6d86 --- /dev/null +++ b/test/otel/solutions/keda/keda_test.go @@ -0,0 +1,393 @@ +//go:build integration + +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package keda + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// =========================================================================== +// Metric Existence Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDAMetricsExist — verify each expected metric is present in +// CloudWatch. +// --------------------------------------------------------------------------- + +func TestKEDAMetricsExist(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + results, err := queryCache.Get(ctx, metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available (is KEDA installed?)", metricName) + }) + } +} + +// =========================================================================== +// Instrumentation Scope Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDAInstrumentation — verify instrumentation scope name for all +// KEDA metrics. +// --------------------------------------------------------------------------- + +func TestKEDAInstrumentation(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + results, err := queryCache.Get(ctx, metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + r := r + name, ok := r.Labels.Instrumentation["@name"] + require.True(t, ok, "%s missing @instrumentation.@name", metricName) + require.Equal(t, scopeKEDA, name, "%s instrumentation name", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDAInstrumentationConsistent — verify all data points for a +// metric report the same instrumentation scope (no mixed sources). +// --------------------------------------------------------------------------- + +func TestKEDAInstrumentationConsistent(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + results, err := queryCache.Get(ctx, metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + names := make(map[string]struct{}) + for _, r := range results { + if n, ok := r.Labels.Instrumentation["@name"]; ok { + names[n] = struct{}{} + } + } + require.Equal(t, 1, len(names), "%s has %d distinct instrumentation names", metricName, len(names)) + }) + } +} + +// =========================================================================== +// Datapoint Label Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDAExpectedLabels — verify expected datapoint labels are present +// on metrics that declare them. +// --------------------------------------------------------------------------- + +func TestKEDAExpectedLabels(t *testing.T) { + t.Parallel() + for _, md := range kedaMetrics { + md := md + if len(md.ExpectedLabels) == 0 { + continue + } + t.Run(md.Name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + results, err := queryCache.Get(ctx, md.Name) + require.NoError(t, err, "querying %s", md.Name) + require.NotEmpty(t, results, "%s not available", md.Name) + for _, r := range results { + r := r + for _, label := range md.ExpectedLabels { + label := label + _, ok := r.Labels.Datapoint[label] + require.True(t, ok, "%s missing expected label '%s'", md.Name, label) + } + } + }) + } +} + +// =========================================================================== +// K8s Resource Attribute Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDAClusterIdentity — k8s.cluster.name must match the configured +// cluster. +// --------------------------------------------------------------------------- + +func TestKEDAClusterIdentity(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + clusterName, ok := r.Labels.Resource["k8s.cluster.name"] + require.True(t, ok, "%s missing @resource.k8s.cluster.name", metricName) + require.Equal(t, cfg.ClusterName, clusterName, "%s k8s.cluster.name", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDANamespace — all KEDA metrics must originate from the +// keda namespace. +// --------------------------------------------------------------------------- + +func TestKEDANamespace(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + ns, ok := r.Labels.Resource["k8s.namespace.name"] + require.True(t, ok, "%s missing @resource.k8s.namespace.name", metricName) + require.Equal(t, "keda", ns, "%s k8s.namespace.name", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDADeploymentName — k8s.deployment.name must be "keda-operator". +// --------------------------------------------------------------------------- + +func TestKEDADeploymentName(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + deploy, ok := r.Labels.Resource["k8s.deployment.name"] + require.True(t, ok, "%s missing @resource.k8s.deployment.name", metricName) + require.Equal(t, "keda-operator", deploy, "%s k8s.deployment.name", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDAPodName — k8s.pod.name must start with "keda-operator-". +// --------------------------------------------------------------------------- + +func TestKEDAPodName(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + podName, ok := r.Labels.Resource["k8s.pod.name"] + require.True(t, ok, "%s missing @resource.k8s.pod.name", metricName) + require.True(t, strings.HasPrefix(podName, "keda-operator-"), + "%s k8s.pod.name should start with 'keda-operator-', got %q", metricName, podName) + } + }) + } +} + +// =========================================================================== +// Cloud Resource Attribute Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDACloudProvider — cloud.provider must be "aws". +// --------------------------------------------------------------------------- + +func TestKEDACloudProvider(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + provider, ok := r.Labels.Resource["cloud.provider"] + require.True(t, ok, "%s missing @resource.cloud.provider", metricName) + require.Equal(t, "aws", provider, "%s cloud.provider", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDACloudPlatform — cloud.platform must be "aws_eks". +// --------------------------------------------------------------------------- + +func TestKEDACloudPlatform(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + platform, ok := r.Labels.Resource["cloud.platform"] + require.True(t, ok, "%s missing @resource.cloud.platform", metricName) + require.Equal(t, "aws_eks", platform, "%s cloud.platform", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDACloudRegion — cloud.region must match the configured region. +// --------------------------------------------------------------------------- + +func TestKEDACloudRegion(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + region, ok := r.Labels.Resource["cloud.region"] + require.True(t, ok, "%s missing @resource.cloud.region", metricName) + require.Equal(t, cfg.Region, region, "%s cloud.region", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDACloudAccountID — cloud.account.id must match the test account. +// --------------------------------------------------------------------------- + +func TestKEDACloudAccountID(t *testing.T) { + t.Parallel() + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + acctID, ok := r.Labels.Resource["cloud.account.id"] + require.True(t, ok, "%s missing @resource.cloud.account.id", metricName) + require.Equal(t, cfg.AccountID, acctID, "%s cloud.account.id", metricName) + } + }) + } +} + +// --------------------------------------------------------------------------- +// TestKEDACloudResourceID — cloud.resource_id must be a valid EKS +// cluster ARN. +// --------------------------------------------------------------------------- + +func TestKEDACloudResourceID(t *testing.T) { + t.Parallel() + expectedARNPrefix := fmt.Sprintf("arn:aws:eks:%s:", cfg.Region) + expectedARNSuffix := fmt.Sprintf(":cluster/%s", cfg.ClusterName) + + for _, metricName := range kedaMetricNames() { + metricName := metricName + t.Run(metricName, func(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), metricName) + require.NoError(t, err, "querying %s", metricName) + require.NotEmpty(t, results, "%s not available", metricName) + for _, r := range results { + arn, ok := r.Labels.Resource["cloud.resource_id"] + require.True(t, ok, "%s missing @resource.cloud.resource_id", metricName) + require.True(t, strings.HasPrefix(arn, expectedARNPrefix), + "%s cloud.resource_id should start with %q, got %q", metricName, expectedARNPrefix, arn) + require.True(t, strings.HasSuffix(arn, expectedARNSuffix), + "%s cloud.resource_id should end with %q, got %q", metricName, expectedARNSuffix, arn) + } + }) + } +} + +// =========================================================================== +// Metric Value Sanity Tests +// =========================================================================== + +// --------------------------------------------------------------------------- +// TestKEDABuildInfoValue — keda_build_info must always be 1. +// --------------------------------------------------------------------------- + +func TestKEDABuildInfoValue(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), "keda_build_info") + require.NoError(t, err, "querying keda_build_info") + require.NotEmpty(t, results, "keda_build_info not available") + for _, r := range results { + require.Equal(t, float64(1), r.Value, "keda_build_info should be 1") + } +} + +// --------------------------------------------------------------------------- +// TestKEDAScalerActiveValue — keda_scaler_active must be 1 for the cron +// trigger (always active). +// --------------------------------------------------------------------------- + +func TestKEDAScalerActiveValue(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), "keda_scaler_active") + require.NoError(t, err, "querying keda_scaler_active") + require.NotEmpty(t, results, "keda_scaler_active not available") + for _, r := range results { + require.Equal(t, float64(1), r.Value, + "keda_scaler_active should be 1 (cron trigger is always active)") + } +} + +// --------------------------------------------------------------------------- +// TestKEDAScalerMetricsValueNonNegative — keda_scaler_metrics_value must +// be >= 0. +// --------------------------------------------------------------------------- + +func TestKEDAScalerMetricsValueNonNegative(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), "keda_scaler_metrics_value") + require.NoError(t, err, "querying keda_scaler_metrics_value") + require.NotEmpty(t, results, "keda_scaler_metrics_value not available") + for _, r := range results { + require.True(t, r.Value >= 0, + "keda_scaler_metrics_value should be >= 0, got %f", r.Value) + } +} diff --git a/test/otel/solutions/keda/metrics_test.go b/test/otel/solutions/keda/metrics_test.go new file mode 100644 index 00000000..ba99b391 --- /dev/null +++ b/test/otel/solutions/keda/metrics_test.go @@ -0,0 +1,44 @@ +//go:build integration + +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package keda + +import "github.com/aws/amazon-cloudwatch-agent-test/util/otelmetrics" + +// kedaMetrics defines the KEDA controller metrics expected in CloudWatch. +// These are Prometheus metrics scraped from the KEDA operator pods and +// forwarded by the CloudWatch Agent's KEDA integration pipeline. +var kedaMetrics = []otelmetrics.MetricDefinition{ + // Build info + {Name: "keda_build_info", MetricType: "gauge", Scope: otelmetrics.ScopeCluster}, + + // Scaler metrics + {Name: "keda_scaler_metrics_value", MetricType: "gauge", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaler", "scaledObject", "metric"}}, + {Name: "keda_scaler_metrics_latency_seconds", MetricType: "gauge", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaler", "scaledObject", "metric"}, Unit: "s"}, + {Name: "keda_scaler_active", MetricType: "gauge", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaler", "scaledObject", "metric"}}, + {Name: "keda_scaler_detail_errors_total", MetricType: "counter", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaler", "scaledObject"}, Unit: "1"}, + + // Scaled object metrics + {Name: "keda_scaled_object_paused", MetricType: "gauge", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaledObject", "namespace"}}, + {Name: "keda_scaled_object_errors_total", MetricType: "counter", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaledObject", "namespace"}, Unit: "1"}, + + // Scaled job metrics + {Name: "keda_scaled_job_errors_total", MetricType: "counter", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"scaledJob", "namespace"}, Unit: "1"}, + + // Controller metrics + {Name: "keda_internal_scale_loop_latency_seconds", MetricType: "gauge", Scope: otelmetrics.ScopeCluster, Unit: "s"}, + + // Resource registration totals + {Name: "keda_resource_registered_total", MetricType: "counter", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"type", "namespace"}, Unit: "1"}, + {Name: "keda_trigger_registered_total", MetricType: "counter", Scope: otelmetrics.ScopeCluster, ExpectedLabels: []string{"type"}, Unit: "1"}, +} + +func kedaMetricNames() []string { + names := make([]string, len(kedaMetrics)) + for i, d := range kedaMetrics { + names[i] = d.Name + } + return names +} diff --git a/test/otel/solutions/keda/setup_test.go b/test/otel/solutions/keda/setup_test.go new file mode 100644 index 00000000..e9337c6d --- /dev/null +++ b/test/otel/solutions/keda/setup_test.go @@ -0,0 +1,101 @@ +//go:build integration + +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package keda + +import ( + "context" + "flag" + "fmt" + "os" + "testing" + "time" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sts" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/otelmetrics" +) + +var ( + cfg otelmetrics.TestConfig + client *otelmetrics.OtelMetricsClient + queryCache *otelmetrics.QueryCache +) + +// Instrumentation scope name constant. +const scopeKEDA = "github.com/kedacore/keda" + +// Instance types in the cluster. +var clusterHostTypes = []string{"t3.medium"} + +func TestMain(m *testing.M) { + environment.RegisterEnvironmentMetaDataFlags() + flag.Parse() + env := environment.GetEnvironmentMetaData() + + region := env.Region + if region == "" { + region = os.Getenv("AWS_REGION") + } + if region == "" { + fmt.Fprintf(os.Stderr, "Region not set\n") + os.Exit(1) + } + + clusterName := env.EKSClusterName + if clusterName == "" { + clusterName = os.Getenv("CLUSTER_NAME") + } + if clusterName == "" { + fmt.Fprintf(os.Stderr, "Cluster name not set\n") + os.Exit(1) + } + + ctx := context.Background() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + fmt.Fprintf(os.Stderr, "AWS config error: %v\n", err) + os.Exit(1) + } + stsClient := sts.NewFromConfig(awsCfg) + identity, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) + if err != nil { + fmt.Fprintf(os.Stderr, "STS GetCallerIdentity error: %v\n", err) + os.Exit(1) + } + + cfg = otelmetrics.TestConfig{ + Region: region, + Endpoint: fmt.Sprintf("https://monitoring.%s.amazonaws.com", region), + Timeout: 30 * time.Second, + MaxRetries: 3, + ClusterName: clusterName, + AccountID: *identity.Account, + SigningService: "monitoring", + } + + client, err = otelmetrics.NewClient(ctx, cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "Client error: %v\n", err) + os.Exit(1) + } + + hostMappings := []otelmetrics.SourceHostMapping{ + {Source: otelmetrics.SourceKEDA, HostTypes: nil}, + } + + registry := otelmetrics.NewSourceRegistry(clusterHostTypes, hostMappings, + otelmetrics.SourceMapping{Source: otelmetrics.SourceKEDA, Metrics: kedaMetrics}, + ) + + queryCache = otelmetrics.NewQueryCache(client, cfg.ClusterName, + otelmetrics.WithHostTypes(clusterHostTypes), + otelmetrics.WithSourceRegistry(registry), + ) + + os.Exit(m.Run()) +} diff --git a/util/otelmetrics/source_registry.go b/util/otelmetrics/source_registry.go index 4e91a417..5bd89c8b 100644 --- a/util/otelmetrics/source_registry.go +++ b/util/otelmetrics/source_registry.go @@ -18,6 +18,7 @@ const ( SourceControlPlane SourceKubeStateMetrics SourceKSMNodeScoped + SourceKEDA ) // SourceMapping pairs a MetricSource with its metric definitions.