From c1e6ecd8c663a9c3820e316fb00dcce71df2842e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20=C5=9Awi=C4=85tek?= Date: Sun, 31 Mar 2024 22:48:48 +0200 Subject: [PATCH] Generate only TargetAllocator CR from Collector CR This is hidden behind a feature flag. Nothing changes by default. --- .github/workflows/e2e.yaml | 2 + Makefile | 5 + ...emetry-operator.clusterserviceversion.yaml | 4 +- ...emetry-operator.clusterserviceversion.yaml | 4 +- config/rbac/role.yaml | 2 + controllers/builder_test.go | 1950 ++++++++++++++++- controllers/common.go | 32 +- .../opentelemetrycollector_controller.go | 1 + controllers/reconcile_test.go | 84 +- controllers/suite_test.go | 5 + controllers/targetallocator_controller.go | 96 +- .../targetallocator_reconciler_test.go | 125 ++ internal/manifests/collector/collector.go | 4 + .../collector/targetallocator_test.go | 17 +- internal/manifests/mutate.go | 14 + main.go | 34 +- pkg/featuregate/featuregate.go | 8 + 17 files changed, 2275 insertions(+), 112 deletions(-) create mode 100644 controllers/targetallocator_reconciler_test.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index ce4cc811db..d5e7e20b87 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -49,6 +49,8 @@ jobs: - group: e2e-native-sidecar setup: "add-operator-arg OPERATOR_ARG='--feature-gates=operator.sidecarcontainers.native' prepare-e2e" kube-version: "1.29" + - group: e2e-targetallocator + setup: "enable-targetallocator-cr prepare-e2e" steps: - name: Check out code into the Go module directory uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 97742ff360..74d0d4c4a4 100644 --- a/Makefile +++ b/Makefile @@ -213,6 +213,11 @@ add-rbac-permissions-to-operator: manifests kustomize cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/rbac.yaml cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/replicaset.yaml +.PHONY: enable-targetallocator-cr +enable-targetallocator-cr: + @$(MAKE) add-operator-arg OPERATOR_ARG='--feature-gates=operator.collector.targetallocatorcr' + cd config/crd && $(KUSTOMIZE) edit add resource bases/opentelemetry.io_targetallocators.yaml + # Deploy controller in the current Kubernetes context, configured in ~/.kube/config .PHONY: deploy deploy: set-image-controller diff --git a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml index cf2f802a05..8b65f00ad3 100644 --- a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: Logging & Tracing,Monitoring certified: "false" containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator - createdAt: "2024-10-16T10:10:50Z" + createdAt: "2024-10-28T12:09:04Z" description: Provides the OpenTelemetry components, including the Collector operators.operatorframework.io/builder: operator-sdk-v1.29.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -389,6 +389,7 @@ spec: - opentelemetry.io resources: - opampbridges + - targetallocators verbs: - create - delete @@ -409,6 +410,7 @@ spec: - opampbridges/status - opentelemetrycollectors/finalizers - opentelemetrycollectors/status + - targetallocators/status verbs: - get - patch diff --git a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml index 8ab2219366..be1b61ad5c 100644 --- a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: Logging & Tracing,Monitoring certified: "false" containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator - createdAt: "2024-10-16T10:10:50Z" + createdAt: "2024-10-28T12:09:05Z" description: Provides the OpenTelemetry components, including the Collector operators.operatorframework.io/builder: operator-sdk-v1.29.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -389,6 +389,7 @@ spec: - opentelemetry.io resources: - opampbridges + - targetallocators verbs: - create - delete @@ -409,6 +410,7 @@ spec: - opampbridges/status - opentelemetrycollectors/finalizers - opentelemetrycollectors/status + - targetallocators/status verbs: - get - patch diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0991eb4d38..a03aeb18e8 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -135,6 +135,7 @@ rules: - opentelemetry.io resources: - opampbridges + - targetallocators verbs: - create - delete @@ -155,6 +156,7 @@ rules: - opampbridges/status - opentelemetrycollectors/finalizers - opentelemetrycollectors/status + - targetallocators/status verbs: - get - patch diff --git a/controllers/builder_test.go b/controllers/builder_test.go index eb4566cfb0..6a5f4803c1 100644 --- a/controllers/builder_test.go +++ b/controllers/builder_test.go @@ -42,6 +42,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils" + "github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator" "github.com/open-telemetry/opentelemetry-operator/pkg/featuregate" ) @@ -1202,7 +1203,7 @@ endpoint: ws://opamp-server:4320/v1/opamp } } -func TestBuildTargetAllocator(t *testing.T) { +func TestBuildCollectorTargetAllocatorResources(t *testing.T) { var goodConfigYaml = ` receivers: prometheus: @@ -2865,3 +2866,1950 @@ prometheus_cr: }) } } + +func TestBuildCollectorTargetAllocatorCR(t *testing.T) { + var goodConfigYaml = ` +receivers: + prometheus: + config: + scrape_configs: + - job_name: 'example' + relabel_configs: + - source_labels: ['__meta_service_id'] + target_label: 'job' + replacement: 'my_service_$$1' + - source_labels: ['__meta_service_name'] + target_label: 'instance' + replacement: '$1' + metric_relabel_configs: + - source_labels: ['job'] + target_label: 'job' + replacement: '$$1_$2' +exporters: + debug: +service: + pipelines: + metrics: + receivers: [prometheus] + exporters: [debug] +` + + goodConfig := v1beta1.Config{} + err := go_yaml.Unmarshal([]byte(goodConfigYaml), &goodConfig) + require.NoError(t, err) + + goodConfigHash, _ := manifestutils.GetConfigMapSHA(goodConfig) + goodConfigHash = goodConfigHash[:8] + + one := int32(1) + type args struct { + instance v1beta1.OpenTelemetryCollector + } + tests := []struct { + name string + args args + want []client.Object + featuregates []string + wantErr bool + opts []config.Option + }{ + { + name: "base case", + args: args{ + instance: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{ + Image: "test", + Replicas: &one, + }, + Mode: "statefulset", + Config: goodConfig, + TargetAllocator: v1beta1.TargetAllocatorEmbedded{ + Enabled: true, + FilterStrategy: "relabel-config", + AllocationStrategy: v1beta1.TargetAllocatorAllocationStrategyConsistentHashing, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + }, + }, + }, + }, + want: []client.Object{ + &appsv1.StatefulSet{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Spec: appsv1.StatefulSetSpec{ + ServiceName: "test-collector", + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: selectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-operator-config/sha256": "42773025f65feaf30df59a306a9e38f1aaabe94c8310983beaddb7f648d699b0", + "prometheus.io/path": "/metrics", + "prometheus.io/port": "8888", + "prometheus.io/scrape": "true", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "otc-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-collector-" + goodConfigHash, + }, + Items: []corev1.KeyToPath{ + { + Key: "collector.yaml", + Path: "collector.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "otc-container", + Image: "test", + Args: []string{ + "--config=/conf/collector.yaml", + }, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "SHARD", + Value: "0", + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "metrics", + HostPort: 0, + ContainerPort: 8888, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "otc-internal", + MountPath: "/conf", + }, + }, + }, + }, + ShareProcessNamespace: ptr.To(false), + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ServiceAccountName: "test-collector", + }, + }, + PodManagementPolicy: "Parallel", + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector-" + goodConfigHash, + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Data: map[string]string{ + "collector.yaml": "exporters:\n debug: null\nreceivers:\n prometheus:\n config: {}\n target_allocator:\n collector_id: ${POD_NAME}\n endpoint: http://test-targetallocator:80\n interval: 30s\nservice:\n pipelines:\n metrics:\n exporters:\n - debug\n receivers:\n - prometheus\n", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector-monitoring", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector-monitoring", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + "operator.opentelemetry.io/collector-service-type": "monitoring", + "operator.opentelemetry.io/collector-monitoring-service": "Exists", + }, + Annotations: map[string]string{}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "monitoring", + Port: 8888, + }, + }, + Selector: selectorLabels, + }, + }, + &v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + AllocationStrategy: v1beta1.TargetAllocatorAllocationStrategyConsistentHashing, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "enable metrics case", + args: args{ + instance: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{ + Image: "test", + Replicas: &one, + }, + Mode: "statefulset", + Config: goodConfig, + TargetAllocator: v1beta1.TargetAllocatorEmbedded{ + Enabled: true, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + FilterStrategy: "relabel-config", + Observability: v1beta1.ObservabilitySpec{ + Metrics: v1beta1.MetricsConfigSpec{ + EnableMetrics: true, + }, + }, + }, + }, + }, + }, + want: []client.Object{ + &appsv1.StatefulSet{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Spec: appsv1.StatefulSetSpec{ + ServiceName: "test-collector", + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: selectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-operator-config/sha256": "42773025f65feaf30df59a306a9e38f1aaabe94c8310983beaddb7f648d699b0", + "prometheus.io/path": "/metrics", + "prometheus.io/port": "8888", + "prometheus.io/scrape": "true", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "otc-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-collector-" + goodConfigHash, + }, + Items: []corev1.KeyToPath{ + { + Key: "collector.yaml", + Path: "collector.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "otc-container", + Image: "test", + Args: []string{ + "--config=/conf/collector.yaml", + }, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "SHARD", + Value: "0", + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "metrics", + HostPort: 0, + ContainerPort: 8888, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "otc-internal", + MountPath: "/conf", + }, + }, + }, + }, + ShareProcessNamespace: ptr.To(false), + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ServiceAccountName: "test-collector", + }, + }, + PodManagementPolicy: "Parallel", + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector-" + goodConfigHash, + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + Data: map[string]string{ + "collector.yaml": "exporters:\n debug: null\nreceivers:\n prometheus:\n config: {}\n target_allocator:\n collector_id: ${POD_NAME}\n endpoint: http://test-targetallocator:80\n interval: 30s\nservice:\n pipelines:\n metrics:\n exporters:\n - debug\n receivers:\n - prometheus\n", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{}, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-collector-monitoring", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-collector-monitoring", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + "operator.opentelemetry.io/collector-service-type": "monitoring", + "operator.opentelemetry.io/collector-monitoring-service": "Exists", + }, + Annotations: map[string]string{}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "monitoring", + Port: 8888, + }, + }, + Selector: selectorLabels, + }, + }, + &v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + Observability: v1beta1.ObservabilitySpec{ + Metrics: v1beta1.MetricsConfigSpec{ + EnableMetrics: true, + }, + }, + }, + }, + }, + wantErr: false, + featuregates: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := []config.Option{ + config.WithCollectorImage("default-collector"), + config.WithTargetAllocatorImage("default-ta-allocator"), + } + opts = append(opts, tt.opts...) + cfg := config.New( + opts..., + ) + params := manifests.Params{ + Log: logr.Discard(), + Config: cfg, + OtelCol: tt.args.instance, + } + targetAllocator, err := collector.TargetAllocator(params) + require.NoError(t, err) + params.TargetAllocator = targetAllocator + featuregates := []string{"operator.collector.targetallocatorcr"} + featuregates = append(featuregates, tt.featuregates...) + fg := strings.Join(featuregates, ",") + flagset := featuregate.Flags(colfeaturegate.GlobalRegistry()) + if err = flagset.Set(featuregate.FeatureGatesFlag, fg); err != nil { + t.Errorf("featuregate setting error = %v", err) + return + } + got, err := BuildCollector(params) + if (err != nil) != tt.wantErr { + t.Errorf("BuildAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equal(t, tt.want, got) + + }) + } +} + +func TestBuildTargetAllocator(t *testing.T) { + type args struct { + instance v1alpha1.TargetAllocator + collector *v1beta1.OpenTelemetryCollector + } + tests := []struct { + name string + args args + want []client.Object + featuregates []string + wantErr bool + opts []config.Option + }{ + { + name: "base case", + args: args{ + instance: v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + ScrapeConfigs: []v1beta1.AnyConfig{ + {Object: map[string]any{ + "job_name": "example", + "metric_relabel_configs": []any{ + map[string]any{ + "replacement": "$1_$2", + "source_labels": []any{"job"}, + "target_label": "job", + }, + }, + "relabel_configs": []any{ + map[string]any{ + "replacement": "my_service_$1", + "source_labels": []any{"__meta_service_id"}, + "target_label": "job", + }, + map[string]any{ + "replacement": "$1", + "source_labels": []any{"__meta_service_name"}, + "target_label": "instance", + }, + }, + }}, + }, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + }, + }, + }, + want: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Data: map[string]string{ + "targetallocator.yaml": `allocation_strategy: consistent-hashing +collector_selector: null +config: + scrape_configs: + - job_name: example + metric_relabel_configs: + - replacement: $1_$2 + source_labels: + - job + target_label: job + relabel_configs: + - replacement: my_service_$1 + source_labels: + - __meta_service_id + target_label: job + - replacement: $1 + source_labels: + - __meta_service_name + target_label: instance +filter_strategy: relabel-config +prometheus_cr: + enabled: true + pod_monitor_selector: null + service_monitor_selector: null +`, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: taSelectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "88ab06aab167d58ae2316ddecc9cf0600b9094d27054781dd6aa6e44dcf902fc", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "ta-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-targetallocator", + }, + Items: []corev1.KeyToPath{ + { + Key: "targetallocator.yaml", + Path: "targetallocator.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "ta-container", + Image: "default-ta-allocator", + Env: []corev1.EnvVar{ + { + Name: "OTELCOL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "http", + HostPort: 0, + ContainerPort: 8080, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ta-internal", + MountPath: "/conf", + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ShareProcessNamespace: ptr.To(false), + ServiceAccountName: "test-targetallocator", + }, + }, + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "targetallocation", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: 1, + StrVal: "http", + }, + }, + }, + Selector: taSelectorLabels, + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "88ab06aab167d58ae2316ddecc9cf0600b9094d27054781dd6aa6e44dcf902fc", + }, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "enable metrics case", + args: args{ + instance: v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + ScrapeConfigs: []v1beta1.AnyConfig{ + {Object: map[string]any{ + "job_name": "example", + "metric_relabel_configs": []any{ + map[string]any{ + "replacement": "$1_$2", + "source_labels": []any{"job"}, + "target_label": "job", + }, + }, + "relabel_configs": []any{ + map[string]any{ + "replacement": "my_service_$1", + "source_labels": []any{"__meta_service_id"}, + "target_label": "job", + }, + map[string]any{ + "replacement": "$1", + "source_labels": []any{"__meta_service_name"}, + "target_label": "instance", + }, + }, + }}, + }, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + AllocationStrategy: v1beta1.TargetAllocatorAllocationStrategyConsistentHashing, + Observability: v1beta1.ObservabilitySpec{ + Metrics: v1beta1.MetricsConfigSpec{ + EnableMetrics: true, + }, + }, + }, + }, + }, + want: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Data: map[string]string{ + "targetallocator.yaml": `allocation_strategy: consistent-hashing +collector_selector: null +config: + scrape_configs: + - job_name: example + metric_relabel_configs: + - replacement: $1_$2 + source_labels: + - job + target_label: job + relabel_configs: + - replacement: my_service_$1 + source_labels: + - __meta_service_id + target_label: job + - replacement: $1 + source_labels: + - __meta_service_name + target_label: instance +filter_strategy: relabel-config +prometheus_cr: + enabled: true + pod_monitor_selector: null + service_monitor_selector: null +`, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: taSelectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "88ab06aab167d58ae2316ddecc9cf0600b9094d27054781dd6aa6e44dcf902fc", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "ta-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-targetallocator", + }, + Items: []corev1.KeyToPath{ + { + Key: "targetallocator.yaml", + Path: "targetallocator.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "ta-container", + Image: "default-ta-allocator", + Env: []corev1.EnvVar{ + { + Name: "OTELCOL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "http", + HostPort: 0, + ContainerPort: 8080, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ta-internal", + MountPath: "/conf", + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + ShareProcessNamespace: ptr.To(false), + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ServiceAccountName: "test-targetallocator", + }, + }, + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "targetallocation", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: 1, + StrVal: "http", + }, + }, + }, + Selector: taSelectorLabels, + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "88ab06aab167d58ae2316ddecc9cf0600b9094d27054781dd6aa6e44dcf902fc", + }, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + &monitoringv1.ServiceMonitor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: monitoringv1.ServiceMonitorSpec{ + Endpoints: []monitoringv1.Endpoint{ + {Port: "targetallocation"}, + }, + Selector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + }, + }, + NamespaceSelector: monitoringv1.NamespaceSelector{ + MatchNames: []string{"test"}, + }, + }, + }, + }, + wantErr: false, + featuregates: []string{}, + }, + { + name: "collector present", + args: args{ + instance: v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + }, + }, + collector: &v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Receivers: v1beta1.AnyConfig{ + Object: map[string]any{ + "prometheus": map[string]any{ + "config": map[string]any{ + "scrape_configs": []any{ + map[string]any{ + "job_name": "example", + "metric_relabel_configs": []any{ + map[string]any{ + "replacement": "$1_$2", + "source_labels": []any{"job"}, + "target_label": "job", + }, + }, + "relabel_configs": []any{ + map[string]any{ + "replacement": "my_service_$1", + "source_labels": []any{"__meta_service_id"}, + "target_label": "job", + }, + map[string]any{ + "replacement": "$1", + "source_labels": []any{"__meta_service_name"}, + "target_label": "instance", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Data: map[string]string{ + "targetallocator.yaml": `allocation_strategy: consistent-hashing +collector_selector: + matchlabels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: test.test + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + matchexpressions: [] +config: + scrape_configs: + - job_name: example + metric_relabel_configs: + - replacement: $1_$2 + source_labels: + - job + target_label: job + relabel_configs: + - replacement: my_service_$1 + source_labels: + - __meta_service_id + target_label: job + - replacement: $1 + source_labels: + - __meta_service_name + target_label: instance +filter_strategy: relabel-config +prometheus_cr: + enabled: true + pod_monitor_selector: null + service_monitor_selector: null +`, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: taSelectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "9d78d2ecfad18bad24dec7e9a825b4ce45657ecbb2e6b32845b585b7c15ea407", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "ta-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-targetallocator", + }, + Items: []corev1.KeyToPath{ + { + Key: "targetallocator.yaml", + Path: "targetallocator.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "ta-container", + Image: "default-ta-allocator", + Env: []corev1.EnvVar{ + { + Name: "OTELCOL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "http", + HostPort: 0, + ContainerPort: 8080, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ta-internal", + MountPath: "/conf", + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ShareProcessNamespace: ptr.To(false), + ServiceAccountName: "test-targetallocator", + }, + }, + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "targetallocation", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: 1, + StrVal: "http", + }, + }, + }, + Selector: taSelectorLabels, + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "9d78d2ecfad18bad24dec7e9a825b4ce45657ecbb2e6b32845b585b7c15ea407", + }, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "mtls", + args: args{ + instance: v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + FilterStrategy: v1beta1.TargetAllocatorFilterStrategyRelabelConfig, + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + Enabled: true, + }, + }, + }, + collector: &v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Receivers: v1beta1.AnyConfig{ + Object: map[string]any{ + "prometheus": map[string]any{ + "config": map[string]any{ + "scrape_configs": []any{ + map[string]any{ + "job_name": "example", + "metric_relabel_configs": []any{ + map[string]any{ + "replacement": "$1_$2", + "source_labels": []any{"job"}, + "target_label": "job", + }, + }, + "relabel_configs": []any{ + map[string]any{ + "replacement": "my_service_$1", + "source_labels": []any{"__meta_service_id"}, + "target_label": "job", + }, + map[string]any{ + "replacement": "$1", + "source_labels": []any{"__meta_service_name"}, + "target_label": "instance", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Data: map[string]string{ + "targetallocator.yaml": `allocation_strategy: consistent-hashing +collector_selector: + matchlabels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: test.test + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + matchexpressions: [] +config: + scrape_configs: + - job_name: example + metric_relabel_configs: + - replacement: $1_$2 + source_labels: + - job + target_label: job + relabel_configs: + - replacement: my_service_$1 + source_labels: + - __meta_service_id + target_label: job + - replacement: $1 + source_labels: + - __meta_service_name + target_label: instance +filter_strategy: relabel-config +https: + ca_file_path: /tls/ca.crt + enabled: true + listen_addr: :8443 + tls_cert_file_path: /tls/tls.crt + tls_key_file_path: /tls/tls.key +prometheus_cr: + enabled: true + pod_monitor_selector: null + service_monitor_selector: null +`, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: taSelectorLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "f1ce0fdbf69924576576d1d6eb2a3cc91a3f72675b3facbb36702d57027bc6ae", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "ta-internal", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-targetallocator", + }, + Items: []corev1.KeyToPath{ + { + Key: "targetallocator.yaml", + Path: "targetallocator.yaml", + }, + }, + }, + }, + }, + { + Name: "test-ta-server-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "test-ta-server-cert", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "ta-container", + Image: "default-ta-allocator", + Env: []corev1.EnvVar{ + { + Name: "OTELCOL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "http", + HostPort: 0, + ContainerPort: 8080, + Protocol: "TCP", + }, + { + Name: "https", + HostPort: 0, + ContainerPort: 8443, + Protocol: "TCP", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ta-internal", + MountPath: "/conf", + }, + { + Name: "test-ta-server-cert", + MountPath: "/tls", + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + DNSPolicy: "ClusterFirst", + DNSConfig: &corev1.PodDNSConfig{}, + ShareProcessNamespace: ptr.To(false), + ServiceAccountName: "test-targetallocator", + }, + }, + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: nil, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "targetallocation", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: 1, + StrVal: "http", + }, + }, + { + Name: "targetallocation-https", + Port: 443, + TargetPort: intstr.IntOrString{ + Type: 1, + StrVal: "https", + }, + }, + }, + Selector: taSelectorLabels, + }, + }, + &policyV1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-targetallocator", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + Annotations: map[string]string{ + "opentelemetry-targetallocator-config/hash": "f1ce0fdbf69924576576d1d6eb2a3cc91a3f72675b3facbb36702d57027bc6ae", + }, + }, + Spec: policyV1.PodDisruptionBudgetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-targetallocator", + "app.kubernetes.io/part-of": "opentelemetry", + }, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + }, + }, + &cmv1.Issuer{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-self-signed-issuer", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-self-signed-issuer", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + SelfSigned: &cmv1.SelfSignedIssuer{ + CRLDistributionPoints: nil, + }, + }, + }, + }, + &cmv1.Certificate{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ca-cert", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-ca-cert", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: cmv1.CertificateSpec{ + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + CommonName: "test-ca-cert", + IsCA: true, + SecretName: "test-ca-cert", + IssuerRef: cmmetav1.ObjectReference{ + Name: "test-self-signed-issuer", + Kind: "Issuer", + }, + }, + }, + &cmv1.Issuer{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ca-issuer", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-ca-issuer", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + CA: &cmv1.CAIssuer{ + SecretName: "test-ca-cert", + }, + }, + }, + }, + &cmv1.Certificate{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ta-server-cert", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-ta-server-cert", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: cmv1.CertificateSpec{ + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + DNSNames: []string{ + "test-targetallocator", + "test-targetallocator.test.svc", + "test-targetallocator.test.svc.cluster.local", + }, + SecretName: "test-ta-server-cert", + IssuerRef: cmmetav1.ObjectReference{ + Name: "test-ca-issuer", + Kind: "Issuer", + }, + Usages: []cmv1.KeyUsage{ + "client auth", + "server auth", + }, + }, + }, + &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ta-client-cert", + Namespace: "test", + Labels: map[string]string{ + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/instance": "test.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/name": "test-ta-client-cert", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: cmv1.CertificateSpec{ + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + DNSNames: []string{ + "test-targetallocator", + "test-targetallocator.test.svc", + "test-targetallocator.test.svc.cluster.local", + }, + SecretName: "test-ta-client-cert", + IssuerRef: cmmetav1.ObjectReference{ + Name: "test-ca-issuer", + Kind: "Issuer", + }, + Usages: []cmv1.KeyUsage{ + "client auth", + "server auth", + }, + }, + }, + }, + wantErr: false, + opts: []config.Option{ + config.WithCertManagerAvailability(certmanager.Available), + }, + featuregates: []string{"operator.targetallocator.mtls"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := []config.Option{ + config.WithCollectorImage("default-collector"), + config.WithTargetAllocatorImage("default-ta-allocator"), + } + opts = append(opts, tt.opts...) + cfg := config.New( + opts..., + ) + params := targetallocator.Params{ + Log: logr.Discard(), + Config: cfg, + TargetAllocator: tt.args.instance, + Collector: tt.args.collector, + } + if len(tt.featuregates) > 0 { + fg := strings.Join(tt.featuregates, ",") + flagset := featuregate.Flags(colfeaturegate.GlobalRegistry()) + if err := flagset.Set(featuregate.FeatureGatesFlag, fg); err != nil { + t.Errorf("featuregate setting error = %v", err) + return + } + } + got, err := BuildTargetAllocator(params) + if (err != nil) != tt.wantErr { + t.Errorf("BuildAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equal(t, tt.want, got) + + }) + } +} diff --git a/controllers/common.go b/controllers/common.go index 3003907913..9f39cdcacd 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -35,6 +35,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/opampbridge" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator" + "github.com/open-telemetry/opentelemetry-operator/pkg/featuregate" ) func isNamespaceScoped(obj client.Object) bool { @@ -59,22 +60,23 @@ func BuildCollector(params manifests.Params) ([]client.Object, error) { } resources = append(resources, objs...) } - // TODO: Remove this after TargetAllocator CRD is reconciled - if params.TargetAllocator != nil { - taParams := targetallocator.Params{ - Client: params.Client, - Scheme: params.Scheme, - Recorder: params.Recorder, - Log: params.Log, - Config: params.Config, - Collector: ¶ms.OtelCol, - TargetAllocator: *params.TargetAllocator, - } - taResources, err := BuildTargetAllocator(taParams) - if err != nil { - return nil, err + if !featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() { + if params.TargetAllocator != nil { + taParams := targetallocator.Params{ + Client: params.Client, + Scheme: params.Scheme, + Recorder: params.Recorder, + Log: params.Log, + Config: params.Config, + Collector: ¶ms.OtelCol, + TargetAllocator: *params.TargetAllocator, + } + taResources, err := BuildTargetAllocator(taParams) + if err != nil { + return nil, err + } + resources = append(resources, taResources...) } - resources = append(resources, taResources...) } return resources, nil } diff --git a/controllers/opentelemetrycollector_controller.go b/controllers/opentelemetrycollector_controller.go index 8c616700a6..21c461454a 100644 --- a/controllers/opentelemetrycollector_controller.go +++ b/controllers/opentelemetrycollector_controller.go @@ -212,6 +212,7 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler { // +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors/status,verbs=get;update;patch // +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors/finalizers,verbs=get;update;patch +// +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators,verbs=get;list;watch;create;update;patch;delete // Reconcile the current state of an OpenTelemetry collector resource with the desired state. func (r *OpenTelemetryCollectorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { diff --git a/controllers/reconcile_test.go b/controllers/reconcile_test.go index 46b0d38837..703489b5d4 100644 --- a/controllers/reconcile_test.go +++ b/controllers/reconcile_test.go @@ -22,7 +22,6 @@ import ( routev1 "github.com/openshift/api/route/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" @@ -41,13 +40,13 @@ import ( k8sreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" - ta "github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator/adapters" "github.com/open-telemetry/opentelemetry-operator/internal/naming" ) @@ -496,10 +495,7 @@ func TestOpenTelemetryCollectorReconciler_Reconcile(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) // Check the TA doesn't exist - exists, err = populateObjectIfExists(t, &v1.ConfigMap{}, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.False(t, exists) - exists, err = populateObjectIfExists(t, &appsv1.Deployment{}, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) + exists, err = populateObjectIfExists(t, &v1alpha1.TargetAllocator{}, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) assert.NoError(t, err) assert.False(t, exists) }, @@ -516,34 +512,35 @@ func TestOpenTelemetryCollectorReconciler_Reconcile(t *testing.T) { exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, namespacedObjectName(naming.ConfigMap(params.Name, configHash), params.Namespace)) assert.NoError(t, err) assert.True(t, exists) - actual := v1.ConfigMap{} - exists, err = populateObjectIfExists(t, &appsv1.Deployment{}, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - exists, err = populateObjectIfExists(t, &actual, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - exists, err = populateObjectIfExists(t, &v1.ServiceAccount{}, namespacedObjectName(naming.TargetAllocatorServiceAccount(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - promConfig, err := ta.ConfigToPromConfig(testCollectorAssertNoErr(t, "test-stateful-ta", baseTaImage, promFile).Spec.Config) - assert.NoError(t, err) - - taConfig := make(map[interface{}]interface{}) - taConfig["collector_selector"] = metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app.kubernetes.io/instance": "default.test-stateful-ta", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/component": "opentelemetry-collector", - "app.kubernetes.io/part-of": "opentelemetry", + actual := v1alpha1.TargetAllocator{} + exists, err = populateObjectIfExists(t, &actual, namespacedObjectName(params.Name, params.Namespace)) + require.NoError(t, err) + require.True(t, exists) + expected := v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: params.Name, + Namespace: params.Namespace, + Labels: nil, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{}, + AllocationStrategy: "consistent-hashing", + FilterStrategy: "relabel-config", + PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{ + ScrapeInterval: &metav1.Duration{Duration: time.Second * 30}, + ServiceMonitorSelector: &metav1.LabelSelector{}, + PodMonitorSelector: &metav1.LabelSelector{}, + }, }, } - taConfig["config"] = promConfig["config"] - taConfig["allocation_strategy"] = "consistent-hashing" - taConfig["filter_strategy"] = "relabel-config" - taConfigYAML, _ := yaml.Marshal(taConfig) - assert.Equal(t, string(taConfigYAML), actual.Data["targetallocator.yaml"]) - assert.NotContains(t, actual.Data["targetallocator.yaml"], "0.0.0.0:10100") + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.Namespace, actual.Namespace) + assert.Equal(t, expected.Labels, actual.Labels) + assert.Equal(t, baseTaImage, actual.Spec.Image) + assert.Equal(t, expected.Spec.AllocationStrategy, actual.Spec.AllocationStrategy) + assert.Equal(t, expected.Spec.FilterStrategy, actual.Spec.FilterStrategy) + assert.Equal(t, expected.Spec.ScrapeConfigs, actual.Spec.ScrapeConfigs) + }, }, wantErr: assert.NoError, @@ -558,14 +555,11 @@ func TestOpenTelemetryCollectorReconciler_Reconcile(t *testing.T) { exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, namespacedObjectName(naming.ConfigMap(params.Name, configHash), params.Namespace)) assert.NoError(t, err) assert.True(t, exists) - actual := v1.ConfigMap{} - exists, err = populateObjectIfExists(t, &appsv1.Deployment{}, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - exists, err = populateObjectIfExists(t, &actual, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - assert.Contains(t, actual.Data["targetallocator.yaml"], "0.0.0.0:10100") + actual := v1alpha1.TargetAllocator{} + exists, err = populateObjectIfExists(t, &actual, namespacedObjectName(params.Name, params.Namespace)) + require.NoError(t, err) + require.True(t, exists) + assert.Nil(t, actual.Spec.ScrapeConfigs) }, }, wantErr: assert.NoError, @@ -575,11 +569,11 @@ func TestOpenTelemetryCollectorReconciler_Reconcile(t *testing.T) { result: controllerruntime.Result{}, checks: []check[v1alpha1.OpenTelemetryCollector]{ func(t *testing.T, params v1alpha1.OpenTelemetryCollector) { - actual := appsv1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, namespacedObjectName(naming.TargetAllocator(params.Name), params.Namespace)) - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, actual.Spec.Template.Spec.Containers[0].Image, updatedTaImage) + actual := v1alpha1.TargetAllocator{} + exists, err := populateObjectIfExists(t, &actual, namespacedObjectName(params.Name, params.Namespace)) + require.NoError(t, err) + require.True(t, exists) + assert.Equal(t, actual.Spec.Image, updatedTaImage) }, }, wantErr: assert.NoError, diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 8cb8a24420..e17c024080 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -200,6 +200,11 @@ func TestMain(m *testing.M) { os.Exit(1) } + if err = v1alpha1.SetupTargetAllocatorWebhook(mgr, config.New(), reviewer); err != nil { + fmt.Printf("failed to SetupWebhookWithManager: %v", err) + os.Exit(1) + } + if err = v1alpha1.SetupOpAMPBridgeWebhook(mgr, config.New()); err != nil { fmt.Printf("failed to SetupWebhookWithManager: %v", err) os.Exit(1) diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index 6b748e4535..23872f3e71 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -17,6 +17,8 @@ package controllers import ( "context" + "fmt" + "slices" "github.com/go-logr/logr" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -24,10 +26,16 @@ import ( corev1 "k8s.io/api/core/v1" policyV1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" @@ -55,7 +63,11 @@ type TargetAllocatorReconcilerParams struct { Config config.Config } -func (r *TargetAllocatorReconciler) getParams(instance v1alpha1.TargetAllocator) targetallocator.Params { +func (r *TargetAllocatorReconciler) getParams(ctx context.Context, instance v1alpha1.TargetAllocator) (targetallocator.Params, error) { + collector, err := r.getCollector(ctx, instance) + if err != nil { + return targetallocator.Params{}, err + } p := targetallocator.Params{ Config: r.config, Client: r.Client, @@ -63,9 +75,30 @@ func (r *TargetAllocatorReconciler) getParams(instance v1alpha1.TargetAllocator) Scheme: r.scheme, Recorder: r.recorder, TargetAllocator: instance, + Collector: collector, + } + + return p, nil +} + +func (r *TargetAllocatorReconciler) getCollector(ctx context.Context, instance v1alpha1.TargetAllocator) (*v1beta1.OpenTelemetryCollector, error) { + var collector v1beta1.OpenTelemetryCollector + ownerReferences := instance.GetOwnerReferences() + collectorIndex := slices.IndexFunc(ownerReferences, func(reference metav1.OwnerReference) bool { + return reference.Kind == "OpenTelemetryCollector" + }) + if collectorIndex != -1 { + collectorRef := ownerReferences[collectorIndex] + collectorKey := client.ObjectKey{Name: collectorRef.Name, Namespace: instance.GetNamespace()} + if err := r.Get(ctx, collectorKey, &collector); err != nil { + return nil, fmt.Errorf( + "error getting owner for TargetAllocator %s/%s: %w", + instance.GetNamespace(), instance.GetName(), err) + } + return &collector, nil } - return p + return nil, nil } // NewTargetAllocatorReconciler creates a new reconciler for TargetAllocator objects. @@ -85,15 +118,14 @@ func NewTargetAllocatorReconciler( } } -// TODO: Uncomment the lines below after enabling the TA controller in main.go -// // +kubebuilder:rbac:groups="",resources=pods;configmaps;services;serviceaccounts;persistentvolumeclaims;persistentvolumes,verbs=get;list;watch;create;update;patch;delete -// // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch -// // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -// // +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete -// // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;podmonitors,verbs=get;list;watch;create;update;patch;delete -// // +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;update;patch -// // +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators,verbs=get;list;watch;update;patch -// // +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators/status,verbs=get;update;patch +// +kubebuilder:rbac:groups="",resources=pods;configmaps;services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;podmonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators/status,verbs=get;update;patch // Reconcile the current state of a TargetAllocator resource with the desired state. func (r *TargetAllocatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -121,32 +153,58 @@ func (r *TargetAllocatorReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, nil } - params := r.getParams(instance) + params, err := r.getParams(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } desiredObjects, buildErr := BuildTargetAllocator(params) if buildErr != nil { return ctrl.Result{}, buildErr } - err := reconcileDesiredObjects(ctx, r.Client, log, ¶ms.TargetAllocator, params.Scheme, desiredObjects, nil) + err = reconcileDesiredObjects(ctx, r.Client, log, ¶ms.TargetAllocator, params.Scheme, desiredObjects, nil) return taStatus.HandleReconcileStatus(ctx, log, params, err) } // SetupWithManager tells the manager what our controller is interested in. func (r *TargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error { - builder := ctrl.NewControllerManagedBy(mgr). + ctrlBuilder := ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.TargetAllocator{}). Owns(&corev1.ConfigMap{}). Owns(&corev1.ServiceAccount{}). Owns(&corev1.Service{}). Owns(&appsv1.Deployment{}). - Owns(&corev1.PersistentVolume{}). - Owns(&corev1.PersistentVolumeClaim{}). Owns(&policyV1.PodDisruptionBudget{}) if featuregate.PrometheusOperatorIsAvailable.IsEnabled() { - builder.Owns(&monitoringv1.ServiceMonitor{}) - builder.Owns(&monitoringv1.PodMonitor{}) + ctrlBuilder.Owns(&monitoringv1.ServiceMonitor{}) + ctrlBuilder.Owns(&monitoringv1.PodMonitor{}) } - return builder.Complete(r) + // watch collectors which have embedded Target Allocator enabled + // we need to do this separately from collector reconciliation, as changes to Config will not lead to changes + // in the TargetAllocator CR + ctrlBuilder.Watches( + &v1beta1.OpenTelemetryCollector{}, + handler.EnqueueRequestsFromMapFunc(getTargetAllocatorForCollector), + builder.WithPredicates( + predicate.NewPredicateFuncs(func(object client.Object) bool { + collector := object.(*v1beta1.OpenTelemetryCollector) + return collector.Spec.TargetAllocator.Enabled + }), + ), + ) + + return ctrlBuilder.Complete(r) +} + +func getTargetAllocatorForCollector(_ context.Context, collector client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: collector.GetName(), + Namespace: collector.GetNamespace(), + }, + }, + } } diff --git a/controllers/targetallocator_reconciler_test.go b/controllers/targetallocator_reconciler_test.go new file mode 100644 index 0000000000..0401a3ef2f --- /dev/null +++ b/controllers/targetallocator_reconciler_test.go @@ -0,0 +1,125 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "context" + "testing" + + routev1 "github.com/openshift/api/route/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +var testLogger = logf.Log.WithName("opamp-bridge-controller-unit-tests") + +var ( + testScheme *runtime.Scheme = scheme.Scheme +) + +func init() { + utilruntime.Must(monitoringv1.AddToScheme(testScheme)) + utilruntime.Must(networkingv1.AddToScheme(testScheme)) + utilruntime.Must(routev1.AddToScheme(testScheme)) + utilruntime.Must(v1alpha1.AddToScheme(testScheme)) + utilruntime.Must(v1beta1.AddToScheme(testScheme)) +} + +func TestTargetAllocatorReconciler_GetCollector(t *testing.T) { + testCollector := &v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-collector", + }, + } + fakeClient := fake.NewFakeClient(testCollector) + reconciler := NewTargetAllocatorReconciler( + fakeClient, + testScheme, + record.NewFakeRecorder(10), + config.New(), + testLogger, + ) + + t.Run("not owned by a collector", func(t *testing.T) { + ta := v1alpha1.TargetAllocator{} + collector, err := reconciler.getCollector(context.Background(), ta) + require.NoError(t, err) + assert.Nil(t, collector) + }) + t.Run("owned by a collector", func(t *testing.T) { + ta := v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "OpenTelemetryCollector", + Name: testCollector.Name, + }, + }, + }, + } + collector, err := reconciler.getCollector(context.Background(), ta) + require.NoError(t, err) + assert.Equal(t, testCollector, collector) + }) + t.Run("owning collector doesn't exist", func(t *testing.T) { + ta := v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "OpenTelemetryCollector", + Name: "non_existent", + }, + }, + }, + } + collector, err := reconciler.getCollector(context.Background(), ta) + assert.Nil(t, collector) + assert.Errorf(t, err, "error getting owner for TargetAllocator default/test: opentelemetrycollectors.opentelemetry.io \"non_existent\" not found") + }) +} + +func TestGetTargetAllocatorForCollector(t *testing.T) { + testCollector := &v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + } + requests := getTargetAllocatorForCollector(context.Background(), testCollector) + expected := []reconcile.Request{{ + NamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "default", + }, + }} + assert.Equal(t, expected, requests) +} diff --git a/internal/manifests/collector/collector.go b/internal/manifests/collector/collector.go index f8e78e5f9f..01b1777276 100644 --- a/internal/manifests/collector/collector.go +++ b/internal/manifests/collector/collector.go @@ -53,6 +53,10 @@ func Build(params manifests.Params) ([]client.Object, error) { manifests.Factory(Ingress), }...) + if featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() { + manifestFactories = append(manifestFactories, manifests.Factory(TargetAllocator)) + } + if params.OtelCol.Spec.Observability.Metrics.EnableMetrics && featuregate.PrometheusOperatorIsAvailable.IsEnabled() { if params.OtelCol.Spec.Mode == v1beta1.ModeSidecar { manifestFactories = append(manifestFactories, manifests.Factory(PodMonitor)) diff --git a/internal/manifests/collector/targetallocator_test.go b/internal/manifests/collector/targetallocator_test.go index 3d281d69fd..77d6e4e6f7 100644 --- a/internal/manifests/collector/targetallocator_test.go +++ b/internal/manifests/collector/targetallocator_test.go @@ -45,17 +45,6 @@ func TestTargetAllocator(t *testing.T) { privileged := true runAsUser := int64(1337) runasGroup := int64(1338) - otelcolConfig := v1beta1.Config{ - Receivers: v1beta1.AnyConfig{ - Object: map[string]interface{}{ - "prometheus": map[string]any{ - "config": map[string]any{ - "scrape_configs": []any{}, - }, - }, - }, - }, - } testCases := []struct { name string @@ -79,7 +68,6 @@ func TestTargetAllocator(t *testing.T) { input: v1beta1.OpenTelemetryCollector{ ObjectMeta: objectMetadata, Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: otelcolConfig, TargetAllocator: v1beta1.TargetAllocatorEmbedded{ Enabled: true, }, @@ -87,7 +75,9 @@ func TestTargetAllocator(t *testing.T) { }, want: &v1alpha1.TargetAllocator{ ObjectMeta: objectMetadata, - Spec: v1alpha1.TargetAllocatorSpec{}, + Spec: v1alpha1.TargetAllocatorSpec{ + GlobalConfig: v1beta1.AnyConfig{}, + }, }, }, { @@ -190,7 +180,6 @@ func TestTargetAllocator(t *testing.T) { }, }, }, - Config: otelcolConfig, }, }, want: &v1alpha1.TargetAllocator{ diff --git a/internal/manifests/mutate.go b/internal/manifests/mutate.go index 27b4e31266..daad2c2247 100644 --- a/internal/manifests/mutate.go +++ b/internal/manifests/mutate.go @@ -32,6 +32,8 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" ) var ( @@ -56,6 +58,7 @@ var ( // - HorizontalPodAutoscaler // - Route // - Secret +// - TargetAllocator // In order for the operator to reconcile other types, they must be added here. // The function returned takes no arguments but instead uses the existing and desired inputs here. Existing is expected // to be set by the controller-runtime package through a client get call. @@ -177,6 +180,11 @@ func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { wantIssuer := desired.(*cmv1.Issuer) mutateIssuer(issuer, wantIssuer) + case *v1alpha1.TargetAllocator: + ta := existing.(*v1alpha1.TargetAllocator) + wantTa := desired.(*v1alpha1.TargetAllocator) + mutateTargetAllocator(ta, wantTa) + default: t := reflect.TypeOf(existing).String() return fmt.Errorf("missing mutate implementation for resource type: %s", t) @@ -270,6 +278,12 @@ func mutatePodMonitor(existing, desired *monitoringv1.PodMonitor) { existing.Spec = desired.Spec } +func mutateTargetAllocator(existing, desired *v1alpha1.TargetAllocator) { + existing.Annotations = desired.Annotations + existing.Labels = desired.Labels + existing.Spec = desired.Spec +} + func mutateService(existing, desired *corev1.Service) { existing.Spec.Ports = desired.Spec.Ports existing.Spec.Selector = desired.Spec.Selector diff --git a/main.go b/main.go index 8d37edce7d..c966a8024c 100644 --- a/main.go +++ b/main.go @@ -398,17 +398,18 @@ func main() { os.Exit(1) } - // TODO: Uncomment the line below to enable the Target Allocator controller - //if err = controllers.NewTargetAllocatorReconciler( - // mgr.GetClient(), - // mgr.GetScheme(), - // mgr.GetEventRecorderFor("targetallocator"), - // cfg, - // ctrl.Log.WithName("controllers").WithName("TargetAllocator"), - //).SetupWithManager(mgr); err != nil { - // setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") - // os.Exit(1) - //} + if featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() { + if err = controllers.NewTargetAllocatorReconciler( + mgr.GetClient(), + mgr.GetScheme(), + mgr.GetEventRecorderFor("targetallocator"), + cfg, + ctrl.Log.WithName("controllers").WithName("TargetAllocator"), + ).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") + os.Exit(1) + } + } if err = controllers.NewOpAMPBridgeReconciler(controllers.OpAMPBridgeReconcilerParams{ Client: mgr.GetClient(), @@ -462,11 +463,12 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "OpenTelemetryCollector") os.Exit(1) } - // TODO: Uncomment the line below to enable the Target Allocator webhook - //if err = otelv1alpha1.SetupTargetAllocatorWebhook(mgr, cfg, reviewer); err != nil { - // setupLog.Error(err, "unable to create webhook", "webhook", "TargetAllocator") - // os.Exit(1) - //} + if featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() { + if err = otelv1alpha1.SetupTargetAllocatorWebhook(mgr, cfg, reviewer); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "TargetAllocator") + os.Exit(1) + } + } if err = otelv1alpha1.SetupInstrumentationWebhook(mgr, cfg); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Instrumentation") os.Exit(1) diff --git a/pkg/featuregate/featuregate.go b/pkg/featuregate/featuregate.go index 0db8286b6e..03a6f8392a 100644 --- a/pkg/featuregate/featuregate.go +++ b/pkg/featuregate/featuregate.go @@ -25,6 +25,14 @@ const ( ) var ( + // CollectorUsesTargetAllocatorCR is the feature gate that enables the OpenTelemetryCollector reconciler to generate + // TargetAllocator CRs instead of generating the manifests for its resources directly. + CollectorUsesTargetAllocatorCR = featuregate.GlobalRegistry().MustRegister( + "operator.collector.targetallocatorcr", + featuregate.StageAlpha, + featuregate.WithRegisterDescription("causes collector reconciliation to create a target allocator CR instead of creating resources directly"), + featuregate.WithRegisterFromVersion("v0.112.0"), + ) // EnableNativeSidecarContainers is the feature gate that controls whether a // sidecar should be injected as a native sidecar or the classic way. // Native sidecar containers have been available since kubernetes v1.28 in