diff --git a/apis/v1alpha1/targetallocator_types.go b/apis/v1alpha1/targetallocator_types.go index a5e47d924f..15283e935c 100644 --- a/apis/v1alpha1/targetallocator_types.go +++ b/apis/v1alpha1/targetallocator_types.go @@ -21,10 +21,11 @@ import ( ) func init() { - v1beta1.SchemeBuilder.Register(&TargetAllocator{}, &TargetAllocatorList{}) + SchemeBuilder.Register(&TargetAllocator{}, &TargetAllocatorList{}) } //+kubebuilder:object:root=true +//+kubebuilder:storageversion //+kubebuilder:subresource:status // TargetAllocator is the Schema for the targetallocators API. diff --git a/controllers/common.go b/controllers/common.go index 31a84bfd39..3003907913 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -67,7 +67,7 @@ func BuildCollector(params manifests.Params) ([]client.Object, error) { Recorder: params.Recorder, Log: params.Log, Config: params.Config, - Collector: params.OtelCol, + Collector: ¶ms.OtelCol, TargetAllocator: *params.TargetAllocator, } taResources, err := BuildTargetAllocator(taParams) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 3e584f8f8a..55a3cf3446 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -182,6 +182,10 @@ func TestMain(m *testing.M) { fmt.Printf("failed to SetupWebhookWithManager: %v", err) 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) diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go new file mode 100644 index 0000000000..6b748e4535 --- /dev/null +++ b/controllers/targetallocator_controller.go @@ -0,0 +1,152 @@ +// 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 contains the main controller, where the reconciliation starts. +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyV1 "k8s.io/api/policy/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator" + taStatus "github.com/open-telemetry/opentelemetry-operator/internal/status/targetallocator" + "github.com/open-telemetry/opentelemetry-operator/pkg/featuregate" +) + +// TargetAllocatorReconciler reconciles a TargetAllocator object. +type TargetAllocatorReconciler struct { + client.Client + recorder record.EventRecorder + scheme *runtime.Scheme + log logr.Logger + config config.Config +} + +// TargetAllocatorReconcilerParams is the set of options to build a new TargetAllocatorReconciler. +type TargetAllocatorReconcilerParams struct { + client.Client + Recorder record.EventRecorder + Scheme *runtime.Scheme + Log logr.Logger + Config config.Config +} + +func (r *TargetAllocatorReconciler) getParams(instance v1alpha1.TargetAllocator) targetallocator.Params { + p := targetallocator.Params{ + Config: r.config, + Client: r.Client, + Log: r.log, + Scheme: r.scheme, + Recorder: r.recorder, + TargetAllocator: instance, + } + + return p +} + +// NewTargetAllocatorReconciler creates a new reconciler for TargetAllocator objects. +func NewTargetAllocatorReconciler( + client client.Client, + scheme *runtime.Scheme, + recorder record.EventRecorder, + config config.Config, + logger logr.Logger, +) *TargetAllocatorReconciler { + return &TargetAllocatorReconciler{ + Client: client, + log: logger, + scheme: scheme, + config: config, + recorder: recorder, + } +} + +// 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 + +// 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) { + log := r.log.WithValues("targetallocator", req.NamespacedName) + + var instance v1alpha1.TargetAllocator + if err := r.Client.Get(ctx, req.NamespacedName, &instance); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "unable to fetch TargetAllocator") + } + + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + // We have a deletion, short circuit and let the deletion happen + if deletionTimestamp := instance.GetDeletionTimestamp(); deletionTimestamp != nil { + return ctrl.Result{}, nil + } + + if instance.Spec.ManagementState == v1beta1.ManagementStateUnmanaged { + log.Info("Skipping reconciliation for unmanaged TargetAllocator resource", "name", req.String()) + // Stop requeueing for unmanaged TargetAllocator custom resources + return ctrl.Result{}, nil + } + + params := r.getParams(instance) + desiredObjects, buildErr := BuildTargetAllocator(params) + if buildErr != nil { + return ctrl.Result{}, buildErr + } + + 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). + 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{}) + } + + return builder.Complete(r) +} diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go new file mode 100644 index 0000000000..36d28a3f68 --- /dev/null +++ b/controllers/targetallocator_controller_test.go @@ -0,0 +1,261 @@ +// 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_test + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + 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/config" +) + +var testLogger = logf.Log.WithName("opamp-bridge-controller-unit-tests") + +func TestNewObjectsOnReconciliation_TargetAllocator(t *testing.T) { + // prepare + cfg := config.New( + config.WithTargetAllocatorImage("default-ta"), + ) + nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} + reconciler := controllers.NewTargetAllocatorReconciler( + k8sClient, + testScheme, + record.NewFakeRecorder(10), + cfg, + testLogger, + ) + created := &v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1alpha1.TargetAllocatorSpec{}, + } + err := k8sClient.Create(context.Background(), created) + require.NoError(t, err) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err = reconciler.Reconcile(context.Background(), req) + + // verify + require.NoError(t, err) + + // the base query for the underlying objects + opts := []client.ListOption{ + client.InNamespace(nsn.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, nsn.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-targetallocator", + }), + } + + // verify that we have at least one object for each of the types we create + // whether we have the right ones is up to the specific tests for each type + { + list := &corev1.ConfigMapList{} + err = k8sClient.List(context.Background(), list, opts...) + assert.NoError(t, err) + assert.NotEmpty(t, list.Items) + } + { + list := &corev1.ServiceAccountList{} + err = k8sClient.List(context.Background(), list, opts...) + assert.NoError(t, err) + assert.NotEmpty(t, list.Items) + } + { + list := &corev1.ServiceList{} + err = k8sClient.List(context.Background(), list, opts...) + assert.NoError(t, err) + assert.NotEmpty(t, list.Items) + } + { + list := &appsv1.DeploymentList{} + err = k8sClient.List(context.Background(), list, opts...) + assert.NoError(t, err) + assert.NotEmpty(t, list.Items) + } + + // cleanup + require.NoError(t, k8sClient.Delete(context.Background(), created)) +} + +func TestSkipWhenInstanceDoesNotExist_TargetAllocator(t *testing.T) { + // prepare + cfg := config.New() + nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} + reconciler := controllers.NewTargetAllocatorReconciler( + k8sClient, + testScheme, + record.NewFakeRecorder(10), + cfg, + testLogger, + ) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err := reconciler.Reconcile(context.Background(), req) + require.NoError(t, err) + + // the base query for the underlying objects + opts := []client.ListOption{ + client.InNamespace(nsn.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, nsn.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-targetallocator", + }), + } + + // verify that no objects have been created + var objList appsv1.DeploymentList + err = k8sClient.List(context.Background(), &objList, opts...) + assert.NoError(t, err) + assert.Empty(t, objList.Items) +} + +func TestUnmanaged_TargetAllocator(t *testing.T) { + // prepare + cfg := config.New( + config.WithTargetAllocatorImage("default-ta"), + ) + nsn := types.NamespacedName{Name: "my-instance-unmanaged", Namespace: "default"} + reconciler := controllers.NewTargetAllocatorReconciler( + k8sClient, + testScheme, + record.NewFakeRecorder(10), + cfg, + testLogger, + ) + unmanaged := &v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{ + ManagementState: v1beta1.ManagementStateUnmanaged, + }, + }, + } + err := k8sClient.Create(context.Background(), unmanaged) + require.NoError(t, err) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err = reconciler.Reconcile(context.Background(), req) + + // verify + require.NoError(t, err) + + // the base query for the underlying objects + opts := []client.ListOption{ + client.InNamespace(nsn.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, nsn.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-targetallocator", + }), + } + + // verify that no objects have been created + var objList appsv1.DeploymentList + err = k8sClient.List(context.Background(), &objList, opts...) + assert.NoError(t, err) + assert.Empty(t, objList.Items) + + // cleanup + require.NoError(t, k8sClient.Delete(context.Background(), unmanaged)) +} + +func TestBuildError_TargetAllocator(t *testing.T) { + // prepare + cfg := config.New( + config.WithTargetAllocatorImage("default-ta"), + ) + nsn := types.NamespacedName{Name: "my-instance-builderror", Namespace: "default"} + reconciler := controllers.NewTargetAllocatorReconciler( + k8sClient, + testScheme, + record.NewFakeRecorder(10), + cfg, + testLogger, + ) + unmanaged := &v1alpha1.TargetAllocator{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1alpha1.TargetAllocatorSpec{ + OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{ + PodDisruptionBudget: &v1beta1.PodDisruptionBudgetSpec{}, + }, + AllocationStrategy: v1beta1.TargetAllocatorAllocationStrategyLeastWeighted, + }, + } + err := k8sClient.Create(context.Background(), unmanaged) + require.NoError(t, err) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err = reconciler.Reconcile(context.Background(), req) + + // verify + require.Error(t, err) + + // the base query for the underlying objects + opts := []client.ListOption{ + client.InNamespace(nsn.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, nsn.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-targetallocator", + }), + } + + // verify that no objects have been created + var objList appsv1.DeploymentList + err = k8sClient.List(context.Background(), &objList, opts...) + assert.NoError(t, err) + assert.Empty(t, objList.Items) + + // cleanup + require.NoError(t, k8sClient.Delete(context.Background(), unmanaged)) +} diff --git a/internal/manifests/targetallocator/configmap.go b/internal/manifests/targetallocator/configmap.go index f7e0408974..496c625ae3 100644 --- a/internal/manifests/targetallocator/configmap.go +++ b/internal/manifests/targetallocator/configmap.go @@ -38,25 +38,38 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) { taSpec := instance.Spec taConfig := make(map[interface{}]interface{}) - - taConfig["collector_selector"] = metav1.LabelSelector{ - MatchLabels: manifestutils.SelectorLabels(params.Collector.ObjectMeta, collector.ComponentOpenTelemetryCollector), - } - // Set config if global or scrape configs set config := map[string]interface{}{} - globalConfig, err := getGlobalConfig(taSpec.GlobalConfig, params.Collector.Spec.Config) - if err != nil { - return nil, err + var ( + globalConfig map[string]any + scrapeConfigs []v1beta1.AnyConfig + collectorSelector *metav1.LabelSelector + err error + ) + if params.Collector != nil { + collectorSelector = &metav1.LabelSelector{ + MatchLabels: manifestutils.SelectorLabels(params.Collector.ObjectMeta, collector.ComponentOpenTelemetryCollector), + } + + globalConfig, err = getGlobalConfig(taSpec.GlobalConfig, params.Collector.Spec.Config) + if err != nil { + return nil, err + } + + scrapeConfigs, err = getScrapeConfigs(taSpec.ScrapeConfigs, params.Collector.Spec.Config) + if err != nil { + return nil, err + } + } else { // if there's no collector, just use what's in the TargetAllocator CR + collectorSelector = nil + globalConfig = taSpec.GlobalConfig.Object + scrapeConfigs = taSpec.ScrapeConfigs } + if len(globalConfig) > 0 { config["global"] = globalConfig } - scrapeConfigs, err := getScrapeConfigs(taSpec.ScrapeConfigs, params.Collector.Spec.Config) - if err != nil { - return nil, err - } if len(scrapeConfigs) > 0 { config["scrape_configs"] = scrapeConfigs } @@ -65,6 +78,8 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) { taConfig["config"] = config } + taConfig["collector_selector"] = collectorSelector + if len(taSpec.AllocationStrategy) > 0 { taConfig["allocation_strategy"] = taSpec.AllocationStrategy } else { diff --git a/internal/manifests/targetallocator/configmap_test.go b/internal/manifests/targetallocator/configmap_test.go index cfa45feb8c..66553bf783 100644 --- a/internal/manifests/targetallocator/configmap_test.go +++ b/internal/manifests/targetallocator/configmap_test.go @@ -31,6 +31,8 @@ import ( func TestDesiredConfigMap(t *testing.T) { expectedLabels := map[string]string{ + "app.kubernetes.io/name": "my-instance-targetallocator", + "app.kubernetes.io/component": "opentelemetry-targetallocator", "app.kubernetes.io/managed-by": "opentelemetry-operator", "app.kubernetes.io/instance": "default.my-instance", "app.kubernetes.io/part-of": "opentelemetry", @@ -47,9 +49,6 @@ func TestDesiredConfigMap(t *testing.T) { } t.Run("should return expected target allocator config map", func(t *testing.T) { - expectedLabels["app.kubernetes.io/component"] = "opentelemetry-targetallocator" - expectedLabels["app.kubernetes.io/name"] = "my-instance-targetallocator" - expectedData := map[string]string{ targetAllocatorFilename: `allocation_strategy: consistent-hashing collector_selector: @@ -79,10 +78,30 @@ filter_strategy: relabel-config assert.Equal(t, expectedData[targetAllocatorFilename], actual.Data[targetAllocatorFilename]) }) - t.Run("should return target allocator config map without scrape configs", func(t *testing.T) { - expectedLabels["app.kubernetes.io/component"] = "opentelemetry-targetallocator" - expectedLabels["app.kubernetes.io/name"] = "my-instance-targetallocator" + t.Run("should return target allocator config map without collector", func(t *testing.T) { + expectedData := map[string]string{ + targetAllocatorFilename: `allocation_strategy: consistent-hashing +collector_selector: null +filter_strategy: relabel-config +`, + } + targetAllocator = targetAllocatorInstance() + targetAllocator.Spec.ScrapeConfigs = []v1beta1.AnyConfig{} + params.TargetAllocator = targetAllocator + testParams := Params{ + Collector: nil, + TargetAllocator: targetAllocator, + } + actual, err := ConfigMap(testParams) + require.NoError(t, err) + params.Collector = collector + + assert.Equal(t, "my-instance-targetallocator", actual.Name) + assert.Equal(t, expectedLabels, actual.Labels) + assert.Equal(t, expectedData[targetAllocatorFilename], actual.Data[targetAllocatorFilename]) + }) + t.Run("should return target allocator config map without scrape configs", func(t *testing.T) { expectedData := map[string]string{ targetAllocatorFilename: `allocation_strategy: consistent-hashing collector_selector: @@ -118,9 +137,6 @@ filter_strategy: relabel-config }) t.Run("should return expected target allocator config map with label selectors", func(t *testing.T) { - expectedLabels["app.kubernetes.io/component"] = "opentelemetry-targetallocator" - expectedLabels["app.kubernetes.io/name"] = "my-instance-targetallocator" - expectedData := map[string]string{ targetAllocatorFilename: `allocation_strategy: consistent-hashing collector_selector: @@ -184,9 +200,6 @@ prometheus_cr: }) t.Run("should return expected target allocator config map with scrape interval set", func(t *testing.T) { - expectedLabels["app.kubernetes.io/component"] = "opentelemetry-targetallocator" - expectedLabels["app.kubernetes.io/name"] = "my-instance-targetallocator" - expectedData := map[string]string{ targetAllocatorFilename: `allocation_strategy: consistent-hashing collector_selector: diff --git a/internal/manifests/targetallocator/deployment_test.go b/internal/manifests/targetallocator/deployment_test.go index cc0d59d906..58e98a9ee3 100644 --- a/internal/manifests/targetallocator/deployment_test.go +++ b/internal/manifests/targetallocator/deployment_test.go @@ -182,7 +182,7 @@ func TestDeploymentPodAnnotations(t *testing.T) { assert.Subset(t, ds.Spec.Template.Annotations, testPodAnnotationValues) } -func collectorInstance() v1beta1.OpenTelemetryCollector { +func collectorInstance() *v1beta1.OpenTelemetryCollector { configYAML, err := os.ReadFile("testdata/test.yaml") if err != nil { fmt.Printf("Error getting yaml file: %v", err) @@ -192,7 +192,7 @@ func collectorInstance() v1beta1.OpenTelemetryCollector { if err != nil { fmt.Printf("Error unmarshalling YAML: %v", err) } - return v1beta1.OpenTelemetryCollector{ + return &v1beta1.OpenTelemetryCollector{ ObjectMeta: metav1.ObjectMeta{ Name: "my-instance", Namespace: "default", @@ -213,7 +213,7 @@ func collectorInstance() v1beta1.OpenTelemetryCollector { func targetAllocatorInstance() v1alpha1.TargetAllocator { collectorInstance := collectorInstance() collectorInstance.Spec.TargetAllocator.Enabled = true - params := manifests.Params{OtelCol: collectorInstance} + params := manifests.Params{OtelCol: *collectorInstance} targetAllocator, _ := collector.TargetAllocator(params) targetAllocator.Spec.Image = "ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-targetallocator:0.47.0" return *targetAllocator diff --git a/internal/manifests/targetallocator/serviceaccount_test.go b/internal/manifests/targetallocator/serviceaccount_test.go index 717a173ff4..cf79af6cba 100644 --- a/internal/manifests/targetallocator/serviceaccount_test.go +++ b/internal/manifests/targetallocator/serviceaccount_test.go @@ -65,16 +65,20 @@ func TestServiceAccountDefault(t *testing.T) { params := Params{ TargetAllocator: v1alpha1.TargetAllocator{ ObjectMeta: metav1.ObjectMeta{ - Name: "my-instance", + Name: "my-instance", + Namespace: "default", + Annotations: map[string]string{ + "prometheus.io/scrape": "false", + }, }, }, } expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "my-instance-targetallocator", - Namespace: params.Collector.Namespace, + Namespace: params.TargetAllocator.Namespace, Labels: manifestutils.Labels(params.TargetAllocator.ObjectMeta, "my-instance-targetallocator", params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil), - Annotations: params.Collector.Annotations, + Annotations: params.TargetAllocator.Annotations, }, } diff --git a/internal/manifests/targetallocator/targetallocator.go b/internal/manifests/targetallocator/targetallocator.go index 19a5ed60d8..e1da206f4f 100644 --- a/internal/manifests/targetallocator/targetallocator.go +++ b/internal/manifests/targetallocator/targetallocator.go @@ -62,7 +62,7 @@ type Params struct { Recorder record.EventRecorder Scheme *runtime.Scheme Log logr.Logger - Collector v1beta1.OpenTelemetryCollector + Collector *v1beta1.OpenTelemetryCollector TargetAllocator v1alpha1.TargetAllocator Config config.Config } diff --git a/internal/status/targetallocator/handle.go b/internal/status/targetallocator/handle.go new file mode 100644 index 0000000000..82750ff006 --- /dev/null +++ b/internal/status/targetallocator/handle.go @@ -0,0 +1,58 @@ +// 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 targetallocator + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator" +) + +const ( + eventTypeNormal = "Normal" + eventTypeWarning = "Warning" + + reasonError = "Error" + reasonStatusFailure = "StatusFailure" + reasonInfo = "Info" +) + +// HandleReconcileStatus handles updating the status of the CRDs managed by the operator. +// TODO: make the status more useful https://github.com/open-telemetry/opentelemetry-operator/issues/1972 +func HandleReconcileStatus(ctx context.Context, log logr.Logger, params targetallocator.Params, err error) (ctrl.Result, error) { + log.V(2).Info("updating opampbridge status") + if err != nil { + params.Recorder.Event(¶ms.TargetAllocator, eventTypeWarning, reasonError, err.Error()) + return ctrl.Result{}, err + } + changed := params.TargetAllocator.DeepCopy() + + statusErr := UpdateTargetAllocatorStatus(ctx, params.Client, changed) + if statusErr != nil { + params.Recorder.Event(changed, eventTypeWarning, reasonStatusFailure, statusErr.Error()) + return ctrl.Result{}, statusErr + } + statusPatch := client.MergeFrom(¶ms.TargetAllocator) + if err := params.Client.Status().Patch(ctx, changed, statusPatch); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to apply status changes to the OpenTelemetry CR: %w", err) + } + params.Recorder.Event(changed, eventTypeNormal, reasonInfo, "applied status changes") + return ctrl.Result{}, nil +} diff --git a/internal/status/targetallocator/targetallocator.go b/internal/status/targetallocator/targetallocator.go new file mode 100644 index 0000000000..8c0df97260 --- /dev/null +++ b/internal/status/targetallocator/targetallocator.go @@ -0,0 +1,31 @@ +// 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 targetallocator + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/version" +) + +func UpdateTargetAllocatorStatus(ctx context.Context, cli client.Client, changed *v1alpha1.TargetAllocator) error { + if changed.Status.Version == "" { + changed.Status.Version = version.TargetAllocator() + } + return nil +} diff --git a/main.go b/main.go index 951ba2ee1a..a348db6496 100644 --- a/main.go +++ b/main.go @@ -384,6 +384,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 err = controllers.NewOpAMPBridgeReconciler(controllers.OpAMPBridgeReconcilerParams{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("OpAMPBridge"), @@ -430,6 +442,11 @@ 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 err = otelv1alpha1.SetupInstrumentationWebhook(mgr, cfg); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Instrumentation") os.Exit(1)