From 130e69dc198c0a1418016ac8b653e82085127fc8 Mon Sep 17 00:00:00 2001 From: Alexis Perez Date: Thu, 8 Jul 2021 15:56:09 -0400 Subject: [PATCH 01/30] load balancer deployment implementation & controller & e2e tests --- controllers/loadbalancer_controller.go | 140 +++++++++++ controllers/loadbalancer_controller_test.go | 228 ++++++++++++++++++ pkg/loadbalancer/container.go | 55 +++++ pkg/loadbalancer/container_test.go | 79 ++++++ pkg/loadbalancer/deployment.go | 58 +++++ pkg/loadbalancer/deployment_test.go | 64 +++++ pkg/loadbalancer/labels.go | 39 +++ pkg/loadbalancer/labels_test.go | 58 +++++ pkg/loadbalancer/volume.go | 43 ++++ pkg/loadbalancer/volume_test.go | 44 ++++ .../e2e/loadbalancer-features/00-assert.yaml | 27 +++ .../e2e/loadbalancer-features/00-install.yaml | 28 +++ 12 files changed, 863 insertions(+) create mode 100644 controllers/loadbalancer_controller.go create mode 100644 controllers/loadbalancer_controller_test.go create mode 100644 pkg/loadbalancer/container.go create mode 100644 pkg/loadbalancer/container_test.go create mode 100644 pkg/loadbalancer/deployment.go create mode 100644 pkg/loadbalancer/deployment_test.go create mode 100644 pkg/loadbalancer/labels.go create mode 100644 pkg/loadbalancer/labels_test.go create mode 100644 pkg/loadbalancer/volume.go create mode 100644 pkg/loadbalancer/volume_test.go create mode 100644 tests/e2e/loadbalancer-features/00-assert.yaml create mode 100644 tests/e2e/loadbalancer-features/00-install.yaml diff --git a/controllers/loadbalancer_controller.go b/controllers/loadbalancer_controller.go new file mode 100644 index 0000000000..852cfec122 --- /dev/null +++ b/controllers/loadbalancer_controller.go @@ -0,0 +1,140 @@ +// 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" + "fmt" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/go-logr/logr" + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// OpenTelemetryLoadbalancerReconciler reconciles a OpenTelemetryLoadbalancer object. +type OpenTelemetryLoadbalancerReconciler struct { + client.Client + log logr.Logger + scheme *runtime.Scheme + config config.Config + tasks []LbTask +} + +// LbTask represents a reconciliation task to be executed by the reconciler. +type LbTask struct { + Name string + Do func(context.Context, reconcile.Params) error + BailOnError bool +} + +// LbParams is the set of options to build a new openTelemetryLoadbalancerReconciler. +type LbParams struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Config config.Config + Tasks []LbTask +} + +// NewLbReconciler creates a new reconciler for OpenTelemetryLoadbalancer objects. +func NewLbReconciler(p LbParams) *OpenTelemetryLoadbalancerReconciler { + if len(p.Tasks) == 0 { + p.Tasks = []LbTask{ + { + "config maps", + reconcile.ConfigMaps, + true, + }, + { + "deployments", + reconcile.Deployments, + true, + }, + { + "services", + reconcile.Services, + true, + }, + } + } + + return &OpenTelemetryLoadbalancerReconciler{ + Client: p.Client, + log: p.Log, + scheme: p.Scheme, + config: p.Config, + tasks: p.Tasks, + } +} + +// Reconcile the current state of an OpenTelemetry LB resource with the desired state. +func (r *OpenTelemetryLoadbalancerReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.log.WithValues("opentelemtryloadbalancer", req.NamespacedName) + + var instance v1alpha1.OpenTelemetryCollector + if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "unable to fetch OpenTelemetryLoadBalancer") + } + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + params := reconcile.Params{ + Config: r.config, + Client: r.Client, + Instance: instance, + Log: log, + Scheme: r.scheme, + } + + if err := r.RunTasks(ctx, params); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// RunTasks runs all the tasks associated with this reconciler. +func (r *OpenTelemetryLoadbalancerReconciler) RunTasks(ctx context.Context, params reconcile.Params) error { + for _, task := range r.tasks { + if err := task.Do(ctx, params); err != nil { + r.log.Error(err, fmt.Sprintf("failed to reconcile %s", task.Name)) + if task.BailOnError { + return err + } + } + } + return nil +} + +// SetupWithManager tells the manager what our controller is interested in. +func (r *OpenTelemetryLoadbalancerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.OpenTelemetryCollector{}). + Owns(&corev1.ConfigMap{}). + Owns(&corev1.Service{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} diff --git a/controllers/loadbalancer_controller_test.go b/controllers/loadbalancer_controller_test.go new file mode 100644 index 0000000000..375165e0d9 --- /dev/null +++ b/controllers/loadbalancer_controller_test.go @@ -0,0 +1,228 @@ +// 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" + "errors" + "fmt" + "io/ioutil" + "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/kubectl/pkg/scheme" + "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/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/controllers" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" +) + +var lblogger = logf.Log.WithName("unit-tests") + +func TestNewObjectsOnLbReconciliation(t *testing.T) { + // prepare + cfg := config.New() + configYAML, err := ioutil.ReadFile("../pkg/loadbalancer/reconcile/suite_test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } + nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} + reconciler := controllers.NewLbReconciler(controllers.LbParams{ + Client: k8sClient, + Log: lblogger, + Scheme: testScheme, + Config: cfg, + }) + created := &v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Config: string(configYAML), + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Mode: "LeastConnection", + }, + }, + } + 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, "loadbalancer"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + + // 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.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 TestContinueOnRecoverableLbFailure(t *testing.T) { + // prepare + taskCalled := false + reconciler := controllers.NewLbReconciler(controllers.LbParams{ + Log: lblogger, + Tasks: []controllers.LbTask{ + { + Name: "should-fail", + Do: func(context.Context, reconcile.Params) error { + return errors.New("should fail!") + }, + BailOnError: false, + }, + { + Name: "should-be-called", + Do: func(context.Context, reconcile.Params) error { + taskCalled = true + return nil + }, + }, + }, + }) + + // test + err := reconciler.RunTasks(context.Background(), reconcile.Params{}) + + // verify + assert.NoError(t, err) + assert.True(t, taskCalled) +} + +func TestBreakOnUnrecoverableLbError(t *testing.T) { + // prepare + cfg := config.New() + taskCalled := false + expectedErr := errors.New("should fail!") + nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} + reconciler := controllers.NewLbReconciler(controllers.LbParams{ + Client: k8sClient, + Log: logger, + Scheme: scheme.Scheme, + Config: cfg, + Tasks: []controllers.LbTask{ + { + Name: "should-fail", + Do: func(context.Context, reconcile.Params) error { + taskCalled = true + return expectedErr + }, + BailOnError: true, + }, + { + Name: "should-not-be-called", + Do: func(context.Context, reconcile.Params) error { + assert.Fail(t, "should not have been called") + return nil + }, + }, + }, + }) + created := &v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + } + err := k8sClient.Create(context.Background(), created) + require.NoError(t, err) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err = reconciler.Reconcile(context.Background(), req) + + // verify + assert.Equal(t, expectedErr, err) + assert.True(t, taskCalled) + + // cleanup + assert.NoError(t, k8sClient.Delete(context.Background(), created)) +} + +func TestLbSkipWhenInstanceDoesNotExist(t *testing.T) { + // prepare + cfg := config.New() + nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} + reconciler := controllers.NewLbReconciler(controllers.LbParams{ + Client: k8sClient, + Log: logger, + Scheme: scheme.Scheme, + Config: cfg, + Tasks: []controllers.LbTask{ + { + Name: "should-not-be-called", + Do: func(context.Context, reconcile.Params) error { + assert.Fail(t, "should not have been called") + return nil + }, + }, + }, + }) + + // test + req := k8sreconcile.Request{ + NamespacedName: nsn, + } + _, err := reconciler.Reconcile(context.Background(), req) + + // verify + assert.NoError(t, err) +} diff --git a/pkg/loadbalancer/container.go b/pkg/loadbalancer/container.go new file mode 100644 index 0000000000..9f7d29df50 --- /dev/null +++ b/pkg/loadbalancer/container.go @@ -0,0 +1,55 @@ +// 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 loadbalancer + +import ( + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// Container builds a container for the given LoadBalancer. +func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) corev1.Container { + image := otelcol.Spec.LoadBalancer.Image + if len(image) == 0 { + image = cfg.LoadBalancerImage() + } + + volumeMounts := []corev1.VolumeMount{{ + Name: naming.LBConfigMapVolume(), + MountPath: "/conf", + }} + + envVars := []corev1.EnvVar{} + + envVars = append(envVars, corev1.EnvVar{ + Name: "OTEL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }) + + return corev1.Container{ + Name: naming.LBContainer(), + Image: image, + Env: envVars, + VolumeMounts: volumeMounts, + } +} diff --git a/pkg/loadbalancer/container_test.go b/pkg/loadbalancer/container_test.go new file mode 100644 index 0000000000..79220c8220 --- /dev/null +++ b/pkg/loadbalancer/container_test.go @@ -0,0 +1,79 @@ +// 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 loadbalancer_test + +import ( + "testing" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/stretchr/testify/assert" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +var logger = logf.Log.WithName("unit-tests") + +func TestContainerNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{} + cfg := config.New(config.WithLoadBalancerImage("default-image")) + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Equal(t, "default-image", c.Image) +} + +func TestContainerWithImageOverridden(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Image: "overridden-image", + }, + }, + } + cfg := config.New(config.WithLoadBalancerImage("default-image")) + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Equal(t, "overridden-image", c.Image) +} + +func TestContainerVolumes(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Image: "default-image", + }, + }, + } + cfg := config.New() + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Len(t, c.VolumeMounts, 1) + assert.Equal(t, naming.LBConfigMapVolume(), c.VolumeMounts[0].Name) +} diff --git a/pkg/loadbalancer/deployment.go b/pkg/loadbalancer/deployment.go new file mode 100644 index 0000000000..6bd4efb415 --- /dev/null +++ b/pkg/loadbalancer/deployment.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 loadbalancer + +import ( + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// Deployment builds the deployment for the given instance. +func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.Deployment { + labels := Labels(otelcol) + labels["app.kubernetes.io/name"] = naming.LoadBalancer(otelcol) + + var replicas int32 = 1 + + return appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.LoadBalancer(otelcol), + Namespace: otelcol.Namespace, + Labels: labels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: otelcol.Annotations, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{Container(cfg, logger, otelcol)}, + Volumes: Volumes(cfg, otelcol), + }, + }, + }, + } +} diff --git a/pkg/loadbalancer/deployment_test.go b/pkg/loadbalancer/deployment_test.go new file mode 100644 index 0000000000..68fbaae412 --- /dev/null +++ b/pkg/loadbalancer/deployment_test.go @@ -0,0 +1,64 @@ +// 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 loadbalancer_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" +) + +var testTolerationValues = []v1.Toleration{ + { + Key: "key", + Value: "Val", + Effect: "NoSchedule", + }, +} + +func TestDeploymentNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Tolerations: testTolerationValues, + }, + } + cfg := config.New() + + // test + d := Deployment(cfg, logger, otelcol) + + // verify + assert.Equal(t, "my-instance-loadbalancer", d.Name) + assert.Equal(t, "my-instance-loadbalancer", d.Labels["app.kubernetes.io/name"]) + + assert.Len(t, d.Spec.Template.Spec.Containers, 1) + + // none of the default annotations should propagate down to the pod + assert.Empty(t, d.Spec.Template.Annotations) + + // the pod selector should match the pod spec's labels + assert.Equal(t, d.Spec.Template.Labels, d.Spec.Selector.MatchLabels) +} diff --git a/pkg/loadbalancer/labels.go b/pkg/loadbalancer/labels.go new file mode 100644 index 0000000000..44e61ba876 --- /dev/null +++ b/pkg/loadbalancer/labels.go @@ -0,0 +1,39 @@ +// 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 loadbalancer + +import ( + "fmt" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" +) + +// Labels return the common labels to all LoadBalancer objects that are part of a managed OpenTelemetryCollector. +func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { + // new map every time, so that we don't touch the instance's label + base := map[string]string{} + if nil != instance.Labels { + for k, v := range instance.Labels { + base[k] = v + } + } + + base["app.kubernetes.io/managed-by"] = "opentelemetry-operator" + base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, "loadbalancer") + base["app.kubernetes.io/part-of"] = "opentelemetry" + base["app.kubernetes.io/component"] = "opentelemetry-loadbalancer" + + return base +} diff --git a/pkg/loadbalancer/labels_test.go b/pkg/loadbalancer/labels_test.go new file mode 100644 index 0000000000..3bbc14b272 --- /dev/null +++ b/pkg/loadbalancer/labels_test.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 loadbalancer_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" +) + +func TestLabelsCommonSet(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + Namespace: "my-ns", + }, + } + + // test + labels := Labels(otelcol) + assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) + assert.Equal(t, "my-ns.loadbalancer", labels["app.kubernetes.io/instance"]) + assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) + assert.Equal(t, "opentelemetry-loadbalancer", labels["app.kubernetes.io/component"]) +} + +func TestLabelsPropagateDown(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"myapp": "mycomponent"}, + }, + } + + // test + labels := Labels(otelcol) + + // verify + assert.Len(t, labels, 5) + assert.Equal(t, "mycomponent", labels["myapp"]) +} diff --git a/pkg/loadbalancer/volume.go b/pkg/loadbalancer/volume.go new file mode 100644 index 0000000000..1baa02b794 --- /dev/null +++ b/pkg/loadbalancer/volume.go @@ -0,0 +1,43 @@ +// 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 collector handles the OpenTelemetry Collector. +package loadbalancer + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// Volumes builds the volumes for the given instance, including the config map volume. +func Volumes(cfg config.Config, otelcol v1alpha1.OpenTelemetryCollector) []corev1.Volume { + volumes := []corev1.Volume{{ + Name: naming.LBConfigMapVolume(), + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: naming.LBConfigMap(otelcol)}, + Items: []corev1.KeyToPath{ + { + Key: cfg.LoadBalancerConfigMapEntry(), + Path: cfg.LoadBalancerConfigMapEntry(), + }}, + }, + }, + }} + + return volumes +} diff --git a/pkg/loadbalancer/volume_test.go b/pkg/loadbalancer/volume_test.go new file mode 100644 index 0000000000..f44b25cd1f --- /dev/null +++ b/pkg/loadbalancer/volume_test.go @@ -0,0 +1,44 @@ +// 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 loadbalancer_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +func TestVolumeNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{} + cfg := config.New() + + // test + volumes := Volumes(cfg, otelcol) + + // verify + assert.Len(t, volumes, 1) + + //check if the number of elements in the volume source items list is 1 + assert.Len(t, volumes[0].VolumeSource.ConfigMap.Items, 1) + + // check that it's the lb-internal volume, with the config map + assert.Equal(t, naming.LBConfigMapVolume(), volumes[0].Name) +} diff --git a/tests/e2e/loadbalancer-features/00-assert.yaml b/tests/e2e/loadbalancer-features/00-assert.yaml new file mode 100644 index 0000000000..9ef86334a0 --- /dev/null +++ b/tests/e2e/loadbalancer-features/00-assert.yaml @@ -0,0 +1,27 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-loadbalancer +spec: + template: + spec: + containers: + - name: lb-container + env: + - name: OTEL_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - mountPath: /conf + name: lb-internal + volumes: + - configMap: + items: + - key: loadbalancer.yaml + path: loadbalancer.yaml + name: stateful-loadbalancer + name: lb-internal +status: + replicas: 1 + readyReplicas: 1 \ No newline at end of file diff --git a/tests/e2e/loadbalancer-features/00-install.yaml b/tests/e2e/loadbalancer-features/00-install.yaml new file mode 100644 index 0000000000..b05c1aa928 --- /dev/null +++ b/tests/e2e/loadbalancer-features/00-install.yaml @@ -0,0 +1,28 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + loadbalancer: + mode: LeastConnection + config: | + receivers: + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] From 2835c16476039a6e2fd6f7fef81dafaab524c320 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Thu, 8 Jul 2021 13:07:13 -0700 Subject: [PATCH 02/30] Load balancer crd update, reconcile logic and e2e tests --- api/v1alpha1/opentelemetrycollector_types.go | 16 + ...ntelemetry.io_opentelemetrycollectors.yaml | 13 + internal/config/main.go | 58 ++-- internal/config/options.go | 29 +- .../adapters/config_to_promConfig.go | 62 ++++ .../adapters/config_to_promConfig_test.go | 92 ++++++ pkg/loadbalancer/reconcile/configmap.go | 192 ++++++++++++ pkg/loadbalancer/reconcile/configmap_test.go | 181 +++++++++++ pkg/loadbalancer/reconcile/deployment.go | 147 +++++++++ pkg/loadbalancer/reconcile/deployment_test.go | 292 ++++++++++++++++++ pkg/loadbalancer/reconcile/params.go | 33 ++ pkg/loadbalancer/reconcile/service.go | 154 +++++++++ pkg/loadbalancer/reconcile/service_test.go | 100 ++++++ pkg/loadbalancer/reconcile/suite_test.go | 196 ++++++++++++ pkg/loadbalancer/reconcile/suite_test.yaml | 19 ++ pkg/naming/main.go | 25 ++ tests/e2e/smoke-loadbalancer/00-assert.yaml | 8 + tests/e2e/smoke-loadbalancer/00-install.yaml | 28 ++ 18 files changed, 1618 insertions(+), 27 deletions(-) create mode 100644 pkg/loadbalancer/adapters/config_to_promConfig.go create mode 100644 pkg/loadbalancer/adapters/config_to_promConfig_test.go create mode 100644 pkg/loadbalancer/reconcile/configmap.go create mode 100644 pkg/loadbalancer/reconcile/configmap_test.go create mode 100644 pkg/loadbalancer/reconcile/deployment.go create mode 100644 pkg/loadbalancer/reconcile/deployment_test.go create mode 100644 pkg/loadbalancer/reconcile/params.go create mode 100644 pkg/loadbalancer/reconcile/service.go create mode 100644 pkg/loadbalancer/reconcile/service_test.go create mode 100644 pkg/loadbalancer/reconcile/suite_test.go create mode 100644 pkg/loadbalancer/reconcile/suite_test.yaml create mode 100644 tests/e2e/smoke-loadbalancer/00-assert.yaml create mode 100644 tests/e2e/smoke-loadbalancer/00-install.yaml diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index d8528fdb39..c279b76d1a 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -41,6 +41,11 @@ type OpenTelemetryCollectorSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Image string `json:"image,omitempty"` + // LoadBalancer indicates a value which determines whether to spawn a LoadBalancer resource or not. + // +optional + // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true + LoadBalancer OpenTelemetryLoadBalancer `json:"loadbalancer,omitempty"` + // Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) // +optional // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true @@ -116,6 +121,17 @@ type OpenTelemetryCollectorStatus struct { Messages []string `json:"messages,omitempty"` } +// OpenTelemetryLoadBalancer defines the configurations for the LoadBalancer. +type OpenTelemetryLoadBalancer struct { + // Use indicates the option to use the loadbalancer option or not. + // +optional + Mode string `json:"mode,omitempty"` + + // Image indicates the container image to use for the OpenTelemetry LoadBalancer. + // +optional + Image string `json:"image,omitempty"` +} + // +kubebuilder:object:root=true // +kubebuilder:resource:shortName=otelcol;otelcols // +kubebuilder:subresource:status diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index c3d3e613a9..962edfd1aa 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -172,6 +172,19 @@ spec: description: Image indicates the container image to use for the OpenTelemetry Collector. type: string + loadbalancer: + description: LoadBalancer indicates a value which determines whether + to spawn a LoadBalancer resource or not. + properties: + image: + description: Image indicates the container image to use for the + OpenTelemetry LoadBalancer. + type: string + mode: + description: Use indicates the option to use the loadbalancer + option or not. + type: string + type: object mode: description: Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) diff --git a/internal/config/main.go b/internal/config/main.go index 52b9e49586..aeba8abf67 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -29,8 +29,9 @@ import ( ) const ( - defaultAutoDetectFrequency = 5 * time.Second - defaultCollectorConfigMapEntry = "collector.yaml" + defaultAutoDetectFrequency = 5 * time.Second + defaultCollectorConfigMapEntry = "collector.yaml" + defaultLoadBalancerConfigMapEntry = "loadbalancer.yaml" ) // Config holds the static configuration for this operator. @@ -44,21 +45,24 @@ type Config struct { onChange []func() error // config state - collectorImage string - collectorConfigMapEntry string - platform platform.Platform - version version.Version + collectorImage string + collectorConfigMapEntry string + loadbalancerImage string + loadBalancerConfigMapEntry string + platform platform.Platform + version version.Version } // New constructs a new configuration based on the given options. func New(opts ...Option) Config { // initialize with the default values o := options{ - autoDetectFrequency: defaultAutoDetectFrequency, - collectorConfigMapEntry: defaultCollectorConfigMapEntry, - logger: logf.Log.WithName("config"), - platform: platform.Unknown, - version: version.Get(), + autoDetectFrequency: defaultAutoDetectFrequency, + collectorConfigMapEntry: defaultCollectorConfigMapEntry, + loadBalancerConfigMapEntry: defaultLoadBalancerConfigMapEntry, + logger: logf.Log.WithName("config"), + platform: platform.Unknown, + version: version.Get(), } for _, opt := range opts { opt(&o) @@ -70,15 +74,21 @@ func New(opts ...Option) Config { o.collectorImage = fmt.Sprintf("otel/opentelemetry-collector:%s", o.version.OpenTelemetryCollector) } + if len(o.loadbalancerImage) == 0 { + o.loadbalancerImage = fmt.Sprintf("raul9595/otel-loadbalancer") + } + return Config{ - autoDetect: o.autoDetect, - autoDetectFrequency: o.autoDetectFrequency, - collectorImage: o.collectorImage, - collectorConfigMapEntry: o.collectorConfigMapEntry, - logger: o.logger, - onChange: o.onChange, - platform: o.platform, - version: o.version, + autoDetect: o.autoDetect, + autoDetectFrequency: o.autoDetectFrequency, + collectorImage: o.collectorImage, + collectorConfigMapEntry: o.collectorConfigMapEntry, + loadbalancerImage: o.loadbalancerImage, + loadBalancerConfigMapEntry: o.loadBalancerConfigMapEntry, + logger: o.logger, + onChange: o.onChange, + platform: o.platform, + version: o.version, } } @@ -155,6 +165,16 @@ func (c *Config) CollectorConfigMapEntry() string { return c.collectorConfigMapEntry } +// LoadBalancerImage represents the flag to override the OpenTelemetry LoadBalancer container image. +func (c *Config) LoadBalancerImage() string { + return c.loadbalancerImage +} + +// LoadBalancerConfigMapEntry represents the configuration file name for the LoadBalancer. Immutable. +func (c *Config) LoadBalancerConfigMapEntry() string { + return c.loadBalancerConfigMapEntry +} + // Platform represents the type of the platform this operator is running. func (c *Config) Platform() platform.Platform { return c.platform diff --git a/internal/config/options.go b/internal/config/options.go index 41d39bfe39..30b5af017d 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -28,14 +28,16 @@ import ( type Option func(c *options) type options struct { - autoDetect autodetect.AutoDetect - autoDetectFrequency time.Duration - collectorImage string - collectorConfigMapEntry string - logger logr.Logger - onChange []func() error - platform platform.Platform - version version.Version + autoDetect autodetect.AutoDetect + autoDetectFrequency time.Duration + loadbalancerImage string + collectorImage string + collectorConfigMapEntry string + loadBalancerConfigMapEntry string + logger logr.Logger + onChange []func() error + platform platform.Platform + version version.Version } func WithAutoDetect(a autodetect.AutoDetect) Option { @@ -48,6 +50,12 @@ func WithAutoDetectFrequency(t time.Duration) Option { o.autoDetectFrequency = t } } +func WithLoadBalancerImage(s string) Option { + return func(o *options) { + o.loadbalancerImage = s + } +} + func WithCollectorImage(s string) Option { return func(o *options) { o.collectorImage = s @@ -58,6 +66,11 @@ func WithCollectorConfigMapEntry(s string) Option { o.collectorConfigMapEntry = s } } +func WithLoadBalancerConfigMapEntry(s string) Option { + return func(o *options) { + o.loadBalancerConfigMapEntry = s + } +} func WithLogger(logger logr.Logger) Option { return func(o *options) { o.logger = logger diff --git a/pkg/loadbalancer/adapters/config_to_promConfig.go b/pkg/loadbalancer/adapters/config_to_promConfig.go new file mode 100644 index 0000000000..aee0ef6d26 --- /dev/null +++ b/pkg/loadbalancer/adapters/config_to_promConfig.go @@ -0,0 +1,62 @@ +// 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 adapters + +import ( + "fmt" +) + +func ErrorNoComponent(component string) string { + return fmt.Sprintf("no %s available as part of the configuration", component) +} + +func ErrorNotAMap(component string) string { + return fmt.Sprintf("%s property in the configuration doesn't contain valid %s", component, component) +} + +// ConfigToPromConfig converts the incoming configuration object into a the Prometheus receiver config. +func ConfigToPromConfig(config map[interface{}]interface{}) (map[interface{}]interface{}, string) { + receiversProperty, ok := config["receivers"] + if !ok { + return nil, ErrorNoComponent("receivers") + } + + receivers, ok := receiversProperty.(map[interface{}]interface{}) + if !ok { + return nil, ErrorNotAMap("receivers") + } + + prometheusProperty, ok := receivers["prometheus"] + if !ok { + return nil, ErrorNoComponent("prometheus") + } + + prometheus, ok := prometheusProperty.(map[interface{}]interface{}) + if !ok { + return nil, ErrorNotAMap("prometheus") + } + + prometheusConfigProperty, ok := prometheus["config"] + if !ok { + return nil, ErrorNoComponent("prometheusConfig") + } + + prometheusConfig, ok := prometheusConfigProperty.(map[interface{}]interface{}) + if !ok { + return nil, ErrorNotAMap("prometheusConfig") + } + + return prometheusConfig, "" +} diff --git a/pkg/loadbalancer/adapters/config_to_promConfig_test.go b/pkg/loadbalancer/adapters/config_to_promConfig_test.go new file mode 100644 index 0000000000..41745d80ff --- /dev/null +++ b/pkg/loadbalancer/adapters/config_to_promConfig_test.go @@ -0,0 +1,92 @@ +// 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 adapters_test + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" +) + +var logger = logf.Log.WithName("unit-tests") + +func TestExtractPromConfigFromConfig(t *testing.T) { + configStr := `receivers: + examplereceiver: + endpoint: "0.0.0.0:12345" + examplereceiver/settings: + endpoint: "0.0.0.0:12346" + prometheus: + config: + scrape_config: + job_name: otel-collector + scrape_interval: 10s + jaeger/custom: + protocols: + thrift_http: + endpoint: 0.0.0.0:15268 +` + expectedData := map[interface{}]interface{}{ + "scrape_config": map[interface{}]interface{}{ + "job_name": "otel-collector", + "scrape_interval": "10s", + }, + } + + // prepare + config, err := adapters.ConfigFromString(configStr) + require.NoError(t, err) + require.NotEmpty(t, config) + + // test + promConfig, notify := lbadapters.ConfigToPromConfig(config) + assert.Equal(t, notify, "") + + // verify + assert.Equal(t, expectedData, promConfig) +} + +func TestExtractPromConfigFromNullConfig(t *testing.T) { + configStr := `receivers: + examplereceiver: + endpoint: "0.0.0.0:12345" + examplereceiver/settings: + endpoint: "0.0.0.0:12346" + prometheus: + config: + jaeger/custom: + protocols: + thrift_http: + endpoint: 0.0.0.0:15268 +` + + // prepare + config, err := adapters.ConfigFromString(configStr) + require.NoError(t, err) + require.NotEmpty(t, config) + + // test + promConfig, notify := lbadapters.ConfigToPromConfig(config) + assert.Equal(t, notify, lbadapters.ErrorNotAMap("prometheusConfig")) + + // verify + assert.True(t, reflect.ValueOf(promConfig).IsNil()) +} diff --git a/pkg/loadbalancer/reconcile/configmap.go b/pkg/loadbalancer/reconcile/configmap.go new file mode 100644 index 0000000000..44e03cd301 --- /dev/null +++ b/pkg/loadbalancer/reconcile/configmap.go @@ -0,0 +1,192 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete + +// ConfigMaps reconciles the config map(s) required for the instance in the current context. +func ConfigMaps(ctx context.Context, params Params) error { + desired := []corev1.ConfigMap{} + cm, notify, err := desiredConfigMap(ctx, params) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } + if notify == "" { + desired = append(desired, cm) + } + + // first, handle the create/update parts + if err := expectedConfigMaps(ctx, params, desired, true); err != nil { + return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) + } + + // then, delete the extra objects + if err := deleteConfigMaps(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) + } + + return nil +} + +func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, string, error) { + name := naming.LBConfigMap(params.Instance) + labels := loadbalancer.Labels(params.Instance) + labels["app.kubernetes.io/name"] = name + + config, err := adapters.ConfigFromString(params.Instance.Spec.Config) + if err != nil { + return corev1.ConfigMap{}, "", err + } + + promConfig, notify := lbadapters.ConfigToPromConfig(config) + if notify != "" { + return corev1.ConfigMap{}, notify, nil + } + + lbConfig := make(map[interface{}]interface{}) + lbConfig["mode"] = params.Instance.Spec.LoadBalancer.Mode + lbConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + lbConfig["config"] = promConfig + lbConfigYAML, _ := yaml.Marshal(lbConfig) + + return corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + Annotations: params.Instance.Annotations, + }, + Data: map[string]string{ + "loadbalancer.yaml": string(lbConfigYAML), + }, + }, "", nil +} + +func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &corev1.ConfigMap{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && errors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + if errors.IsAlreadyExists(err) && retry { + // let's try again? we probably had multiple updates at one, and now it exists already + if err := expectedConfigMaps(ctx, params, expected, false); err != nil { + // somethin else happened now... + return err + } + + // we succeeded in the retry, exit this attempt + return nil + } + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Annotations == nil { + updated.Annotations = map[string]string{} + } + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + + updated.Data = desired.Data + updated.BinaryData = desired.BinaryData + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Annotations { + updated.ObjectMeta.Annotations[k] = v + } + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + + params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) + } + + return nil +} + +func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &corev1.ConfigMapList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "configmap.name", existing.Name, "configmap.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/loadbalancer/reconcile/configmap_test.go b/pkg/loadbalancer/reconcile/configmap_test.go new file mode 100644 index 0000000000..6004e9d4cf --- /dev/null +++ b/pkg/loadbalancer/reconcile/configmap_test.go @@ -0,0 +1,181 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestDesiredConfigMap(t *testing.T) { + t.Run("should return expected config map", func(t *testing.T) { + expectedLables := map[string]string{ + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/component": "opentelemetry-loadbalancer", + "app.kubernetes.io/name": "test-loadbalancer", + } + + expectedData := map[string]string{ + "loadbalancer.yaml": `config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: + - 0.0.0.0:8888 + - 0.0.0.0:9999 +label_selector: + app.kubernetes.io/instance: default.test + app.kubernetes.io/managed-by: opentelemetry-operator +mode: LeastConnection +`, + } + + actual, notify, err := desiredConfigMap(context.Background(), params()) + assert.NoError(t, err) + assert.Equal(t, notify, "") + + assert.Equal(t, "test-loadbalancer", actual.Name) + assert.Equal(t, expectedLables, actual.Labels) + assert.Equal(t, expectedData, actual.Data) + + }) + +} + +func TestExpectedConfigMap(t *testing.T) { + param := params() + t.Run("should create config map", func(t *testing.T) { + configMap, notify, err := desiredConfigMap(context.Background(), param) + assert.NoError(t, err) + assert.Equal(t, notify, "") + err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + }) + + t.Run("should update config map", func(t *testing.T) { + + newParam := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Mode: "LeastConnection", + }, + Config: "", + }, + }, + Scheme: testScheme, + Log: logger, + } + cm, notify, err := desiredConfigMap(context.Background(), newParam) + assert.NoError(t, err) + assert.Equal(t, notify, "no receivers available as part of the configuration") + createObjectIfNotExists(t, "test-loadbalancer", &cm) + + configMap, notify, err := desiredConfigMap(context.Background(), param) + assert.NoError(t, err) + assert.Equal(t, notify, "") + err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) + assert.NoError(t, err) + + actual := v1.ConfigMap{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + + config, err := adapters.ConfigFromString(param.Instance.Spec.Config) + assert.NoError(t, err) + + parmConfig, notify := lbadapters.ConfigToPromConfig(config) + assert.Equal(t, notify, "") + + lbConfig := make(map[interface{}]interface{}) + lbConfig["mode"] = "LeastConnection" + lbConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + lbConfig["config"] = parmConfig + lbConfigYAML, _ := yaml.Marshal(lbConfig) + + assert.Equal(t, string(lbConfigYAML), actual.Data["loadbalancer.yaml"]) + }) + + t.Run("should delete config map", func(t *testing.T) { + + deletecm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-delete-loadbalancer", + Namespace: "default", + Labels: map[string]string{ + "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }, + }, + } + createObjectIfNotExists(t, "test-delete-loadbalancer", &deletecm) + + exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + assert.True(t, exists) + + configMap, notify, err := desiredConfigMap(context.Background(), param) + assert.NoError(t, err) + assert.Equal(t, notify, "") + err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) + assert.NoError(t, err) + + exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + assert.False(t, exists) + }) +} diff --git a/pkg/loadbalancer/reconcile/deployment.go b/pkg/loadbalancer/reconcile/deployment.go new file mode 100644 index 0000000000..85f5ed68ba --- /dev/null +++ b/pkg/loadbalancer/reconcile/deployment.go @@ -0,0 +1,147 @@ +// 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 reconcile + +import ( + "context" + "fmt" + "reflect" + + appsv1 "k8s.io/api/apps/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" +) + +// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete + +// Deployments reconciles the deployment(s) required for the instance in the current context. +func Deployments(ctx context.Context, params Params) error { + desired := []appsv1.Deployment{} + config, err := adapters.ConfigFromString(params.Instance.Spec.Config) + if err != nil { + return fmt.Errorf("failed to parse the config: %v", err) + } + + // Notify returns an empty string if there is a valid Prometheus configuration + _, notify := lbadapters.ConfigToPromConfig(config) + + if params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && len(params.Instance.Spec.LoadBalancer.Mode) > 0 && notify == "" { + desired = append(desired, loadbalancer.Deployment(params.Config, params.Log, params.Instance)) + } + + // first, handle the create/update parts + if err := expectedDeployments(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected deployments: %v", err) + } + + // then, delete the extra objects + if err := deleteDeployments(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) + } + + return nil +} + +func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &appsv1.Deployment{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } else if !deploymentImageChanged(&desired, existing) { + continue + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + + updated.Spec = desired.Spec + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + + params.Log.V(2).Info("applied", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) + } + + return nil +} + +func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &appsv1.DeploymentList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "deployment.name", existing.Name, "deployment.namespace", existing.Namespace) + } + } + + return nil +} + +func deploymentImageChanged(desired *appsv1.Deployment, actual *appsv1.Deployment) bool { + return !reflect.DeepEqual(desired.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) +} diff --git a/pkg/loadbalancer/reconcile/deployment_test.go b/pkg/loadbalancer/reconcile/deployment_test.go new file mode 100644 index 0000000000..6dec0b1727 --- /dev/null +++ b/pkg/loadbalancer/reconcile/deployment_test.go @@ -0,0 +1,292 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestExpectedDeployments(t *testing.T) { + param := params() + expectedDeploy := loadbalancer.Deployment(param.Config, logger, param.Instance) + + t.Run("should create deployment", func(t *testing.T) { + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + + t.Run("should not create deployment when otel collector mode is not StatefulSet", func(t *testing.T) { + modes := []v1alpha1.Mode{v1alpha1.ModeDaemonSet, v1alpha1.ModeDeployment, v1alpha1.ModeSidecar} + + for _, mode := range modes { + newParam := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: mode, + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Mode: "LeastConnection", + }, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + + `, + }, + }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + if newParam.Instance.Spec.Mode == v1alpha1.ModeStatefulSet { + expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + } + + assert.Len(t, expected, 0) + } + }) + + t.Run("should not create deployment when loadbalancer mode is not set", func(t *testing.T) { + newParam := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + + `, + }, + }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + if len(newParam.Instance.Spec.LoadBalancer.Mode) > 0 { + expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + } + + assert.Len(t, expected, 0) + }) + + t.Run("should not create deployment when there is no valid Prometheus config", func(t *testing.T) { + newParam := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Config: "", + }, + }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + config, err := adapters.ConfigFromString(newParam.Instance.Spec.Config) + assert.NoError(t, err) + + _, notify := lbadapters.ConfigToPromConfig(config) + if notify == "" { + expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + } + + assert.Len(t, expected, 0) + }) + + t.Run("should not update deployment container when the config is updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-loadbalancer", &expectedDeploy) + orgUID := expectedDeploy.OwnerReferences[0].UID + + updatedDeploy := loadbalancer.Deployment(newParams().Config, logger, param.Instance) + + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.Equal(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + + t.Run("should update deployment container when the container image is updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-loadbalancer", &expectedDeploy) + orgUID := expectedDeploy.OwnerReferences[0].UID + + updatedParam := newParams("test/test-img") + updatedDeploy := loadbalancer.Deployment(updatedParam.Config, logger, updatedParam.Instance) + + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.NotEqual(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + + t.Run("should delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Labels = labels + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, + } + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.False(t, exists) + + }) + + t.Run("should not delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, + } + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.True(t, exists) + + }) +} diff --git a/pkg/loadbalancer/reconcile/params.go b/pkg/loadbalancer/reconcile/params.go new file mode 100644 index 0000000000..e9e6fa3dbf --- /dev/null +++ b/pkg/loadbalancer/reconcile/params.go @@ -0,0 +1,33 @@ +// 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 reconcile + +import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +// Params holds the reconciliation-specific parameters. +type Params struct { + Config config.Config + Client client.Client + Instance v1alpha1.OpenTelemetryCollector + Log logr.Logger + Scheme *runtime.Scheme +} diff --git a/pkg/loadbalancer/reconcile/service.go b/pkg/loadbalancer/reconcile/service.go new file mode 100644 index 0000000000..dcd3236f7e --- /dev/null +++ b/pkg/loadbalancer/reconcile/service.go @@ -0,0 +1,154 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete + +// Services reconciles the service(s) required for the instance in the current context. +func Services(ctx context.Context, params Params) error { + desired := []corev1.Service{ + desiredService(ctx, params), + } + + // first, handle the create/update parts + if err := expectedServices(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected services: %v", err) + } + + // then, delete the extra objects + if err := deleteServices(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + } + + return nil +} + +func desiredService(ctx context.Context, params Params) corev1.Service { + labels := loadbalancer.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.LBService(params.Instance) + + selector := loadbalancer.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.LoadBalancer(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.LBService(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "loadbalancing", + Port: 80, + TargetPort: intstr.FromInt(80), + }}, + }, + } +} + +func expectedServices(ctx context.Context, params Params, expected []corev1.Service) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &corev1.Service{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "service.name", desired.Name, "service.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + updated.Spec.Ports = desired.Spec.Ports + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + + params.Log.V(2).Info("applied", "service.name", desired.Name, "service.namespace", desired.Namespace) + } + + return nil +} + +func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &corev1.ServiceList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "service.name", existing.Name, "service.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/loadbalancer/reconcile/service_test.go b/pkg/loadbalancer/reconcile/service_test.go new file mode 100644 index 0000000000..db8edf69c1 --- /dev/null +++ b/pkg/loadbalancer/reconcile/service_test.go @@ -0,0 +1,100 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +func TestDesiredService(t *testing.T) { + t.Run("should return service with default port", func(t *testing.T) { + expected := service("test-loadbalancer") + actual := desiredService(context.Background(), params()) + + assert.Equal(t, expected, actual) + }) + +} + +func TestExpectedServices(t *testing.T) { + t.Run("should create the service", func(t *testing.T) { + err := expectedServices(context.Background(), params(), []corev1.Service{service("loadbalancer")}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "loadbalancer"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) +} + +func TestDeleteServices(t *testing.T) { + t.Run("should delete excess services", func(t *testing.T) { + deleteService := service("test-delete-loadbalancer", 8888) + createObjectIfNotExists(t, "test-delete-loadbalancer", &deleteService) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + assert.NoError(t, err) + assert.True(t, exists) + + err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(context.Background(), params())}) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func service(name string, portOpt ...int32) corev1.Service { + port := int32(80) + if len(portOpt) > 0 { + port = portOpt[0] + } + params := params() + labels := loadbalancer.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.LBService(params.Instance) + + selector := loadbalancer.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.LoadBalancer(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "loadbalancing", + Port: port, + TargetPort: intstr.FromInt(80), + }}, + }, + } +} diff --git a/pkg/loadbalancer/reconcile/suite_test.go b/pkg/loadbalancer/reconcile/suite_test.go new file mode 100644 index 0000000000..a9a104d2d8 --- /dev/null +++ b/pkg/loadbalancer/reconcile/suite_test.go @@ -0,0 +1,196 @@ +// 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 reconcile + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "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/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +var k8sClient client.Client +var testEnv *envtest.Environment +var testScheme *runtime.Scheme = scheme.Scheme +var logger = logf.Log.WithName("unit-tests") + +var instanceUID = uuid.NewUUID() + +func TestMain(m *testing.M) { + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + } + + cfg, err := testEnv.Start() + if err != nil { + fmt.Printf("failed to start testEnv: %v", err) + os.Exit(1) + } + + if err := v1alpha1.AddToScheme(testScheme); err != nil { + fmt.Printf("failed to register scheme: %v", err) + os.Exit(1) + } + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) + if err != nil { + fmt.Printf("failed to setup a Kubernetes client: %v", err) + os.Exit(1) + } + + code := m.Run() + + err = testEnv.Stop() + if err != nil { + fmt.Printf("failed to stop testEnv: %v", err) + os.Exit(1) + } + + os.Exit(code) +} + +func params() Params { + replicas := int32(1) + configYAML, err := ioutil.ReadFile("suite_test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } + return Params{ + Config: config.New(), + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Mode: "LeastConnection", + }, + Replicas: &replicas, + Config: string(configYAML), + }, + }, + Scheme: testScheme, + Log: logger, + } +} + +func newParams(containerImage ...string) Params { + replicas := int32(1) + configYAML, err := ioutil.ReadFile("suite_test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } + + cfg := config.New() + defaultContainerImage := cfg.LoadBalancerImage() + if len(containerImage) > 0 && len(containerImage[0]) > 0 { + defaultContainerImage = containerImage[0] + } + + return Params{ + Config: cfg, + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ + Mode: "LeastConnections", + Image: defaultContainerImage, + }, + Replicas: &replicas, + Config: string(configYAML), + }, + }, + Scheme: testScheme, + Log: logger, + } +} + +func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { + tb.Helper() + err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) + if errors.IsNotFound(err) { + err := k8sClient.Create(context.Background(), + object) + assert.NoError(tb, err) + } +} + +func populateObjectIfExists(t testing.TB, object client.Object, namespacedName types.NamespacedName) (bool, error) { + t.Helper() + err := k8sClient.Get(context.Background(), namespacedName, object) + if errors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil + +} diff --git a/pkg/loadbalancer/reconcile/suite_test.yaml b/pkg/loadbalancer/reconcile/suite_test.yaml new file mode 100644 index 0000000000..63e354e574 --- /dev/null +++ b/pkg/loadbalancer/reconcile/suite_test.yaml @@ -0,0 +1,19 @@ +processors: +receivers: + prometheus: + config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888', '0.0.0.0:9999' ] + +exporters: + logging: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] \ No newline at end of file diff --git a/pkg/naming/main.go b/pkg/naming/main.go index cf18051a48..ac933c9e2d 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -26,21 +26,41 @@ func ConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// LBConfigMap returns the name for the config map used in the LoadBalancer. +func LBConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +} + // ConfigMapVolume returns the name to use for the config map's volume in the pod. func ConfigMapVolume() string { return "otc-internal" } +// LBConfigMapVolume returns the name to use for the config map's volume in the LoadBalancer pod. +func LBConfigMapVolume() string { + return "lb-internal" +} + // Container returns the name to use for the container in the pod. func Container() string { return "otc-container" } +// LBContainer returns the name to use for the container in the LoadBalancer pod. +func LBContainer() string { + return "lb-container" +} + // Collector builds the collector (deployment/daemonset) name based on the instance. func Collector(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// LoadBalancer returns the LoadBalancer deployment resource name. +func LoadBalancer(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +} + // HeadlessService builds the name for the headless service based on the instance. func HeadlessService(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-headless", Service(otelcol)) @@ -56,6 +76,11 @@ func Service(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// LBService returns the name to use for the LoadBalancer service +func LBService(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +} + // ServiceAccount builds the service account name based on the instance. func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) diff --git a/tests/e2e/smoke-loadbalancer/00-assert.yaml b/tests/e2e/smoke-loadbalancer/00-assert.yaml new file mode 100644 index 0000000000..ade50d8607 --- /dev/null +++ b/tests/e2e/smoke-loadbalancer/00-assert.yaml @@ -0,0 +1,8 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-loadbalancer +status: + replicas: 1 + readyReplicas: 1 + \ No newline at end of file diff --git a/tests/e2e/smoke-loadbalancer/00-install.yaml b/tests/e2e/smoke-loadbalancer/00-install.yaml new file mode 100644 index 0000000000..b05c1aa928 --- /dev/null +++ b/tests/e2e/smoke-loadbalancer/00-install.yaml @@ -0,0 +1,28 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + loadbalancer: + mode: LeastConnection + config: | + receivers: + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] From 6f81007dddee76bc7ac0fe04056e20441a18fbed Mon Sep 17 00:00:00 2001 From: Alexis Perez Date: Thu, 8 Jul 2021 18:08:35 -0400 Subject: [PATCH 03/30] Reset last commit and only uploading changes to main.go --- main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.go b/main.go index 0e2aa7ee9b..b90da693b5 100644 --- a/main.go +++ b/main.go @@ -155,6 +155,16 @@ func main() { os.Exit(1) } + if err = controllers.NewLbReconciler(controllers.LbParams{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("LoadBalancer"), + Scheme: mgr.GetScheme(), + Config: cfg, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "LoadBalancer") + os.Exit(1) + } + if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = (&opentelemetryiov1alpha1.OpenTelemetryCollector{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "OpenTelemetryCollector") From 31f97fe335f82bc27f2d082511d8a55257ef4329 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Thu, 8 Jul 2021 15:45:58 -0700 Subject: [PATCH 04/30] Updated bundle and api with new autogenerated resources --- api/v1alpha1/zz_generated.deepcopy.go | 16 ++++++++++++++++ ...opentelemetry.io_opentelemetrycollectors.yaml | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3a2e11fa66..bfaf26e3db 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -97,6 +97,7 @@ func (in *OpenTelemetryCollectorSpec) DeepCopyInto(out *OpenTelemetryCollectorSp *out = new(int32) **out = **in } + out.LoadBalancer = in.LoadBalancer if in.SecurityContext != nil { in, out := &in.SecurityContext, &out.SecurityContext *out = new(v1.SecurityContext) @@ -176,3 +177,18 @@ func (in *OpenTelemetryCollectorStatus) DeepCopy() *OpenTelemetryCollectorStatus in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenTelemetryLoadBalancer) DeepCopyInto(out *OpenTelemetryLoadBalancer) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryLoadBalancer. +func (in *OpenTelemetryLoadBalancer) DeepCopy() *OpenTelemetryLoadBalancer { + if in == nil { + return nil + } + out := new(OpenTelemetryLoadBalancer) + in.DeepCopyInto(out) + return out +} diff --git a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml index 414326175f..ea41dbeae9 100644 --- a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -184,6 +184,19 @@ spec: description: Image indicates the container image to use for the OpenTelemetry Collector. type: string + loadbalancer: + description: LoadBalancer indicates a value which determines whether + to spawn a LoadBalancer resource or not. + properties: + image: + description: Image indicates the container image to use for the + OpenTelemetry LoadBalancer. + type: string + mode: + description: Use indicates the option to use the loadbalancer + option or not. + type: string + type: object mode: description: Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) From 47c5fe8dccbd1136d8d22f22860994df7a37093f Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 9 Jul 2021 11:31:56 -0700 Subject: [PATCH 05/30] Updated code with lint fixes --- controllers/loadbalancer_controller.go | 7 ++++--- internal/config/main.go | 2 +- pkg/loadbalancer/adapters/config_to_promConfig_test.go | 3 --- pkg/loadbalancer/reconcile/configmap_test.go | 7 ++++--- pkg/loadbalancer/reconcile/deployment_test.go | 9 +++++---- pkg/loadbalancer/reconcile/service.go | 4 ++-- pkg/loadbalancer/reconcile/service_test.go | 4 ++-- pkg/naming/main.go | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/controllers/loadbalancer_controller.go b/controllers/loadbalancer_controller.go index 852cfec122..d948b6a593 100644 --- a/controllers/loadbalancer_controller.go +++ b/controllers/loadbalancer_controller.go @@ -22,14 +22,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "github.com/go-logr/logr" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" ) // OpenTelemetryLoadbalancerReconciler reconciles a OpenTelemetryLoadbalancer object. diff --git a/internal/config/main.go b/internal/config/main.go index aeba8abf67..cc60a345d2 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -75,7 +75,7 @@ func New(opts ...Option) Config { } if len(o.loadbalancerImage) == 0 { - o.loadbalancerImage = fmt.Sprintf("raul9595/otel-loadbalancer") + o.loadbalancerImage = "raul9595/otel-loadbalancer" } return Config{ diff --git a/pkg/loadbalancer/adapters/config_to_promConfig_test.go b/pkg/loadbalancer/adapters/config_to_promConfig_test.go index 41745d80ff..55af5c369a 100644 --- a/pkg/loadbalancer/adapters/config_to_promConfig_test.go +++ b/pkg/loadbalancer/adapters/config_to_promConfig_test.go @@ -20,14 +20,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" ) -var logger = logf.Log.WithName("unit-tests") - func TestExtractPromConfigFromConfig(t *testing.T) { configStr := `receivers: examplereceiver: diff --git a/pkg/loadbalancer/reconcile/configmap_test.go b/pkg/loadbalancer/reconcile/configmap_test.go index 6004e9d4cf..3373f149f5 100644 --- a/pkg/loadbalancer/reconcile/configmap_test.go +++ b/pkg/loadbalancer/reconcile/configmap_test.go @@ -18,15 +18,16 @@ import ( "context" "testing" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" ) func TestDesiredConfigMap(t *testing.T) { diff --git a/pkg/loadbalancer/reconcile/deployment_test.go b/pkg/loadbalancer/reconcile/deployment_test.go index 6dec0b1727..597a728d5a 100644 --- a/pkg/loadbalancer/reconcile/deployment_test.go +++ b/pkg/loadbalancer/reconcile/deployment_test.go @@ -18,15 +18,16 @@ import ( "context" "testing" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" "github.com/stretchr/testify/assert" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" ) func TestExpectedDeployments(t *testing.T) { diff --git a/pkg/loadbalancer/reconcile/service.go b/pkg/loadbalancer/reconcile/service.go index dcd3236f7e..88a35bec45 100644 --- a/pkg/loadbalancer/reconcile/service.go +++ b/pkg/loadbalancer/reconcile/service.go @@ -35,7 +35,7 @@ import ( // Services reconciles the service(s) required for the instance in the current context. func Services(ctx context.Context, params Params) error { desired := []corev1.Service{ - desiredService(ctx, params), + desiredService(params), } // first, handle the create/update parts @@ -51,7 +51,7 @@ func Services(ctx context.Context, params Params) error { return nil } -func desiredService(ctx context.Context, params Params) corev1.Service { +func desiredService(params Params) corev1.Service { labels := loadbalancer.Labels(params.Instance) labels["app.kubernetes.io/name"] = naming.LBService(params.Instance) diff --git a/pkg/loadbalancer/reconcile/service_test.go b/pkg/loadbalancer/reconcile/service_test.go index db8edf69c1..13d21b59f8 100644 --- a/pkg/loadbalancer/reconcile/service_test.go +++ b/pkg/loadbalancer/reconcile/service_test.go @@ -31,7 +31,7 @@ import ( func TestDesiredService(t *testing.T) { t.Run("should return service with default port", func(t *testing.T) { expected := service("test-loadbalancer") - actual := desiredService(context.Background(), params()) + actual := desiredService(params()) assert.Equal(t, expected, actual) }) @@ -60,7 +60,7 @@ func TestDeleteServices(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(context.Background(), params())}) + err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(params())}) assert.NoError(t, err) exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) diff --git a/pkg/naming/main.go b/pkg/naming/main.go index ac933c9e2d..06a20a9656 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -76,7 +76,7 @@ func Service(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } -// LBService returns the name to use for the LoadBalancer service +// LBService returns the name to use for the LoadBalancer service. func LBService(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-loadbalancer", otelcol.Name) } From 8ef4cdec974c5a7c806d4d44e7af9c11d3ca56b1 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Mon, 12 Jul 2021 16:34:45 -0700 Subject: [PATCH 06/30] Updated code to include minor fixes --- api/v1alpha1/mode.go | 7 +++++ api/v1alpha1/opentelemetrycollector_types.go | 2 +- controllers/loadbalancer_controller_test.go | 4 +-- .../adapters/config_to_promConfig.go | 24 +++++++-------- .../adapters/config_to_promConfig_test.go | 9 +++--- pkg/loadbalancer/labels.go | 2 +- pkg/loadbalancer/labels_test.go | 2 +- pkg/loadbalancer/reconcile/configmap.go | 22 +++++--------- pkg/loadbalancer/reconcile/configmap_test.go | 29 ++++++++----------- pkg/loadbalancer/reconcile/deployment.go | 14 +++------ pkg/loadbalancer/reconcile/deployment_test.go | 13 +++------ pkg/loadbalancer/reconcile/helper.go | 29 +++++++++++++++++++ pkg/loadbalancer/reconcile/service.go | 12 ++++++-- pkg/loadbalancer/reconcile/suite_test.go | 4 +-- pkg/loadbalancer/reconcile/suite_test.yaml | 2 +- 15 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 pkg/loadbalancer/reconcile/helper.go diff --git a/api/v1alpha1/mode.go b/api/v1alpha1/mode.go index 37747e73f8..694063ad24 100644 --- a/api/v1alpha1/mode.go +++ b/api/v1alpha1/mode.go @@ -18,6 +18,8 @@ type ( // Mode represents how the collector should be deployed (deployment vs. daemonset) // +kubebuilder:validation:Enum=daemonset;deployment;sidecar;statefulset Mode string + // LbMode represents the algorithms supported by the default loadbalancer implementation for target sharding. + LbMode string ) const ( @@ -33,3 +35,8 @@ const ( // ModeStatefulSet specifies that the collector should be deployed as a Kubernetes StatefulSet. ModeStatefulSet Mode = "statefulset" ) + +const ( + // ModeLeastConnection specifies that the loadbalancer should use the LeastConnection algorithm for target sharding. + ModeLeastConnection LbMode = "LeastConnection" +) diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index c279b76d1a..a6f9c1d46d 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -125,7 +125,7 @@ type OpenTelemetryCollectorStatus struct { type OpenTelemetryLoadBalancer struct { // Use indicates the option to use the loadbalancer option or not. // +optional - Mode string `json:"mode,omitempty"` + Mode LbMode `json:"mode,omitempty"` // Image indicates the container image to use for the OpenTelemetry LoadBalancer. // +optional diff --git a/controllers/loadbalancer_controller_test.go b/controllers/loadbalancer_controller_test.go index 375165e0d9..99c8c4ece1 100644 --- a/controllers/loadbalancer_controller_test.go +++ b/controllers/loadbalancer_controller_test.go @@ -63,7 +63,7 @@ func TestNewObjectsOnLbReconciliation(t *testing.T) { Mode: v1alpha1.ModeStatefulSet, Config: string(configYAML), LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: "LeastConnection", + Mode: v1alpha1.ModeLeastConnection, }, }, } @@ -83,7 +83,7 @@ func TestNewObjectsOnLbReconciliation(t *testing.T) { opts := []client.ListOption{ client.InNamespace(nsn.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Name, "loadbalancer"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/adapters/config_to_promConfig.go b/pkg/loadbalancer/adapters/config_to_promConfig.go index aee0ef6d26..eb34f2dfc8 100644 --- a/pkg/loadbalancer/adapters/config_to_promConfig.go +++ b/pkg/loadbalancer/adapters/config_to_promConfig.go @@ -18,45 +18,45 @@ import ( "fmt" ) -func ErrorNoComponent(component string) string { - return fmt.Sprintf("no %s available as part of the configuration", component) +func errorNoComponent(component string) error { + return fmt.Errorf("no %s available as part of the configuration", component) } -func ErrorNotAMap(component string) string { - return fmt.Sprintf("%s property in the configuration doesn't contain valid %s", component, component) +func errorNotAMap(component string) error { + return fmt.Errorf("%s property in the configuration doesn't contain valid %s", component, component) } // ConfigToPromConfig converts the incoming configuration object into a the Prometheus receiver config. -func ConfigToPromConfig(config map[interface{}]interface{}) (map[interface{}]interface{}, string) { +func ConfigToPromConfig(config map[interface{}]interface{}) (map[interface{}]interface{}, error) { receiversProperty, ok := config["receivers"] if !ok { - return nil, ErrorNoComponent("receivers") + return nil, errorNoComponent("receivers") } receivers, ok := receiversProperty.(map[interface{}]interface{}) if !ok { - return nil, ErrorNotAMap("receivers") + return nil, errorNotAMap("receivers") } prometheusProperty, ok := receivers["prometheus"] if !ok { - return nil, ErrorNoComponent("prometheus") + return nil, errorNoComponent("prometheus") } prometheus, ok := prometheusProperty.(map[interface{}]interface{}) if !ok { - return nil, ErrorNotAMap("prometheus") + return nil, errorNotAMap("prometheus") } prometheusConfigProperty, ok := prometheus["config"] if !ok { - return nil, ErrorNoComponent("prometheusConfig") + return nil, errorNoComponent("prometheusConfig") } prometheusConfig, ok := prometheusConfigProperty.(map[interface{}]interface{}) if !ok { - return nil, ErrorNotAMap("prometheusConfig") + return nil, errorNotAMap("prometheusConfig") } - return prometheusConfig, "" + return prometheusConfig, nil } diff --git a/pkg/loadbalancer/adapters/config_to_promConfig_test.go b/pkg/loadbalancer/adapters/config_to_promConfig_test.go index 55af5c369a..17b39cdab5 100644 --- a/pkg/loadbalancer/adapters/config_to_promConfig_test.go +++ b/pkg/loadbalancer/adapters/config_to_promConfig_test.go @@ -15,6 +15,7 @@ package adapters_test import ( + "fmt" "reflect" "testing" @@ -54,8 +55,8 @@ func TestExtractPromConfigFromConfig(t *testing.T) { require.NotEmpty(t, config) // test - promConfig, notify := lbadapters.ConfigToPromConfig(config) - assert.Equal(t, notify, "") + promConfig, err := lbadapters.ConfigToPromConfig(config) + assert.NoError(t, err) // verify assert.Equal(t, expectedData, promConfig) @@ -81,8 +82,8 @@ func TestExtractPromConfigFromNullConfig(t *testing.T) { require.NotEmpty(t, config) // test - promConfig, notify := lbadapters.ConfigToPromConfig(config) - assert.Equal(t, notify, lbadapters.ErrorNotAMap("prometheusConfig")) + promConfig, err := lbadapters.ConfigToPromConfig(config) + assert.Equal(t, err, fmt.Errorf("%s property in the configuration doesn't contain valid %s", "prometheusConfig", "prometheusConfig")) // verify assert.True(t, reflect.ValueOf(promConfig).IsNil()) diff --git a/pkg/loadbalancer/labels.go b/pkg/loadbalancer/labels.go index 44e61ba876..f14c5854b4 100644 --- a/pkg/loadbalancer/labels.go +++ b/pkg/loadbalancer/labels.go @@ -31,7 +31,7 @@ func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { } base["app.kubernetes.io/managed-by"] = "opentelemetry-operator" - base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, "loadbalancer") + base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Name, "loadbalancer") base["app.kubernetes.io/part-of"] = "opentelemetry" base["app.kubernetes.io/component"] = "opentelemetry-loadbalancer" diff --git a/pkg/loadbalancer/labels_test.go b/pkg/loadbalancer/labels_test.go index 3bbc14b272..2ad9c32d1c 100644 --- a/pkg/loadbalancer/labels_test.go +++ b/pkg/loadbalancer/labels_test.go @@ -36,7 +36,7 @@ func TestLabelsCommonSet(t *testing.T) { // test labels := Labels(otelcol) assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) - assert.Equal(t, "my-ns.loadbalancer", labels["app.kubernetes.io/instance"]) + assert.Equal(t, "my-instance.loadbalancer", labels["app.kubernetes.io/instance"]) assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) assert.Equal(t, "opentelemetry-loadbalancer", labels["app.kubernetes.io/component"]) } diff --git a/pkg/loadbalancer/reconcile/configmap.go b/pkg/loadbalancer/reconcile/configmap.go index 44e03cd301..9628705c98 100644 --- a/pkg/loadbalancer/reconcile/configmap.go +++ b/pkg/loadbalancer/reconcile/configmap.go @@ -26,9 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" ) @@ -37,11 +35,12 @@ import ( // ConfigMaps reconciles the config map(s) required for the instance in the current context. func ConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - cm, notify, err := desiredConfigMap(ctx, params) + cm, err := desiredConfigMap(ctx, params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } - if notify == "" { + + if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { desired = append(desired, cm) } @@ -58,19 +57,14 @@ func ConfigMaps(ctx context.Context, params Params) error { return nil } -func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, string, error) { +func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { name := naming.LBConfigMap(params.Instance) labels := loadbalancer.Labels(params.Instance) labels["app.kubernetes.io/name"] = name - config, err := adapters.ConfigFromString(params.Instance.Spec.Config) + promConfig, err := checkConfig(params) if err != nil { - return corev1.ConfigMap{}, "", err - } - - promConfig, notify := lbadapters.ConfigToPromConfig(config) - if notify != "" { - return corev1.ConfigMap{}, notify, nil + return corev1.ConfigMap{}, err } lbConfig := make(map[interface{}]interface{}) @@ -92,7 +86,7 @@ func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, strin Data: map[string]string{ "loadbalancer.yaml": string(lbConfigYAML), }, - }, "", nil + }, nil } func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { @@ -162,7 +156,7 @@ func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.Conf opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/configmap_test.go b/pkg/loadbalancer/reconcile/configmap_test.go index 3373f149f5..aea43a4ade 100644 --- a/pkg/loadbalancer/reconcile/configmap_test.go +++ b/pkg/loadbalancer/reconcile/configmap_test.go @@ -34,7 +34,7 @@ func TestDesiredConfigMap(t *testing.T) { t.Run("should return expected config map", func(t *testing.T) { expectedLables := map[string]string{ "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/instance": "test.loadbalancer", "app.kubernetes.io/part-of": "opentelemetry", "app.kubernetes.io/component": "opentelemetry-loadbalancer", "app.kubernetes.io/name": "test-loadbalancer", @@ -56,9 +56,8 @@ mode: LeastConnection `, } - actual, notify, err := desiredConfigMap(context.Background(), params()) + actual, err := desiredConfigMap(context.Background(), params()) assert.NoError(t, err) - assert.Equal(t, notify, "") assert.Equal(t, "test-loadbalancer", actual.Name) assert.Equal(t, expectedLables, actual.Labels) @@ -71,9 +70,8 @@ mode: LeastConnection func TestExpectedConfigMap(t *testing.T) { param := params() t.Run("should create config map", func(t *testing.T) { - configMap, notify, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) - assert.Equal(t, notify, "") err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -109,7 +107,7 @@ func TestExpectedConfigMap(t *testing.T) { NodePort: 0, }}, LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: "LeastConnection", + Mode: v1alpha1.ModeLeastConnection, }, Config: "", }, @@ -117,14 +115,12 @@ func TestExpectedConfigMap(t *testing.T) { Scheme: testScheme, Log: logger, } - cm, notify, err := desiredConfigMap(context.Background(), newParam) - assert.NoError(t, err) - assert.Equal(t, notify, "no receivers available as part of the configuration") + cm, err := desiredConfigMap(context.Background(), newParam) + assert.EqualError(t, err, "no receivers available as part of the configuration") createObjectIfNotExists(t, "test-loadbalancer", &cm) - configMap, notify, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) - assert.Equal(t, notify, "") err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -138,11 +134,11 @@ func TestExpectedConfigMap(t *testing.T) { config, err := adapters.ConfigFromString(param.Instance.Spec.Config) assert.NoError(t, err) - parmConfig, notify := lbadapters.ConfigToPromConfig(config) - assert.Equal(t, notify, "") + parmConfig, err := lbadapters.ConfigToPromConfig(config) + assert.NoError(t, err) lbConfig := make(map[interface{}]interface{}) - lbConfig["mode"] = "LeastConnection" + lbConfig["mode"] = string(v1alpha1.ModeLeastConnection) lbConfig["label_selector"] = map[string]string{ "app.kubernetes.io/instance": "default.test", "app.kubernetes.io/managed-by": "opentelemetry-operator", @@ -160,7 +156,7 @@ func TestExpectedConfigMap(t *testing.T) { Name: "test-delete-loadbalancer", Namespace: "default", Labels: map[string]string{ - "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/instance": "test.loadbalancer", "app.kubernetes.io/managed-by": "opentelemetry-operator", }, }, @@ -170,9 +166,8 @@ func TestExpectedConfigMap(t *testing.T) { exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) assert.True(t, exists) - configMap, notify, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) - assert.Equal(t, notify, "") err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) assert.NoError(t, err) diff --git a/pkg/loadbalancer/reconcile/deployment.go b/pkg/loadbalancer/reconcile/deployment.go index 85f5ed68ba..77365243d1 100644 --- a/pkg/loadbalancer/reconcile/deployment.go +++ b/pkg/loadbalancer/reconcile/deployment.go @@ -25,10 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete @@ -36,15 +33,12 @@ import ( // Deployments reconciles the deployment(s) required for the instance in the current context. func Deployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - config, err := adapters.ConfigFromString(params.Instance.Spec.Config) + _, err := checkConfig(params) if err != nil { - return fmt.Errorf("failed to parse the config: %v", err) + return fmt.Errorf("failed to parse Promtheus config: %v", err) } - // Notify returns an empty string if there is a valid Prometheus configuration - _, notify := lbadapters.ConfigToPromConfig(config) - - if params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && len(params.Instance.Spec.LoadBalancer.Mode) > 0 && notify == "" { + if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { desired = append(desired, loadbalancer.Deployment(params.Config, params.Log, params.Instance)) } @@ -113,7 +107,7 @@ func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Dep opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/deployment_test.go b/pkg/loadbalancer/reconcile/deployment_test.go index 597a728d5a..cac48b70bb 100644 --- a/pkg/loadbalancer/reconcile/deployment_test.go +++ b/pkg/loadbalancer/reconcile/deployment_test.go @@ -64,7 +64,7 @@ func TestExpectedDeployments(t *testing.T) { Spec: v1alpha1.OpenTelemetryCollectorSpec{ Mode: mode, LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: "LeastConnection", + Mode: v1alpha1.ModeLeastConnection, }, Config: ` receivers: @@ -165,16 +165,11 @@ func TestExpectedDeployments(t *testing.T) { Scheme: testScheme, Log: logger, } - expected := []v1.Deployment{} config, err := adapters.ConfigFromString(newParam.Instance.Spec.Config) assert.NoError(t, err) - _, notify := lbadapters.ConfigToPromConfig(config) - if notify == "" { - expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) - } - - assert.Len(t, expected, 0) + _, err = lbadapters.ConfigToPromConfig(config) + assert.EqualError(t, err, "no receivers available as part of the configuration") }) t.Run("should not update deployment container when the config is updated", func(t *testing.T) { @@ -220,7 +215,7 @@ func TestExpectedDeployments(t *testing.T) { t.Run("should delete deployment", func(t *testing.T) { labels := map[string]string{ - "app.kubernetes.io/instance": "default.loadbalancer", + "app.kubernetes.io/instance": "test.loadbalancer", "app.kubernetes.io/managed-by": "opentelemetry-operator", } deploy := v1.Deployment{} diff --git a/pkg/loadbalancer/reconcile/helper.go b/pkg/loadbalancer/reconcile/helper.go new file mode 100644 index 0000000000..f62f11fe1c --- /dev/null +++ b/pkg/loadbalancer/reconcile/helper.go @@ -0,0 +1,29 @@ +package reconcile + +import ( + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" +) + +func checkMode(mode v1alpha1.Mode, lbMode v1alpha1.LbMode) bool { + deploy := false + if mode == v1alpha1.ModeStatefulSet && len(lbMode) > 0 { + deploy = true + } + return deploy +} + +func checkConfig(params Params) (map[interface{}]interface{}, error) { + config, err := adapters.ConfigFromString(params.Instance.Spec.Config) + if err != nil { + return nil, err + } + + promConfig, err := lbadapters.ConfigToPromConfig(config) + if err != nil { + return nil, err + } + + return promConfig, nil +} diff --git a/pkg/loadbalancer/reconcile/service.go b/pkg/loadbalancer/reconcile/service.go index 88a35bec45..9f58027bac 100644 --- a/pkg/loadbalancer/reconcile/service.go +++ b/pkg/loadbalancer/reconcile/service.go @@ -34,8 +34,14 @@ import ( // Services reconciles the service(s) required for the instance in the current context. func Services(ctx context.Context, params Params) error { - desired := []corev1.Service{ - desiredService(params), + desired := []corev1.Service{} + _, err := checkConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Promtheus config: %v", err) + } + + if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { + desired = append(desired, desiredService(params)) } // first, handle the create/update parts @@ -124,7 +130,7 @@ func deleteServices(ctx context.Context, params Params, expected []corev1.Servic opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/suite_test.go b/pkg/loadbalancer/reconcile/suite_test.go index a9a104d2d8..d4b2063ba5 100644 --- a/pkg/loadbalancer/reconcile/suite_test.go +++ b/pkg/loadbalancer/reconcile/suite_test.go @@ -111,7 +111,7 @@ func params() Params { NodePort: 0, }}, LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: "LeastConnection", + Mode: v1alpha1.ModeLeastConnection, }, Replicas: &replicas, Config: string(configYAML), @@ -160,7 +160,7 @@ func newParams(containerImage ...string) Params { NodePort: 0, }}, LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: "LeastConnections", + Mode: v1alpha1.ModeLeastConnection, Image: defaultContainerImage, }, Replicas: &replicas, diff --git a/pkg/loadbalancer/reconcile/suite_test.yaml b/pkg/loadbalancer/reconcile/suite_test.yaml index 63e354e574..20c32050b3 100644 --- a/pkg/loadbalancer/reconcile/suite_test.yaml +++ b/pkg/loadbalancer/reconcile/suite_test.yaml @@ -14,6 +14,6 @@ exporters: service: pipelines: traces: - receivers: [jaeger] + receivers: [prometheus] processors: [] exporters: [logging] \ No newline at end of file From c73470ed0b7385a860e469f3716f86b8bbfa49d6 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Mon, 12 Jul 2021 16:54:50 -0700 Subject: [PATCH 07/30] Update helper.go to add header --- pkg/loadbalancer/reconcile/helper.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/loadbalancer/reconcile/helper.go b/pkg/loadbalancer/reconcile/helper.go index f62f11fe1c..9562295e15 100644 --- a/pkg/loadbalancer/reconcile/helper.go +++ b/pkg/loadbalancer/reconcile/helper.go @@ -1,3 +1,17 @@ +// 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 reconcile import ( From 7482e1334e84c70951030e54564ad3603bcc16c7 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Mon, 12 Jul 2021 17:30:54 -0700 Subject: [PATCH 08/30] Updated comment and bundles --- api/v1alpha1/opentelemetrycollector_types.go | 2 +- .../manifests/opentelemetry.io_opentelemetrycollectors.yaml | 4 ++-- .../crd/bases/opentelemetry.io_opentelemetrycollectors.yaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index a6f9c1d46d..05cd7b0b90 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -123,7 +123,7 @@ type OpenTelemetryCollectorStatus struct { // OpenTelemetryLoadBalancer defines the configurations for the LoadBalancer. type OpenTelemetryLoadBalancer struct { - // Use indicates the option to use the loadbalancer option or not. + // Mode indicates the algorithm to use for target sharding in the loadbalancer. // +optional Mode LbMode `json:"mode,omitempty"` diff --git a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml index ea41dbeae9..08cea4e95f 100644 --- a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -193,8 +193,8 @@ spec: OpenTelemetry LoadBalancer. type: string mode: - description: Use indicates the option to use the loadbalancer - option or not. + description: Mode indicates the algorithm to use for target sharding + in the loadbalancer. type: string type: object mode: diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index 962edfd1aa..d6632557ab 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -181,8 +181,8 @@ spec: OpenTelemetry LoadBalancer. type: string mode: - description: Use indicates the option to use the loadbalancer - option or not. + description: Mode indicates the algorithm to use for target sharding + in the loadbalancer. type: string type: object mode: From 729d4ea8d20dadd013d879ca91134d47375ee5e2 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Mon, 12 Jul 2021 18:43:31 -0700 Subject: [PATCH 09/30] Updated lb reconcile helper function and its invocations --- pkg/loadbalancer/reconcile/configmap.go | 10 +++++----- pkg/loadbalancer/reconcile/deployment.go | 10 +++++----- pkg/loadbalancer/reconcile/helper.go | 15 +++------------ pkg/loadbalancer/reconcile/service.go | 10 +++++----- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/pkg/loadbalancer/reconcile/configmap.go b/pkg/loadbalancer/reconcile/configmap.go index 9628705c98..6666954f47 100644 --- a/pkg/loadbalancer/reconcile/configmap.go +++ b/pkg/loadbalancer/reconcile/configmap.go @@ -35,12 +35,12 @@ import ( // ConfigMaps reconciles the config map(s) required for the instance in the current context. func ConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - cm, err := desiredConfigMap(ctx, params) - if err != nil { - return fmt.Errorf("failed to parse config: %v", err) - } - if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { + if checkMode(params) { + cm, err := desiredConfigMap(ctx, params) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } desired = append(desired, cm) } diff --git a/pkg/loadbalancer/reconcile/deployment.go b/pkg/loadbalancer/reconcile/deployment.go index 77365243d1..e097c44cd2 100644 --- a/pkg/loadbalancer/reconcile/deployment.go +++ b/pkg/loadbalancer/reconcile/deployment.go @@ -33,12 +33,12 @@ import ( // Deployments reconciles the deployment(s) required for the instance in the current context. func Deployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - _, err := checkConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Promtheus config: %v", err) - } - if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { + if checkMode(params) { + _, err := checkConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Promtheus config: %v", err) + } desired = append(desired, loadbalancer.Deployment(params.Config, params.Log, params.Instance)) } diff --git a/pkg/loadbalancer/reconcile/helper.go b/pkg/loadbalancer/reconcile/helper.go index 9562295e15..299ab66221 100644 --- a/pkg/loadbalancer/reconcile/helper.go +++ b/pkg/loadbalancer/reconcile/helper.go @@ -20,12 +20,8 @@ import ( lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" ) -func checkMode(mode v1alpha1.Mode, lbMode v1alpha1.LbMode) bool { - deploy := false - if mode == v1alpha1.ModeStatefulSet && len(lbMode) > 0 { - deploy = true - } - return deploy +func checkMode(params Params) bool { + return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && len(params.Instance.Spec.LoadBalancer.Mode) > 0 } func checkConfig(params Params) (map[interface{}]interface{}, error) { @@ -34,10 +30,5 @@ func checkConfig(params Params) (map[interface{}]interface{}, error) { return nil, err } - promConfig, err := lbadapters.ConfigToPromConfig(config) - if err != nil { - return nil, err - } - - return promConfig, nil + return lbadapters.ConfigToPromConfig(config) } diff --git a/pkg/loadbalancer/reconcile/service.go b/pkg/loadbalancer/reconcile/service.go index 9f58027bac..1d7e9efd4b 100644 --- a/pkg/loadbalancer/reconcile/service.go +++ b/pkg/loadbalancer/reconcile/service.go @@ -35,12 +35,12 @@ import ( // Services reconciles the service(s) required for the instance in the current context. func Services(ctx context.Context, params Params) error { desired := []corev1.Service{} - _, err := checkConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Promtheus config: %v", err) - } - if checkMode(params.Instance.Spec.Mode, params.Instance.Spec.LoadBalancer.Mode) { + if checkMode(params) { + _, err := checkConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Promtheus config: %v", err) + } desired = append(desired, desiredService(params)) } From 2dcf8a1650342ec71b260e97a41fbebc980524ff Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 16 Jul 2021 16:19:35 -0700 Subject: [PATCH 10/30] Updated naming scheme and changed CR config --- api/v1alpha1/mode.go | 7 -- api/v1alpha1/opentelemetrycollector_types.go | 14 ++-- api/v1alpha1/zz_generated.deepcopy.go | 10 +-- ...ntelemetry.io_opentelemetrycollectors.yaml | 26 ++++---- ...ntelemetry.io_opentelemetrycollectors.yaml | 26 ++++---- ...oller.go => targetallocator_controller.go} | 38 +++++------ ....go => targetallocator_controller_test.go} | 39 +++++------ internal/config/main.go | 66 +++++++++---------- internal/config/options.go | 28 ++++---- main.go | 6 +- pkg/naming/main.go | 30 ++++----- .../adapters/config_to_promConfig.go | 0 .../adapters/config_to_promConfig_test.go | 6 +- .../container.go | 12 ++-- .../container_test.go | 19 +++--- .../deployment.go | 6 +- .../deployment_test.go | 7 +- .../labels.go | 8 +-- .../labels_test.go | 7 +- .../reconcile/configmap.go | 19 +++--- .../reconcile/configmap_test.go | 46 +++++++------ .../reconcile/deployment.go | 6 +- .../reconcile/deployment_test.go | 62 +++++------------ .../reconcile/helper.go | 6 +- .../reconcile/params.go | 0 .../reconcile/service.go | 20 +++--- .../reconcile/service_test.go | 30 ++++----- .../reconcile/suite_test.go | 12 ++-- .../reconcile/suite_test.yaml | 2 +- .../volume.go | 11 ++-- .../volume_test.go | 7 +- .../e2e/loadbalancer-features/00-assert.yaml | 14 ++-- .../e2e/loadbalancer-features/00-install.yaml | 27 ++++---- tests/e2e/smoke-loadbalancer/00-assert.yaml | 2 +- tests/e2e/smoke-loadbalancer/00-install.yaml | 27 ++++---- 35 files changed, 305 insertions(+), 341 deletions(-) rename controllers/{loadbalancer_controller.go => targetallocator_controller.go} (68%) rename controllers/{loadbalancer_controller_test.go => targetallocator_controller_test.go} (83%) rename pkg/{loadbalancer => targetallocator}/adapters/config_to_promConfig.go (100%) rename pkg/{loadbalancer => targetallocator}/adapters/config_to_promConfig_test.go (91%) rename pkg/{loadbalancer => targetallocator}/container.go (85%) rename pkg/{loadbalancer => targetallocator}/container_test.go (78%) rename pkg/{loadbalancer => targetallocator}/deployment.go (92%) rename pkg/{loadbalancer => targetallocator}/deployment_test.go (87%) rename pkg/{loadbalancer => targetallocator}/labels.go (82%) rename pkg/{loadbalancer => targetallocator}/labels_test.go (84%) rename pkg/{loadbalancer => targetallocator}/reconcile/configmap.go (92%) rename pkg/{loadbalancer => targetallocator}/reconcile/configmap_test.go (76%) rename pkg/{loadbalancer => targetallocator}/reconcile/deployment.go (95%) rename pkg/{loadbalancer => targetallocator}/reconcile/deployment_test.go (74%) rename pkg/{loadbalancer => targetallocator}/reconcile/helper.go (85%) rename pkg/{loadbalancer => targetallocator}/reconcile/params.go (100%) rename pkg/{loadbalancer => targetallocator}/reconcile/service.go (90%) rename pkg/{loadbalancer => targetallocator}/reconcile/service_test.go (72%) rename pkg/{loadbalancer => targetallocator}/reconcile/suite_test.go (94%) rename pkg/{loadbalancer => targetallocator}/reconcile/suite_test.yaml (91%) rename pkg/{loadbalancer => targetallocator}/volume.go (84%) rename pkg/{loadbalancer => targetallocator}/volume_test.go (84%) diff --git a/api/v1alpha1/mode.go b/api/v1alpha1/mode.go index 694063ad24..37747e73f8 100644 --- a/api/v1alpha1/mode.go +++ b/api/v1alpha1/mode.go @@ -18,8 +18,6 @@ type ( // Mode represents how the collector should be deployed (deployment vs. daemonset) // +kubebuilder:validation:Enum=daemonset;deployment;sidecar;statefulset Mode string - // LbMode represents the algorithms supported by the default loadbalancer implementation for target sharding. - LbMode string ) const ( @@ -35,8 +33,3 @@ const ( // ModeStatefulSet specifies that the collector should be deployed as a Kubernetes StatefulSet. ModeStatefulSet Mode = "statefulset" ) - -const ( - // ModeLeastConnection specifies that the loadbalancer should use the LeastConnection algorithm for target sharding. - ModeLeastConnection LbMode = "LeastConnection" -) diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index 05cd7b0b90..77172ef012 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -41,10 +41,10 @@ type OpenTelemetryCollectorSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Image string `json:"image,omitempty"` - // LoadBalancer indicates a value which determines whether to spawn a LoadBalancer resource or not. + // TargetAllocator indicates a value which determines whether to spawn a target allocation resource or not. // +optional // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true - LoadBalancer OpenTelemetryLoadBalancer `json:"loadbalancer,omitempty"` + TargetAllocator OpenTelemetryTargetAllocator `json:"targetAllocator,omitempty"` // Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) // +optional @@ -121,13 +121,13 @@ type OpenTelemetryCollectorStatus struct { Messages []string `json:"messages,omitempty"` } -// OpenTelemetryLoadBalancer defines the configurations for the LoadBalancer. -type OpenTelemetryLoadBalancer struct { - // Mode indicates the algorithm to use for target sharding in the loadbalancer. +// OpenTelemetryTargetAllocator defines the configurations for the Prometheus target allocator. +type OpenTelemetryTargetAllocator struct { + // Enabled indicates whether to use a target allocation mechanism for Prometheus targets or not. // +optional - Mode LbMode `json:"mode,omitempty"` + Enabled bool `json:"enabled,omitempty"` - // Image indicates the container image to use for the OpenTelemetry LoadBalancer. + // Image indicates the container image to use for the OpenTelemetry TargetAllocator. // +optional Image string `json:"image,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bfaf26e3db..d29db7cd6a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -97,7 +97,7 @@ func (in *OpenTelemetryCollectorSpec) DeepCopyInto(out *OpenTelemetryCollectorSp *out = new(int32) **out = **in } - out.LoadBalancer = in.LoadBalancer + out.TargetAllocator = in.TargetAllocator if in.SecurityContext != nil { in, out := &in.SecurityContext, &out.SecurityContext *out = new(v1.SecurityContext) @@ -179,16 +179,16 @@ func (in *OpenTelemetryCollectorStatus) DeepCopy() *OpenTelemetryCollectorStatus } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OpenTelemetryLoadBalancer) DeepCopyInto(out *OpenTelemetryLoadBalancer) { +func (in *OpenTelemetryTargetAllocator) DeepCopyInto(out *OpenTelemetryTargetAllocator) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryLoadBalancer. -func (in *OpenTelemetryLoadBalancer) DeepCopy() *OpenTelemetryLoadBalancer { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryTargetAllocator. +func (in *OpenTelemetryTargetAllocator) DeepCopy() *OpenTelemetryTargetAllocator { if in == nil { return nil } - out := new(OpenTelemetryLoadBalancer) + out := new(OpenTelemetryTargetAllocator) in.DeepCopyInto(out) return out } diff --git a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml index 08cea4e95f..c2baf8970e 100644 --- a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -184,19 +184,6 @@ spec: description: Image indicates the container image to use for the OpenTelemetry Collector. type: string - loadbalancer: - description: LoadBalancer indicates a value which determines whether - to spawn a LoadBalancer resource or not. - properties: - image: - description: Image indicates the container image to use for the - OpenTelemetry LoadBalancer. - type: string - mode: - description: Mode indicates the algorithm to use for target sharding - in the loadbalancer. - type: string - type: object mode: description: Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) @@ -444,6 +431,19 @@ spec: description: ServiceAccount indicates the name of an existing service account to use with this instance. type: string + targetAllocator: + description: TargetAllocator indicates a value which determines whether + to spawn a target allocation resource or not. + properties: + enabled: + description: Enabled indicates whether to use a target allocation + mechanism for Prometheus targets or not. + type: boolean + image: + description: Image indicates the container image to use for the + OpenTelemetry target allocator. + type: string + type: object tolerations: description: Toleration to schedule OpenTelemetry Collector pods. This is only relevant to daemonsets, statefulsets and deployments diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index d6632557ab..39bd27561e 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -172,19 +172,6 @@ spec: description: Image indicates the container image to use for the OpenTelemetry Collector. type: string - loadbalancer: - description: LoadBalancer indicates a value which determines whether - to spawn a LoadBalancer resource or not. - properties: - image: - description: Image indicates the container image to use for the - OpenTelemetry LoadBalancer. - type: string - mode: - description: Mode indicates the algorithm to use for target sharding - in the loadbalancer. - type: string - type: object mode: description: Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) @@ -432,6 +419,19 @@ spec: description: ServiceAccount indicates the name of an existing service account to use with this instance. type: string + targetAllocator: + description: TargetAllocator indicates a value which determines whether + to spawn a target allocation resource or not. + properties: + enabled: + description: Enabled indicates whether to use a target allocation + mechanism for Prometheus targets or not. + type: boolean + image: + description: Image indicates the container image to use for the + OpenTelemetry target allocator. + type: string + type: object tolerations: description: Toleration to schedule OpenTelemetry Collector pods. This is only relevant to daemonsets, statefulsets and deployments diff --git a/controllers/loadbalancer_controller.go b/controllers/targetallocator_controller.go similarity index 68% rename from controllers/loadbalancer_controller.go rename to controllers/targetallocator_controller.go index d948b6a593..b8af2a8537 100644 --- a/controllers/loadbalancer_controller.go +++ b/controllers/targetallocator_controller.go @@ -30,38 +30,38 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" ) -// OpenTelemetryLoadbalancerReconciler reconciles a OpenTelemetryLoadbalancer object. -type OpenTelemetryLoadbalancerReconciler struct { +// OpenTelemetryTargetAllocatorReconciler reconciles a OpenTelemetryTargetAllocator object. +type OpenTelemetryTargetAllocatorReconciler struct { client.Client log logr.Logger scheme *runtime.Scheme config config.Config - tasks []LbTask + tasks []TgAlTask } -// LbTask represents a reconciliation task to be executed by the reconciler. -type LbTask struct { +// TgAlTask represents a reconciliation task to be executed by the reconciler. +type TgAlTask struct { Name string Do func(context.Context, reconcile.Params) error BailOnError bool } -// LbParams is the set of options to build a new openTelemetryLoadbalancerReconciler. -type LbParams struct { +// TgAlParams is the set of options to build a new OpenTelemetryTargetAllocatorReconciler. +type TgAlParams struct { client.Client Log logr.Logger Scheme *runtime.Scheme Config config.Config - Tasks []LbTask + Tasks []TgAlTask } -// NewLbReconciler creates a new reconciler for OpenTelemetryLoadbalancer objects. -func NewLbReconciler(p LbParams) *OpenTelemetryLoadbalancerReconciler { +// NewTgAlReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. +func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { - p.Tasks = []LbTask{ + p.Tasks = []TgAlTask{ { "config maps", reconcile.ConfigMaps, @@ -80,7 +80,7 @@ func NewLbReconciler(p LbParams) *OpenTelemetryLoadbalancerReconciler { } } - return &OpenTelemetryLoadbalancerReconciler{ + return &OpenTelemetryTargetAllocatorReconciler{ Client: p.Client, log: p.Log, scheme: p.Scheme, @@ -89,15 +89,15 @@ func NewLbReconciler(p LbParams) *OpenTelemetryLoadbalancerReconciler { } } -// Reconcile the current state of an OpenTelemetry LB resource with the desired state. -func (r *OpenTelemetryLoadbalancerReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { +// Reconcile the current state of an OpenTelemetry target allocation resource with the desired state. +func (r *OpenTelemetryTargetAllocatorReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() - log := r.log.WithValues("opentelemtryloadbalancer", req.NamespacedName) + log := r.log.WithValues("opentelemtrytargetallocator", req.NamespacedName) var instance v1alpha1.OpenTelemetryCollector if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { if !apierrors.IsNotFound(err) { - log.Error(err, "unable to fetch OpenTelemetryLoadBalancer") + log.Error(err, "unable to fetch OpenTelemetryTargetAllocator") } return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -118,7 +118,7 @@ func (r *OpenTelemetryLoadbalancerReconciler) Reconcile(_ context.Context, req c } // RunTasks runs all the tasks associated with this reconciler. -func (r *OpenTelemetryLoadbalancerReconciler) RunTasks(ctx context.Context, params reconcile.Params) error { +func (r *OpenTelemetryTargetAllocatorReconciler) RunTasks(ctx context.Context, params reconcile.Params) error { for _, task := range r.tasks { if err := task.Do(ctx, params); err != nil { r.log.Error(err, fmt.Sprintf("failed to reconcile %s", task.Name)) @@ -131,7 +131,7 @@ func (r *OpenTelemetryLoadbalancerReconciler) RunTasks(ctx context.Context, para } // SetupWithManager tells the manager what our controller is interested in. -func (r *OpenTelemetryLoadbalancerReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *OpenTelemetryTargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.OpenTelemetryCollector{}). Owns(&corev1.ConfigMap{}). diff --git a/controllers/loadbalancer_controller_test.go b/controllers/targetallocator_controller_test.go similarity index 83% rename from controllers/loadbalancer_controller_test.go rename to controllers/targetallocator_controller_test.go index 99c8c4ece1..692b1cfe69 100644 --- a/controllers/loadbalancer_controller_test.go +++ b/controllers/targetallocator_controller_test.go @@ -29,28 +29,25 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubectl/pkg/scheme" "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/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" ) -var lblogger = logf.Log.WithName("unit-tests") - -func TestNewObjectsOnLbReconciliation(t *testing.T) { +func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { // prepare cfg := config.New() - configYAML, err := ioutil.ReadFile("../pkg/loadbalancer/reconcile/suite_test.yaml") + configYAML, err := ioutil.ReadFile("../pkg/targetallocator/reconcile/suite_test.yaml") if err != nil { fmt.Printf("Error getting yaml file: %v", err) } nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewLbReconciler(controllers.LbParams{ + reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ Client: k8sClient, - Log: lblogger, + Log: logger, Scheme: testScheme, Config: cfg, }) @@ -62,8 +59,8 @@ func TestNewObjectsOnLbReconciliation(t *testing.T) { Spec: v1alpha1.OpenTelemetryCollectorSpec{ Mode: v1alpha1.ModeStatefulSet, Config: string(configYAML), - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: v1alpha1.ModeLeastConnection, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, }, }, } @@ -83,7 +80,7 @@ func TestNewObjectsOnLbReconciliation(t *testing.T) { opts := []client.ListOption{ client.InNamespace(nsn.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Name, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Name, "targetallocator"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } @@ -114,12 +111,12 @@ func TestNewObjectsOnLbReconciliation(t *testing.T) { } -func TestContinueOnRecoverableLbFailure(t *testing.T) { +func TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { // prepare taskCalled := false - reconciler := controllers.NewLbReconciler(controllers.LbParams{ - Log: lblogger, - Tasks: []controllers.LbTask{ + reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ + Log: logger, + Tasks: []controllers.TgAlTask{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -145,18 +142,18 @@ func TestContinueOnRecoverableLbFailure(t *testing.T) { assert.True(t, taskCalled) } -func TestBreakOnUnrecoverableLbError(t *testing.T) { +func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { // prepare cfg := config.New() taskCalled := false expectedErr := errors.New("should fail!") nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewLbReconciler(controllers.LbParams{ + reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.LbTask{ + Tasks: []controllers.TgAlTask{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -197,16 +194,16 @@ func TestBreakOnUnrecoverableLbError(t *testing.T) { assert.NoError(t, k8sClient.Delete(context.Background(), created)) } -func TestLbSkipWhenInstanceDoesNotExist(t *testing.T) { +func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { // prepare cfg := config.New() nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewLbReconciler(controllers.LbParams{ + reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.LbTask{ + Tasks: []controllers.TgAlTask{ { Name: "should-not-be-called", Do: func(context.Context, reconcile.Params) error { diff --git a/internal/config/main.go b/internal/config/main.go index cc60a345d2..b2a57dd6ea 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -29,9 +29,9 @@ import ( ) const ( - defaultAutoDetectFrequency = 5 * time.Second - defaultCollectorConfigMapEntry = "collector.yaml" - defaultLoadBalancerConfigMapEntry = "loadbalancer.yaml" + defaultAutoDetectFrequency = 5 * time.Second + defaultCollectorConfigMapEntry = "collector.yaml" + defaultTargetAllocatorConfigMapEntry = "targetallocator.yaml" ) // Config holds the static configuration for this operator. @@ -45,24 +45,24 @@ type Config struct { onChange []func() error // config state - collectorImage string - collectorConfigMapEntry string - loadbalancerImage string - loadBalancerConfigMapEntry string - platform platform.Platform - version version.Version + collectorImage string + collectorConfigMapEntry string + targetAllocatorImage string + targetAllocatorConfigMapEntry string + platform platform.Platform + version version.Version } // New constructs a new configuration based on the given options. func New(opts ...Option) Config { // initialize with the default values o := options{ - autoDetectFrequency: defaultAutoDetectFrequency, - collectorConfigMapEntry: defaultCollectorConfigMapEntry, - loadBalancerConfigMapEntry: defaultLoadBalancerConfigMapEntry, - logger: logf.Log.WithName("config"), - platform: platform.Unknown, - version: version.Get(), + autoDetectFrequency: defaultAutoDetectFrequency, + collectorConfigMapEntry: defaultCollectorConfigMapEntry, + targetAllocatorConfigMapEntry: defaultTargetAllocatorConfigMapEntry, + logger: logf.Log.WithName("config"), + platform: platform.Unknown, + version: version.Get(), } for _, opt := range opts { opt(&o) @@ -74,21 +74,21 @@ func New(opts ...Option) Config { o.collectorImage = fmt.Sprintf("otel/opentelemetry-collector:%s", o.version.OpenTelemetryCollector) } - if len(o.loadbalancerImage) == 0 { - o.loadbalancerImage = "raul9595/otel-loadbalancer" + if len(o.targetAllocatorImage) == 0 { + o.targetAllocatorImage = "raul9595/otel-loadbalancer" } return Config{ - autoDetect: o.autoDetect, - autoDetectFrequency: o.autoDetectFrequency, - collectorImage: o.collectorImage, - collectorConfigMapEntry: o.collectorConfigMapEntry, - loadbalancerImage: o.loadbalancerImage, - loadBalancerConfigMapEntry: o.loadBalancerConfigMapEntry, - logger: o.logger, - onChange: o.onChange, - platform: o.platform, - version: o.version, + autoDetect: o.autoDetect, + autoDetectFrequency: o.autoDetectFrequency, + collectorImage: o.collectorImage, + collectorConfigMapEntry: o.collectorConfigMapEntry, + targetAllocatorImage: o.targetAllocatorImage, + targetAllocatorConfigMapEntry: o.targetAllocatorConfigMapEntry, + logger: o.logger, + onChange: o.onChange, + platform: o.platform, + version: o.version, } } @@ -165,14 +165,14 @@ func (c *Config) CollectorConfigMapEntry() string { return c.collectorConfigMapEntry } -// LoadBalancerImage represents the flag to override the OpenTelemetry LoadBalancer container image. -func (c *Config) LoadBalancerImage() string { - return c.loadbalancerImage +// TargetAllocatorImage represents the flag to override the OpenTelemetry TargetAllocator container image. +func (c *Config) TargetAllocatorImage() string { + return c.targetAllocatorImage } -// LoadBalancerConfigMapEntry represents the configuration file name for the LoadBalancer. Immutable. -func (c *Config) LoadBalancerConfigMapEntry() string { - return c.loadBalancerConfigMapEntry +// TargetAllocatorConfigMapEntry represents the configuration file name for the TargetAllocator. Immutable. +func (c *Config) TargetAllocatorConfigMapEntry() string { + return c.targetAllocatorConfigMapEntry } // Platform represents the type of the platform this operator is running. diff --git a/internal/config/options.go b/internal/config/options.go index 30b5af017d..b8a2821457 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -28,16 +28,16 @@ import ( type Option func(c *options) type options struct { - autoDetect autodetect.AutoDetect - autoDetectFrequency time.Duration - loadbalancerImage string - collectorImage string - collectorConfigMapEntry string - loadBalancerConfigMapEntry string - logger logr.Logger - onChange []func() error - platform platform.Platform - version version.Version + autoDetect autodetect.AutoDetect + autoDetectFrequency time.Duration + targetAllocatorImage string + collectorImage string + collectorConfigMapEntry string + targetAllocatorConfigMapEntry string + logger logr.Logger + onChange []func() error + platform platform.Platform + version version.Version } func WithAutoDetect(a autodetect.AutoDetect) Option { @@ -50,9 +50,9 @@ func WithAutoDetectFrequency(t time.Duration) Option { o.autoDetectFrequency = t } } -func WithLoadBalancerImage(s string) Option { +func WithTargetAllocatorImage(s string) Option { return func(o *options) { - o.loadbalancerImage = s + o.targetAllocatorImage = s } } @@ -66,9 +66,9 @@ func WithCollectorConfigMapEntry(s string) Option { o.collectorConfigMapEntry = s } } -func WithLoadBalancerConfigMapEntry(s string) Option { +func WithTargetAllocatorConfigMapEntry(s string) Option { return func(o *options) { - o.loadBalancerConfigMapEntry = s + o.targetAllocatorConfigMapEntry = s } } func WithLogger(logger logr.Logger) Option { diff --git a/main.go b/main.go index b90da693b5..95cffd6189 100644 --- a/main.go +++ b/main.go @@ -155,13 +155,13 @@ func main() { os.Exit(1) } - if err = controllers.NewLbReconciler(controllers.LbParams{ + if err = controllers.NewTgAlReconciler(controllers.TgAlParams{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("LoadBalancer"), + Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), Scheme: mgr.GetScheme(), Config: cfg, }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "LoadBalancer") + setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") os.Exit(1) } diff --git a/pkg/naming/main.go b/pkg/naming/main.go index 06a20a9656..49970f4ec7 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -26,9 +26,9 @@ func ConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } -// LBConfigMap returns the name for the config map used in the LoadBalancer. -func LBConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { - return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +// TAConfigMap returns the name for the config map used in the TargetAllocator. +func TAConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) } // ConfigMapVolume returns the name to use for the config map's volume in the pod. @@ -36,9 +36,9 @@ func ConfigMapVolume() string { return "otc-internal" } -// LBConfigMapVolume returns the name to use for the config map's volume in the LoadBalancer pod. -func LBConfigMapVolume() string { - return "lb-internal" +// TAConfigMapVolume returns the name to use for the config map's volume in the TargetAllocator pod. +func TAConfigMapVolume() string { + return "ta-internal" } // Container returns the name to use for the container in the pod. @@ -46,9 +46,9 @@ func Container() string { return "otc-container" } -// LBContainer returns the name to use for the container in the LoadBalancer pod. -func LBContainer() string { - return "lb-container" +// TAContainer returns the name to use for the container in the TargetAllocator pod. +func TAContainer() string { + return "ta-container" } // Collector builds the collector (deployment/daemonset) name based on the instance. @@ -56,9 +56,9 @@ func Collector(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } -// LoadBalancer returns the LoadBalancer deployment resource name. -func LoadBalancer(otelcol v1alpha1.OpenTelemetryCollector) string { - return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +// TargetAllocator returns the TargetAllocator deployment resource name. +func TargetAllocator(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) } // HeadlessService builds the name for the headless service based on the instance. @@ -76,9 +76,9 @@ func Service(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } -// LBService returns the name to use for the LoadBalancer service. -func LBService(otelcol v1alpha1.OpenTelemetryCollector) string { - return fmt.Sprintf("%s-loadbalancer", otelcol.Name) +// TAService returns the name to use for the TargetAllocator service. +func TAService(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) } // ServiceAccount builds the service account name based on the instance. diff --git a/pkg/loadbalancer/adapters/config_to_promConfig.go b/pkg/targetallocator/adapters/config_to_promConfig.go similarity index 100% rename from pkg/loadbalancer/adapters/config_to_promConfig.go rename to pkg/targetallocator/adapters/config_to_promConfig.go diff --git a/pkg/loadbalancer/adapters/config_to_promConfig_test.go b/pkg/targetallocator/adapters/config_to_promConfig_test.go similarity index 91% rename from pkg/loadbalancer/adapters/config_to_promConfig_test.go rename to pkg/targetallocator/adapters/config_to_promConfig_test.go index 17b39cdab5..208c9989c7 100644 --- a/pkg/loadbalancer/adapters/config_to_promConfig_test.go +++ b/pkg/targetallocator/adapters/config_to_promConfig_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) func TestExtractPromConfigFromConfig(t *testing.T) { @@ -55,7 +55,7 @@ func TestExtractPromConfigFromConfig(t *testing.T) { require.NotEmpty(t, config) // test - promConfig, err := lbadapters.ConfigToPromConfig(config) + promConfig, err := ta.ConfigToPromConfig(config) assert.NoError(t, err) // verify @@ -82,7 +82,7 @@ func TestExtractPromConfigFromNullConfig(t *testing.T) { require.NotEmpty(t, config) // test - promConfig, err := lbadapters.ConfigToPromConfig(config) + promConfig, err := ta.ConfigToPromConfig(config) assert.Equal(t, err, fmt.Errorf("%s property in the configuration doesn't contain valid %s", "prometheusConfig", "prometheusConfig")) // verify diff --git a/pkg/loadbalancer/container.go b/pkg/targetallocator/container.go similarity index 85% rename from pkg/loadbalancer/container.go rename to pkg/targetallocator/container.go index 9f7d29df50..5aea95e685 100644 --- a/pkg/loadbalancer/container.go +++ b/pkg/targetallocator/container.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer +package targetallocator import ( "github.com/go-logr/logr" @@ -23,15 +23,15 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/naming" ) -// Container builds a container for the given LoadBalancer. +// Container builds a container for the given TargetAllocator. func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) corev1.Container { - image := otelcol.Spec.LoadBalancer.Image + image := otelcol.Spec.TargetAllocator.Image if len(image) == 0 { - image = cfg.LoadBalancerImage() + image = cfg.TargetAllocatorImage() } volumeMounts := []corev1.VolumeMount{{ - Name: naming.LBConfigMapVolume(), + Name: naming.TAConfigMapVolume(), MountPath: "/conf", }} @@ -47,7 +47,7 @@ func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelem }) return corev1.Container{ - Name: naming.LBContainer(), + Name: naming.TAContainer(), Image: image, Env: envVars, VolumeMounts: volumeMounts, diff --git a/pkg/loadbalancer/container_test.go b/pkg/targetallocator/container_test.go similarity index 78% rename from pkg/loadbalancer/container_test.go rename to pkg/targetallocator/container_test.go index 79220c8220..98a8c3a0e4 100644 --- a/pkg/loadbalancer/container_test.go +++ b/pkg/targetallocator/container_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer_test +package targetallocator import ( "testing" @@ -23,7 +23,6 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" ) @@ -32,7 +31,7 @@ var logger = logf.Log.WithName("unit-tests") func TestContainerNewDefault(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{} - cfg := config.New(config.WithLoadBalancerImage("default-image")) + cfg := config.New(config.WithTargetAllocatorImage("default-image")) // test c := Container(cfg, logger, otelcol) @@ -45,12 +44,13 @@ func TestContainerWithImageOverridden(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ Spec: v1alpha1.OpenTelemetryCollectorSpec{ - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Image: "overridden-image", + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + Image: "overridden-image", }, }, } - cfg := config.New(config.WithLoadBalancerImage("default-image")) + cfg := config.New(config.WithTargetAllocatorImage("default-image")) // test c := Container(cfg, logger, otelcol) @@ -63,8 +63,9 @@ func TestContainerVolumes(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ Spec: v1alpha1.OpenTelemetryCollectorSpec{ - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Image: "default-image", + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + Image: "default-image", }, }, } @@ -75,5 +76,5 @@ func TestContainerVolumes(t *testing.T) { // verify assert.Len(t, c.VolumeMounts, 1) - assert.Equal(t, naming.LBConfigMapVolume(), c.VolumeMounts[0].Name) + assert.Equal(t, naming.TAConfigMapVolume(), c.VolumeMounts[0].Name) } diff --git a/pkg/loadbalancer/deployment.go b/pkg/targetallocator/deployment.go similarity index 92% rename from pkg/loadbalancer/deployment.go rename to pkg/targetallocator/deployment.go index 6bd4efb415..d05052dae3 100644 --- a/pkg/loadbalancer/deployment.go +++ b/pkg/targetallocator/deployment.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer +package targetallocator import ( "github.com/go-logr/logr" @@ -28,13 +28,13 @@ import ( // Deployment builds the deployment for the given instance. func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.Deployment { labels := Labels(otelcol) - labels["app.kubernetes.io/name"] = naming.LoadBalancer(otelcol) + labels["app.kubernetes.io/name"] = naming.TargetAllocator(otelcol) var replicas int32 = 1 return appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: naming.LoadBalancer(otelcol), + Name: naming.TargetAllocator(otelcol), Namespace: otelcol.Namespace, Labels: labels, }, diff --git a/pkg/loadbalancer/deployment_test.go b/pkg/targetallocator/deployment_test.go similarity index 87% rename from pkg/loadbalancer/deployment_test.go rename to pkg/targetallocator/deployment_test.go index 68fbaae412..4cd9fd4133 100644 --- a/pkg/loadbalancer/deployment_test.go +++ b/pkg/targetallocator/deployment_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer_test +package targetallocator import ( "testing" @@ -24,7 +24,6 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" ) var testTolerationValues = []v1.Toleration{ @@ -51,8 +50,8 @@ func TestDeploymentNewDefault(t *testing.T) { d := Deployment(cfg, logger, otelcol) // verify - assert.Equal(t, "my-instance-loadbalancer", d.Name) - assert.Equal(t, "my-instance-loadbalancer", d.Labels["app.kubernetes.io/name"]) + assert.Equal(t, "my-instance-targetallocator", d.Name) + assert.Equal(t, "my-instance-targetallocator", d.Labels["app.kubernetes.io/name"]) assert.Len(t, d.Spec.Template.Spec.Containers, 1) diff --git a/pkg/loadbalancer/labels.go b/pkg/targetallocator/labels.go similarity index 82% rename from pkg/loadbalancer/labels.go rename to pkg/targetallocator/labels.go index f14c5854b4..f97c4b2fb5 100644 --- a/pkg/loadbalancer/labels.go +++ b/pkg/targetallocator/labels.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer +package targetallocator import ( "fmt" @@ -20,7 +20,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" ) -// Labels return the common labels to all LoadBalancer objects that are part of a managed OpenTelemetryCollector. +// Labels return the common labels to all TargetAllocator objects that are part of a managed OpenTelemetryCollector. func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { // new map every time, so that we don't touch the instance's label base := map[string]string{} @@ -31,9 +31,9 @@ func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { } base["app.kubernetes.io/managed-by"] = "opentelemetry-operator" - base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Name, "loadbalancer") + base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Name, "targetallocator") base["app.kubernetes.io/part-of"] = "opentelemetry" - base["app.kubernetes.io/component"] = "opentelemetry-loadbalancer" + base["app.kubernetes.io/component"] = "opentelemetry-targetallocator" return base } diff --git a/pkg/loadbalancer/labels_test.go b/pkg/targetallocator/labels_test.go similarity index 84% rename from pkg/loadbalancer/labels_test.go rename to pkg/targetallocator/labels_test.go index 2ad9c32d1c..8295c39bbd 100644 --- a/pkg/loadbalancer/labels_test.go +++ b/pkg/targetallocator/labels_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer_test +package targetallocator import ( "testing" @@ -21,7 +21,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" ) func TestLabelsCommonSet(t *testing.T) { @@ -36,9 +35,9 @@ func TestLabelsCommonSet(t *testing.T) { // test labels := Labels(otelcol) assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) - assert.Equal(t, "my-instance.loadbalancer", labels["app.kubernetes.io/instance"]) + assert.Equal(t, "my-instance.targetallocator", labels["app.kubernetes.io/instance"]) assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) - assert.Equal(t, "opentelemetry-loadbalancer", labels["app.kubernetes.io/component"]) + assert.Equal(t, "opentelemetry-targetallocator", labels["app.kubernetes.io/component"]) } func TestLabelsPropagateDown(t *testing.T) { diff --git a/pkg/loadbalancer/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go similarity index 92% rename from pkg/loadbalancer/reconcile/configmap.go rename to pkg/targetallocator/reconcile/configmap.go index 6666954f47..6c5a6a666e 100644 --- a/pkg/loadbalancer/reconcile/configmap.go +++ b/pkg/targetallocator/reconcile/configmap.go @@ -26,8 +26,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete @@ -58,8 +58,8 @@ func ConfigMaps(ctx context.Context, params Params) error { } func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { - name := naming.LBConfigMap(params.Instance) - labels := loadbalancer.Labels(params.Instance) + name := naming.TAConfigMap(params.Instance) + labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name promConfig, err := checkConfig(params) @@ -67,14 +67,13 @@ func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error return corev1.ConfigMap{}, err } - lbConfig := make(map[interface{}]interface{}) - lbConfig["mode"] = params.Instance.Spec.LoadBalancer.Mode - lbConfig["label_selector"] = map[string]string{ + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), "app.kubernetes.io/managed-by": "opentelemetry-operator", } - lbConfig["config"] = promConfig - lbConfigYAML, _ := yaml.Marshal(lbConfig) + taConfig["config"] = promConfig + taConfigYAML, _ := yaml.Marshal(taConfig) return corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -84,7 +83,7 @@ func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error Annotations: params.Instance.Annotations, }, Data: map[string]string{ - "loadbalancer.yaml": string(lbConfigYAML), + "targetallocator.yaml": string(taConfigYAML), }, }, nil } @@ -156,7 +155,7 @@ func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.Conf opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/configmap_test.go b/pkg/targetallocator/reconcile/configmap_test.go similarity index 76% rename from pkg/loadbalancer/reconcile/configmap_test.go rename to pkg/targetallocator/reconcile/configmap_test.go index aea43a4ade..e15e8c6176 100644 --- a/pkg/loadbalancer/reconcile/configmap_test.go +++ b/pkg/targetallocator/reconcile/configmap_test.go @@ -27,21 +27,21 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) func TestDesiredConfigMap(t *testing.T) { t.Run("should return expected config map", func(t *testing.T) { expectedLables := map[string]string{ "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "test.loadbalancer", + "app.kubernetes.io/instance": "test.targetallocator", "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-loadbalancer", - "app.kubernetes.io/name": "test-loadbalancer", + "app.kubernetes.io/component": "opentelemetry-targetallocator", + "app.kubernetes.io/name": "test-targetallocator", } expectedData := map[string]string{ - "loadbalancer.yaml": `config: + "targetallocator.yaml": `config: scrape_configs: job_name: otel-collector scrape_interval: 10s @@ -52,14 +52,13 @@ func TestDesiredConfigMap(t *testing.T) { label_selector: app.kubernetes.io/instance: default.test app.kubernetes.io/managed-by: opentelemetry-operator -mode: LeastConnection `, } actual, err := desiredConfigMap(context.Background(), params()) assert.NoError(t, err) - assert.Equal(t, "test-loadbalancer", actual.Name) + assert.Equal(t, "test-targetallocator", actual.Name) assert.Equal(t, expectedLables, actual.Labels) assert.Equal(t, expectedData, actual.Data) @@ -75,7 +74,7 @@ func TestExpectedConfigMap(t *testing.T) { err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) - exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -106,8 +105,8 @@ func TestExpectedConfigMap(t *testing.T) { }, NodePort: 0, }}, - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: v1alpha1.ModeLeastConnection, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, }, Config: "", }, @@ -117,7 +116,7 @@ func TestExpectedConfigMap(t *testing.T) { } cm, err := desiredConfigMap(context.Background(), newParam) assert.EqualError(t, err, "no receivers available as part of the configuration") - createObjectIfNotExists(t, "test-loadbalancer", &cm) + createObjectIfNotExists(t, "test-targetallocator", &cm) configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) @@ -125,7 +124,7 @@ func TestExpectedConfigMap(t *testing.T) { assert.NoError(t, err) actual := v1.ConfigMap{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -134,36 +133,35 @@ func TestExpectedConfigMap(t *testing.T) { config, err := adapters.ConfigFromString(param.Instance.Spec.Config) assert.NoError(t, err) - parmConfig, err := lbadapters.ConfigToPromConfig(config) + parmConfig, err := ta.ConfigToPromConfig(config) assert.NoError(t, err) - lbConfig := make(map[interface{}]interface{}) - lbConfig["mode"] = string(v1alpha1.ModeLeastConnection) - lbConfig["label_selector"] = map[string]string{ + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ "app.kubernetes.io/instance": "default.test", "app.kubernetes.io/managed-by": "opentelemetry-operator", } - lbConfig["config"] = parmConfig - lbConfigYAML, _ := yaml.Marshal(lbConfig) + taConfig["config"] = parmConfig + taConfigYAML, _ := yaml.Marshal(taConfig) - assert.Equal(t, string(lbConfigYAML), actual.Data["loadbalancer.yaml"]) + assert.Equal(t, string(taConfigYAML), actual.Data["targetallocator.yaml"]) }) t.Run("should delete config map", func(t *testing.T) { deletecm := v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-delete-loadbalancer", + Name: "test-delete-targetallocator", Namespace: "default", Labels: map[string]string{ - "app.kubernetes.io/instance": "test.loadbalancer", + "app.kubernetes.io/instance": "test.targetallocator", "app.kubernetes.io/managed-by": "opentelemetry-operator", }, }, } - createObjectIfNotExists(t, "test-delete-loadbalancer", &deletecm) + createObjectIfNotExists(t, "test-delete-targetallocator", &deletecm) - exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.True(t, exists) configMap, err := desiredConfigMap(context.Background(), param) @@ -171,7 +169,7 @@ func TestExpectedConfigMap(t *testing.T) { err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) assert.NoError(t, err) - exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.False(t, exists) }) } diff --git a/pkg/loadbalancer/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go similarity index 95% rename from pkg/loadbalancer/reconcile/deployment.go rename to pkg/targetallocator/reconcile/deployment.go index e097c44cd2..5b50827cfe 100644 --- a/pkg/loadbalancer/reconcile/deployment.go +++ b/pkg/targetallocator/reconcile/deployment.go @@ -25,7 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete @@ -39,7 +39,7 @@ func Deployments(ctx context.Context, params Params) error { if err != nil { return fmt.Errorf("failed to parse Promtheus config: %v", err) } - desired = append(desired, loadbalancer.Deployment(params.Config, params.Log, params.Instance)) + desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) } // first, handle the create/update parts @@ -107,7 +107,7 @@ func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Dep opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/deployment_test.go b/pkg/targetallocator/reconcile/deployment_test.go similarity index 74% rename from pkg/loadbalancer/reconcile/deployment_test.go rename to pkg/targetallocator/reconcile/deployment_test.go index cac48b70bb..135a7759a3 100644 --- a/pkg/loadbalancer/reconcile/deployment_test.go +++ b/pkg/targetallocator/reconcile/deployment_test.go @@ -25,20 +25,18 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) func TestExpectedDeployments(t *testing.T) { param := params() - expectedDeploy := loadbalancer.Deployment(param.Config, logger, param.Instance) + expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) t.Run("should create deployment", func(t *testing.T) { err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -63,8 +61,8 @@ func TestExpectedDeployments(t *testing.T) { }, Spec: v1alpha1.OpenTelemetryCollectorSpec{ Mode: mode, - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: v1alpha1.ModeLeastConnection, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, }, Config: ` receivers: @@ -91,14 +89,14 @@ func TestExpectedDeployments(t *testing.T) { } expected := []v1.Deployment{} if newParam.Instance.Spec.Mode == v1alpha1.ModeStatefulSet { - expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) } assert.Len(t, expected, 0) } }) - t.Run("should not create deployment when loadbalancer mode is not set", func(t *testing.T) { + t.Run("should not create deployment when targetallocator is not enabled", func(t *testing.T) { newParam := Params{ Client: k8sClient, Instance: v1alpha1.OpenTelemetryCollector{ @@ -137,53 +135,25 @@ func TestExpectedDeployments(t *testing.T) { Log: logger, } expected := []v1.Deployment{} - if len(newParam.Instance.Spec.LoadBalancer.Mode) > 0 { - expected = append(expected, loadbalancer.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + if newParam.Instance.Spec.TargetAllocator.Enabled { + expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) } assert.Len(t, expected, 0) }) - t.Run("should not create deployment when there is no valid Prometheus config", func(t *testing.T) { - newParam := Params{ - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Config: "", - }, - }, - Scheme: testScheme, - Log: logger, - } - config, err := adapters.ConfigFromString(newParam.Instance.Spec.Config) - assert.NoError(t, err) - - _, err = lbadapters.ConfigToPromConfig(config) - assert.EqualError(t, err, "no receivers available as part of the configuration") - }) - t.Run("should not update deployment container when the config is updated", func(t *testing.T) { ctx := context.Background() - createObjectIfNotExists(t, "test-loadbalancer", &expectedDeploy) + createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) orgUID := expectedDeploy.OwnerReferences[0].UID - updatedDeploy := loadbalancer.Deployment(newParams().Config, logger, param.Instance) + updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -194,17 +164,17 @@ func TestExpectedDeployments(t *testing.T) { t.Run("should update deployment container when the container image is updated", func(t *testing.T) { ctx := context.Background() - createObjectIfNotExists(t, "test-loadbalancer", &expectedDeploy) + createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) orgUID := expectedDeploy.OwnerReferences[0].UID updatedParam := newParams("test/test-img") - updatedDeploy := loadbalancer.Deployment(updatedParam.Config, logger, updatedParam.Instance) + updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-loadbalancer"}) + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -215,7 +185,7 @@ func TestExpectedDeployments(t *testing.T) { t.Run("should delete deployment", func(t *testing.T) { labels := map[string]string{ - "app.kubernetes.io/instance": "test.loadbalancer", + "app.kubernetes.io/instance": "test.targetallocator", "app.kubernetes.io/managed-by": "opentelemetry-operator", } deploy := v1.Deployment{} diff --git a/pkg/loadbalancer/reconcile/helper.go b/pkg/targetallocator/reconcile/helper.go similarity index 85% rename from pkg/loadbalancer/reconcile/helper.go rename to pkg/targetallocator/reconcile/helper.go index 299ab66221..273d84d978 100644 --- a/pkg/loadbalancer/reconcile/helper.go +++ b/pkg/targetallocator/reconcile/helper.go @@ -17,11 +17,11 @@ package reconcile import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - lbadapters "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer/adapters" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) func checkMode(params Params) bool { - return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && len(params.Instance.Spec.LoadBalancer.Mode) > 0 + return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled } func checkConfig(params Params) (map[interface{}]interface{}, error) { @@ -30,5 +30,5 @@ func checkConfig(params Params) (map[interface{}]interface{}, error) { return nil, err } - return lbadapters.ConfigToPromConfig(config) + return ta.ConfigToPromConfig(config) } diff --git a/pkg/loadbalancer/reconcile/params.go b/pkg/targetallocator/reconcile/params.go similarity index 100% rename from pkg/loadbalancer/reconcile/params.go rename to pkg/targetallocator/reconcile/params.go diff --git a/pkg/loadbalancer/reconcile/service.go b/pkg/targetallocator/reconcile/service.go similarity index 90% rename from pkg/loadbalancer/reconcile/service.go rename to pkg/targetallocator/reconcile/service.go index 1d7e9efd4b..b5ce46d245 100644 --- a/pkg/loadbalancer/reconcile/service.go +++ b/pkg/targetallocator/reconcile/service.go @@ -26,8 +26,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete @@ -58,24 +58,24 @@ func Services(ctx context.Context, params Params) error { } func desiredService(params Params) corev1.Service { - labels := loadbalancer.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.LBService(params.Instance) + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - selector := loadbalancer.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.LoadBalancer(params.Instance) + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) return corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: naming.LBService(params.Instance), + Name: naming.TAService(params.Instance), Namespace: params.Instance.Namespace, Labels: labels, }, Spec: corev1.ServiceSpec{ Selector: selector, Ports: []corev1.ServicePort{{ - Name: "loadbalancing", - Port: 80, - TargetPort: intstr.FromInt(80), + Name: "targetallocation", + Port: 443, + TargetPort: intstr.FromInt(443), }}, }, } @@ -130,7 +130,7 @@ func deleteServices(ctx context.Context, params Params, expected []corev1.Servic opts := []client.ListOption{ client.InNamespace(params.Instance.Namespace), client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "loadbalancer"), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), "app.kubernetes.io/managed-by": "opentelemetry-operator", }), } diff --git a/pkg/loadbalancer/reconcile/service_test.go b/pkg/targetallocator/reconcile/service_test.go similarity index 72% rename from pkg/loadbalancer/reconcile/service_test.go rename to pkg/targetallocator/reconcile/service_test.go index 13d21b59f8..aa1672a11f 100644 --- a/pkg/loadbalancer/reconcile/service_test.go +++ b/pkg/targetallocator/reconcile/service_test.go @@ -24,13 +24,13 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) func TestDesiredService(t *testing.T) { t.Run("should return service with default port", func(t *testing.T) { - expected := service("test-loadbalancer") + expected := service("test-targetallocator") actual := desiredService(params()) assert.Equal(t, expected, actual) @@ -40,10 +40,10 @@ func TestDesiredService(t *testing.T) { func TestExpectedServices(t *testing.T) { t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), params(), []corev1.Service{service("loadbalancer")}) + err := expectedServices(context.Background(), params(), []corev1.Service{service("targetallocator")}) assert.NoError(t, err) - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "loadbalancer"}) + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) assert.NoError(t, err) assert.True(t, exists) @@ -53,17 +53,17 @@ func TestExpectedServices(t *testing.T) { func TestDeleteServices(t *testing.T) { t.Run("should delete excess services", func(t *testing.T) { - deleteService := service("test-delete-loadbalancer", 8888) - createObjectIfNotExists(t, "test-delete-loadbalancer", &deleteService) + deleteService := service("test-delete-targetallocator", 8888) + createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(params())}) assert.NoError(t, err) - exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-loadbalancer"}) + exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.NoError(t, err) assert.False(t, exists) @@ -71,16 +71,16 @@ func TestDeleteServices(t *testing.T) { } func service(name string, portOpt ...int32) corev1.Service { - port := int32(80) + port := int32(443) if len(portOpt) > 0 { port = portOpt[0] } params := params() - labels := loadbalancer.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.LBService(params.Instance) + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - selector := loadbalancer.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.LoadBalancer(params.Instance) + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) return corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -91,9 +91,9 @@ func service(name string, portOpt ...int32) corev1.Service { Spec: corev1.ServiceSpec{ Selector: selector, Ports: []corev1.ServicePort{{ - Name: "loadbalancing", + Name: "targetallocation", Port: port, - TargetPort: intstr.FromInt(80), + TargetPort: intstr.FromInt(443), }}, }, } diff --git a/pkg/loadbalancer/reconcile/suite_test.go b/pkg/targetallocator/reconcile/suite_test.go similarity index 94% rename from pkg/loadbalancer/reconcile/suite_test.go rename to pkg/targetallocator/reconcile/suite_test.go index d4b2063ba5..cef4205192 100644 --- a/pkg/loadbalancer/reconcile/suite_test.go +++ b/pkg/targetallocator/reconcile/suite_test.go @@ -110,8 +110,8 @@ func params() Params { }, NodePort: 0, }}, - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: v1alpha1.ModeLeastConnection, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, }, Replicas: &replicas, Config: string(configYAML), @@ -130,7 +130,7 @@ func newParams(containerImage ...string) Params { } cfg := config.New() - defaultContainerImage := cfg.LoadBalancerImage() + defaultContainerImage := cfg.TargetAllocatorImage() if len(containerImage) > 0 && len(containerImage[0]) > 0 { defaultContainerImage = containerImage[0] } @@ -159,9 +159,9 @@ func newParams(containerImage ...string) Params { }, NodePort: 0, }}, - LoadBalancer: v1alpha1.OpenTelemetryLoadBalancer{ - Mode: v1alpha1.ModeLeastConnection, - Image: defaultContainerImage, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + Image: defaultContainerImage, }, Replicas: &replicas, Config: string(configYAML), diff --git a/pkg/loadbalancer/reconcile/suite_test.yaml b/pkg/targetallocator/reconcile/suite_test.yaml similarity index 91% rename from pkg/loadbalancer/reconcile/suite_test.yaml rename to pkg/targetallocator/reconcile/suite_test.yaml index 20c32050b3..63e354e574 100644 --- a/pkg/loadbalancer/reconcile/suite_test.yaml +++ b/pkg/targetallocator/reconcile/suite_test.yaml @@ -14,6 +14,6 @@ exporters: service: pipelines: traces: - receivers: [prometheus] + receivers: [jaeger] processors: [] exporters: [logging] \ No newline at end of file diff --git a/pkg/loadbalancer/volume.go b/pkg/targetallocator/volume.go similarity index 84% rename from pkg/loadbalancer/volume.go rename to pkg/targetallocator/volume.go index 1baa02b794..33f901ff6e 100644 --- a/pkg/loadbalancer/volume.go +++ b/pkg/targetallocator/volume.go @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package collector handles the OpenTelemetry Collector. -package loadbalancer +package targetallocator import ( corev1 "k8s.io/api/core/v1" @@ -26,14 +25,14 @@ import ( // Volumes builds the volumes for the given instance, including the config map volume. func Volumes(cfg config.Config, otelcol v1alpha1.OpenTelemetryCollector) []corev1.Volume { volumes := []corev1.Volume{{ - Name: naming.LBConfigMapVolume(), + Name: naming.TAConfigMapVolume(), VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: naming.LBConfigMap(otelcol)}, + LocalObjectReference: corev1.LocalObjectReference{Name: naming.TAConfigMap(otelcol)}, Items: []corev1.KeyToPath{ { - Key: cfg.LoadBalancerConfigMapEntry(), - Path: cfg.LoadBalancerConfigMapEntry(), + Key: cfg.TargetAllocatorConfigMapEntry(), + Path: cfg.TargetAllocatorConfigMapEntry(), }}, }, }, diff --git a/pkg/loadbalancer/volume_test.go b/pkg/targetallocator/volume_test.go similarity index 84% rename from pkg/loadbalancer/volume_test.go rename to pkg/targetallocator/volume_test.go index f44b25cd1f..11b0da1f2b 100644 --- a/pkg/loadbalancer/volume_test.go +++ b/pkg/targetallocator/volume_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loadbalancer_test +package targetallocator import ( "testing" @@ -21,7 +21,6 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - . "github.com/open-telemetry/opentelemetry-operator/pkg/loadbalancer" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" ) @@ -39,6 +38,6 @@ func TestVolumeNewDefault(t *testing.T) { //check if the number of elements in the volume source items list is 1 assert.Len(t, volumes[0].VolumeSource.ConfigMap.Items, 1) - // check that it's the lb-internal volume, with the config map - assert.Equal(t, naming.LBConfigMapVolume(), volumes[0].Name) + // check that it's the ta-internal volume, with the config map + assert.Equal(t, naming.TAConfigMapVolume(), volumes[0].Name) } diff --git a/tests/e2e/loadbalancer-features/00-assert.yaml b/tests/e2e/loadbalancer-features/00-assert.yaml index 9ef86334a0..863b0c88ae 100644 --- a/tests/e2e/loadbalancer-features/00-assert.yaml +++ b/tests/e2e/loadbalancer-features/00-assert.yaml @@ -1,12 +1,12 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: stateful-loadbalancer + name: stateful-targetallocator spec: template: spec: containers: - - name: lb-container + - name: ta-container env: - name: OTEL_NAMESPACE valueFrom: @@ -14,14 +14,14 @@ spec: fieldPath: metadata.namespace volumeMounts: - mountPath: /conf - name: lb-internal + name: ta-internal volumes: - configMap: items: - - key: loadbalancer.yaml - path: loadbalancer.yaml - name: stateful-loadbalancer - name: lb-internal + - key: targetallocator.yaml + path: targetallocator.yaml + name: stateful-targetallocator + name: ta-internal status: replicas: 1 readyReplicas: 1 \ No newline at end of file diff --git a/tests/e2e/loadbalancer-features/00-install.yaml b/tests/e2e/loadbalancer-features/00-install.yaml index b05c1aa928..54c412dc20 100644 --- a/tests/e2e/loadbalancer-features/00-install.yaml +++ b/tests/e2e/loadbalancer-features/00-install.yaml @@ -4,10 +4,14 @@ metadata: name: stateful spec: mode: statefulset - loadbalancer: - mode: LeastConnection + targetAllocator: + enabled: true config: | receivers: + jaeger: + protocols: + grpc: + # Collect own metrics prometheus: config: @@ -17,12 +21,13 @@ spec: static_configs: - targets: [ '0.0.0.0:8888' ] - processors: - exporters: - logging: - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] diff --git a/tests/e2e/smoke-loadbalancer/00-assert.yaml b/tests/e2e/smoke-loadbalancer/00-assert.yaml index ade50d8607..4131a8be41 100644 --- a/tests/e2e/smoke-loadbalancer/00-assert.yaml +++ b/tests/e2e/smoke-loadbalancer/00-assert.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: stateful-loadbalancer + name: stateful-targetallocator status: replicas: 1 readyReplicas: 1 diff --git a/tests/e2e/smoke-loadbalancer/00-install.yaml b/tests/e2e/smoke-loadbalancer/00-install.yaml index b05c1aa928..36eb9c9b1c 100644 --- a/tests/e2e/smoke-loadbalancer/00-install.yaml +++ b/tests/e2e/smoke-loadbalancer/00-install.yaml @@ -4,10 +4,14 @@ metadata: name: stateful spec: mode: statefulset - loadbalancer: - mode: LeastConnection + targetAllocator: + enabled: true config: | receivers: + jaeger: + protocols: + grpc: + # Collect own metrics prometheus: config: @@ -17,12 +21,13 @@ spec: static_configs: - targets: [ '0.0.0.0:8888' ] - processors: - exporters: - logging: - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] From a0d3f34a3550837f43c4f3749130e141a94b41f4 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 16 Jul 2021 16:21:44 -0700 Subject: [PATCH 11/30] Updated bundle files --- bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml | 2 +- config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml index c2baf8970e..90a6b7c871 100644 --- a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -441,7 +441,7 @@ spec: type: boolean image: description: Image indicates the container image to use for the - OpenTelemetry target allocator. + OpenTelemetry TargetAllocator. type: string type: object tolerations: diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index 39bd27561e..9c41529672 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -429,7 +429,7 @@ spec: type: boolean image: description: Image indicates the container image to use for the - OpenTelemetry target allocator. + OpenTelemetry TargetAllocator. type: string type: object tolerations: From 492749e223fb6b1c4a212c7f63f0ad4b4c65c56f Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 20 Jul 2021 16:44:24 -0700 Subject: [PATCH 12/30] Minor changes --- controllers/targetallocator_controller.go | 2 ++ internal/config/main.go | 3 ++- pkg/targetallocator/reconcile/configmap.go | 2 +- pkg/targetallocator/reconcile/deployment.go | 2 +- pkg/targetallocator/reconcile/helper.go | 2 +- pkg/targetallocator/reconcile/service.go | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index b8af2a8537..bf05545e1c 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -90,6 +90,8 @@ func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { } // Reconcile the current state of an OpenTelemetry target allocation resource with the desired state. +// follows the same design as the opentelemetrycollector controller where an empty context is passed +// and a new context initialized inside func (r *OpenTelemetryTargetAllocatorReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() log := r.log.WithValues("opentelemtrytargetallocator", req.NamespacedName) diff --git a/internal/config/main.go b/internal/config/main.go index b2a57dd6ea..5fc713d3d1 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -75,7 +75,8 @@ func New(opts ...Option) Config { } if len(o.targetAllocatorImage) == 0 { - o.targetAllocatorImage = "raul9595/otel-loadbalancer" + // will be replcaed with target allocator image once approved and available + o.targetAllocatorImage = "" } return Config{ diff --git a/pkg/targetallocator/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go index 6c5a6a666e..1ec4afca71 100644 --- a/pkg/targetallocator/reconcile/configmap.go +++ b/pkg/targetallocator/reconcile/configmap.go @@ -36,7 +36,7 @@ import ( func ConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - if checkMode(params) { + if checkEnabled(params) { cm, err := desiredConfigMap(ctx, params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) diff --git a/pkg/targetallocator/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go index 5b50827cfe..4793d7540a 100644 --- a/pkg/targetallocator/reconcile/deployment.go +++ b/pkg/targetallocator/reconcile/deployment.go @@ -34,7 +34,7 @@ import ( func Deployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - if checkMode(params) { + if checkEnabled(params) { _, err := checkConfig(params) if err != nil { return fmt.Errorf("failed to parse Promtheus config: %v", err) diff --git a/pkg/targetallocator/reconcile/helper.go b/pkg/targetallocator/reconcile/helper.go index 273d84d978..a9dbda762a 100644 --- a/pkg/targetallocator/reconcile/helper.go +++ b/pkg/targetallocator/reconcile/helper.go @@ -20,7 +20,7 @@ import ( ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func checkMode(params Params) bool { +func checkEnabled(params Params) bool { return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled } diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go index b5ce46d245..bd58c1f2b6 100644 --- a/pkg/targetallocator/reconcile/service.go +++ b/pkg/targetallocator/reconcile/service.go @@ -36,7 +36,7 @@ import ( func Services(ctx context.Context, params Params) error { desired := []corev1.Service{} - if checkMode(params) { + if checkEnabled(params) { _, err := checkConfig(params) if err != nil { return fmt.Errorf("failed to parse Promtheus config: %v", err) From 6a72424cddcb943ffb7aac27de7d0728e97beedb Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 20 Jul 2021 17:25:20 -0700 Subject: [PATCH 13/30] Lint and default file fixes --- controllers/targetallocator_controller.go | 2 +- internal/config/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index bf05545e1c..4eb1a8fd6d 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -91,7 +91,7 @@ func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { // Reconcile the current state of an OpenTelemetry target allocation resource with the desired state. // follows the same design as the opentelemetrycollector controller where an empty context is passed -// and a new context initialized inside +// and a new context initialized inside. func (r *OpenTelemetryTargetAllocatorReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() log := r.log.WithValues("opentelemtrytargetallocator", req.NamespacedName) diff --git a/internal/config/main.go b/internal/config/main.go index 5fc713d3d1..49c49e9545 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -76,7 +76,7 @@ func New(opts ...Option) Config { if len(o.targetAllocatorImage) == 0 { // will be replcaed with target allocator image once approved and available - o.targetAllocatorImage = "" + o.targetAllocatorImage = "raul9595/otel-loadbalancer:0.0.1" } return Config{ From 8657668cf6237c6eb6e4074c71e7414f33322820 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 30 Jul 2021 00:30:13 -0700 Subject: [PATCH 14/30] Added rolebinding to automate manual setting --- config/rbac/role.yaml | 28 ++++ controllers/targetallocator_controller.go | 13 ++ pkg/naming/main.go | 10 ++ pkg/targetallocator/reconcile/deployment.go | 2 +- pkg/targetallocator/reconcile/role.go | 136 ++++++++++++++++++ pkg/targetallocator/reconcile/role_test.go | 89 ++++++++++++ pkg/targetallocator/reconcile/rolebinding.go | 135 +++++++++++++++++ .../reconcile/rolebinding_test.go | 94 ++++++++++++ pkg/targetallocator/reconcile/service.go | 2 +- 9 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 pkg/targetallocator/reconcile/role.go create mode 100644 pkg/targetallocator/reconcile/role_test.go create mode 100644 pkg/targetallocator/reconcile/rolebinding.go create mode 100644 pkg/targetallocator/reconcile/rolebinding_test.go diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cebff2bbaa..0783ad3d8c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -32,6 +32,14 @@ rules: verbs: - list - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch - apiGroups: - "" resources: @@ -129,3 +137,23 @@ rules: - get - patch - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - create + - delete + - get + - list + - watch diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index 4eb1a8fd6d..a5b1d95ab7 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -24,6 +24,7 @@ import ( "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -62,6 +63,16 @@ type TgAlParams struct { func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { p.Tasks = []TgAlTask{ + { + "roles", + reconcile.Roles, + true, + }, + { + "role bindings", + reconcile.RoleBindings, + true, + }, { "config maps", reconcile.ConfigMaps, @@ -136,6 +147,8 @@ func (r *OpenTelemetryTargetAllocatorReconciler) RunTasks(ctx context.Context, p func (r *OpenTelemetryTargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.OpenTelemetryCollector{}). + Owns(&rbacv1.Role{}). + Owns(&rbacv1.RoleBinding{}). Owns(&corev1.ConfigMap{}). Owns(&corev1.Service{}). Owns(&appsv1.Deployment{}). diff --git a/pkg/naming/main.go b/pkg/naming/main.go index 49970f4ec7..cf6bc9f025 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -85,3 +85,13 @@ func TAService(otelcol v1alpha1.OpenTelemetryCollector) string { func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } + +// TARole builds the role name based on the instance. +func TARole(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-view", otelcol.Name) +} + +// TARoleBinding builds the role binding name based on the instance. +func TARoleBinding(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) +} diff --git a/pkg/targetallocator/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go index 4793d7540a..db7293f317 100644 --- a/pkg/targetallocator/reconcile/deployment.go +++ b/pkg/targetallocator/reconcile/deployment.go @@ -37,7 +37,7 @@ func Deployments(ctx context.Context, params Params) error { if checkEnabled(params) { _, err := checkConfig(params) if err != nil { - return fmt.Errorf("failed to parse Promtheus config: %v", err) + return fmt.Errorf("failed to parse Prometheus config: %v", err) } desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) } diff --git a/pkg/targetallocator/reconcile/role.go b/pkg/targetallocator/reconcile/role.go new file mode 100644 index 0000000000..fe069b12d9 --- /dev/null +++ b/pkg/targetallocator/reconcile/role.go @@ -0,0 +1,136 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;create;list;watch;delete + +// Roles reconciles the role(s) required for the instance in the current context. +func Roles(ctx context.Context, params Params) error { + desired := []rbacv1.Role{} + + if checkEnabled(params) { + _, err := checkConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, desiredRoles(params)) + } + + // first, handle the create/update parts + if err := expectedRoles(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected services: %v", err) + } + + // then, delete the extra objects + if err := deleteRoles(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + } + + return nil +} + +func desiredRoles(params Params) rbacv1.Role { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TARole(params.Instance) + + return rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TARole(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Rules: []rbacv1.PolicyRule{{ + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "watch", "list"}, + }}, + } +} + +func expectedRoles(ctx context.Context, params Params, expected []rbacv1.Role) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &rbacv1.Role{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "role.name", desired.Name, "role.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + params.Log.V(2).Info("applied", "role.name", desired.Name, "role.namespace", desired.Namespace) + } + + return nil +} + +func deleteRoles(ctx context.Context, params Params, expected []rbacv1.Role) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &rbacv1.RoleList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "role.name", existing.Name, "role.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/targetallocator/reconcile/role_test.go b/pkg/targetallocator/reconcile/role_test.go new file mode 100644 index 0000000000..d8d463113e --- /dev/null +++ b/pkg/targetallocator/reconcile/role_test.go @@ -0,0 +1,89 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +func TestDesiredRole(t *testing.T) { + t.Run("should return role", func(t *testing.T) { + expected := role("test-view") + actual := desiredRoles(params()) + + assert.Equal(t, expected, actual) + }) + +} + +func TestExpectedRoles(t *testing.T) { + t.Run("should create the role", func(t *testing.T) { + err := expectedRoles(context.Background(), params(), []rbacv1.Role{role("view")}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "view"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) +} + +func TestDeleteRoles(t *testing.T) { + t.Run("should delete excess roles", func(t *testing.T) { + deleteRole := role("test-delete-view") + createObjectIfNotExists(t, "test-delete-view", &deleteRole) + + exists, err := populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "test-delete-view"}) + assert.NoError(t, err) + assert.True(t, exists) + + err = deleteRoles(context.Background(), params(), []rbacv1.Role{desiredRoles(params())}) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "test-delete-view"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func role(name string) rbacv1.Role { + params := params() + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TARole(params.Instance) + + return rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Rules: []rbacv1.PolicyRule{{ + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "watch", "list"}, + }}, + } +} diff --git a/pkg/targetallocator/reconcile/rolebinding.go b/pkg/targetallocator/reconcile/rolebinding.go new file mode 100644 index 0000000000..6e8d0c22ba --- /dev/null +++ b/pkg/targetallocator/reconcile/rolebinding.go @@ -0,0 +1,135 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + rbacv1 "k8s.io/api/rbac/v1" + v1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;create;list;watch;delete + +// RoleBindings reconciles the role binding(s) required for the instance in the current context. +func RoleBindings(ctx context.Context, params Params) error { + desired := []rbacv1.RoleBinding{} + + if checkEnabled(params) { + _, err := checkConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, desiredRoleBinding(params)) + } + + // first, handle the create/update parts + if err := expectedRoleBindings(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected services: %v", err) + } + + // then, delete the extra objects + if err := deleteRoleBindings(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + } + + return nil +} + +func desiredRoleBinding(params Params) rbacv1.RoleBinding { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TARoleBinding(params.Instance) + + return rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TARoleBinding(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Subjects: []rbacv1.Subject{{ + Kind: "ServiceAccount", + Name: "default", + Namespace: params.Instance.Namespace, + }}, + RoleRef: v1.RoleRef{ + Kind: "Role", + Name: naming.TARole(params.Instance), + }, + } +} + +func expectedRoleBindings(ctx context.Context, params Params, expected []rbacv1.RoleBinding) error { + for _, obj := range expected { + desired := obj + + existing := &rbacv1.RoleBinding{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "rolebinding.name", desired.Name, "rolebinding.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + params.Log.V(2).Info("applied", "rolebinding.name", desired.Name, "rolebinding.namespace", desired.Namespace) + } + + return nil +} + +func deleteRoleBindings(ctx context.Context, params Params, expected []rbacv1.RoleBinding) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &rbacv1.RoleBindingList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "rolebinding.name", existing.Name, "rolebinding.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/targetallocator/reconcile/rolebinding_test.go b/pkg/targetallocator/reconcile/rolebinding_test.go new file mode 100644 index 0000000000..ffc601a5a1 --- /dev/null +++ b/pkg/targetallocator/reconcile/rolebinding_test.go @@ -0,0 +1,94 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + rbacv1 "k8s.io/api/rbac/v1" + v1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +func TestDesiredRoleBinding(t *testing.T) { + t.Run("should return role binding", func(t *testing.T) { + expected := roleBinding("test-targetallocator") + actual := desiredRoleBinding(params()) + + assert.Equal(t, expected, actual) + }) + +} + +func TestExpectedRoleBindings(t *testing.T) { + t.Run("should create the role binding", func(t *testing.T) { + err := expectedRoleBindings(context.Background(), params(), []rbacv1.RoleBinding{roleBinding("targetallocator")}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) +} + +func TestDeleteRoleBindings(t *testing.T) { + t.Run("should delete excess role bindings", func(t *testing.T) { + deleteRB := roleBinding("test-delete-targetallocator") + createObjectIfNotExists(t, "test-delete-targetallocator", &deleteRB) + + exists, err := populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.True(t, exists) + + err = deleteRoleBindings(context.Background(), params(), []rbacv1.RoleBinding{desiredRoleBinding(params())}) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func roleBinding(name string) rbacv1.RoleBinding { + params := params() + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TARoleBinding(params.Instance) + + return rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Subjects: []rbacv1.Subject{{ + Kind: "ServiceAccount", + Name: "default", + Namespace: params.Instance.Namespace, + }}, + RoleRef: v1.RoleRef{ + Kind: "Role", + Name: naming.TARole(params.Instance), + }, + } +} diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go index bd58c1f2b6..3d69460fc4 100644 --- a/pkg/targetallocator/reconcile/service.go +++ b/pkg/targetallocator/reconcile/service.go @@ -39,7 +39,7 @@ func Services(ctx context.Context, params Params) error { if checkEnabled(params) { _, err := checkConfig(params) if err != nil { - return fmt.Errorf("failed to parse Promtheus config: %v", err) + return fmt.Errorf("failed to parse Prometheus config: %v", err) } desired = append(desired, desiredService(params)) } From 0528856a2cf812fff9fd494a84520bb74709958f Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 30 Jul 2021 00:38:12 -0700 Subject: [PATCH 15/30] Update opentelemetry-operator.clusterserviceversion.yaml --- ...emetry-operator.clusterserviceversion.yaml | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml index 1f0d528533..7e4f1fceb5 100644 --- a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -21,11 +21,11 @@ metadata: containerImage: quay.io/opentelemetry/opentelemetry-operator createdAt: "2020-12-16T13:37:00+00:00" description: Provides the OpenTelemetry components, including the Collector - operators.operatorframework.io/builder: operator-sdk-v1.8.0+git + operators.operatorframework.io/builder: operator-sdk-v1.8.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 repository: github.com/open-telemetry/opentelemetry-operator support: OpenTelemetry Community - name: opentelemetry-operator.v0.29.0 + name: opentelemetry-operator.v0.29.0-21-g8657668 namespace: placeholder spec: apiservicedefinitions: {} @@ -77,6 +77,14 @@ spec: verbs: - list - watch + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch - apiGroups: - "" resources: @@ -174,6 +182,26 @@ spec: - get - patch - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - create + - delete + - get + - list + - watch - apiGroups: - authentication.k8s.io resources: @@ -297,7 +325,7 @@ spec: maturity: alpha provider: name: OpenTelemetry Community - version: 0.29.0 + version: 0.29.0-21-g8657668 webhookdefinitions: - admissionReviewVersions: - v1 From bf1010ba2a17ef72a325a3deb233c0ede573bfd4 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 3 Aug 2021 12:33:50 -0700 Subject: [PATCH 16/30] Removed role/rolebinding files & Minor changes --- ...emetry-operator.clusterserviceversion.yaml | 32 +---- config/rbac/role.yaml | 28 ---- controllers/targetallocator_controller.go | 24 +--- .../targetallocator_controller_test.go | 13 +- pkg/naming/main.go | 10 -- pkg/targetallocator/container.go | 2 +- pkg/targetallocator/deployment_test.go | 12 -- pkg/targetallocator/reconcile/configmap.go | 4 +- pkg/targetallocator/reconcile/deployment.go | 4 +- pkg/targetallocator/reconcile/helper.go | 4 +- pkg/targetallocator/reconcile/role.go | 136 ------------------ pkg/targetallocator/reconcile/role_test.go | 89 ------------ pkg/targetallocator/reconcile/rolebinding.go | 135 ----------------- .../reconcile/rolebinding_test.go | 94 ------------ pkg/targetallocator/reconcile/service.go | 4 +- 15 files changed, 22 insertions(+), 569 deletions(-) delete mode 100644 pkg/targetallocator/reconcile/role.go delete mode 100644 pkg/targetallocator/reconcile/role_test.go delete mode 100644 pkg/targetallocator/reconcile/rolebinding.go delete mode 100644 pkg/targetallocator/reconcile/rolebinding_test.go diff --git a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml index e2ea419bd5..76ec2bdbac 100644 --- a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -25,7 +25,7 @@ metadata: operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 repository: github.com/open-telemetry/opentelemetry-operator support: OpenTelemetry Community - name: opentelemetry-operator.v0.30.0 + name: opentelemetry-operator.v0.30.0-21-g1a921cb namespace: placeholder spec: apiservicedefinitions: {} @@ -77,14 +77,6 @@ spec: verbs: - list - watch - - apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - watch - apiGroups: - "" resources: @@ -182,26 +174,6 @@ spec: - get - patch - update - - apiGroups: - - rbac.authorization.k8s.io - resources: - - rolebindings - verbs: - - create - - delete - - get - - list - - watch - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - verbs: - - create - - delete - - get - - list - - watch - apiGroups: - authentication.k8s.io resources: @@ -325,7 +297,7 @@ spec: maturity: alpha provider: name: OpenTelemetry Community - version: 0.30.0 + version: 0.30.0-21-g1a921cb webhookdefinitions: - admissionReviewVersions: - v1 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0783ad3d8c..cebff2bbaa 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -32,14 +32,6 @@ rules: verbs: - list - watch -- apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - watch - apiGroups: - "" resources: @@ -137,23 +129,3 @@ rules: - get - patch - update -- apiGroups: - - rbac.authorization.k8s.io - resources: - - rolebindings - verbs: - - create - - delete - - get - - list - - watch -- apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - verbs: - - create - - delete - - get - - list - - watch diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index a5b1d95ab7..c05562a291 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -19,14 +19,12 @@ import ( "context" "fmt" - ctrl "sigs.k8s.io/controller-runtime" - "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" @@ -59,20 +57,10 @@ type TgAlParams struct { Tasks []TgAlTask } -// NewTgAlReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. -func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { +// NewTargetAlllocatorReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. +func NewTargetAlllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { p.Tasks = []TgAlTask{ - { - "roles", - reconcile.Roles, - true, - }, - { - "role bindings", - reconcile.RoleBindings, - true, - }, { "config maps", reconcile.ConfigMaps, @@ -105,12 +93,12 @@ func NewTgAlReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { // and a new context initialized inside. func (r *OpenTelemetryTargetAllocatorReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() - log := r.log.WithValues("opentelemtrytargetallocator", req.NamespacedName) + log := r.log.WithValues("opentelemetrytargetallocator", req.NamespacedName) var instance v1alpha1.OpenTelemetryCollector if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { if !apierrors.IsNotFound(err) { - log.Error(err, "unable to fetch OpenTelemetryTargetAllocator") + log.Error(err, "unable to fetch OpenTelemetryCollector") } return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -147,8 +135,6 @@ func (r *OpenTelemetryTargetAllocatorReconciler) RunTasks(ctx context.Context, p func (r *OpenTelemetryTargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.OpenTelemetryCollector{}). - Owns(&rbacv1.Role{}). - Owns(&rbacv1.RoleBinding{}). Owns(&corev1.ConfigMap{}). Owns(&corev1.Service{}). Owns(&appsv1.Deployment{}). diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go index 692b1cfe69..348e0fa818 100644 --- a/controllers/targetallocator_controller_test.go +++ b/controllers/targetallocator_controller_test.go @@ -41,11 +41,10 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { // prepare cfg := config.New() configYAML, err := ioutil.ReadFile("../pkg/targetallocator/reconcile/suite_test.yaml") - if err != nil { - fmt.Printf("Error getting yaml file: %v", err) - } + require.NoError(t, err) + nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: testScheme, @@ -114,7 +113,7 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { func TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { // prepare taskCalled := false - reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ Log: logger, Tasks: []controllers.TgAlTask{ { @@ -148,7 +147,7 @@ func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { taskCalled := false expectedErr := errors.New("should fail!") nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, @@ -198,7 +197,7 @@ func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { // prepare cfg := config.New() nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewTgAlReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, diff --git a/pkg/naming/main.go b/pkg/naming/main.go index cf6bc9f025..49970f4ec7 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -85,13 +85,3 @@ func TAService(otelcol v1alpha1.OpenTelemetryCollector) string { func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } - -// TARole builds the role name based on the instance. -func TARole(otelcol v1alpha1.OpenTelemetryCollector) string { - return fmt.Sprintf("%s-view", otelcol.Name) -} - -// TARoleBinding builds the role binding name based on the instance. -func TARoleBinding(otelcol v1alpha1.OpenTelemetryCollector) string { - return fmt.Sprintf("%s-targetallocator", otelcol.Name) -} diff --git a/pkg/targetallocator/container.go b/pkg/targetallocator/container.go index 5aea95e685..139f9126e6 100644 --- a/pkg/targetallocator/container.go +++ b/pkg/targetallocator/container.go @@ -38,7 +38,7 @@ func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelem envVars := []corev1.EnvVar{} envVars = append(envVars, corev1.EnvVar{ - Name: "OTEL_NAMESPACE", + Name: "OTELCOL_NAMESPACE", ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ FieldPath: "metadata.namespace", diff --git a/pkg/targetallocator/deployment_test.go b/pkg/targetallocator/deployment_test.go index 4cd9fd4133..ce8e4f0fcb 100644 --- a/pkg/targetallocator/deployment_test.go +++ b/pkg/targetallocator/deployment_test.go @@ -19,30 +19,18 @@ import ( "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" ) -var testTolerationValues = []v1.Toleration{ - { - Key: "key", - Value: "Val", - Effect: "NoSchedule", - }, -} - func TestDeploymentNewDefault(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ ObjectMeta: metav1.ObjectMeta{ Name: "my-instance", }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Tolerations: testTolerationValues, - }, } cfg := config.New() diff --git a/pkg/targetallocator/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go index 1ec4afca71..91f05d19f0 100644 --- a/pkg/targetallocator/reconcile/configmap.go +++ b/pkg/targetallocator/reconcile/configmap.go @@ -36,7 +36,7 @@ import ( func ConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - if checkEnabled(params) { + if IsAllocatorEnabled(params) { cm, err := desiredConfigMap(ctx, params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) @@ -62,7 +62,7 @@ func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name - promConfig, err := checkConfig(params) + promConfig, err := GetPromConfig(params) if err != nil { return corev1.ConfigMap{}, err } diff --git a/pkg/targetallocator/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go index db7293f317..a0161645f9 100644 --- a/pkg/targetallocator/reconcile/deployment.go +++ b/pkg/targetallocator/reconcile/deployment.go @@ -34,8 +34,8 @@ import ( func Deployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - if checkEnabled(params) { - _, err := checkConfig(params) + if IsAllocatorEnabled(params) { + _, err := GetPromConfig(params) if err != nil { return fmt.Errorf("failed to parse Prometheus config: %v", err) } diff --git a/pkg/targetallocator/reconcile/helper.go b/pkg/targetallocator/reconcile/helper.go index a9dbda762a..7094031056 100644 --- a/pkg/targetallocator/reconcile/helper.go +++ b/pkg/targetallocator/reconcile/helper.go @@ -20,11 +20,11 @@ import ( ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func checkEnabled(params Params) bool { +func IsAllocatorEnabled(params Params) bool { return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled } -func checkConfig(params Params) (map[interface{}]interface{}, error) { +func GetPromConfig(params Params) (map[interface{}]interface{}, error) { config, err := adapters.ConfigFromString(params.Instance.Spec.Config) if err != nil { return nil, err diff --git a/pkg/targetallocator/reconcile/role.go b/pkg/targetallocator/reconcile/role.go deleted file mode 100644 index fe069b12d9..0000000000 --- a/pkg/targetallocator/reconcile/role.go +++ /dev/null @@ -1,136 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - rbacv1 "k8s.io/api/rbac/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch -// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;create;list;watch;delete - -// Roles reconciles the role(s) required for the instance in the current context. -func Roles(ctx context.Context, params Params) error { - desired := []rbacv1.Role{} - - if checkEnabled(params) { - _, err := checkConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, desiredRoles(params)) - } - - // first, handle the create/update parts - if err := expectedRoles(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) - } - - // then, delete the extra objects - if err := deleteRoles(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) - } - - return nil -} - -func desiredRoles(params Params) rbacv1.Role { - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TARole(params.Instance) - - return rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: naming.TARole(params.Instance), - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Rules: []rbacv1.PolicyRule{{ - APIGroups: []string{""}, - Resources: []string{"pods"}, - Verbs: []string{"get", "watch", "list"}, - }}, - } -} - -func expectedRoles(ctx context.Context, params Params, expected []rbacv1.Role) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &rbacv1.Role{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "role.name", desired.Name, "role.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - params.Log.V(2).Info("applied", "role.name", desired.Name, "role.namespace", desired.Namespace) - } - - return nil -} - -func deleteRoles(ctx context.Context, params Params, expected []rbacv1.Role) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &rbacv1.RoleList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "role.name", existing.Name, "role.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/targetallocator/reconcile/role_test.go b/pkg/targetallocator/reconcile/role_test.go deleted file mode 100644 index d8d463113e..0000000000 --- a/pkg/targetallocator/reconcile/role_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestDesiredRole(t *testing.T) { - t.Run("should return role", func(t *testing.T) { - expected := role("test-view") - actual := desiredRoles(params()) - - assert.Equal(t, expected, actual) - }) - -} - -func TestExpectedRoles(t *testing.T) { - t.Run("should create the role", func(t *testing.T) { - err := expectedRoles(context.Background(), params(), []rbacv1.Role{role("view")}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "view"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) -} - -func TestDeleteRoles(t *testing.T) { - t.Run("should delete excess roles", func(t *testing.T) { - deleteRole := role("test-delete-view") - createObjectIfNotExists(t, "test-delete-view", &deleteRole) - - exists, err := populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "test-delete-view"}) - assert.NoError(t, err) - assert.True(t, exists) - - err = deleteRoles(context.Background(), params(), []rbacv1.Role{desiredRoles(params())}) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &rbacv1.Role{}, types.NamespacedName{Namespace: "default", Name: "test-delete-view"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func role(name string) rbacv1.Role { - params := params() - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TARole(params.Instance) - - return rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Rules: []rbacv1.PolicyRule{{ - APIGroups: []string{""}, - Resources: []string{"pods"}, - Verbs: []string{"get", "watch", "list"}, - }}, - } -} diff --git a/pkg/targetallocator/reconcile/rolebinding.go b/pkg/targetallocator/reconcile/rolebinding.go deleted file mode 100644 index 6e8d0c22ba..0000000000 --- a/pkg/targetallocator/reconcile/rolebinding.go +++ /dev/null @@ -1,135 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - rbacv1 "k8s.io/api/rbac/v1" - v1 "k8s.io/api/rbac/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;create;list;watch;delete - -// RoleBindings reconciles the role binding(s) required for the instance in the current context. -func RoleBindings(ctx context.Context, params Params) error { - desired := []rbacv1.RoleBinding{} - - if checkEnabled(params) { - _, err := checkConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, desiredRoleBinding(params)) - } - - // first, handle the create/update parts - if err := expectedRoleBindings(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) - } - - // then, delete the extra objects - if err := deleteRoleBindings(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) - } - - return nil -} - -func desiredRoleBinding(params Params) rbacv1.RoleBinding { - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TARoleBinding(params.Instance) - - return rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: naming.TARoleBinding(params.Instance), - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Subjects: []rbacv1.Subject{{ - Kind: "ServiceAccount", - Name: "default", - Namespace: params.Instance.Namespace, - }}, - RoleRef: v1.RoleRef{ - Kind: "Role", - Name: naming.TARole(params.Instance), - }, - } -} - -func expectedRoleBindings(ctx context.Context, params Params, expected []rbacv1.RoleBinding) error { - for _, obj := range expected { - desired := obj - - existing := &rbacv1.RoleBinding{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "rolebinding.name", desired.Name, "rolebinding.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - params.Log.V(2).Info("applied", "rolebinding.name", desired.Name, "rolebinding.namespace", desired.Namespace) - } - - return nil -} - -func deleteRoleBindings(ctx context.Context, params Params, expected []rbacv1.RoleBinding) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &rbacv1.RoleBindingList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "rolebinding.name", existing.Name, "rolebinding.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/targetallocator/reconcile/rolebinding_test.go b/pkg/targetallocator/reconcile/rolebinding_test.go deleted file mode 100644 index ffc601a5a1..0000000000 --- a/pkg/targetallocator/reconcile/rolebinding_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestDesiredRoleBinding(t *testing.T) { - t.Run("should return role binding", func(t *testing.T) { - expected := roleBinding("test-targetallocator") - actual := desiredRoleBinding(params()) - - assert.Equal(t, expected, actual) - }) - -} - -func TestExpectedRoleBindings(t *testing.T) { - t.Run("should create the role binding", func(t *testing.T) { - err := expectedRoleBindings(context.Background(), params(), []rbacv1.RoleBinding{roleBinding("targetallocator")}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) -} - -func TestDeleteRoleBindings(t *testing.T) { - t.Run("should delete excess role bindings", func(t *testing.T) { - deleteRB := roleBinding("test-delete-targetallocator") - createObjectIfNotExists(t, "test-delete-targetallocator", &deleteRB) - - exists, err := populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.True(t, exists) - - err = deleteRoleBindings(context.Background(), params(), []rbacv1.RoleBinding{desiredRoleBinding(params())}) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &rbacv1.RoleBinding{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func roleBinding(name string) rbacv1.RoleBinding { - params := params() - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TARoleBinding(params.Instance) - - return rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Subjects: []rbacv1.Subject{{ - Kind: "ServiceAccount", - Name: "default", - Namespace: params.Instance.Namespace, - }}, - RoleRef: v1.RoleRef{ - Kind: "Role", - Name: naming.TARole(params.Instance), - }, - } -} diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go index 3d69460fc4..3dc2993076 100644 --- a/pkg/targetallocator/reconcile/service.go +++ b/pkg/targetallocator/reconcile/service.go @@ -36,8 +36,8 @@ import ( func Services(ctx context.Context, params Params) error { desired := []corev1.Service{} - if checkEnabled(params) { - _, err := checkConfig(params) + if IsAllocatorEnabled(params) { + _, err := GetPromConfig(params) if err != nil { return fmt.Errorf("failed to parse Prometheus config: %v", err) } From 38ca88a48ca1bfb1086a8105c9321a98c5509ad9 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 3 Aug 2021 12:44:08 -0700 Subject: [PATCH 17/30] Minor changes --- .../opentelemetry-operator.clusterserviceversion.yaml | 4 ++-- main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml index 76ec2bdbac..f3e69732d3 100644 --- a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -25,7 +25,7 @@ metadata: operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 repository: github.com/open-telemetry/opentelemetry-operator support: OpenTelemetry Community - name: opentelemetry-operator.v0.30.0-21-g1a921cb + name: opentelemetry-operator.0.31.0 namespace: placeholder spec: apiservicedefinitions: {} @@ -297,7 +297,7 @@ spec: maturity: alpha provider: name: OpenTelemetry Community - version: 0.30.0-21-g1a921cb + version: 0.31.0 webhookdefinitions: - admissionReviewVersions: - v1 diff --git a/main.go b/main.go index 95cffd6189..b6850e9c2b 100644 --- a/main.go +++ b/main.go @@ -155,7 +155,7 @@ func main() { os.Exit(1) } - if err = controllers.NewTgAlReconciler(controllers.TgAlParams{ + if err = controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), Scheme: mgr.GetScheme(), From e339ebaa8a294d9a3c0d5d3de6f87293ee9662a1 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 3 Aug 2021 13:08:05 -0700 Subject: [PATCH 18/30] Added error check in configmap reconcile & spelling correction --- controllers/targetallocator_controller.go | 2 +- controllers/targetallocator_controller_test.go | 8 ++++---- main.go | 2 +- pkg/targetallocator/reconcile/configmap.go | 5 ++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index c05562a291..3f7d5e92c0 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -58,7 +58,7 @@ type TgAlParams struct { } // NewTargetAlllocatorReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. -func NewTargetAlllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { +func NewTargetAllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { p.Tasks = []TgAlTask{ { diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go index 348e0fa818..b79b569a00 100644 --- a/controllers/targetallocator_controller_test.go +++ b/controllers/targetallocator_controller_test.go @@ -44,7 +44,7 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { require.NoError(t, err) nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: testScheme, @@ -113,7 +113,7 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { func TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { // prepare taskCalled := false - reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Log: logger, Tasks: []controllers.TgAlTask{ { @@ -147,7 +147,7 @@ func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { taskCalled := false expectedErr := errors.New("should fail!") nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, @@ -197,7 +197,7 @@ func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { // prepare cfg := config.New() nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, diff --git a/main.go b/main.go index b6850e9c2b..424ed67bd6 100644 --- a/main.go +++ b/main.go @@ -155,7 +155,7 @@ func main() { os.Exit(1) } - if err = controllers.NewTargetAlllocatorReconciler(controllers.TgAlParams{ + if err = controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), Scheme: mgr.GetScheme(), diff --git a/pkg/targetallocator/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go index 91f05d19f0..b768fdb6ec 100644 --- a/pkg/targetallocator/reconcile/configmap.go +++ b/pkg/targetallocator/reconcile/configmap.go @@ -73,7 +73,10 @@ func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error "app.kubernetes.io/managed-by": "opentelemetry-operator", } taConfig["config"] = promConfig - taConfigYAML, _ := yaml.Marshal(taConfig) + taConfigYAML, err := yaml.Marshal(taConfig) + if err != nil { + return corev1.ConfigMap{}, err + } return corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ From 6f39c47306cde3d76fc951fdec446e9988217c87 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 3 Aug 2021 14:38:41 -0700 Subject: [PATCH 19/30] Updated target allocator KUTTL tests & renamed folder --- .../e2e/loadbalancer-features/00-assert.yaml | 27 ------- tests/e2e/smoke-loadbalancer/00-assert.yaml | 8 -- .../e2e/smoke-targetallocator/00-install.yaml | 4 + .../e2e/smoke-targetallocator/01-assert.yaml | 21 ++++++ .../01-install.yaml} | 0 .../e2e/smoke-targetallocator/02-install.yaml | 4 + .../targetallocator-features/00-install.yaml | 4 + .../targetallocator-features/01-assert.yaml | 74 +++++++++++++++++++ .../01-install.yaml} | 13 ++++ .../targetallocator-features/02-install.yaml | 4 + 10 files changed, 124 insertions(+), 35 deletions(-) delete mode 100644 tests/e2e/loadbalancer-features/00-assert.yaml delete mode 100644 tests/e2e/smoke-loadbalancer/00-assert.yaml create mode 100644 tests/e2e/smoke-targetallocator/00-install.yaml create mode 100644 tests/e2e/smoke-targetallocator/01-assert.yaml rename tests/e2e/{smoke-loadbalancer/00-install.yaml => smoke-targetallocator/01-install.yaml} (100%) create mode 100644 tests/e2e/smoke-targetallocator/02-install.yaml create mode 100644 tests/e2e/targetallocator-features/00-install.yaml create mode 100644 tests/e2e/targetallocator-features/01-assert.yaml rename tests/e2e/{loadbalancer-features/00-install.yaml => targetallocator-features/01-install.yaml} (70%) create mode 100644 tests/e2e/targetallocator-features/02-install.yaml diff --git a/tests/e2e/loadbalancer-features/00-assert.yaml b/tests/e2e/loadbalancer-features/00-assert.yaml deleted file mode 100644 index 863b0c88ae..0000000000 --- a/tests/e2e/loadbalancer-features/00-assert.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: stateful-targetallocator -spec: - template: - spec: - containers: - - name: ta-container - env: - - name: OTEL_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - volumeMounts: - - mountPath: /conf - name: ta-internal - volumes: - - configMap: - items: - - key: targetallocator.yaml - path: targetallocator.yaml - name: stateful-targetallocator - name: ta-internal -status: - replicas: 1 - readyReplicas: 1 \ No newline at end of file diff --git a/tests/e2e/smoke-loadbalancer/00-assert.yaml b/tests/e2e/smoke-loadbalancer/00-assert.yaml deleted file mode 100644 index 4131a8be41..0000000000 --- a/tests/e2e/smoke-loadbalancer/00-assert.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: stateful-targetallocator -status: - replicas: 1 - readyReplicas: 1 - \ No newline at end of file diff --git a/tests/e2e/smoke-targetallocator/00-install.yaml b/tests/e2e/smoke-targetallocator/00-install.yaml new file mode 100644 index 0000000000..6b758e1d18 --- /dev/null +++ b/tests/e2e/smoke-targetallocator/00-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create clusterrolebinding default-view-$NAMESPACE --clusterrole=view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/smoke-targetallocator/01-assert.yaml b/tests/e2e/smoke-targetallocator/01-assert.yaml new file mode 100644 index 0000000000..20e774ce99 --- /dev/null +++ b/tests/e2e/smoke-targetallocator/01-assert.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stateful-collector +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-targetallocator +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stateful-targetallocator + \ No newline at end of file diff --git a/tests/e2e/smoke-loadbalancer/00-install.yaml b/tests/e2e/smoke-targetallocator/01-install.yaml similarity index 100% rename from tests/e2e/smoke-loadbalancer/00-install.yaml rename to tests/e2e/smoke-targetallocator/01-install.yaml diff --git a/tests/e2e/smoke-targetallocator/02-install.yaml b/tests/e2e/smoke-targetallocator/02-install.yaml new file mode 100644 index 0000000000..6bdb21adad --- /dev/null +++ b/tests/e2e/smoke-targetallocator/02-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl delete clusterrolebinding default-view-$NAMESPACE \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/00-install.yaml b/tests/e2e/targetallocator-features/00-install.yaml new file mode 100644 index 0000000000..6b758e1d18 --- /dev/null +++ b/tests/e2e/targetallocator-features/00-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create clusterrolebinding default-view-$NAMESPACE --clusterrole=view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/01-assert.yaml b/tests/e2e/targetallocator-features/01-assert.yaml new file mode 100644 index 0000000000..cca13596e9 --- /dev/null +++ b/tests/e2e/targetallocator-features/01-assert.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stateful-collector +spec: + podManagementPolicy: Parallel + template: + spec: + containers: + - args: + - --config=/conf/collector.yaml + name: otc-container + volumeMounts: + - mountPath: /conf + name: otc-internal + - mountPath: /usr/share/testvolume + name: testvolume + volumes: + - configMap: + items: + - key: collector.yaml + path: collector.yaml + name: stateful-collector + name: otc-internal + - emptyDir: {} + name: testvolume + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: testvolume + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + volumeMode: Filesystem +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-targetallocator +spec: + template: + spec: + containers: + - name: ta-container + env: + - name: OTELCOL_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - mountPath: /conf + name: ta-internal + volumes: + - configMap: + items: + - key: targetallocator.yaml + path: targetallocator.yaml + name: stateful-targetallocator + name: ta-internal +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stateful-targetallocator \ No newline at end of file diff --git a/tests/e2e/loadbalancer-features/00-install.yaml b/tests/e2e/targetallocator-features/01-install.yaml similarity index 70% rename from tests/e2e/loadbalancer-features/00-install.yaml rename to tests/e2e/targetallocator-features/01-install.yaml index 54c412dc20..fc30d5d3e6 100644 --- a/tests/e2e/loadbalancer-features/00-install.yaml +++ b/tests/e2e/targetallocator-features/01-install.yaml @@ -4,6 +4,19 @@ metadata: name: stateful spec: mode: statefulset + volumes: + - name: testvolume + volumeMounts: + - name: testvolume + mountPath: /usr/share/testvolume + volumeClaimTemplates: + - metadata: + name: testvolume + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi targetAllocator: enabled: true config: | diff --git a/tests/e2e/targetallocator-features/02-install.yaml b/tests/e2e/targetallocator-features/02-install.yaml new file mode 100644 index 0000000000..6bdb21adad --- /dev/null +++ b/tests/e2e/targetallocator-features/02-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl delete clusterrolebinding default-view-$NAMESPACE \ No newline at end of file From 367c6237b9c0e9c77d141229748d91429ff31791 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Wed, 4 Aug 2021 10:14:41 -0700 Subject: [PATCH 20/30] Updated folder structure to reduce code duplication between collector and targetallocator & Minor changes --- .../opentelemetrycollector_webhook.go | 5 + .../opentelemetrycollector_controller.go | 8 +- .../opentelemetrycollector_controller_test.go | 2 +- controllers/targetallocator_controller.go | 30 +- .../targetallocator_controller_test.go | 18 +- main.go | 11 +- pkg/collector/container_test.go | 3 +- pkg/collector/deployment_test.go | 1 - pkg/collector/reconcile/configmap.go | 174 ---------- pkg/collector/reconcile/configmap_test.go | 141 -------- pkg/collector/reconcile/deployment.go | 135 -------- pkg/collector/reconcile/deployment_test.go | 130 ------- pkg/collector/reconcile/service_test.go | 231 ------------- pkg/collector/reconcile/suite_test.go | 156 --------- .../reconcile/configmap.go | 82 ++++- .../reconcile/configmap_test.go | 148 +++++++- pkg/{collector => }/reconcile/daemonset.go | 0 .../reconcile/daemonset_test.go | 2 +- .../reconcile/deployment.go | 69 ++-- .../reconcile/deployment_test.go | 215 ++++++++---- pkg/{targetallocator => }/reconcile/helper.go | 5 - .../reconcile/opentelemetry.go | 0 .../reconcile/opentelemetry_test.go | 4 +- pkg/{collector => }/reconcile/params.go | 0 pkg/{collector => }/reconcile/service.go | 86 ++++- pkg/reconcile/service_test.go | 320 ++++++++++++++++++ .../reconcile/serviceaccount.go | 0 .../reconcile/serviceaccount_test.go | 10 +- pkg/{collector => }/reconcile/statefulset.go | 0 .../reconcile/statefulset_test.go | 2 +- .../reconcile/suite_test.go | 62 +++- .../reconcile/suite_test.yaml | 0 pkg/targetallocator/container_test.go | 3 +- pkg/targetallocator/deployment_test.go | 1 - pkg/targetallocator/reconcile/params.go | 33 -- pkg/targetallocator/reconcile/service.go | 160 --------- pkg/targetallocator/reconcile/service_test.go | 100 ------ 37 files changed, 894 insertions(+), 1453 deletions(-) delete mode 100644 pkg/collector/reconcile/configmap.go delete mode 100644 pkg/collector/reconcile/configmap_test.go delete mode 100644 pkg/collector/reconcile/deployment.go delete mode 100644 pkg/collector/reconcile/deployment_test.go delete mode 100644 pkg/collector/reconcile/service_test.go delete mode 100644 pkg/collector/reconcile/suite_test.go rename pkg/{targetallocator => }/reconcile/configmap.go (70%) rename pkg/{targetallocator => }/reconcile/configmap_test.go (53%) rename pkg/{collector => }/reconcile/daemonset.go (100%) rename pkg/{collector => }/reconcile/daemonset_test.go (99%) rename pkg/{targetallocator => }/reconcile/deployment.go (69%) rename pkg/{targetallocator => }/reconcile/deployment_test.go (58%) rename pkg/{targetallocator => }/reconcile/helper.go (81%) rename pkg/{collector => }/reconcile/opentelemetry.go (100%) rename pkg/{collector => }/reconcile/opentelemetry_test.go (92%) rename pkg/{collector => }/reconcile/params.go (100%) rename pkg/{collector => }/reconcile/service.go (78%) create mode 100644 pkg/reconcile/service_test.go rename pkg/{collector => }/reconcile/serviceaccount.go (100%) rename pkg/{collector => }/reconcile/serviceaccount_test.go (85%) rename pkg/{collector => }/reconcile/statefulset.go (100%) rename pkg/{collector => }/reconcile/statefulset_test.go (99%) rename pkg/{targetallocator => }/reconcile/suite_test.go (79%) rename pkg/{targetallocator => }/reconcile/suite_test.yaml (100%) delete mode 100644 pkg/targetallocator/reconcile/params.go delete mode 100644 pkg/targetallocator/reconcile/service.go delete mode 100644 pkg/targetallocator/reconcile/service_test.go diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index d0ace6187b..e25437d344 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -91,5 +91,10 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error { return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the attribute 'tolerations'", r.Spec.Mode) } + // validate target allocation + if r.Spec.TargetAllocator.Enabled && !(r.Spec.Mode == ModeStatefulSet) { + return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the target allocation deployment", r.Spec.Mode) + } + return nil } diff --git a/controllers/opentelemetrycollector_controller.go b/controllers/opentelemetrycollector_controller.go index 27a54ff62a..9a40403605 100644 --- a/controllers/opentelemetrycollector_controller.go +++ b/controllers/opentelemetrycollector_controller.go @@ -30,7 +30,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" ) // OpenTelemetryCollectorReconciler reconciles a OpenTelemetryCollector object. @@ -66,7 +66,7 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler { p.Tasks = []Task{ { "config maps", - reconcile.ConfigMaps, + reconcile.CollectorConfigMaps, true, }, { @@ -76,12 +76,12 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler { }, { "services", - reconcile.Services, + reconcile.CollectorServices, true, }, { "deployments", - reconcile.Deployments, + reconcile.CollectorDeployments, true, }, { diff --git a/controllers/opentelemetrycollector_controller_test.go b/controllers/opentelemetrycollector_controller_test.go index 7f2d203900..4cc14ec5fe 100644 --- a/controllers/opentelemetrycollector_controller_test.go +++ b/controllers/opentelemetrycollector_controller_test.go @@ -36,7 +36,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" ) var logger = logf.Log.WithName("unit-tests") diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index 3f7d5e92c0..9a73fc691b 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -29,7 +29,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" ) // OpenTelemetryTargetAllocatorReconciler reconciles a OpenTelemetryTargetAllocator object. @@ -38,42 +38,26 @@ type OpenTelemetryTargetAllocatorReconciler struct { log logr.Logger scheme *runtime.Scheme config config.Config - tasks []TgAlTask -} - -// TgAlTask represents a reconciliation task to be executed by the reconciler. -type TgAlTask struct { - Name string - Do func(context.Context, reconcile.Params) error - BailOnError bool -} - -// TgAlParams is the set of options to build a new OpenTelemetryTargetAllocatorReconciler. -type TgAlParams struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Config config.Config - Tasks []TgAlTask + tasks []Task } // NewTargetAlllocatorReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. -func NewTargetAllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { +func NewTargetAllocatorReconciler(p Params) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { - p.Tasks = []TgAlTask{ + p.Tasks = []Task{ { "config maps", - reconcile.ConfigMaps, + reconcile.TAConfigMaps, true, }, { "deployments", - reconcile.Deployments, + reconcile.TADeployments, true, }, { "services", - reconcile.Services, + reconcile.TAServices, true, }, } diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go index b79b569a00..ea136455dd 100644 --- a/controllers/targetallocator_controller_test.go +++ b/controllers/targetallocator_controller_test.go @@ -34,17 +34,17 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" ) func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { // prepare cfg := config.New() - configYAML, err := ioutil.ReadFile("../pkg/targetallocator/reconcile/suite_test.yaml") + configYAML, err := ioutil.ReadFile("../pkg/reconcile/suite_test.yaml") require.NoError(t, err) nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ Client: k8sClient, Log: logger, Scheme: testScheme, @@ -113,9 +113,9 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { func TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { // prepare taskCalled := false - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ Log: logger, - Tasks: []controllers.TgAlTask{ + Tasks: []controllers.Task{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -147,12 +147,12 @@ func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { taskCalled := false expectedErr := errors.New("should fail!") nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.TgAlTask{ + Tasks: []controllers.Task{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -197,12 +197,12 @@ func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { // prepare cfg := config.New() nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.TgAlTask{ + Tasks: []controllers.Task{ { Name: "should-not-be-called", Do: func(context.Context, reconcile.Params) error { diff --git a/main.go b/main.go index 424ed67bd6..9f75b648a8 100644 --- a/main.go +++ b/main.go @@ -155,11 +155,12 @@ func main() { os.Exit(1) } - if err = controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), - Scheme: mgr.GetScheme(), - Config: cfg, + if err = controllers.NewTargetAllocatorReconciler(controllers.Params{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), + Scheme: mgr.GetScheme(), + Config: cfg, + Recorder: mgr.GetEventRecorderFor("opentelemetry-targetallocator"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") os.Exit(1) diff --git a/pkg/collector/container_test.go b/pkg/collector/container_test.go index 5ada9ec80e..82c70aab95 100644 --- a/pkg/collector/container_test.go +++ b/pkg/collector/container_test.go @@ -17,12 +17,11 @@ package collector_test import ( "testing" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" logf "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/stretchr/testify/assert" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" . "github.com/open-telemetry/opentelemetry-operator/pkg/collector" diff --git a/pkg/collector/deployment_test.go b/pkg/collector/deployment_test.go index dea1c308c4..1cfe13594e 100644 --- a/pkg/collector/deployment_test.go +++ b/pkg/collector/deployment_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go deleted file mode 100644 index 82d57b5393..0000000000 --- a/pkg/collector/reconcile/configmap.go +++ /dev/null @@ -1,174 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - "reflect" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" -) - -// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete - -// ConfigMaps reconciles the config map(s) required for the instance in the current context. -func ConfigMaps(ctx context.Context, params Params) error { - desired := []corev1.ConfigMap{ - desiredConfigMap(ctx, params), - } - - // first, handle the create/update parts - if err := expectedConfigMaps(ctx, params, desired, true); err != nil { - return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) - } - - // then, delete the extra objects - if err := deleteConfigMaps(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) - } - - return nil -} - -func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { - name := naming.ConfigMap(params.Instance) - labels := collector.Labels(params.Instance) - labels["app.kubernetes.io/name"] = name - - return corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - Annotations: params.Instance.Annotations, - }, - Data: map[string]string{ - "collector.yaml": params.Instance.Spec.Config, - }, - } -} - -func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &corev1.ConfigMap{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && errors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - if errors.IsAlreadyExists(err) && retry { - // let's try again? we probably had multiple updates at one, and now it exists already - if err := expectedConfigMaps(ctx, params, expected, false); err != nil { - // somethin else happened now... - return err - } - - // we succeeded in the retry, exit this attempt - return nil - } - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Annotations == nil { - updated.Annotations = map[string]string{} - } - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - - updated.Data = desired.Data - updated.BinaryData = desired.BinaryData - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Annotations { - updated.ObjectMeta.Annotations[k] = v - } - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - if configMapChanged(&desired, existing) { - params.Recorder.Event(updated, "Normal", "ConfigUpdate ", fmt.Sprintf("OpenTelemetry Config changed - %s/%s", desired.Namespace, desired.Name)) - } - - params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) - } - - return nil -} - -func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &corev1.ConfigMapList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "configmap.name", existing.Name, "configmap.namespace", existing.Namespace) - } - } - - return nil -} - -func configMapChanged(desired *corev1.ConfigMap, actual *corev1.ConfigMap) bool { - return !reflect.DeepEqual(desired.Data, actual.Data) - -} diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go deleted file mode 100644 index 7eaee37fc5..0000000000 --- a/pkg/collector/reconcile/configmap_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" -) - -func TestDesiredConfigMap(t *testing.T) { - t.Run("should return expected config map", func(t *testing.T) { - expectedLables := map[string]string{ - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-collector", - "app.kubernetes.io/name": "test-collector", - } - - expectedData := map[string]string{ - "collector.yaml": ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, - } - - actual := desiredConfigMap(context.Background(), params()) - - assert.Equal(t, "test-collector", actual.Name) - assert.Equal(t, expectedLables, actual.Labels) - assert.Equal(t, expectedData, actual.Data) - - }) - -} - -func TestExpectedConfigMap(t *testing.T) { - t.Run("should create config map", func(t *testing.T) { - err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - }) - - t.Run("should update config map", func(t *testing.T) { - - param := Params{ - Config: config.New(), - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - }, - Scheme: testScheme, - Log: logger, - Recorder: record.NewFakeRecorder(10), - } - cm := desiredConfigMap(context.Background(), param) - createObjectIfNotExists(t, "test-collector", &cm) - - err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) - assert.NoError(t, err) - - actual := v1.ConfigMap{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Equal(t, params().Instance.Spec.Config, actual.Data["collector.yaml"]) - }) - - t.Run("should delete config map", func(t *testing.T) { - - deletecm := v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-delete-collector", - Namespace: "default", - Labels: map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }, - }, - } - createObjectIfNotExists(t, "test-delete-collector", &deletecm) - - exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) - assert.True(t, exists) - - err := deleteConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}) - assert.NoError(t, err) - - exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) - assert.False(t, exists) - }) -} diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go deleted file mode 100644 index 41b0f0de65..0000000000 --- a/pkg/collector/reconcile/deployment.go +++ /dev/null @@ -1,135 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - appsv1 "k8s.io/api/apps/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" -) - -// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete - -// Deployments reconciles the deployment(s) required for the instance in the current context. -func Deployments(ctx context.Context, params Params) error { - desired := []appsv1.Deployment{} - if params.Instance.Spec.Mode == "deployment" { - desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) - } - - // first, handle the create/update parts - if err := expectedDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected deployments: %v", err) - } - - // then, delete the extra objects - if err := deleteDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) - } - - return nil -} - -func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &appsv1.Deployment{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Annotations == nil { - updated.Annotations = map[string]string{} - } - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - - updated.Spec = desired.Spec - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Annotations { - updated.ObjectMeta.Annotations[k] = v - } - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - - params.Log.V(2).Info("applied", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) - } - - return nil -} - -func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &appsv1.DeploymentList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "deployment.name", existing.Name, "deployment.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go deleted file mode 100644 index 7295ef05fc..0000000000 --- a/pkg/collector/reconcile/deployment_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" -) - -func TestExpectedDeployments(t *testing.T) { - param := params() - expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) - - t.Run("should create deployment", func(t *testing.T) { - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) - t.Run("should update deployment", func(t *testing.T) { - createObjectIfNotExists(t, "test-collector", &expectedDeploy) - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Equal(t, int32(2), *actual.Spec.Replicas) - }) - - t.Run("should delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Labels = labels - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, - }, - }, - } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.False(t, exists) - - }) - - t.Run("should not delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, - }, - }, - } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.True(t, exists) - - }) -} diff --git a/pkg/collector/reconcile/service_test.go b/pkg/collector/reconcile/service_test.go deleted file mode 100644 index 461f5f69ab..0000000000 --- a/pkg/collector/reconcile/service_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" -) - -func TestExtractPortNumbersAndNames(t *testing.T) { - t.Run("should return extracted port names and numbers", func(t *testing.T) { - ports := []v1.ServicePort{{Name: "web", Port: 8080}, {Name: "tcp", Port: 9200}} - expectedPortNames := map[string]bool{"web": true, "tcp": true} - expectedPortNumbers := map[int32]bool{8080: true, 9200: true} - - actualPortNumbers, actualPortNames := extractPortNumbersAndNames(ports) - assert.Equal(t, expectedPortNames, actualPortNames) - assert.Equal(t, expectedPortNumbers, actualPortNumbers) - - }) -} - -func TestFilterPort(t *testing.T) { - - tests := []struct { - name string - candidate v1.ServicePort - portNumbers map[int32]bool - portNames map[string]bool - expected v1.ServicePort - }{ - { - name: "should filter out duplicate port", - candidate: v1.ServicePort{Name: "web", Port: 8080}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"test": true, "metrics": true}, - }, - - { - name: "should not filter unique port", - candidate: v1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"test": true, "metrics": true}, - expected: v1.ServicePort{Name: "web", Port: 8090}, - }, - - { - name: "should change the duplicate portName", - candidate: v1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"web": true, "metrics": true}, - expected: v1.ServicePort{Name: "port-8090", Port: 8090}, - }, - - { - name: "should return nil if fallback name clashes with existing portName", - candidate: v1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"web": true, "port-8090": true}, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := filterPort(logger, test.candidate, test.portNumbers, test.portNames) - if test.expected != (v1.ServicePort{}) { - assert.Equal(t, test.expected, *actual) - return - } - assert.Nil(t, actual) - - }) - - } -} - -func TestDesiredService(t *testing.T) { - t.Run("should return nil service for unknown receiver and protocol", func(t *testing.T) { - params := Params{ - Config: config.Config{}, - Client: k8sClient, - Log: logger, - Instance: v1alpha1.OpenTelemetryCollector{ - Spec: v1alpha1.OpenTelemetryCollectorSpec{Config: `receivers: - test: - protocols: - unknown:`}, - }, - } - - actual := desiredService(context.Background(), params) - assert.Nil(t, actual) - - }) - t.Run("should return service with port mentioned in Instance.Spec.Ports and inferred ports", func(t *testing.T) { - - jaegerPorts := v1.ServicePort{ - Name: "jaeger-grpc", - Protocol: "TCP", - Port: 14250, - } - ports := append(params().Instance.Spec.Ports, jaegerPorts) - expected := service("test-collector", ports) - actual := desiredService(context.Background(), params()) - - assert.Equal(t, expected, *actual) - - }) - -} - -func TestExpectedServices(t *testing.T) { - t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), params(), []v1.Service{service("test-collector", params().Instance.Spec.Ports)}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) - t.Run("should update service", func(t *testing.T) { - serviceInstance := service("test-collector", params().Instance.Spec.Ports) - createObjectIfNotExists(t, "test-collector", &serviceInstance) - - extraPorts := v1.ServicePort{ - Name: "port-web", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - } - - ports := append(params().Instance.Spec.Ports, extraPorts) - err := expectedServices(context.Background(), params(), []v1.Service{service("test-collector", ports)}) - assert.NoError(t, err) - - actual := v1.Service{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Contains(t, actual.Spec.Ports, extraPorts) - - }) -} - -func TestDeleteServices(t *testing.T) { - t.Run("should delete excess services", func(t *testing.T) { - ports := []v1.ServicePort{{ - Port: 80, - Name: "web", - }} - deleteService := service("delete-service-collector", ports) - createObjectIfNotExists(t, "delete-service-collector", &deleteService) - - exists, err := populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) - assert.NoError(t, err) - assert.True(t, exists) - - desired := desiredService(context.Background(), params()) - err = deleteServices(context.Background(), params(), []v1.Service{*desired}) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func TestHeadlessService(t *testing.T) { - t.Run("should return headless service", func(t *testing.T) { - actual := headless(context.Background(), params()) - assert.Equal(t, actual.Spec.ClusterIP, "None") - }) -} - -func TestMonitoringService(t *testing.T) { - t.Run("returned service should expose monitoring port", func(t *testing.T) { - expected := []v1.ServicePort{{ - Name: "monitoring", - Port: 8888, - }} - actual := monitoringService(context.Background(), params()) - assert.Equal(t, expected, actual.Spec.Ports) - - }) -} - -func service(name string, ports []v1.ServicePort) v1.Service { - labels := collector.Labels(params().Instance) - labels["app.kubernetes.io/name"] = name - - selector := labels - return v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - Labels: labels, - Annotations: params().Instance.Annotations, - }, - Spec: v1.ServiceSpec{ - Selector: selector, - ClusterIP: "", - Ports: ports, - }, - } -} diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go deleted file mode 100644 index d2469ad44a..0000000000 --- a/pkg/collector/reconcile/suite_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - "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/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" -) - -var k8sClient client.Client -var testEnv *envtest.Environment -var testScheme *runtime.Scheme = scheme.Scheme -var logger = logf.Log.WithName("unit-tests") - -var instanceUID = uuid.NewUUID() - -func TestMain(m *testing.M) { - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, - } - - cfg, err := testEnv.Start() - if err != nil { - fmt.Printf("failed to start testEnv: %v", err) - os.Exit(1) - } - - if err := v1alpha1.AddToScheme(testScheme); err != nil { - fmt.Printf("failed to register scheme: %v", err) - os.Exit(1) - } - // +kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) - if err != nil { - fmt.Printf("failed to setup a Kubernetes client: %v", err) - os.Exit(1) - } - - code := m.Run() - - err = testEnv.Stop() - if err != nil { - fmt.Printf("failed to stop testEnv: %v", err) - os.Exit(1) - } - - os.Exit(code) -} - -func params() Params { - replicas := int32(2) - return Params{ - Config: config.New(), - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Ports: []v1.ServicePort{{ - Name: "web", - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 80, - }, - NodePort: 0, - }}, - Replicas: &replicas, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, - }, - }, - Scheme: testScheme, - Log: logger, - Recorder: record.NewFakeRecorder(10), - } -} - -func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { - tb.Helper() - err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) - if errors.IsNotFound(err) { - err := k8sClient.Create(context.Background(), - object) - assert.NoError(tb, err) - } -} - -func populateObjectIfExists(t testing.TB, object client.Object, namespacedName types.NamespacedName) (bool, error) { - t.Helper() - err := k8sClient.Get(context.Background(), namespacedName, object) - if errors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - return true, nil - -} diff --git a/pkg/targetallocator/reconcile/configmap.go b/pkg/reconcile/configmap.go similarity index 70% rename from pkg/targetallocator/reconcile/configmap.go rename to pkg/reconcile/configmap.go index b768fdb6ec..85b193b930 100644 --- a/pkg/targetallocator/reconcile/configmap.go +++ b/pkg/reconcile/configmap.go @@ -17,6 +17,7 @@ package reconcile import ( "context" "fmt" + "reflect" "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" @@ -26,18 +27,45 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete -// ConfigMaps reconciles the config map(s) required for the instance in the current context. -func ConfigMaps(ctx context.Context, params Params) error { +// CollectorConfigMaps reconciles the config map(s) required for the collector instance in the current context. +func CollectorConfigMaps(ctx context.Context, params Params) error { + desired := []corev1.ConfigMap{ + desiredCollectorConfigMap(ctx, params), + } + + // first, handle the create/update parts + if err := expectedConfigMaps(ctx, params, desired, true); err != nil { + return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) + } + + // then, delete the extra objects + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + if err := deleteConfigMaps(ctx, params, desired, opts); err != nil { + return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) + } + + return nil +} + +// TAConfigMaps reconciles the config map(s) required for the target allocator instance in the current context. +func TAConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - if IsAllocatorEnabled(params) { - cm, err := desiredConfigMap(ctx, params) + if params.Instance.Spec.TargetAllocator.Enabled { + cm, err := desiredTAConfigMap(ctx, params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } @@ -50,14 +78,39 @@ func ConfigMaps(ctx context.Context, params Params) error { } // then, delete the extra objects - if err := deleteConfigMaps(ctx, params, desired); err != nil { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + if err := deleteConfigMaps(ctx, params, desired, opts); err != nil { return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) } return nil } -func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { +func desiredCollectorConfigMap(_ context.Context, params Params) corev1.ConfigMap { + name := naming.ConfigMap(params.Instance) + labels := collector.Labels(params.Instance) + labels["app.kubernetes.io/name"] = name + + return corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + Annotations: params.Instance.Annotations, + }, + Data: map[string]string{ + "collector.yaml": params.Instance.Spec.Config, + }, + } +} + +func desiredTAConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { name := naming.TAConfigMap(params.Instance) labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name @@ -147,6 +200,9 @@ func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.Co if err := params.Client.Patch(ctx, updated, patch); err != nil { return fmt.Errorf("failed to apply changes: %w", err) } + if configMapChanged(&desired, existing) { + params.Recorder.Event(updated, "Normal", "ConfigUpdate ", fmt.Sprintf("OpenTelemetry Config changed - %s/%s", desired.Namespace, desired.Name)) + } params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) } @@ -154,14 +210,7 @@ func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.Co return nil } -func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } +func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, opts []client.ListOption) error { list := &corev1.ConfigMapList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) @@ -186,3 +235,8 @@ func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.Conf return nil } + +func configMapChanged(desired *corev1.ConfigMap, actual *corev1.ConfigMap) bool { + return !reflect.DeepEqual(desired.Data, actual.Data) + +} diff --git a/pkg/targetallocator/reconcile/configmap_test.go b/pkg/reconcile/configmap_test.go similarity index 53% rename from pkg/targetallocator/reconcile/configmap_test.go rename to pkg/reconcile/configmap_test.go index e15e8c6176..2a95086b3f 100644 --- a/pkg/targetallocator/reconcile/configmap_test.go +++ b/pkg/reconcile/configmap_test.go @@ -24,13 +24,57 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func TestDesiredConfigMap(t *testing.T) { +func TestDesiredCollectorConfigMap(t *testing.T) { + t.Run("should return expected config map", func(t *testing.T) { + expectedLables := map[string]string{ + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/name": "test-collector", + } + + expectedData := map[string]string{ + "collector.yaml": ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + +`, + } + + actual := desiredCollectorConfigMap(context.Background(), paramsCollector()) + + assert.Equal(t, "test-collector", actual.Name) + assert.Equal(t, expectedLables, actual.Labels) + assert.Equal(t, expectedData, actual.Data) + + }) + +} + +func TestDesiredTAConfigMap(t *testing.T) { t.Run("should return expected config map", func(t *testing.T) { expectedLables := map[string]string{ "app.kubernetes.io/managed-by": "opentelemetry-operator", @@ -55,7 +99,7 @@ label_selector: `, } - actual, err := desiredConfigMap(context.Background(), params()) + actual, err := desiredTAConfigMap(context.Background(), paramsTA()) assert.NoError(t, err) assert.Equal(t, "test-targetallocator", actual.Name) @@ -66,10 +110,89 @@ label_selector: } -func TestExpectedConfigMap(t *testing.T) { - param := params() +func TestExpectedCollectorConfigMap(t *testing.T) { + param := paramsCollector() + t.Run("should create config map", func(t *testing.T) { + err := expectedConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, true) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + }) + + t.Run("should update config map", func(t *testing.T) { + + param := Params{ + Config: config.New(), + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + }, + Scheme: testScheme, + Log: logger, + Recorder: record.NewFakeRecorder(10), + } + cm := desiredCollectorConfigMap(context.Background(), param) + createObjectIfNotExists(t, "test-collector", &cm) + + err := expectedConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, true) + assert.NoError(t, err) + + actual := v1.ConfigMap{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Equal(t, param.Instance.Spec.Config, actual.Data["collector.yaml"]) + }) + + t.Run("should delete config map", func(t *testing.T) { + + deletecm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-delete-collector", + Namespace: "default", + Labels: map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }, + }, + } + createObjectIfNotExists(t, "test-delete-collector", &deletecm) + + exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) + assert.True(t, exists) + + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err := deleteConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, opts) + assert.NoError(t, err) + + exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) + assert.False(t, exists) + }) +} + +func TestExpectedTAConfigMap(t *testing.T) { + param := paramsTA() t.Run("should create config map", func(t *testing.T) { - configMap, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredTAConfigMap(context.Background(), param) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -114,11 +237,11 @@ func TestExpectedConfigMap(t *testing.T) { Scheme: testScheme, Log: logger, } - cm, err := desiredConfigMap(context.Background(), newParam) + cm, err := desiredTAConfigMap(context.Background(), newParam) assert.EqualError(t, err, "no receivers available as part of the configuration") createObjectIfNotExists(t, "test-targetallocator", &cm) - configMap, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredTAConfigMap(context.Background(), param) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -164,9 +287,16 @@ func TestExpectedConfigMap(t *testing.T) { exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.True(t, exists) - configMap, err := desiredConfigMap(context.Background(), param) + configMap, err := desiredTAConfigMap(context.Background(), param) assert.NoError(t, err) - err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "test.targetallocator", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, opts) assert.NoError(t, err) exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) diff --git a/pkg/collector/reconcile/daemonset.go b/pkg/reconcile/daemonset.go similarity index 100% rename from pkg/collector/reconcile/daemonset.go rename to pkg/reconcile/daemonset.go diff --git a/pkg/collector/reconcile/daemonset_test.go b/pkg/reconcile/daemonset_test.go similarity index 99% rename from pkg/collector/reconcile/daemonset_test.go rename to pkg/reconcile/daemonset_test.go index 8a4751e8b6..24e8e99759 100644 --- a/pkg/collector/reconcile/daemonset_test.go +++ b/pkg/reconcile/daemonset_test.go @@ -28,7 +28,7 @@ import ( ) func TestExpectedDaemonsets(t *testing.T) { - param := params() + param := paramsCollector() expectedDs := collector.DaemonSet(param.Config, logger, param.Instance) t.Run("should create Daemonset", func(t *testing.T) { diff --git a/pkg/targetallocator/reconcile/deployment.go b/pkg/reconcile/deployment.go similarity index 69% rename from pkg/targetallocator/reconcile/deployment.go rename to pkg/reconcile/deployment.go index a0161645f9..751b58b459 100644 --- a/pkg/targetallocator/reconcile/deployment.go +++ b/pkg/reconcile/deployment.go @@ -17,7 +17,6 @@ package reconcile import ( "context" "fmt" - "reflect" appsv1 "k8s.io/api/apps/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -25,16 +24,44 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete -// Deployments reconciles the deployment(s) required for the instance in the current context. -func Deployments(ctx context.Context, params Params) error { +// CollectorDeployments reconciles the deployment(s) required for the instance in the current context. +func CollectorDeployments(ctx context.Context, params Params) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + desired := []appsv1.Deployment{} + if params.Instance.Spec.Mode == "deployment" { + desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) + } + + // first, handle the create/update parts + if err := expectedDeployments(ctx, params, desired, false); err != nil { + return fmt.Errorf("failed to reconcile the expected deployments: %v", err) + } + + // then, delete the extra objects + if err := deleteDeployments(ctx, params, desired, opts); err != nil { + return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) + } + + return nil +} + +// TADeployments reconciles the deployment(s) required for the instance in the current context. +func TADeployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - if IsAllocatorEnabled(params) { + if params.Instance.Spec.TargetAllocator.Enabled { _, err := GetPromConfig(params) if err != nil { return fmt.Errorf("failed to parse Prometheus config: %v", err) @@ -43,19 +70,26 @@ func Deployments(ctx context.Context, params Params) error { } // first, handle the create/update parts - if err := expectedDeployments(ctx, params, desired); err != nil { + if err := expectedDeployments(ctx, params, desired, true); err != nil { return fmt.Errorf("failed to reconcile the expected deployments: %v", err) } // then, delete the extra objects - if err := deleteDeployments(ctx, params, desired); err != nil { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + if err := deleteDeployments(ctx, params, desired, opts); err != nil { return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) } return nil } -func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { +func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment, isTargetAllocator bool) error { for _, obj := range expected { desired := obj @@ -74,8 +108,6 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D continue } else if err != nil { return fmt.Errorf("failed to get: %w", err) - } else if !deploymentImageChanged(&desired, existing) { - continue } // it exists already, merge the two if the end result isn't identical to the existing one @@ -84,7 +116,11 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D updated.Labels = map[string]string{} } - updated.Spec = desired.Spec + if isTargetAllocator { + updated.Spec.Template.Spec.Containers[0].Image = desired.Spec.Template.Spec.Containers[0].Image + } else { + updated.Spec = desired.Spec + } updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences for k, v := range desired.ObjectMeta.Labels { @@ -103,14 +139,7 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D return nil } -func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } +func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment, opts []client.ListOption) error { list := &appsv1.DeploymentList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) @@ -135,7 +164,3 @@ func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Dep return nil } - -func deploymentImageChanged(desired *appsv1.Deployment, actual *appsv1.Deployment) bool { - return !reflect.DeepEqual(desired.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) -} diff --git a/pkg/targetallocator/reconcile/deployment_test.go b/pkg/reconcile/deployment_test.go similarity index 58% rename from pkg/targetallocator/reconcile/deployment_test.go rename to pkg/reconcile/deployment_test.go index 135a7759a3..f3b1eeea81 100644 --- a/pkg/targetallocator/reconcile/deployment_test.go +++ b/pkg/reconcile/deployment_test.go @@ -23,77 +23,142 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) -func TestExpectedDeployments(t *testing.T) { - param := params() - expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) +func TestExpectedCollectorDeployments(t *testing.T) { + param := paramsCollector() + expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) t.Run("should create deployment", func(t *testing.T) { - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, false) assert.NoError(t, err) - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) assert.NoError(t, err) assert.True(t, exists) }) + t.Run("should update deployment", func(t *testing.T) { + createObjectIfNotExists(t, "test-collector", &expectedDeploy) + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, false) + assert.NoError(t, err) - t.Run("should not create deployment when otel collector mode is not StatefulSet", func(t *testing.T) { - modes := []v1alpha1.Mode{v1alpha1.ModeDaemonSet, v1alpha1.ModeDeployment, v1alpha1.ModeSidecar} - - for _, mode := range modes { - newParam := Params{ - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: mode, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - }, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - - `, - }, + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Equal(t, int32(2), *actual.Spec.Replicas) + }) + + t.Run("should delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Labels = labels + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, + } + createObjectIfNotExists(t, "dummy", &deploy) + + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.False(t, exists) + + }) + + t.Run("should not delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, }, - Scheme: testScheme, - Log: logger, - } - expected := []v1.Deployment{} - if newParam.Instance.Spec.Mode == v1alpha1.ModeStatefulSet { - expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) - } - - assert.Len(t, expected, 0) + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, } + createObjectIfNotExists(t, "dummy", &deploy) + + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", + }), + } + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.True(t, exists) + + }) +} + +func TestExpectedTADeployments(t *testing.T) { + param := paramsTA() + expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) + + t.Run("should create deployment", func(t *testing.T) { + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, true) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + }) t.Run("should not create deployment when targetallocator is not enabled", func(t *testing.T) { @@ -149,7 +214,7 @@ func TestExpectedDeployments(t *testing.T) { updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}, true) assert.NoError(t, err) actual := v1.Deployment{} @@ -170,7 +235,7 @@ func TestExpectedDeployments(t *testing.T) { updatedParam := newParams("test/test-img") updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}, true) assert.NoError(t, err) actual := v1.Deployment{} @@ -189,7 +254,7 @@ func TestExpectedDeployments(t *testing.T) { "app.kubernetes.io/managed-by": "opentelemetry-operator", } deploy := v1.Deployment{} - deploy.Name = "dummy" + deploy.Name = "dummy-ta" deploy.Namespace = "default" deploy.Labels = labels deploy.Spec = v1.DeploymentSpec{ @@ -202,19 +267,26 @@ func TestExpectedDeployments(t *testing.T) { }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "dummy", + Name: "dummy-ta", Image: "busybox", }}, }, }, } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + createObjectIfNotExists(t, "dummy-ta", &deploy) + + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "test.targetallocator", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) assert.NoError(t, err) actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy-ta"}) assert.False(t, exists) @@ -226,7 +298,7 @@ func TestExpectedDeployments(t *testing.T) { "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", } deploy := v1.Deployment{} - deploy.Name = "dummy" + deploy.Name = "dummy-ta" deploy.Namespace = "default" deploy.Spec = v1.DeploymentSpec{ Selector: &metav1.LabelSelector{ @@ -238,19 +310,26 @@ func TestExpectedDeployments(t *testing.T) { }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "dummy", + Name: "dummy-ta", Image: "busybox", }}, }, }, } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + createObjectIfNotExists(t, "dummy-ta", &deploy) + + opts := []client.ListOption{ + client.InNamespace("default"), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": "test.targetallocator", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) assert.NoError(t, err) actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy-ta"}) assert.True(t, exists) diff --git a/pkg/targetallocator/reconcile/helper.go b/pkg/reconcile/helper.go similarity index 81% rename from pkg/targetallocator/reconcile/helper.go rename to pkg/reconcile/helper.go index 7094031056..06ea1db6a4 100644 --- a/pkg/targetallocator/reconcile/helper.go +++ b/pkg/reconcile/helper.go @@ -15,15 +15,10 @@ package reconcile import ( - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func IsAllocatorEnabled(params Params) bool { - return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled -} - func GetPromConfig(params Params) (map[interface{}]interface{}, error) { config, err := adapters.ConfigFromString(params.Instance.Spec.Config) if err != nil { diff --git a/pkg/collector/reconcile/opentelemetry.go b/pkg/reconcile/opentelemetry.go similarity index 100% rename from pkg/collector/reconcile/opentelemetry.go rename to pkg/reconcile/opentelemetry.go diff --git a/pkg/collector/reconcile/opentelemetry_test.go b/pkg/reconcile/opentelemetry_test.go similarity index 92% rename from pkg/collector/reconcile/opentelemetry_test.go rename to pkg/reconcile/opentelemetry_test.go index 50c08d78e1..f608623c58 100644 --- a/pkg/collector/reconcile/opentelemetry_test.go +++ b/pkg/reconcile/opentelemetry_test.go @@ -26,9 +26,9 @@ import ( func TestSelf(t *testing.T) { t.Run("should add version to the status", func(t *testing.T) { - instance := params().Instance + instance := paramsCollector().Instance createObjectIfNotExists(t, "test", &instance) - err := Self(context.Background(), params()) + err := Self(context.Background(), paramsCollector()) assert.NoError(t, err) actual := v1alpha1.OpenTelemetryCollector{} diff --git a/pkg/collector/reconcile/params.go b/pkg/reconcile/params.go similarity index 100% rename from pkg/collector/reconcile/params.go rename to pkg/reconcile/params.go diff --git a/pkg/collector/reconcile/service.go b/pkg/reconcile/service.go similarity index 78% rename from pkg/collector/reconcile/service.go rename to pkg/reconcile/service.go index 4f5f09813f..8894e21d9c 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/reconcile/service.go @@ -23,6 +23,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -30,16 +31,17 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete -// Services reconciles the service(s) required for the instance in the current context. -func Services(ctx context.Context, params Params) error { +// CollectorServices reconciles the service(s) required for the instance in the current context. +func CollectorServices(ctx context.Context, params Params) error { desired := []corev1.Service{} if params.Instance.Spec.Mode != v1alpha1.ModeSidecar { type builder func(context.Context, Params) *corev1.Service - for _, builder := range []builder{desiredService, headless, monitoringService} { + for _, builder := range []builder{desiredCollectorService, headless, monitoringService} { svc := builder(ctx, params) // add only the non-nil to the list if svc != nil { @@ -54,14 +56,53 @@ func Services(ctx context.Context, params Params) error { } // then, delete the extra objects - if err := deleteServices(ctx, params, desired); err != nil { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + if err := deleteServices(ctx, params, desired, opts); err != nil { + return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + } + + return nil +} + +// TAServices reconciles the service(s) required for the instance in the current context. +func TAServices(ctx context.Context, params Params) error { + desired := []corev1.Service{} + + if params.Instance.Spec.TargetAllocator.Enabled { + _, err := GetPromConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, desiredTAService(params)) + } + + // first, handle the create/update parts + if err := expectedServices(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected services: %v", err) + } + + // then, delete the extra objects + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + if err := deleteServices(ctx, params, desired, opts); err != nil { return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) } return nil } -func desiredService(ctx context.Context, params Params) *corev1.Service { +func desiredCollectorService(ctx context.Context, params Params) *corev1.Service { labels := collector.Labels(params.Instance) labels["app.kubernetes.io/name"] = naming.Service(params.Instance) @@ -121,8 +162,32 @@ func desiredService(ctx context.Context, params Params) *corev1.Service { } } +func desiredTAService(params Params) corev1.Service { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TAService(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: 443, + TargetPort: intstr.FromInt(443), + }}, + }, + } +} + func headless(ctx context.Context, params Params) *corev1.Service { - h := desiredService(ctx, params) + h := desiredCollectorService(ctx, params) if h == nil { return nil } @@ -208,14 +273,7 @@ func expectedServices(ctx context.Context, params Params, expected []corev1.Serv return nil } -func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } +func deleteServices(ctx context.Context, params Params, expected []corev1.Service, opts []client.ListOption) error { list := &corev1.ServiceList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) diff --git a/pkg/reconcile/service_test.go b/pkg/reconcile/service_test.go new file mode 100644 index 0000000000..edeec9e527 --- /dev/null +++ b/pkg/reconcile/service_test.go @@ -0,0 +1,320 @@ +// 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 reconcile + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +func TestExtractPortNumbersAndNames(t *testing.T) { + t.Run("should return extracted port names and numbers", func(t *testing.T) { + ports := []corev1.ServicePort{{Name: "web", Port: 8080}, {Name: "tcp", Port: 9200}} + expectedPortNames := map[string]bool{"web": true, "tcp": true} + expectedPortNumbers := map[int32]bool{8080: true, 9200: true} + + actualPortNumbers, actualPortNames := extractPortNumbersAndNames(ports) + assert.Equal(t, expectedPortNames, actualPortNames) + assert.Equal(t, expectedPortNumbers, actualPortNumbers) + + }) +} + +func TestFilterPort(t *testing.T) { + + tests := []struct { + name string + candidate corev1.ServicePort + portNumbers map[int32]bool + portNames map[string]bool + expected corev1.ServicePort + }{ + { + name: "should filter out duplicate port", + candidate: corev1.ServicePort{Name: "web", Port: 8080}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"test": true, "metrics": true}, + }, + + { + name: "should not filter unique port", + candidate: corev1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"test": true, "metrics": true}, + expected: corev1.ServicePort{Name: "web", Port: 8090}, + }, + + { + name: "should change the duplicate portName", + candidate: corev1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"web": true, "metrics": true}, + expected: corev1.ServicePort{Name: "port-8090", Port: 8090}, + }, + + { + name: "should return nil if fallback name clashes with existing portName", + candidate: corev1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"web": true, "port-8090": true}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := filterPort(logger, test.candidate, test.portNumbers, test.portNames) + if test.expected != (corev1.ServicePort{}) { + assert.Equal(t, test.expected, *actual) + return + } + assert.Nil(t, actual) + + }) + + } +} + +func TestDesiredCollectorService(t *testing.T) { + t.Run("should return nil service for unknown receiver and protocol", func(t *testing.T) { + params := Params{ + Config: config.Config{}, + Client: k8sClient, + Log: logger, + Instance: v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{Config: `receivers: + test: + protocols: + unknown:`}, + }, + } + + actual := desiredCollectorService(context.Background(), params) + assert.Nil(t, actual) + + }) + t.Run("should return service with port mentioned in Instance.Spec.Ports and inferred ports", func(t *testing.T) { + + jaegerPorts := corev1.ServicePort{ + Name: "jaeger-grpc", + Protocol: "TCP", + Port: 14250, + } + ports := append(paramsCollector().Instance.Spec.Ports, jaegerPorts) + expected := serviceCollector("test-collector", ports) + actual := desiredCollectorService(context.Background(), paramsCollector()) + + assert.Equal(t, expected, *actual) + + }) + +} + +func TestExpectedCollectorServices(t *testing.T) { + t.Run("should create the service", func(t *testing.T) { + err := expectedServices(context.Background(), paramsCollector(), []corev1.Service{serviceCollector("test-collector", paramsCollector().Instance.Spec.Ports)}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + t.Run("should update service", func(t *testing.T) { + serviceInstance := serviceCollector("test-collector", paramsCollector().Instance.Spec.Ports) + createObjectIfNotExists(t, "test-collector", &serviceInstance) + + extraPorts := corev1.ServicePort{ + Name: "port-web", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), + } + + ports := append(paramsCollector().Instance.Spec.Ports, extraPorts) + err := expectedServices(context.Background(), paramsCollector(), []corev1.Service{serviceCollector("test-collector", ports)}) + assert.NoError(t, err) + + actual := corev1.Service{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Contains(t, actual.Spec.Ports, extraPorts) + + }) +} + +func TestDeleteCollectorServices(t *testing.T) { + t.Run("should delete excess services", func(t *testing.T) { + ports := []corev1.ServicePort{{ + Port: 80, + Name: "web", + }} + deleteService := serviceCollector("delete-service-collector", ports) + createObjectIfNotExists(t, "delete-service-collector", &deleteService) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) + assert.NoError(t, err) + assert.True(t, exists) + + desired := desiredCollectorService(context.Background(), paramsCollector()) + opts := []client.ListOption{ + client.InNamespace(paramsTA().Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", paramsCollector().Instance.Namespace, paramsCollector().Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err = deleteServices(context.Background(), paramsCollector(), []corev1.Service{*desired}, opts) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func TestHeadlessService(t *testing.T) { + t.Run("should return headless service", func(t *testing.T) { + actual := headless(context.Background(), paramsCollector()) + assert.Equal(t, actual.Spec.ClusterIP, "None") + }) +} + +func TestMonitoringService(t *testing.T) { + t.Run("returned service should expose monitoring port", func(t *testing.T) { + expected := []corev1.ServicePort{{ + Name: "monitoring", + Port: 8888, + }} + actual := monitoringService(context.Background(), paramsCollector()) + assert.Equal(t, expected, actual.Spec.Ports) + + }) +} + +func TestDesiredTAService(t *testing.T) { + t.Run("should return service with default port", func(t *testing.T) { + expected := serviceTA("test-targetallocator") + actual := desiredTAService(paramsTA()) + + assert.Equal(t, expected, actual) + }) + +} + +func TestExpectedTAServices(t *testing.T) { + t.Run("should create the service", func(t *testing.T) { + err := expectedServices(context.Background(), paramsTA(), []corev1.Service{serviceTA("targetallocator")}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) +} + +func TestDeleteTAServices(t *testing.T) { + t.Run("should delete excess services", func(t *testing.T) { + deleteService := serviceTA("test-delete-targetallocator", 8888) + createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.True(t, exists) + + opts := []client.ListOption{ + client.InNamespace(paramsTA().Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", paramsTA().Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + err = deleteServices(context.Background(), paramsTA(), []corev1.Service{desiredTAService(paramsTA())}, opts) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func serviceCollector(name string, ports []corev1.ServicePort) corev1.Service { + labels := collector.Labels(paramsCollector().Instance) + labels["app.kubernetes.io/name"] = name + + selector := labels + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + Labels: labels, + Annotations: paramsCollector().Instance.Annotations, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + ClusterIP: "", + Ports: ports, + }, + } +} + +func serviceTA(name string, portOpt ...int32) corev1.Service { + port := int32(443) + if len(portOpt) > 0 { + port = portOpt[0] + } + params := paramsTA() + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: port, + TargetPort: intstr.FromInt(443), + }}, + }, + } +} diff --git a/pkg/collector/reconcile/serviceaccount.go b/pkg/reconcile/serviceaccount.go similarity index 100% rename from pkg/collector/reconcile/serviceaccount.go rename to pkg/reconcile/serviceaccount.go diff --git a/pkg/collector/reconcile/serviceaccount_test.go b/pkg/reconcile/serviceaccount_test.go similarity index 85% rename from pkg/collector/reconcile/serviceaccount_test.go rename to pkg/reconcile/serviceaccount_test.go index 2886fae935..be995751a4 100644 --- a/pkg/collector/reconcile/serviceaccount_test.go +++ b/pkg/reconcile/serviceaccount_test.go @@ -28,8 +28,8 @@ import ( func TestExpectedServiceAccounts(t *testing.T) { t.Run("should create service account", func(t *testing.T) { - desired := collector.ServiceAccount(params().Instance) - err := expectedServiceAccounts(context.Background(), params(), []v1.ServiceAccount{desired}) + desired := collector.ServiceAccount(paramsCollector().Instance) + err := expectedServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{desired}) assert.NoError(t, err) exists, err := populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) @@ -50,7 +50,7 @@ func TestExpectedServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = expectedServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) + err = expectedServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) assert.NoError(t, err) actual := v1.ServiceAccount{} @@ -77,7 +77,7 @@ func TestDeleteServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = deleteServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) + err = deleteServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) assert.NoError(t, err) exists, err = populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) @@ -101,7 +101,7 @@ func TestDeleteServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = deleteServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) + err = deleteServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) assert.NoError(t, err) exists, err = populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) diff --git a/pkg/collector/reconcile/statefulset.go b/pkg/reconcile/statefulset.go similarity index 100% rename from pkg/collector/reconcile/statefulset.go rename to pkg/reconcile/statefulset.go diff --git a/pkg/collector/reconcile/statefulset_test.go b/pkg/reconcile/statefulset_test.go similarity index 99% rename from pkg/collector/reconcile/statefulset_test.go rename to pkg/reconcile/statefulset_test.go index 4a0de27f27..5a77ea10f8 100644 --- a/pkg/collector/reconcile/statefulset_test.go +++ b/pkg/reconcile/statefulset_test.go @@ -28,7 +28,7 @@ import ( ) func TestExpectedStatefulsets(t *testing.T) { - param := params() + param := paramsCollector() expectedSs := collector.StatefulSet(param.Config, logger, param.Instance) t.Run("should create StatefulSet", func(t *testing.T) { diff --git a/pkg/targetallocator/reconcile/suite_test.go b/pkg/reconcile/suite_test.go similarity index 79% rename from pkg/targetallocator/reconcile/suite_test.go rename to pkg/reconcile/suite_test.go index cef4205192..560adabd13 100644 --- a/pkg/targetallocator/reconcile/suite_test.go +++ b/pkg/reconcile/suite_test.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -48,7 +49,7 @@ var instanceUID = uuid.NewUUID() func TestMain(m *testing.M) { testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, } cfg, err := testEnv.Start() @@ -80,7 +81,7 @@ func TestMain(m *testing.M) { os.Exit(code) } -func params() Params { +func paramsTA() Params { replicas := int32(1) configYAML, err := ioutil.ReadFile("suite_test.yaml") if err != nil { @@ -117,8 +118,61 @@ func params() Params { Config: string(configYAML), }, }, - Scheme: testScheme, - Log: logger, + Scheme: testScheme, + Log: logger, + Recorder: record.NewFakeRecorder(10), + } +} + +func paramsCollector() Params { + replicas := int32(2) + return Params{ + Config: config.New(), + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + Replicas: &replicas, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + +`, + }, + }, + Scheme: testScheme, + Log: logger, + Recorder: record.NewFakeRecorder(10), } } diff --git a/pkg/targetallocator/reconcile/suite_test.yaml b/pkg/reconcile/suite_test.yaml similarity index 100% rename from pkg/targetallocator/reconcile/suite_test.yaml rename to pkg/reconcile/suite_test.yaml diff --git a/pkg/targetallocator/container_test.go b/pkg/targetallocator/container_test.go index 98a8c3a0e4..61b130e988 100644 --- a/pkg/targetallocator/container_test.go +++ b/pkg/targetallocator/container_test.go @@ -17,9 +17,8 @@ package targetallocator import ( "testing" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/stretchr/testify/assert" + logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" diff --git a/pkg/targetallocator/deployment_test.go b/pkg/targetallocator/deployment_test.go index ce8e4f0fcb..302c78ac74 100644 --- a/pkg/targetallocator/deployment_test.go +++ b/pkg/targetallocator/deployment_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" diff --git a/pkg/targetallocator/reconcile/params.go b/pkg/targetallocator/reconcile/params.go deleted file mode 100644 index e9e6fa3dbf..0000000000 --- a/pkg/targetallocator/reconcile/params.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 reconcile - -import ( - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" -) - -// Params holds the reconciliation-specific parameters. -type Params struct { - Config config.Config - Client client.Client - Instance v1alpha1.OpenTelemetryCollector - Log logr.Logger - Scheme *runtime.Scheme -} diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go deleted file mode 100644 index 3dc2993076..0000000000 --- a/pkg/targetallocator/reconcile/service.go +++ /dev/null @@ -1,160 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete - -// Services reconciles the service(s) required for the instance in the current context. -func Services(ctx context.Context, params Params) error { - desired := []corev1.Service{} - - if IsAllocatorEnabled(params) { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, desiredService(params)) - } - - // first, handle the create/update parts - if err := expectedServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) - } - - // then, delete the extra objects - if err := deleteServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) - } - - return nil -} - -func desiredService(params Params) corev1.Service { - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: naming.TAService(params.Instance), - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: 443, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} - -func expectedServices(ctx context.Context, params Params, expected []corev1.Service) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &corev1.Service{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "service.name", desired.Name, "service.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - updated.Spec.Ports = desired.Spec.Ports - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - - params.Log.V(2).Info("applied", "service.name", desired.Name, "service.namespace", desired.Namespace) - } - - return nil -} - -func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &corev1.ServiceList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "service.name", existing.Name, "service.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/targetallocator/reconcile/service_test.go b/pkg/targetallocator/reconcile/service_test.go deleted file mode 100644 index aa1672a11f..0000000000 --- a/pkg/targetallocator/reconcile/service_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestDesiredService(t *testing.T) { - t.Run("should return service with default port", func(t *testing.T) { - expected := service("test-targetallocator") - actual := desiredService(params()) - - assert.Equal(t, expected, actual) - }) - -} - -func TestExpectedServices(t *testing.T) { - t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), params(), []corev1.Service{service("targetallocator")}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) -} - -func TestDeleteServices(t *testing.T) { - t.Run("should delete excess services", func(t *testing.T) { - deleteService := service("test-delete-targetallocator", 8888) - createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.True(t, exists) - - err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(params())}) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func service(name string, portOpt ...int32) corev1.Service { - port := int32(443) - if len(portOpt) > 0 { - port = portOpt[0] - } - params := params() - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: port, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} From 7dd75b3330bd2dda413579d1ecf8d5cded0b7469 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Wed, 4 Aug 2021 15:23:33 -0700 Subject: [PATCH 21/30] Updated kuttl tests to use namespace-scope resources --- .../e2e/smoke-targetallocator/00-install.yaml | 12 +++-- .../e2e/smoke-targetallocator/01-install.yaml | 37 ++------------ .../{01-assert.yaml => 02-assert.yaml} | 0 .../e2e/smoke-targetallocator/02-install.yaml | 37 ++++++++++++-- .../e2e/smoke-targetallocator/03-install.yaml | 5 ++ .../targetallocator-features/00-install.yaml | 12 +++-- .../targetallocator-features/01-install.yaml | 50 ++----------------- .../{01-assert.yaml => 02-assert.yaml} | 0 .../targetallocator-features/02-install.yaml | 50 +++++++++++++++++-- .../targetallocator-features/03-install.yaml | 5 ++ 10 files changed, 113 insertions(+), 95 deletions(-) rename tests/e2e/smoke-targetallocator/{01-assert.yaml => 02-assert.yaml} (100%) create mode 100644 tests/e2e/smoke-targetallocator/03-install.yaml rename tests/e2e/targetallocator-features/{01-assert.yaml => 02-assert.yaml} (100%) create mode 100644 tests/e2e/targetallocator-features/03-install.yaml diff --git a/tests/e2e/smoke-targetallocator/00-install.yaml b/tests/e2e/smoke-targetallocator/00-install.yaml index 6b758e1d18..9e5058d82b 100644 --- a/tests/e2e/smoke-targetallocator/00-install.yaml +++ b/tests/e2e/smoke-targetallocator/00-install.yaml @@ -1,4 +1,8 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create clusterrolebinding default-view-$NAMESPACE --clusterrole=view --serviceaccount=$NAMESPACE:default \ No newline at end of file +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-view +rules: +- apiGroups: [""] + resources: [ "pods" ] + verbs: [ "get", "list", "watch"] diff --git a/tests/e2e/smoke-targetallocator/01-install.yaml b/tests/e2e/smoke-targetallocator/01-install.yaml index 36eb9c9b1c..d0ccd90869 100644 --- a/tests/e2e/smoke-targetallocator/01-install.yaml +++ b/tests/e2e/smoke-targetallocator/01-install.yaml @@ -1,33 +1,4 @@ -apiVersion: opentelemetry.io/v1alpha1 -kind: OpenTelemetryCollector -metadata: - name: stateful -spec: - mode: statefulset - targetAllocator: - enabled: true - config: | - receivers: - jaeger: - protocols: - grpc: - - # Collect own metrics - prometheus: - config: - scrape_configs: - - job_name: 'otel-collector' - scrape_interval: 10s - static_configs: - - targets: [ '0.0.0.0:8888' ] - - processors: - - exporters: - logging: - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE create rolebinding default-view-$NAMESPACE --role=pod-view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/smoke-targetallocator/01-assert.yaml b/tests/e2e/smoke-targetallocator/02-assert.yaml similarity index 100% rename from tests/e2e/smoke-targetallocator/01-assert.yaml rename to tests/e2e/smoke-targetallocator/02-assert.yaml diff --git a/tests/e2e/smoke-targetallocator/02-install.yaml b/tests/e2e/smoke-targetallocator/02-install.yaml index 6bdb21adad..36eb9c9b1c 100644 --- a/tests/e2e/smoke-targetallocator/02-install.yaml +++ b/tests/e2e/smoke-targetallocator/02-install.yaml @@ -1,4 +1,33 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl delete clusterrolebinding default-view-$NAMESPACE \ No newline at end of file +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + targetAllocator: + enabled: true + config: | + receivers: + jaeger: + protocols: + grpc: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] diff --git a/tests/e2e/smoke-targetallocator/03-install.yaml b/tests/e2e/smoke-targetallocator/03-install.yaml new file mode 100644 index 0000000000..49a5c5c58c --- /dev/null +++ b/tests/e2e/smoke-targetallocator/03-install.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE delete rolebinding default-view-$NAMESPACE + command: kubectl -n $NAMESPACE delete role pod-view \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/00-install.yaml b/tests/e2e/targetallocator-features/00-install.yaml index 6b758e1d18..aeffa74b71 100644 --- a/tests/e2e/targetallocator-features/00-install.yaml +++ b/tests/e2e/targetallocator-features/00-install.yaml @@ -1,4 +1,8 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create clusterrolebinding default-view-$NAMESPACE --clusterrole=view --serviceaccount=$NAMESPACE:default \ No newline at end of file +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-view +rules: +- apiGroups: [""] + resources: [ "pods" ] + verbs: [ "get", "list", "watch"] \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/01-install.yaml b/tests/e2e/targetallocator-features/01-install.yaml index fc30d5d3e6..d0ccd90869 100644 --- a/tests/e2e/targetallocator-features/01-install.yaml +++ b/tests/e2e/targetallocator-features/01-install.yaml @@ -1,46 +1,4 @@ -apiVersion: opentelemetry.io/v1alpha1 -kind: OpenTelemetryCollector -metadata: - name: stateful -spec: - mode: statefulset - volumes: - - name: testvolume - volumeMounts: - - name: testvolume - mountPath: /usr/share/testvolume - volumeClaimTemplates: - - metadata: - name: testvolume - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 1Gi - targetAllocator: - enabled: true - config: | - receivers: - jaeger: - protocols: - grpc: - - # Collect own metrics - prometheus: - config: - scrape_configs: - - job_name: 'otel-collector' - scrape_interval: 10s - static_configs: - - targets: [ '0.0.0.0:8888' ] - - processors: - - exporters: - logging: - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE create rolebinding default-view-$NAMESPACE --role=pod-view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/01-assert.yaml b/tests/e2e/targetallocator-features/02-assert.yaml similarity index 100% rename from tests/e2e/targetallocator-features/01-assert.yaml rename to tests/e2e/targetallocator-features/02-assert.yaml diff --git a/tests/e2e/targetallocator-features/02-install.yaml b/tests/e2e/targetallocator-features/02-install.yaml index 6bdb21adad..fc30d5d3e6 100644 --- a/tests/e2e/targetallocator-features/02-install.yaml +++ b/tests/e2e/targetallocator-features/02-install.yaml @@ -1,4 +1,46 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl delete clusterrolebinding default-view-$NAMESPACE \ No newline at end of file +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + volumes: + - name: testvolume + volumeMounts: + - name: testvolume + mountPath: /usr/share/testvolume + volumeClaimTemplates: + - metadata: + name: testvolume + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi + targetAllocator: + enabled: true + config: | + receivers: + jaeger: + protocols: + grpc: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] diff --git a/tests/e2e/targetallocator-features/03-install.yaml b/tests/e2e/targetallocator-features/03-install.yaml new file mode 100644 index 0000000000..49a5c5c58c --- /dev/null +++ b/tests/e2e/targetallocator-features/03-install.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE delete rolebinding default-view-$NAMESPACE + command: kubectl -n $NAMESPACE delete role pod-view \ No newline at end of file From db4037350406a1b05761aca01787f4a3fc331499 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Thu, 5 Aug 2021 10:27:19 -0700 Subject: [PATCH 22/30] Revert part of "Updated folder structure to reduce code duplication between collector and targetallocator & Minor changes" This reverts commit 367c6237b9c0e9c77d141229748d91429ff31791 - only the folder structure change --- .../opentelemetrycollector_controller.go | 8 +- .../opentelemetrycollector_controller_test.go | 2 +- controllers/targetallocator_controller.go | 30 +- .../targetallocator_controller_test.go | 18 +- main.go | 11 +- pkg/collector/reconcile/configmap.go | 174 ++++++++++ pkg/collector/reconcile/configmap_test.go | 141 ++++++++ pkg/{ => collector}/reconcile/daemonset.go | 0 .../reconcile/daemonset_test.go | 2 +- pkg/collector/reconcile/deployment.go | 135 ++++++++ pkg/collector/reconcile/deployment_test.go | 130 +++++++ .../reconcile/opentelemetry.go | 0 .../reconcile/opentelemetry_test.go | 4 +- pkg/{ => collector}/reconcile/params.go | 0 pkg/{ => collector}/reconcile/service.go | 86 +---- pkg/collector/reconcile/service_test.go | 231 +++++++++++++ .../reconcile/serviceaccount.go | 0 .../reconcile/serviceaccount_test.go | 10 +- pkg/{ => collector}/reconcile/statefulset.go | 0 .../reconcile/statefulset_test.go | 2 +- pkg/collector/reconcile/suite_test.go | 156 +++++++++ pkg/reconcile/service_test.go | 320 ------------------ .../reconcile/configmap.go | 82 +---- .../reconcile/configmap_test.go | 148 +------- .../reconcile/deployment.go | 69 ++-- .../reconcile/deployment_test.go | 215 ++++-------- pkg/{ => targetallocator}/reconcile/helper.go | 5 + pkg/targetallocator/reconcile/params.go | 33 ++ pkg/targetallocator/reconcile/service.go | 160 +++++++++ pkg/targetallocator/reconcile/service_test.go | 100 ++++++ .../reconcile/suite_test.go | 62 +--- .../reconcile/suite_test.yaml | 0 32 files changed, 1447 insertions(+), 887 deletions(-) create mode 100644 pkg/collector/reconcile/configmap.go create mode 100644 pkg/collector/reconcile/configmap_test.go rename pkg/{ => collector}/reconcile/daemonset.go (100%) rename pkg/{ => collector}/reconcile/daemonset_test.go (99%) create mode 100644 pkg/collector/reconcile/deployment.go create mode 100644 pkg/collector/reconcile/deployment_test.go rename pkg/{ => collector}/reconcile/opentelemetry.go (100%) rename pkg/{ => collector}/reconcile/opentelemetry_test.go (92%) rename pkg/{ => collector}/reconcile/params.go (100%) rename pkg/{ => collector}/reconcile/service.go (78%) create mode 100644 pkg/collector/reconcile/service_test.go rename pkg/{ => collector}/reconcile/serviceaccount.go (100%) rename pkg/{ => collector}/reconcile/serviceaccount_test.go (85%) rename pkg/{ => collector}/reconcile/statefulset.go (100%) rename pkg/{ => collector}/reconcile/statefulset_test.go (99%) create mode 100644 pkg/collector/reconcile/suite_test.go delete mode 100644 pkg/reconcile/service_test.go rename pkg/{ => targetallocator}/reconcile/configmap.go (70%) rename pkg/{ => targetallocator}/reconcile/configmap_test.go (53%) rename pkg/{ => targetallocator}/reconcile/deployment.go (69%) rename pkg/{ => targetallocator}/reconcile/deployment_test.go (58%) rename pkg/{ => targetallocator}/reconcile/helper.go (81%) create mode 100644 pkg/targetallocator/reconcile/params.go create mode 100644 pkg/targetallocator/reconcile/service.go create mode 100644 pkg/targetallocator/reconcile/service_test.go rename pkg/{ => targetallocator}/reconcile/suite_test.go (79%) rename pkg/{ => targetallocator}/reconcile/suite_test.yaml (100%) diff --git a/controllers/opentelemetrycollector_controller.go b/controllers/opentelemetrycollector_controller.go index 9a40403605..27a54ff62a 100644 --- a/controllers/opentelemetrycollector_controller.go +++ b/controllers/opentelemetrycollector_controller.go @@ -30,7 +30,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/reconcile" ) // OpenTelemetryCollectorReconciler reconciles a OpenTelemetryCollector object. @@ -66,7 +66,7 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler { p.Tasks = []Task{ { "config maps", - reconcile.CollectorConfigMaps, + reconcile.ConfigMaps, true, }, { @@ -76,12 +76,12 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler { }, { "services", - reconcile.CollectorServices, + reconcile.Services, true, }, { "deployments", - reconcile.CollectorDeployments, + reconcile.Deployments, true, }, { diff --git a/controllers/opentelemetrycollector_controller_test.go b/controllers/opentelemetrycollector_controller_test.go index 4cc14ec5fe..7f2d203900 100644 --- a/controllers/opentelemetrycollector_controller_test.go +++ b/controllers/opentelemetrycollector_controller_test.go @@ -36,7 +36,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/reconcile" ) var logger = logf.Log.WithName("unit-tests") diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go index 9a73fc691b..3f7d5e92c0 100644 --- a/controllers/targetallocator_controller.go +++ b/controllers/targetallocator_controller.go @@ -29,7 +29,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" ) // OpenTelemetryTargetAllocatorReconciler reconciles a OpenTelemetryTargetAllocator object. @@ -38,26 +38,42 @@ type OpenTelemetryTargetAllocatorReconciler struct { log logr.Logger scheme *runtime.Scheme config config.Config - tasks []Task + tasks []TgAlTask +} + +// TgAlTask represents a reconciliation task to be executed by the reconciler. +type TgAlTask struct { + Name string + Do func(context.Context, reconcile.Params) error + BailOnError bool +} + +// TgAlParams is the set of options to build a new OpenTelemetryTargetAllocatorReconciler. +type TgAlParams struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Config config.Config + Tasks []TgAlTask } // NewTargetAlllocatorReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. -func NewTargetAllocatorReconciler(p Params) *OpenTelemetryTargetAllocatorReconciler { +func NewTargetAllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { if len(p.Tasks) == 0 { - p.Tasks = []Task{ + p.Tasks = []TgAlTask{ { "config maps", - reconcile.TAConfigMaps, + reconcile.ConfigMaps, true, }, { "deployments", - reconcile.TADeployments, + reconcile.Deployments, true, }, { "services", - reconcile.TAServices, + reconcile.Services, true, }, } diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go index ea136455dd..b79b569a00 100644 --- a/controllers/targetallocator_controller_test.go +++ b/controllers/targetallocator_controller_test.go @@ -34,17 +34,17 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/reconcile" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" ) func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { // prepare cfg := config.New() - configYAML, err := ioutil.ReadFile("../pkg/reconcile/suite_test.yaml") + configYAML, err := ioutil.ReadFile("../pkg/targetallocator/reconcile/suite_test.yaml") require.NoError(t, err) nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: testScheme, @@ -113,9 +113,9 @@ func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { func TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { // prepare taskCalled := false - reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Log: logger, - Tasks: []controllers.Task{ + Tasks: []controllers.TgAlTask{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -147,12 +147,12 @@ func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { taskCalled := false expectedErr := errors.New("should fail!") nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.Task{ + Tasks: []controllers.TgAlTask{ { Name: "should-fail", Do: func(context.Context, reconcile.Params) error { @@ -197,12 +197,12 @@ func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { // prepare cfg := config.New() nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.Params{ + reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ Client: k8sClient, Log: logger, Scheme: scheme.Scheme, Config: cfg, - Tasks: []controllers.Task{ + Tasks: []controllers.TgAlTask{ { Name: "should-not-be-called", Do: func(context.Context, reconcile.Params) error { diff --git a/main.go b/main.go index 9f75b648a8..424ed67bd6 100644 --- a/main.go +++ b/main.go @@ -155,12 +155,11 @@ func main() { os.Exit(1) } - if err = controllers.NewTargetAllocatorReconciler(controllers.Params{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), - Scheme: mgr.GetScheme(), - Config: cfg, - Recorder: mgr.GetEventRecorderFor("opentelemetry-targetallocator"), + if err = controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), + Scheme: mgr.GetScheme(), + Config: cfg, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") os.Exit(1) diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go new file mode 100644 index 0000000000..82d57b5393 --- /dev/null +++ b/pkg/collector/reconcile/configmap.go @@ -0,0 +1,174 @@ +// 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 reconcile + +import ( + "context" + "fmt" + "reflect" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete + +// ConfigMaps reconciles the config map(s) required for the instance in the current context. +func ConfigMaps(ctx context.Context, params Params) error { + desired := []corev1.ConfigMap{ + desiredConfigMap(ctx, params), + } + + // first, handle the create/update parts + if err := expectedConfigMaps(ctx, params, desired, true); err != nil { + return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) + } + + // then, delete the extra objects + if err := deleteConfigMaps(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) + } + + return nil +} + +func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { + name := naming.ConfigMap(params.Instance) + labels := collector.Labels(params.Instance) + labels["app.kubernetes.io/name"] = name + + return corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + Annotations: params.Instance.Annotations, + }, + Data: map[string]string{ + "collector.yaml": params.Instance.Spec.Config, + }, + } +} + +func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &corev1.ConfigMap{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && errors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + if errors.IsAlreadyExists(err) && retry { + // let's try again? we probably had multiple updates at one, and now it exists already + if err := expectedConfigMaps(ctx, params, expected, false); err != nil { + // somethin else happened now... + return err + } + + // we succeeded in the retry, exit this attempt + return nil + } + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Annotations == nil { + updated.Annotations = map[string]string{} + } + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + + updated.Data = desired.Data + updated.BinaryData = desired.BinaryData + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Annotations { + updated.ObjectMeta.Annotations[k] = v + } + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + if configMapChanged(&desired, existing) { + params.Recorder.Event(updated, "Normal", "ConfigUpdate ", fmt.Sprintf("OpenTelemetry Config changed - %s/%s", desired.Namespace, desired.Name)) + } + + params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) + } + + return nil +} + +func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &corev1.ConfigMapList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "configmap.name", existing.Name, "configmap.namespace", existing.Namespace) + } + } + + return nil +} + +func configMapChanged(desired *corev1.ConfigMap, actual *corev1.ConfigMap) bool { + return !reflect.DeepEqual(desired.Data, actual.Data) + +} diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go new file mode 100644 index 0000000000..7eaee37fc5 --- /dev/null +++ b/pkg/collector/reconcile/configmap_test.go @@ -0,0 +1,141 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +func TestDesiredConfigMap(t *testing.T) { + t.Run("should return expected config map", func(t *testing.T) { + expectedLables := map[string]string{ + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/name": "test-collector", + } + + expectedData := map[string]string{ + "collector.yaml": ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + +`, + } + + actual := desiredConfigMap(context.Background(), params()) + + assert.Equal(t, "test-collector", actual.Name) + assert.Equal(t, expectedLables, actual.Labels) + assert.Equal(t, expectedData, actual.Data) + + }) + +} + +func TestExpectedConfigMap(t *testing.T) { + t.Run("should create config map", func(t *testing.T) { + err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + }) + + t.Run("should update config map", func(t *testing.T) { + + param := Params{ + Config: config.New(), + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + }, + Scheme: testScheme, + Log: logger, + Recorder: record.NewFakeRecorder(10), + } + cm := desiredConfigMap(context.Background(), param) + createObjectIfNotExists(t, "test-collector", &cm) + + err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) + assert.NoError(t, err) + + actual := v1.ConfigMap{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Equal(t, params().Instance.Spec.Config, actual.Data["collector.yaml"]) + }) + + t.Run("should delete config map", func(t *testing.T) { + + deletecm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-delete-collector", + Namespace: "default", + Labels: map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }, + }, + } + createObjectIfNotExists(t, "test-delete-collector", &deletecm) + + exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) + assert.True(t, exists) + + err := deleteConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}) + assert.NoError(t, err) + + exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) + assert.False(t, exists) + }) +} diff --git a/pkg/reconcile/daemonset.go b/pkg/collector/reconcile/daemonset.go similarity index 100% rename from pkg/reconcile/daemonset.go rename to pkg/collector/reconcile/daemonset.go diff --git a/pkg/reconcile/daemonset_test.go b/pkg/collector/reconcile/daemonset_test.go similarity index 99% rename from pkg/reconcile/daemonset_test.go rename to pkg/collector/reconcile/daemonset_test.go index 24e8e99759..8a4751e8b6 100644 --- a/pkg/reconcile/daemonset_test.go +++ b/pkg/collector/reconcile/daemonset_test.go @@ -28,7 +28,7 @@ import ( ) func TestExpectedDaemonsets(t *testing.T) { - param := paramsCollector() + param := params() expectedDs := collector.DaemonSet(param.Config, logger, param.Instance) t.Run("should create Daemonset", func(t *testing.T) { diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go new file mode 100644 index 0000000000..41b0f0de65 --- /dev/null +++ b/pkg/collector/reconcile/deployment.go @@ -0,0 +1,135 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" +) + +// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete + +// Deployments reconciles the deployment(s) required for the instance in the current context. +func Deployments(ctx context.Context, params Params) error { + desired := []appsv1.Deployment{} + if params.Instance.Spec.Mode == "deployment" { + desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) + } + + // first, handle the create/update parts + if err := expectedDeployments(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected deployments: %v", err) + } + + // then, delete the extra objects + if err := deleteDeployments(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) + } + + return nil +} + +func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &appsv1.Deployment{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Annotations == nil { + updated.Annotations = map[string]string{} + } + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + + updated.Spec = desired.Spec + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Annotations { + updated.ObjectMeta.Annotations[k] = v + } + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + + params.Log.V(2).Info("applied", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) + } + + return nil +} + +func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &appsv1.DeploymentList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "deployment.name", existing.Name, "deployment.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go new file mode 100644 index 0000000000..7295ef05fc --- /dev/null +++ b/pkg/collector/reconcile/deployment_test.go @@ -0,0 +1,130 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" +) + +func TestExpectedDeployments(t *testing.T) { + param := params() + expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) + + t.Run("should create deployment", func(t *testing.T) { + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + t.Run("should update deployment", func(t *testing.T) { + createObjectIfNotExists(t, "test-collector", &expectedDeploy) + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Equal(t, int32(2), *actual.Spec.Replicas) + }) + + t.Run("should delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Labels = labels + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, + } + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.False(t, exists) + + }) + + t.Run("should not delete deployment", func(t *testing.T) { + labels := map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", + } + deploy := v1.Deployment{} + deploy.Name = "dummy" + deploy.Namespace = "default" + deploy.Spec = v1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "dummy", + Image: "busybox", + }}, + }, + }, + } + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) + + assert.True(t, exists) + + }) +} diff --git a/pkg/reconcile/opentelemetry.go b/pkg/collector/reconcile/opentelemetry.go similarity index 100% rename from pkg/reconcile/opentelemetry.go rename to pkg/collector/reconcile/opentelemetry.go diff --git a/pkg/reconcile/opentelemetry_test.go b/pkg/collector/reconcile/opentelemetry_test.go similarity index 92% rename from pkg/reconcile/opentelemetry_test.go rename to pkg/collector/reconcile/opentelemetry_test.go index f608623c58..50c08d78e1 100644 --- a/pkg/reconcile/opentelemetry_test.go +++ b/pkg/collector/reconcile/opentelemetry_test.go @@ -26,9 +26,9 @@ import ( func TestSelf(t *testing.T) { t.Run("should add version to the status", func(t *testing.T) { - instance := paramsCollector().Instance + instance := params().Instance createObjectIfNotExists(t, "test", &instance) - err := Self(context.Background(), paramsCollector()) + err := Self(context.Background(), params()) assert.NoError(t, err) actual := v1alpha1.OpenTelemetryCollector{} diff --git a/pkg/reconcile/params.go b/pkg/collector/reconcile/params.go similarity index 100% rename from pkg/reconcile/params.go rename to pkg/collector/reconcile/params.go diff --git a/pkg/reconcile/service.go b/pkg/collector/reconcile/service.go similarity index 78% rename from pkg/reconcile/service.go rename to pkg/collector/reconcile/service.go index 8894e21d9c..4f5f09813f 100644 --- a/pkg/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -23,7 +23,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -31,17 +30,16 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete -// CollectorServices reconciles the service(s) required for the instance in the current context. -func CollectorServices(ctx context.Context, params Params) error { +// Services reconciles the service(s) required for the instance in the current context. +func Services(ctx context.Context, params Params) error { desired := []corev1.Service{} if params.Instance.Spec.Mode != v1alpha1.ModeSidecar { type builder func(context.Context, Params) *corev1.Service - for _, builder := range []builder{desiredCollectorService, headless, monitoringService} { + for _, builder := range []builder{desiredService, headless, monitoringService} { svc := builder(ctx, params) // add only the non-nil to the list if svc != nil { @@ -56,53 +54,14 @@ func CollectorServices(ctx context.Context, params Params) error { } // then, delete the extra objects - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - if err := deleteServices(ctx, params, desired, opts); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) - } - - return nil -} - -// TAServices reconciles the service(s) required for the instance in the current context. -func TAServices(ctx context.Context, params Params) error { - desired := []corev1.Service{} - - if params.Instance.Spec.TargetAllocator.Enabled { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, desiredTAService(params)) - } - - // first, handle the create/update parts - if err := expectedServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) - } - - // then, delete the extra objects - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - if err := deleteServices(ctx, params, desired, opts); err != nil { + if err := deleteServices(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) } return nil } -func desiredCollectorService(ctx context.Context, params Params) *corev1.Service { +func desiredService(ctx context.Context, params Params) *corev1.Service { labels := collector.Labels(params.Instance) labels["app.kubernetes.io/name"] = naming.Service(params.Instance) @@ -162,32 +121,8 @@ func desiredCollectorService(ctx context.Context, params Params) *corev1.Service } } -func desiredTAService(params Params) corev1.Service { - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: naming.TAService(params.Instance), - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: 443, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} - func headless(ctx context.Context, params Params) *corev1.Service { - h := desiredCollectorService(ctx, params) + h := desiredService(ctx, params) if h == nil { return nil } @@ -273,7 +208,14 @@ func expectedServices(ctx context.Context, params Params, expected []corev1.Serv return nil } -func deleteServices(ctx context.Context, params Params, expected []corev1.Service, opts []client.ListOption) error { +func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } list := &corev1.ServiceList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) diff --git a/pkg/collector/reconcile/service_test.go b/pkg/collector/reconcile/service_test.go new file mode 100644 index 0000000000..461f5f69ab --- /dev/null +++ b/pkg/collector/reconcile/service_test.go @@ -0,0 +1,231 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector" +) + +func TestExtractPortNumbersAndNames(t *testing.T) { + t.Run("should return extracted port names and numbers", func(t *testing.T) { + ports := []v1.ServicePort{{Name: "web", Port: 8080}, {Name: "tcp", Port: 9200}} + expectedPortNames := map[string]bool{"web": true, "tcp": true} + expectedPortNumbers := map[int32]bool{8080: true, 9200: true} + + actualPortNumbers, actualPortNames := extractPortNumbersAndNames(ports) + assert.Equal(t, expectedPortNames, actualPortNames) + assert.Equal(t, expectedPortNumbers, actualPortNumbers) + + }) +} + +func TestFilterPort(t *testing.T) { + + tests := []struct { + name string + candidate v1.ServicePort + portNumbers map[int32]bool + portNames map[string]bool + expected v1.ServicePort + }{ + { + name: "should filter out duplicate port", + candidate: v1.ServicePort{Name: "web", Port: 8080}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"test": true, "metrics": true}, + }, + + { + name: "should not filter unique port", + candidate: v1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"test": true, "metrics": true}, + expected: v1.ServicePort{Name: "web", Port: 8090}, + }, + + { + name: "should change the duplicate portName", + candidate: v1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"web": true, "metrics": true}, + expected: v1.ServicePort{Name: "port-8090", Port: 8090}, + }, + + { + name: "should return nil if fallback name clashes with existing portName", + candidate: v1.ServicePort{Name: "web", Port: 8090}, + portNumbers: map[int32]bool{8080: true, 9200: true}, + portNames: map[string]bool{"web": true, "port-8090": true}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := filterPort(logger, test.candidate, test.portNumbers, test.portNames) + if test.expected != (v1.ServicePort{}) { + assert.Equal(t, test.expected, *actual) + return + } + assert.Nil(t, actual) + + }) + + } +} + +func TestDesiredService(t *testing.T) { + t.Run("should return nil service for unknown receiver and protocol", func(t *testing.T) { + params := Params{ + Config: config.Config{}, + Client: k8sClient, + Log: logger, + Instance: v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{Config: `receivers: + test: + protocols: + unknown:`}, + }, + } + + actual := desiredService(context.Background(), params) + assert.Nil(t, actual) + + }) + t.Run("should return service with port mentioned in Instance.Spec.Ports and inferred ports", func(t *testing.T) { + + jaegerPorts := v1.ServicePort{ + Name: "jaeger-grpc", + Protocol: "TCP", + Port: 14250, + } + ports := append(params().Instance.Spec.Ports, jaegerPorts) + expected := service("test-collector", ports) + actual := desiredService(context.Background(), params()) + + assert.Equal(t, expected, *actual) + + }) + +} + +func TestExpectedServices(t *testing.T) { + t.Run("should create the service", func(t *testing.T) { + err := expectedServices(context.Background(), params(), []v1.Service{service("test-collector", params().Instance.Spec.Ports)}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + t.Run("should update service", func(t *testing.T) { + serviceInstance := service("test-collector", params().Instance.Spec.Ports) + createObjectIfNotExists(t, "test-collector", &serviceInstance) + + extraPorts := v1.ServicePort{ + Name: "port-web", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), + } + + ports := append(params().Instance.Spec.Ports, extraPorts) + err := expectedServices(context.Background(), params(), []v1.Service{service("test-collector", ports)}) + assert.NoError(t, err) + + actual := v1.Service{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + assert.Contains(t, actual.Spec.Ports, extraPorts) + + }) +} + +func TestDeleteServices(t *testing.T) { + t.Run("should delete excess services", func(t *testing.T) { + ports := []v1.ServicePort{{ + Port: 80, + Name: "web", + }} + deleteService := service("delete-service-collector", ports) + createObjectIfNotExists(t, "delete-service-collector", &deleteService) + + exists, err := populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) + assert.NoError(t, err) + assert.True(t, exists) + + desired := desiredService(context.Background(), params()) + err = deleteServices(context.Background(), params(), []v1.Service{*desired}) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &v1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func TestHeadlessService(t *testing.T) { + t.Run("should return headless service", func(t *testing.T) { + actual := headless(context.Background(), params()) + assert.Equal(t, actual.Spec.ClusterIP, "None") + }) +} + +func TestMonitoringService(t *testing.T) { + t.Run("returned service should expose monitoring port", func(t *testing.T) { + expected := []v1.ServicePort{{ + Name: "monitoring", + Port: 8888, + }} + actual := monitoringService(context.Background(), params()) + assert.Equal(t, expected, actual.Spec.Ports) + + }) +} + +func service(name string, ports []v1.ServicePort) v1.Service { + labels := collector.Labels(params().Instance) + labels["app.kubernetes.io/name"] = name + + selector := labels + return v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + Labels: labels, + Annotations: params().Instance.Annotations, + }, + Spec: v1.ServiceSpec{ + Selector: selector, + ClusterIP: "", + Ports: ports, + }, + } +} diff --git a/pkg/reconcile/serviceaccount.go b/pkg/collector/reconcile/serviceaccount.go similarity index 100% rename from pkg/reconcile/serviceaccount.go rename to pkg/collector/reconcile/serviceaccount.go diff --git a/pkg/reconcile/serviceaccount_test.go b/pkg/collector/reconcile/serviceaccount_test.go similarity index 85% rename from pkg/reconcile/serviceaccount_test.go rename to pkg/collector/reconcile/serviceaccount_test.go index be995751a4..2886fae935 100644 --- a/pkg/reconcile/serviceaccount_test.go +++ b/pkg/collector/reconcile/serviceaccount_test.go @@ -28,8 +28,8 @@ import ( func TestExpectedServiceAccounts(t *testing.T) { t.Run("should create service account", func(t *testing.T) { - desired := collector.ServiceAccount(paramsCollector().Instance) - err := expectedServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{desired}) + desired := collector.ServiceAccount(params().Instance) + err := expectedServiceAccounts(context.Background(), params(), []v1.ServiceAccount{desired}) assert.NoError(t, err) exists, err := populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) @@ -50,7 +50,7 @@ func TestExpectedServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = expectedServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) + err = expectedServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) assert.NoError(t, err) actual := v1.ServiceAccount{} @@ -77,7 +77,7 @@ func TestDeleteServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = deleteServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) + err = deleteServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) assert.NoError(t, err) exists, err = populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) @@ -101,7 +101,7 @@ func TestDeleteServiceAccounts(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - err = deleteServiceAccounts(context.Background(), paramsCollector(), []v1.ServiceAccount{collector.ServiceAccount(paramsCollector().Instance)}) + err = deleteServiceAccounts(context.Background(), params(), []v1.ServiceAccount{collector.ServiceAccount(params().Instance)}) assert.NoError(t, err) exists, err = populateObjectIfExists(t, &v1.ServiceAccount{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) diff --git a/pkg/reconcile/statefulset.go b/pkg/collector/reconcile/statefulset.go similarity index 100% rename from pkg/reconcile/statefulset.go rename to pkg/collector/reconcile/statefulset.go diff --git a/pkg/reconcile/statefulset_test.go b/pkg/collector/reconcile/statefulset_test.go similarity index 99% rename from pkg/reconcile/statefulset_test.go rename to pkg/collector/reconcile/statefulset_test.go index 5a77ea10f8..4a0de27f27 100644 --- a/pkg/reconcile/statefulset_test.go +++ b/pkg/collector/reconcile/statefulset_test.go @@ -28,7 +28,7 @@ import ( ) func TestExpectedStatefulsets(t *testing.T) { - param := paramsCollector() + param := params() expectedSs := collector.StatefulSet(param.Config, logger, param.Instance) t.Run("should create StatefulSet", func(t *testing.T) { diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go new file mode 100644 index 0000000000..d2469ad44a --- /dev/null +++ b/pkg/collector/reconcile/suite_test.go @@ -0,0 +1,156 @@ +// 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 reconcile + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "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/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +var k8sClient client.Client +var testEnv *envtest.Environment +var testScheme *runtime.Scheme = scheme.Scheme +var logger = logf.Log.WithName("unit-tests") + +var instanceUID = uuid.NewUUID() + +func TestMain(m *testing.M) { + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + } + + cfg, err := testEnv.Start() + if err != nil { + fmt.Printf("failed to start testEnv: %v", err) + os.Exit(1) + } + + if err := v1alpha1.AddToScheme(testScheme); err != nil { + fmt.Printf("failed to register scheme: %v", err) + os.Exit(1) + } + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) + if err != nil { + fmt.Printf("failed to setup a Kubernetes client: %v", err) + os.Exit(1) + } + + code := m.Run() + + err = testEnv.Stop() + if err != nil { + fmt.Printf("failed to stop testEnv: %v", err) + os.Exit(1) + } + + os.Exit(code) +} + +func params() Params { + replicas := int32(2) + return Params{ + Config: config.New(), + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + Replicas: &replicas, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + +`, + }, + }, + Scheme: testScheme, + Log: logger, + Recorder: record.NewFakeRecorder(10), + } +} + +func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { + tb.Helper() + err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) + if errors.IsNotFound(err) { + err := k8sClient.Create(context.Background(), + object) + assert.NoError(tb, err) + } +} + +func populateObjectIfExists(t testing.TB, object client.Object, namespacedName types.NamespacedName) (bool, error) { + t.Helper() + err := k8sClient.Get(context.Background(), namespacedName, object) + if errors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil + +} diff --git a/pkg/reconcile/service_test.go b/pkg/reconcile/service_test.go deleted file mode 100644 index edeec9e527..0000000000 --- a/pkg/reconcile/service_test.go +++ /dev/null @@ -1,320 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestExtractPortNumbersAndNames(t *testing.T) { - t.Run("should return extracted port names and numbers", func(t *testing.T) { - ports := []corev1.ServicePort{{Name: "web", Port: 8080}, {Name: "tcp", Port: 9200}} - expectedPortNames := map[string]bool{"web": true, "tcp": true} - expectedPortNumbers := map[int32]bool{8080: true, 9200: true} - - actualPortNumbers, actualPortNames := extractPortNumbersAndNames(ports) - assert.Equal(t, expectedPortNames, actualPortNames) - assert.Equal(t, expectedPortNumbers, actualPortNumbers) - - }) -} - -func TestFilterPort(t *testing.T) { - - tests := []struct { - name string - candidate corev1.ServicePort - portNumbers map[int32]bool - portNames map[string]bool - expected corev1.ServicePort - }{ - { - name: "should filter out duplicate port", - candidate: corev1.ServicePort{Name: "web", Port: 8080}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"test": true, "metrics": true}, - }, - - { - name: "should not filter unique port", - candidate: corev1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"test": true, "metrics": true}, - expected: corev1.ServicePort{Name: "web", Port: 8090}, - }, - - { - name: "should change the duplicate portName", - candidate: corev1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"web": true, "metrics": true}, - expected: corev1.ServicePort{Name: "port-8090", Port: 8090}, - }, - - { - name: "should return nil if fallback name clashes with existing portName", - candidate: corev1.ServicePort{Name: "web", Port: 8090}, - portNumbers: map[int32]bool{8080: true, 9200: true}, - portNames: map[string]bool{"web": true, "port-8090": true}, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := filterPort(logger, test.candidate, test.portNumbers, test.portNames) - if test.expected != (corev1.ServicePort{}) { - assert.Equal(t, test.expected, *actual) - return - } - assert.Nil(t, actual) - - }) - - } -} - -func TestDesiredCollectorService(t *testing.T) { - t.Run("should return nil service for unknown receiver and protocol", func(t *testing.T) { - params := Params{ - Config: config.Config{}, - Client: k8sClient, - Log: logger, - Instance: v1alpha1.OpenTelemetryCollector{ - Spec: v1alpha1.OpenTelemetryCollectorSpec{Config: `receivers: - test: - protocols: - unknown:`}, - }, - } - - actual := desiredCollectorService(context.Background(), params) - assert.Nil(t, actual) - - }) - t.Run("should return service with port mentioned in Instance.Spec.Ports and inferred ports", func(t *testing.T) { - - jaegerPorts := corev1.ServicePort{ - Name: "jaeger-grpc", - Protocol: "TCP", - Port: 14250, - } - ports := append(paramsCollector().Instance.Spec.Ports, jaegerPorts) - expected := serviceCollector("test-collector", ports) - actual := desiredCollectorService(context.Background(), paramsCollector()) - - assert.Equal(t, expected, *actual) - - }) - -} - -func TestExpectedCollectorServices(t *testing.T) { - t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), paramsCollector(), []corev1.Service{serviceCollector("test-collector", paramsCollector().Instance.Spec.Ports)}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) - t.Run("should update service", func(t *testing.T) { - serviceInstance := serviceCollector("test-collector", paramsCollector().Instance.Spec.Ports) - createObjectIfNotExists(t, "test-collector", &serviceInstance) - - extraPorts := corev1.ServicePort{ - Name: "port-web", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - } - - ports := append(paramsCollector().Instance.Spec.Ports, extraPorts) - err := expectedServices(context.Background(), paramsCollector(), []corev1.Service{serviceCollector("test-collector", ports)}) - assert.NoError(t, err) - - actual := corev1.Service{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Contains(t, actual.Spec.Ports, extraPorts) - - }) -} - -func TestDeleteCollectorServices(t *testing.T) { - t.Run("should delete excess services", func(t *testing.T) { - ports := []corev1.ServicePort{{ - Port: 80, - Name: "web", - }} - deleteService := serviceCollector("delete-service-collector", ports) - createObjectIfNotExists(t, "delete-service-collector", &deleteService) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) - assert.NoError(t, err) - assert.True(t, exists) - - desired := desiredCollectorService(context.Background(), paramsCollector()) - opts := []client.ListOption{ - client.InNamespace(paramsTA().Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", paramsCollector().Instance.Namespace, paramsCollector().Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err = deleteServices(context.Background(), paramsCollector(), []corev1.Service{*desired}, opts) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "delete-service-collector"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func TestHeadlessService(t *testing.T) { - t.Run("should return headless service", func(t *testing.T) { - actual := headless(context.Background(), paramsCollector()) - assert.Equal(t, actual.Spec.ClusterIP, "None") - }) -} - -func TestMonitoringService(t *testing.T) { - t.Run("returned service should expose monitoring port", func(t *testing.T) { - expected := []corev1.ServicePort{{ - Name: "monitoring", - Port: 8888, - }} - actual := monitoringService(context.Background(), paramsCollector()) - assert.Equal(t, expected, actual.Spec.Ports) - - }) -} - -func TestDesiredTAService(t *testing.T) { - t.Run("should return service with default port", func(t *testing.T) { - expected := serviceTA("test-targetallocator") - actual := desiredTAService(paramsTA()) - - assert.Equal(t, expected, actual) - }) - -} - -func TestExpectedTAServices(t *testing.T) { - t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), paramsTA(), []corev1.Service{serviceTA("targetallocator")}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) -} - -func TestDeleteTAServices(t *testing.T) { - t.Run("should delete excess services", func(t *testing.T) { - deleteService := serviceTA("test-delete-targetallocator", 8888) - createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.True(t, exists) - - opts := []client.ListOption{ - client.InNamespace(paramsTA().Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", paramsTA().Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err = deleteServices(context.Background(), paramsTA(), []corev1.Service{desiredTAService(paramsTA())}, opts) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func serviceCollector(name string, ports []corev1.ServicePort) corev1.Service { - labels := collector.Labels(paramsCollector().Instance) - labels["app.kubernetes.io/name"] = name - - selector := labels - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - Labels: labels, - Annotations: paramsCollector().Instance.Annotations, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - ClusterIP: "", - Ports: ports, - }, - } -} - -func serviceTA(name string, portOpt ...int32) corev1.Service { - port := int32(443) - if len(portOpt) > 0 { - port = portOpt[0] - } - params := paramsTA() - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: port, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} diff --git a/pkg/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go similarity index 70% rename from pkg/reconcile/configmap.go rename to pkg/targetallocator/reconcile/configmap.go index 85b193b930..b768fdb6ec 100644 --- a/pkg/reconcile/configmap.go +++ b/pkg/targetallocator/reconcile/configmap.go @@ -17,7 +17,6 @@ package reconcile import ( "context" "fmt" - "reflect" "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" @@ -27,45 +26,18 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete -// CollectorConfigMaps reconciles the config map(s) required for the collector instance in the current context. -func CollectorConfigMaps(ctx context.Context, params Params) error { - desired := []corev1.ConfigMap{ - desiredCollectorConfigMap(ctx, params), - } - - // first, handle the create/update parts - if err := expectedConfigMaps(ctx, params, desired, true); err != nil { - return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) - } - - // then, delete the extra objects - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - if err := deleteConfigMaps(ctx, params, desired, opts); err != nil { - return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) - } - - return nil -} - -// TAConfigMaps reconciles the config map(s) required for the target allocator instance in the current context. -func TAConfigMaps(ctx context.Context, params Params) error { +// ConfigMaps reconciles the config map(s) required for the instance in the current context. +func ConfigMaps(ctx context.Context, params Params) error { desired := []corev1.ConfigMap{} - if params.Instance.Spec.TargetAllocator.Enabled { - cm, err := desiredTAConfigMap(ctx, params) + if IsAllocatorEnabled(params) { + cm, err := desiredConfigMap(ctx, params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } @@ -78,39 +50,14 @@ func TAConfigMaps(ctx context.Context, params Params) error { } // then, delete the extra objects - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - if err := deleteConfigMaps(ctx, params, desired, opts); err != nil { + if err := deleteConfigMaps(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) } return nil } -func desiredCollectorConfigMap(_ context.Context, params Params) corev1.ConfigMap { - name := naming.ConfigMap(params.Instance) - labels := collector.Labels(params.Instance) - labels["app.kubernetes.io/name"] = name - - return corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - Annotations: params.Instance.Annotations, - }, - Data: map[string]string{ - "collector.yaml": params.Instance.Spec.Config, - }, - } -} - -func desiredTAConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { +func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { name := naming.TAConfigMap(params.Instance) labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name @@ -200,9 +147,6 @@ func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.Co if err := params.Client.Patch(ctx, updated, patch); err != nil { return fmt.Errorf("failed to apply changes: %w", err) } - if configMapChanged(&desired, existing) { - params.Recorder.Event(updated, "Normal", "ConfigUpdate ", fmt.Sprintf("OpenTelemetry Config changed - %s/%s", desired.Namespace, desired.Name)) - } params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) } @@ -210,7 +154,14 @@ func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.Co return nil } -func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, opts []client.ListOption) error { +func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } list := &corev1.ConfigMapList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) @@ -235,8 +186,3 @@ func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.Conf return nil } - -func configMapChanged(desired *corev1.ConfigMap, actual *corev1.ConfigMap) bool { - return !reflect.DeepEqual(desired.Data, actual.Data) - -} diff --git a/pkg/reconcile/configmap_test.go b/pkg/targetallocator/reconcile/configmap_test.go similarity index 53% rename from pkg/reconcile/configmap_test.go rename to pkg/targetallocator/reconcile/configmap_test.go index 2a95086b3f..e15e8c6176 100644 --- a/pkg/reconcile/configmap_test.go +++ b/pkg/targetallocator/reconcile/configmap_test.go @@ -24,57 +24,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func TestDesiredCollectorConfigMap(t *testing.T) { - t.Run("should return expected config map", func(t *testing.T) { - expectedLables := map[string]string{ - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-collector", - "app.kubernetes.io/name": "test-collector", - } - - expectedData := map[string]string{ - "collector.yaml": ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, - } - - actual := desiredCollectorConfigMap(context.Background(), paramsCollector()) - - assert.Equal(t, "test-collector", actual.Name) - assert.Equal(t, expectedLables, actual.Labels) - assert.Equal(t, expectedData, actual.Data) - - }) - -} - -func TestDesiredTAConfigMap(t *testing.T) { +func TestDesiredConfigMap(t *testing.T) { t.Run("should return expected config map", func(t *testing.T) { expectedLables := map[string]string{ "app.kubernetes.io/managed-by": "opentelemetry-operator", @@ -99,7 +55,7 @@ label_selector: `, } - actual, err := desiredTAConfigMap(context.Background(), paramsTA()) + actual, err := desiredConfigMap(context.Background(), params()) assert.NoError(t, err) assert.Equal(t, "test-targetallocator", actual.Name) @@ -110,89 +66,10 @@ label_selector: } -func TestExpectedCollectorConfigMap(t *testing.T) { - param := paramsCollector() - t.Run("should create config map", func(t *testing.T) { - err := expectedConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, true) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - }) - - t.Run("should update config map", func(t *testing.T) { - - param := Params{ - Config: config.New(), - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - }, - Scheme: testScheme, - Log: logger, - Recorder: record.NewFakeRecorder(10), - } - cm := desiredCollectorConfigMap(context.Background(), param) - createObjectIfNotExists(t, "test-collector", &cm) - - err := expectedConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, true) - assert.NoError(t, err) - - actual := v1.ConfigMap{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Equal(t, param.Instance.Spec.Config, actual.Data["collector.yaml"]) - }) - - t.Run("should delete config map", func(t *testing.T) { - - deletecm := v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-delete-collector", - Namespace: "default", - Labels: map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }, - }, - } - createObjectIfNotExists(t, "test-delete-collector", &deletecm) - - exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) - assert.True(t, exists) - - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err := deleteConfigMaps(context.Background(), param, []v1.ConfigMap{desiredCollectorConfigMap(context.Background(), param)}, opts) - assert.NoError(t, err) - - exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-collector"}) - assert.False(t, exists) - }) -} - -func TestExpectedTAConfigMap(t *testing.T) { - param := paramsTA() +func TestExpectedConfigMap(t *testing.T) { + param := params() t.Run("should create config map", func(t *testing.T) { - configMap, err := desiredTAConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -237,11 +114,11 @@ func TestExpectedTAConfigMap(t *testing.T) { Scheme: testScheme, Log: logger, } - cm, err := desiredTAConfigMap(context.Background(), newParam) + cm, err := desiredConfigMap(context.Background(), newParam) assert.EqualError(t, err, "no receivers available as part of the configuration") createObjectIfNotExists(t, "test-targetallocator", &cm) - configMap, err := desiredTAConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) assert.NoError(t, err) @@ -287,16 +164,9 @@ func TestExpectedTAConfigMap(t *testing.T) { exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) assert.True(t, exists) - configMap, err := desiredTAConfigMap(context.Background(), param) + configMap, err := desiredConfigMap(context.Background(), param) assert.NoError(t, err) - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, opts) + err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) assert.NoError(t, err) exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) diff --git a/pkg/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go similarity index 69% rename from pkg/reconcile/deployment.go rename to pkg/targetallocator/reconcile/deployment.go index 751b58b459..a0161645f9 100644 --- a/pkg/reconcile/deployment.go +++ b/pkg/targetallocator/reconcile/deployment.go @@ -17,6 +17,7 @@ package reconcile import ( "context" "fmt" + "reflect" appsv1 "k8s.io/api/apps/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -24,44 +25,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete -// CollectorDeployments reconciles the deployment(s) required for the instance in the current context. -func CollectorDeployments(ctx context.Context, params Params) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - desired := []appsv1.Deployment{} - if params.Instance.Spec.Mode == "deployment" { - desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) - } - - // first, handle the create/update parts - if err := expectedDeployments(ctx, params, desired, false); err != nil { - return fmt.Errorf("failed to reconcile the expected deployments: %v", err) - } - - // then, delete the extra objects - if err := deleteDeployments(ctx, params, desired, opts); err != nil { - return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) - } - - return nil -} - -// TADeployments reconciles the deployment(s) required for the instance in the current context. -func TADeployments(ctx context.Context, params Params) error { +// Deployments reconciles the deployment(s) required for the instance in the current context. +func Deployments(ctx context.Context, params Params) error { desired := []appsv1.Deployment{} - if params.Instance.Spec.TargetAllocator.Enabled { + if IsAllocatorEnabled(params) { _, err := GetPromConfig(params) if err != nil { return fmt.Errorf("failed to parse Prometheus config: %v", err) @@ -70,26 +43,19 @@ func TADeployments(ctx context.Context, params Params) error { } // first, handle the create/update parts - if err := expectedDeployments(ctx, params, desired, true); err != nil { + if err := expectedDeployments(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the expected deployments: %v", err) } // then, delete the extra objects - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - if err := deleteDeployments(ctx, params, desired, opts); err != nil { + if err := deleteDeployments(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) } return nil } -func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment, isTargetAllocator bool) error { +func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { for _, obj := range expected { desired := obj @@ -108,6 +74,8 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D continue } else if err != nil { return fmt.Errorf("failed to get: %w", err) + } else if !deploymentImageChanged(&desired, existing) { + continue } // it exists already, merge the two if the end result isn't identical to the existing one @@ -116,11 +84,7 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D updated.Labels = map[string]string{} } - if isTargetAllocator { - updated.Spec.Template.Spec.Containers[0].Image = desired.Spec.Template.Spec.Containers[0].Image - } else { - updated.Spec = desired.Spec - } + updated.Spec = desired.Spec updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences for k, v := range desired.ObjectMeta.Labels { @@ -139,7 +103,14 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D return nil } -func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment, opts []client.ListOption) error { +func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } list := &appsv1.DeploymentList{} if err := params.Client.List(ctx, list, opts...); err != nil { return fmt.Errorf("failed to list: %w", err) @@ -164,3 +135,7 @@ func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Dep return nil } + +func deploymentImageChanged(desired *appsv1.Deployment, actual *appsv1.Deployment) bool { + return !reflect.DeepEqual(desired.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) +} diff --git a/pkg/reconcile/deployment_test.go b/pkg/targetallocator/reconcile/deployment_test.go similarity index 58% rename from pkg/reconcile/deployment_test.go rename to pkg/targetallocator/reconcile/deployment_test.go index f3b1eeea81..135a7759a3 100644 --- a/pkg/reconcile/deployment_test.go +++ b/pkg/targetallocator/reconcile/deployment_test.go @@ -23,142 +23,77 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) -func TestExpectedCollectorDeployments(t *testing.T) { - param := paramsCollector() - expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) +func TestExpectedDeployments(t *testing.T) { + param := params() + expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) t.Run("should create deployment", func(t *testing.T) { - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, false) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) - t.Run("should update deployment", func(t *testing.T) { - createObjectIfNotExists(t, "test-collector", &expectedDeploy) - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, false) + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) - actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-collector"}) + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) assert.NoError(t, err) assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - assert.Equal(t, int32(2), *actual.Spec.Replicas) - }) - - t.Run("should delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Labels = labels - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, - }, - }, - } - createObjectIfNotExists(t, "dummy", &deploy) - - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.False(t, exists) }) - t.Run("should not delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, + t.Run("should not create deployment when otel collector mode is not StatefulSet", func(t *testing.T) { + modes := []v1alpha1.Mode{v1alpha1.ModeDaemonSet, v1alpha1.ModeDeployment, v1alpha1.ModeSidecar} + + for _, mode := range modes { + newParam := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: mode, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + }, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + + `, + }, }, - }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + if newParam.Instance.Spec.Mode == v1alpha1.ModeStatefulSet { + expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) + } + + assert.Len(t, expected, 0) } - createObjectIfNotExists(t, "dummy", &deploy) - - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", - }), - } - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.True(t, exists) - - }) -} - -func TestExpectedTADeployments(t *testing.T) { - param := paramsTA() - expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) - - t.Run("should create deployment", func(t *testing.T) { - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, true) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - }) t.Run("should not create deployment when targetallocator is not enabled", func(t *testing.T) { @@ -214,7 +149,7 @@ func TestExpectedTADeployments(t *testing.T) { updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}, true) + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} @@ -235,7 +170,7 @@ func TestExpectedTADeployments(t *testing.T) { updatedParam := newParams("test/test-img") updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}, true) + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} @@ -254,7 +189,7 @@ func TestExpectedTADeployments(t *testing.T) { "app.kubernetes.io/managed-by": "opentelemetry-operator", } deploy := v1.Deployment{} - deploy.Name = "dummy-ta" + deploy.Name = "dummy" deploy.Namespace = "default" deploy.Labels = labels deploy.Spec = v1.DeploymentSpec{ @@ -267,26 +202,19 @@ func TestExpectedTADeployments(t *testing.T) { }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "dummy-ta", + Name: "dummy", Image: "busybox", }}, }, }, } - createObjectIfNotExists(t, "dummy-ta", &deploy) - - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy-ta"}) + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) assert.False(t, exists) @@ -298,7 +226,7 @@ func TestExpectedTADeployments(t *testing.T) { "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", } deploy := v1.Deployment{} - deploy.Name = "dummy-ta" + deploy.Name = "dummy" deploy.Namespace = "default" deploy.Spec = v1.DeploymentSpec{ Selector: &metav1.LabelSelector{ @@ -310,26 +238,19 @@ func TestExpectedTADeployments(t *testing.T) { }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "dummy-ta", + Name: "dummy", Image: "busybox", }}, }, }, } - createObjectIfNotExists(t, "dummy-ta", &deploy) - - opts := []client.ListOption{ - client.InNamespace("default"), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}, opts) + createObjectIfNotExists(t, "dummy", &deploy) + + err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy-ta"}) + exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) assert.True(t, exists) diff --git a/pkg/reconcile/helper.go b/pkg/targetallocator/reconcile/helper.go similarity index 81% rename from pkg/reconcile/helper.go rename to pkg/targetallocator/reconcile/helper.go index 06ea1db6a4..7094031056 100644 --- a/pkg/reconcile/helper.go +++ b/pkg/targetallocator/reconcile/helper.go @@ -15,10 +15,15 @@ package reconcile import ( + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) +func IsAllocatorEnabled(params Params) bool { + return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled +} + func GetPromConfig(params Params) (map[interface{}]interface{}, error) { config, err := adapters.ConfigFromString(params.Instance.Spec.Config) if err != nil { diff --git a/pkg/targetallocator/reconcile/params.go b/pkg/targetallocator/reconcile/params.go new file mode 100644 index 0000000000..e9e6fa3dbf --- /dev/null +++ b/pkg/targetallocator/reconcile/params.go @@ -0,0 +1,33 @@ +// 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 reconcile + +import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +// Params holds the reconciliation-specific parameters. +type Params struct { + Config config.Config + Client client.Client + Instance v1alpha1.OpenTelemetryCollector + Log logr.Logger + Scheme *runtime.Scheme +} diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go new file mode 100644 index 0000000000..3dc2993076 --- /dev/null +++ b/pkg/targetallocator/reconcile/service.go @@ -0,0 +1,160 @@ +// 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 reconcile + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete + +// Services reconciles the service(s) required for the instance in the current context. +func Services(ctx context.Context, params Params) error { + desired := []corev1.Service{} + + if IsAllocatorEnabled(params) { + _, err := GetPromConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, desiredService(params)) + } + + // first, handle the create/update parts + if err := expectedServices(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the expected services: %v", err) + } + + // then, delete the extra objects + if err := deleteServices(ctx, params, desired); err != nil { + return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + } + + return nil +} + +func desiredService(params Params) corev1.Service { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TAService(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: 443, + TargetPort: intstr.FromInt(443), + }}, + }, + } +} + +func expectedServices(ctx context.Context, params Params, expected []corev1.Service) error { + for _, obj := range expected { + desired := obj + + if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + existing := &corev1.Service{} + nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} + err := params.Client.Get(ctx, nns, existing) + if err != nil && k8serrors.IsNotFound(err) { + if err := params.Client.Create(ctx, &desired); err != nil { + return fmt.Errorf("failed to create: %w", err) + } + params.Log.V(2).Info("created", "service.name", desired.Name, "service.namespace", desired.Namespace) + continue + } else if err != nil { + return fmt.Errorf("failed to get: %w", err) + } + + // it exists already, merge the two if the end result isn't identical to the existing one + updated := existing.DeepCopy() + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences + + for k, v := range desired.ObjectMeta.Labels { + updated.ObjectMeta.Labels[k] = v + } + updated.Spec.Ports = desired.Spec.Ports + + patch := client.MergeFrom(existing) + + if err := params.Client.Patch(ctx, updated, patch); err != nil { + return fmt.Errorf("failed to apply changes: %w", err) + } + + params.Log.V(2).Info("applied", "service.name", desired.Name, "service.namespace", desired.Namespace) + } + + return nil +} + +func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { + opts := []client.ListOption{ + client.InNamespace(params.Instance.Namespace), + client.MatchingLabels(map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + }), + } + list := &corev1.ServiceList{} + if err := params.Client.List(ctx, list, opts...); err != nil { + return fmt.Errorf("failed to list: %w", err) + } + + for i := range list.Items { + existing := list.Items[i] + del := true + for _, keep := range expected { + if keep.Name == existing.Name && keep.Namespace == existing.Namespace { + del = false + } + } + + if del { + if err := params.Client.Delete(ctx, &existing); err != nil { + return fmt.Errorf("failed to delete: %w", err) + } + params.Log.V(2).Info("deleted", "service.name", existing.Name, "service.namespace", existing.Namespace) + } + } + + return nil +} diff --git a/pkg/targetallocator/reconcile/service_test.go b/pkg/targetallocator/reconcile/service_test.go new file mode 100644 index 0000000000..aa1672a11f --- /dev/null +++ b/pkg/targetallocator/reconcile/service_test.go @@ -0,0 +1,100 @@ +// 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 reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" +) + +func TestDesiredService(t *testing.T) { + t.Run("should return service with default port", func(t *testing.T) { + expected := service("test-targetallocator") + actual := desiredService(params()) + + assert.Equal(t, expected, actual) + }) + +} + +func TestExpectedServices(t *testing.T) { + t.Run("should create the service", func(t *testing.T) { + err := expectedServices(context.Background(), params(), []corev1.Service{service("targetallocator")}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) +} + +func TestDeleteServices(t *testing.T) { + t.Run("should delete excess services", func(t *testing.T) { + deleteService := service("test-delete-targetallocator", 8888) + createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) + + exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.True(t, exists) + + err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(params())}) + assert.NoError(t, err) + + exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) + assert.NoError(t, err) + assert.False(t, exists) + + }) +} + +func service(name string, portOpt ...int32) corev1.Service { + port := int32(443) + if len(portOpt) > 0 { + port = portOpt[0] + } + params := params() + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: port, + TargetPort: intstr.FromInt(443), + }}, + }, + } +} diff --git a/pkg/reconcile/suite_test.go b/pkg/targetallocator/reconcile/suite_test.go similarity index 79% rename from pkg/reconcile/suite_test.go rename to pkg/targetallocator/reconcile/suite_test.go index 560adabd13..cef4205192 100644 --- a/pkg/reconcile/suite_test.go +++ b/pkg/targetallocator/reconcile/suite_test.go @@ -31,7 +31,6 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -49,7 +48,7 @@ var instanceUID = uuid.NewUUID() func TestMain(m *testing.M) { testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, } cfg, err := testEnv.Start() @@ -81,7 +80,7 @@ func TestMain(m *testing.M) { os.Exit(code) } -func paramsTA() Params { +func params() Params { replicas := int32(1) configYAML, err := ioutil.ReadFile("suite_test.yaml") if err != nil { @@ -118,61 +117,8 @@ func paramsTA() Params { Config: string(configYAML), }, }, - Scheme: testScheme, - Log: logger, - Recorder: record.NewFakeRecorder(10), - } -} - -func paramsCollector() Params { - replicas := int32(2) - return Params{ - Config: config.New(), - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Ports: []v1.ServicePort{{ - Name: "web", - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 80, - }, - NodePort: 0, - }}, - Replicas: &replicas, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, - }, - }, - Scheme: testScheme, - Log: logger, - Recorder: record.NewFakeRecorder(10), + Scheme: testScheme, + Log: logger, } } diff --git a/pkg/reconcile/suite_test.yaml b/pkg/targetallocator/reconcile/suite_test.yaml similarity index 100% rename from pkg/reconcile/suite_test.yaml rename to pkg/targetallocator/reconcile/suite_test.yaml From 67af90900cf20bb8d1ea9cd62290e88babdbc7d0 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Fri, 6 Aug 2021 08:59:20 -0700 Subject: [PATCH 23/30] Removed separate controller for target allocation --- controllers/targetallocator_controller.go | 142 ---------- .../targetallocator_controller_test.go | 224 --------------- main.go | 10 - pkg/collector/reconcile/configmap.go | 44 +++ pkg/collector/reconcile/configmap_test.go | 169 ++++++++++-- pkg/collector/reconcile/deployment.go | 15 +- pkg/collector/reconcile/deployment_test.go | 104 ++++++- .../reconcile/helper.go | 5 - pkg/collector/reconcile/service.go | 34 +++ pkg/collector/reconcile/suite_test.go | 74 +++-- .../reconcile/test.yaml} | 7 +- pkg/targetallocator/labels.go | 2 +- pkg/targetallocator/labels_test.go | 2 +- pkg/targetallocator/reconcile/configmap.go | 188 ------------- .../reconcile/configmap_test.go | 175 ------------ pkg/targetallocator/reconcile/deployment.go | 141 ---------- .../reconcile/deployment_test.go | 258 ------------------ pkg/targetallocator/reconcile/params.go | 33 --- pkg/targetallocator/reconcile/service.go | 160 ----------- pkg/targetallocator/reconcile/service_test.go | 100 ------- pkg/targetallocator/reconcile/suite_test.go | 196 ------------- 21 files changed, 398 insertions(+), 1685 deletions(-) delete mode 100644 controllers/targetallocator_controller.go delete mode 100644 controllers/targetallocator_controller_test.go rename pkg/{targetallocator => collector}/reconcile/helper.go (81%) rename pkg/{targetallocator/reconcile/suite_test.yaml => collector/reconcile/test.yaml} (79%) delete mode 100644 pkg/targetallocator/reconcile/configmap.go delete mode 100644 pkg/targetallocator/reconcile/configmap_test.go delete mode 100644 pkg/targetallocator/reconcile/deployment.go delete mode 100644 pkg/targetallocator/reconcile/deployment_test.go delete mode 100644 pkg/targetallocator/reconcile/params.go delete mode 100644 pkg/targetallocator/reconcile/service.go delete mode 100644 pkg/targetallocator/reconcile/service_test.go delete mode 100644 pkg/targetallocator/reconcile/suite_test.go diff --git a/controllers/targetallocator_controller.go b/controllers/targetallocator_controller.go deleted file mode 100644 index 3f7d5e92c0..0000000000 --- a/controllers/targetallocator_controller.go +++ /dev/null @@ -1,142 +0,0 @@ -// 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" - "fmt" - - "github.com/go-logr/logr" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" -) - -// OpenTelemetryTargetAllocatorReconciler reconciles a OpenTelemetryTargetAllocator object. -type OpenTelemetryTargetAllocatorReconciler struct { - client.Client - log logr.Logger - scheme *runtime.Scheme - config config.Config - tasks []TgAlTask -} - -// TgAlTask represents a reconciliation task to be executed by the reconciler. -type TgAlTask struct { - Name string - Do func(context.Context, reconcile.Params) error - BailOnError bool -} - -// TgAlParams is the set of options to build a new OpenTelemetryTargetAllocatorReconciler. -type TgAlParams struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Config config.Config - Tasks []TgAlTask -} - -// NewTargetAlllocatorReconciler creates a new reconciler for OpenTelemetryTargetAllocator objects. -func NewTargetAllocatorReconciler(p TgAlParams) *OpenTelemetryTargetAllocatorReconciler { - if len(p.Tasks) == 0 { - p.Tasks = []TgAlTask{ - { - "config maps", - reconcile.ConfigMaps, - true, - }, - { - "deployments", - reconcile.Deployments, - true, - }, - { - "services", - reconcile.Services, - true, - }, - } - } - - return &OpenTelemetryTargetAllocatorReconciler{ - Client: p.Client, - log: p.Log, - scheme: p.Scheme, - config: p.Config, - tasks: p.Tasks, - } -} - -// Reconcile the current state of an OpenTelemetry target allocation resource with the desired state. -// follows the same design as the opentelemetrycollector controller where an empty context is passed -// and a new context initialized inside. -func (r *OpenTelemetryTargetAllocatorReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx := context.Background() - log := r.log.WithValues("opentelemetrytargetallocator", req.NamespacedName) - - var instance v1alpha1.OpenTelemetryCollector - if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "unable to fetch OpenTelemetryCollector") - } - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - params := reconcile.Params{ - Config: r.config, - Client: r.Client, - Instance: instance, - Log: log, - Scheme: r.scheme, - } - - if err := r.RunTasks(ctx, params); err != nil { - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// RunTasks runs all the tasks associated with this reconciler. -func (r *OpenTelemetryTargetAllocatorReconciler) RunTasks(ctx context.Context, params reconcile.Params) error { - for _, task := range r.tasks { - if err := task.Do(ctx, params); err != nil { - r.log.Error(err, fmt.Sprintf("failed to reconcile %s", task.Name)) - if task.BailOnError { - return err - } - } - } - return nil -} - -// SetupWithManager tells the manager what our controller is interested in. -func (r *OpenTelemetryTargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1alpha1.OpenTelemetryCollector{}). - Owns(&corev1.ConfigMap{}). - Owns(&corev1.Service{}). - Owns(&appsv1.Deployment{}). - Complete(r) -} diff --git a/controllers/targetallocator_controller_test.go b/controllers/targetallocator_controller_test.go deleted file mode 100644 index b79b569a00..0000000000 --- a/controllers/targetallocator_controller_test.go +++ /dev/null @@ -1,224 +0,0 @@ -// 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" - "errors" - "fmt" - "io/ioutil" - "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/kubectl/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - k8sreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/controllers" - "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/reconcile" -) - -func TestNewObjectsOnTargetAllocatorReconciliation(t *testing.T) { - // prepare - cfg := config.New() - configYAML, err := ioutil.ReadFile("../pkg/targetallocator/reconcile/suite_test.yaml") - require.NoError(t, err) - - nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Client: k8sClient, - Log: logger, - Scheme: testScheme, - Config: cfg, - }) - created := &v1alpha1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsn.Name, - Namespace: nsn.Namespace, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Config: string(configYAML), - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - }, - }, - } - 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.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - - // 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.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 TestContinueOnRecoverableTargetAllocatorFailure(t *testing.T) { - // prepare - taskCalled := false - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Log: logger, - Tasks: []controllers.TgAlTask{ - { - Name: "should-fail", - Do: func(context.Context, reconcile.Params) error { - return errors.New("should fail!") - }, - BailOnError: false, - }, - { - Name: "should-be-called", - Do: func(context.Context, reconcile.Params) error { - taskCalled = true - return nil - }, - }, - }, - }) - - // test - err := reconciler.RunTasks(context.Background(), reconcile.Params{}) - - // verify - assert.NoError(t, err) - assert.True(t, taskCalled) -} - -func TestBreakOnUnrecoverableTargetAllocatorError(t *testing.T) { - // prepare - cfg := config.New() - taskCalled := false - expectedErr := errors.New("should fail!") - nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Client: k8sClient, - Log: logger, - Scheme: scheme.Scheme, - Config: cfg, - Tasks: []controllers.TgAlTask{ - { - Name: "should-fail", - Do: func(context.Context, reconcile.Params) error { - taskCalled = true - return expectedErr - }, - BailOnError: true, - }, - { - Name: "should-not-be-called", - Do: func(context.Context, reconcile.Params) error { - assert.Fail(t, "should not have been called") - return nil - }, - }, - }, - }) - created := &v1alpha1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsn.Name, - Namespace: nsn.Namespace, - }, - } - err := k8sClient.Create(context.Background(), created) - require.NoError(t, err) - - // test - req := k8sreconcile.Request{ - NamespacedName: nsn, - } - _, err = reconciler.Reconcile(context.Background(), req) - - // verify - assert.Equal(t, expectedErr, err) - assert.True(t, taskCalled) - - // cleanup - assert.NoError(t, k8sClient.Delete(context.Background(), created)) -} - -func TestTargetAllocatorSkipWhenInstanceDoesNotExist(t *testing.T) { - // prepare - cfg := config.New() - nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"} - reconciler := controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Client: k8sClient, - Log: logger, - Scheme: scheme.Scheme, - Config: cfg, - Tasks: []controllers.TgAlTask{ - { - Name: "should-not-be-called", - Do: func(context.Context, reconcile.Params) error { - assert.Fail(t, "should not have been called") - return nil - }, - }, - }, - }) - - // test - req := k8sreconcile.Request{ - NamespacedName: nsn, - } - _, err := reconciler.Reconcile(context.Background(), req) - - // verify - assert.NoError(t, err) -} diff --git a/main.go b/main.go index 424ed67bd6..0e2aa7ee9b 100644 --- a/main.go +++ b/main.go @@ -155,16 +155,6 @@ func main() { os.Exit(1) } - if err = controllers.NewTargetAllocatorReconciler(controllers.TgAlParams{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("TargetAllocator"), - Scheme: mgr.GetScheme(), - Config: cfg, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "TargetAllocator") - os.Exit(1) - } - if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = (&opentelemetryiov1alpha1.OpenTelemetryCollector{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "OpenTelemetryCollector") diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index 82d57b5393..8db554ba53 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -19,6 +19,7 @@ import ( "fmt" "reflect" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +29,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete @@ -38,6 +40,14 @@ func ConfigMaps(ctx context.Context, params Params) error { desiredConfigMap(ctx, params), } + if params.Instance.Spec.TargetAllocator.Enabled { + cm, err := desiredTAConfigMap(ctx, params) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } + desired = append(desired, cm) + } + // first, handle the create/update parts if err := expectedConfigMaps(ctx, params, desired, true); err != nil { return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) @@ -69,6 +79,40 @@ func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { } } +func desiredTAConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { + name := naming.TAConfigMap(params.Instance) + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = name + + promConfig, err := GetPromConfig(params) + if err != nil { + return corev1.ConfigMap{}, err + } + + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + taConfig["config"] = promConfig + taConfigYAML, err := yaml.Marshal(taConfig) + if err != nil { + return corev1.ConfigMap{}, err + } + + return corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + Annotations: params.Instance.Annotations, + }, + Data: map[string]string{ + "targetallocator.yaml": string(taConfigYAML), + }, + }, nil +} + func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { for _, obj := range expected { desired := obj diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index 7eaee37fc5..1d05e04034 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -19,44 +19,53 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) func TestDesiredConfigMap(t *testing.T) { - t.Run("should return expected config map", func(t *testing.T) { - expectedLables := map[string]string{ - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-collector", - "app.kubernetes.io/name": "test-collector", - } + expectedLables := map[string]string{ + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/part-of": "opentelemetry", + } - expectedData := map[string]string{ - "collector.yaml": ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] + t.Run("should return expected collector config map", func(t *testing.T) { + expectedLables["app.kubernetes.io/component"] = "opentelemetry-collector" + expectedLables["app.kubernetes.io/name"] = "test-collector" -`, + expectedData := map[string]string{ + "collector.yaml": `processors: +receivers: + jaeger: + protocols: + grpc: + prometheus: + config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888', '0.0.0.0:9999' ] + +exporters: + logging: + +service: + pipelines: + metrics: + receivers: [prometheus] + processors: [] + exporters: [logging]`, } actual := desiredConfigMap(context.Background(), params()) @@ -67,20 +76,55 @@ func TestDesiredConfigMap(t *testing.T) { }) + t.Run("should return expected target allocator config map", func(t *testing.T) { + expectedLables["app.kubernetes.io/component"] = "opentelemetry-targetallocator" + expectedLables["app.kubernetes.io/name"] = "test-targetallocator" + + expectedData := map[string]string{ + "targetallocator.yaml": `config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: + - 0.0.0.0:8888 + - 0.0.0.0:9999 +label_selector: + app.kubernetes.io/instance: default.test + app.kubernetes.io/managed-by: opentelemetry-operator +`, + } + + actual, err := desiredTAConfigMap(context.Background(), params()) + assert.NoError(t, err) + + assert.Equal(t, "test-targetallocator", actual.Name) + assert.Equal(t, expectedLables, actual.Labels) + assert.Equal(t, expectedData, actual.Data) + + }) + } func TestExpectedConfigMap(t *testing.T) { - t.Run("should create config map", func(t *testing.T) { - err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) + t.Run("should create collector and target allocator config maps", func(t *testing.T) { + configMap, err := desiredTAConfigMap(context.Background(), params()) + assert.NoError(t, err) + err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params()), configMap}, true) assert.NoError(t, err) exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) assert.NoError(t, err) assert.True(t, exists) + + exists, err = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) }) - t.Run("should update config map", func(t *testing.T) { + t.Run("should update collector config map", func(t *testing.T) { param := Params{ Config: config.New(), @@ -115,6 +159,73 @@ func TestExpectedConfigMap(t *testing.T) { assert.Equal(t, params().Instance.Spec.Config, actual.Data["collector.yaml"]) }) + t.Run("should update target allocator config map", func(t *testing.T) { + + param := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + }, + Config: "", + }, + }, + Scheme: testScheme, + Log: logger, + } + cm, err := desiredTAConfigMap(context.Background(), param) + assert.EqualError(t, err, "no receivers available as part of the configuration") + createObjectIfNotExists(t, "test-targetallocator", &cm) + + configMap, err := desiredTAConfigMap(context.Background(), params()) + assert.NoError(t, err) + err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{configMap}, true) + assert.NoError(t, err) + + actual := v1.ConfigMap{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + + config, err := adapters.ConfigFromString(params().Instance.Spec.Config) + assert.NoError(t, err) + + parmConfig, err := ta.ConfigToPromConfig(config) + assert.NoError(t, err) + + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + } + taConfig["config"] = parmConfig + taConfigYAML, _ := yaml.Marshal(taConfig) + + assert.Equal(t, string(taConfigYAML), actual.Data["targetallocator.yaml"]) + }) + t.Run("should delete config map", func(t *testing.T) { deletecm := v1.ConfigMap{ diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go index 41b0f0de65..a46b1d2410 100644 --- a/pkg/collector/reconcile/deployment.go +++ b/pkg/collector/reconcile/deployment.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete @@ -36,6 +37,14 @@ func Deployments(ctx context.Context, params Params) error { desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) } + if params.Instance.Spec.TargetAllocator.Enabled { + _, err := GetPromConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) + } + // first, handle the create/update parts if err := expectedDeployments(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the expected deployments: %v", err) @@ -79,7 +88,11 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D updated.Labels = map[string]string{} } - updated.Spec = desired.Spec + if desired.Labels["app.kubernetes.io/component"] == "opentelemetry-targetallocator" { + updated.Spec.Template.Spec.Containers[0].Image = desired.Spec.Template.Spec.Containers[0].Image + } else { + updated.Spec = desired.Spec + } updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences for k, v := range desired.ObjectMeta.Annotations { diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go index 7295ef05fc..d677704e5d 100644 --- a/pkg/collector/reconcile/deployment_test.go +++ b/pkg/collector/reconcile/deployment_test.go @@ -24,14 +24,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) func TestExpectedDeployments(t *testing.T) { param := params() expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) + expectedTADeploy := targetallocator.Deployment(param.Config, logger, param.Instance) - t.Run("should create deployment", func(t *testing.T) { + t.Run("should create collector deployment", func(t *testing.T) { err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) @@ -41,6 +44,64 @@ func TestExpectedDeployments(t *testing.T) { assert.True(t, exists) }) + + t.Run("should create target allocator deployment", func(t *testing.T) { + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedTADeploy}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + + t.Run("should not create target allocator deployment when targetallocator is not enabled", func(t *testing.T) { + param := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + + `, + }, + }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + if param.Instance.Spec.TargetAllocator.Enabled { + expected = append(expected, targetallocator.Deployment(param.Config, param.Log, param.Instance)) + } + + assert.Len(t, expected, 0) + }) + t.Run("should update deployment", func(t *testing.T) { createObjectIfNotExists(t, "test-collector", &expectedDeploy) err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) @@ -55,6 +116,47 @@ func TestExpectedDeployments(t *testing.T) { assert.Equal(t, int32(2), *actual.Spec.Replicas) }) + t.Run("should not update target allocator deployment when the config is updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) + orgUID := expectedDeploy.OwnerReferences[0].UID + + updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) + + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.Equal(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + + t.Run("should update target allocator deployment when the container image is updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) + orgUID := expectedDeploy.OwnerReferences[0].UID + + updatedParam := newParams("test/test-img") + updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) + + err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.NotEqual(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + t.Run("should delete deployment", func(t *testing.T) { labels := map[string]string{ "app.kubernetes.io/instance": "default.test", diff --git a/pkg/targetallocator/reconcile/helper.go b/pkg/collector/reconcile/helper.go similarity index 81% rename from pkg/targetallocator/reconcile/helper.go rename to pkg/collector/reconcile/helper.go index 7094031056..06ea1db6a4 100644 --- a/pkg/targetallocator/reconcile/helper.go +++ b/pkg/collector/reconcile/helper.go @@ -15,15 +15,10 @@ package reconcile import ( - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) -func IsAllocatorEnabled(params Params) bool { - return params.Instance.Spec.Mode == v1alpha1.ModeStatefulSet && params.Instance.Spec.TargetAllocator.Enabled -} - func GetPromConfig(params Params) (map[interface{}]interface{}, error) { config, err := adapters.ConfigFromString(params.Instance.Spec.Config) if err != nil { diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index 4f5f09813f..0a7faf3dd5 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -23,6 +23,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -30,6 +31,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete @@ -48,6 +50,14 @@ func Services(ctx context.Context, params Params) error { } } + if params.Instance.Spec.TargetAllocator.Enabled { + _, err := GetPromConfig(params) + if err != nil { + return fmt.Errorf("failed to parse Prometheus config: %v", err) + } + desired = append(desired, desiredTAService(params)) + } + // first, handle the create/update parts if err := expectedServices(ctx, params, desired); err != nil { return fmt.Errorf("failed to reconcile the expected services: %v", err) @@ -121,6 +131,30 @@ func desiredService(ctx context.Context, params Params) *corev1.Service { } } +func desiredTAService(params Params) corev1.Service { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TAService(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: 443, + TargetPort: intstr.FromInt(443), + }}, + }, + } +} + func headless(ctx context.Context, params Params) *corev1.Service { h := desiredService(ctx, params) if h == nil { diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go index d2469ad44a..a9e09b7d9b 100644 --- a/pkg/collector/reconcile/suite_test.go +++ b/pkg/collector/reconcile/suite_test.go @@ -17,6 +17,7 @@ package reconcile import ( "context" "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -82,6 +83,10 @@ func TestMain(m *testing.M) { func params() Params { replicas := int32(2) + configYAML, err := ioutil.ReadFile("test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } return Params{ Config: config.New(), Client: k8sClient, @@ -106,24 +111,7 @@ func params() Params { NodePort: 0, }}, Replicas: &replicas, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, + Config: string(configYAML), }, }, Scheme: testScheme, @@ -132,6 +120,56 @@ func params() Params { } } +func newParams(containerImage ...string) Params { + replicas := int32(1) + configYAML, err := ioutil.ReadFile("test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } + + cfg := config.New() + defaultContainerImage := cfg.TargetAllocatorImage() + if len(containerImage) > 0 && len(containerImage[0]) > 0 { + defaultContainerImage = containerImage[0] + } + + return Params{ + Config: cfg, + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + Enabled: true, + Image: defaultContainerImage, + }, + Replicas: &replicas, + Config: string(configYAML), + }, + }, + Scheme: testScheme, + Log: logger, + } +} + func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { tb.Helper() err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) diff --git a/pkg/targetallocator/reconcile/suite_test.yaml b/pkg/collector/reconcile/test.yaml similarity index 79% rename from pkg/targetallocator/reconcile/suite_test.yaml rename to pkg/collector/reconcile/test.yaml index 63e354e574..e88b110245 100644 --- a/pkg/targetallocator/reconcile/suite_test.yaml +++ b/pkg/collector/reconcile/test.yaml @@ -1,5 +1,8 @@ processors: receivers: + jaeger: + protocols: + grpc: prometheus: config: scrape_configs: @@ -13,7 +16,7 @@ exporters: service: pipelines: - traces: - receivers: [jaeger] + metrics: + receivers: [prometheus] processors: [] exporters: [logging] \ No newline at end of file diff --git a/pkg/targetallocator/labels.go b/pkg/targetallocator/labels.go index f97c4b2fb5..f825d633d9 100644 --- a/pkg/targetallocator/labels.go +++ b/pkg/targetallocator/labels.go @@ -31,7 +31,7 @@ func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { } base["app.kubernetes.io/managed-by"] = "opentelemetry-operator" - base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Name, "targetallocator") + base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, instance.Name) base["app.kubernetes.io/part-of"] = "opentelemetry" base["app.kubernetes.io/component"] = "opentelemetry-targetallocator" diff --git a/pkg/targetallocator/labels_test.go b/pkg/targetallocator/labels_test.go index 8295c39bbd..83accd55fb 100644 --- a/pkg/targetallocator/labels_test.go +++ b/pkg/targetallocator/labels_test.go @@ -35,7 +35,7 @@ func TestLabelsCommonSet(t *testing.T) { // test labels := Labels(otelcol) assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) - assert.Equal(t, "my-instance.targetallocator", labels["app.kubernetes.io/instance"]) + assert.Equal(t, "my-ns.my-instance", labels["app.kubernetes.io/instance"]) assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) assert.Equal(t, "opentelemetry-targetallocator", labels["app.kubernetes.io/component"]) } diff --git a/pkg/targetallocator/reconcile/configmap.go b/pkg/targetallocator/reconcile/configmap.go deleted file mode 100644 index b768fdb6ec..0000000000 --- a/pkg/targetallocator/reconcile/configmap.go +++ /dev/null @@ -1,188 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - "gopkg.in/yaml.v2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete - -// ConfigMaps reconciles the config map(s) required for the instance in the current context. -func ConfigMaps(ctx context.Context, params Params) error { - desired := []corev1.ConfigMap{} - - if IsAllocatorEnabled(params) { - cm, err := desiredConfigMap(ctx, params) - if err != nil { - return fmt.Errorf("failed to parse config: %v", err) - } - desired = append(desired, cm) - } - - // first, handle the create/update parts - if err := expectedConfigMaps(ctx, params, desired, true); err != nil { - return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) - } - - // then, delete the extra objects - if err := deleteConfigMaps(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) - } - - return nil -} - -func desiredConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { - name := naming.TAConfigMap(params.Instance) - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = name - - promConfig, err := GetPromConfig(params) - if err != nil { - return corev1.ConfigMap{}, err - } - - taConfig := make(map[interface{}]interface{}) - taConfig["label_selector"] = map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - } - taConfig["config"] = promConfig - taConfigYAML, err := yaml.Marshal(taConfig) - if err != nil { - return corev1.ConfigMap{}, err - } - - return corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - Annotations: params.Instance.Annotations, - }, - Data: map[string]string{ - "targetallocator.yaml": string(taConfigYAML), - }, - }, nil -} - -func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &corev1.ConfigMap{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && errors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - if errors.IsAlreadyExists(err) && retry { - // let's try again? we probably had multiple updates at one, and now it exists already - if err := expectedConfigMaps(ctx, params, expected, false); err != nil { - // somethin else happened now... - return err - } - - // we succeeded in the retry, exit this attempt - return nil - } - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Annotations == nil { - updated.Annotations = map[string]string{} - } - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - - updated.Data = desired.Data - updated.BinaryData = desired.BinaryData - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Annotations { - updated.ObjectMeta.Annotations[k] = v - } - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - - params.Log.V(2).Info("applied", "configmap.name", desired.Name, "configmap.namespace", desired.Namespace) - } - - return nil -} - -func deleteConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &corev1.ConfigMapList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "configmap.name", existing.Name, "configmap.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/targetallocator/reconcile/configmap_test.go b/pkg/targetallocator/reconcile/configmap_test.go deleted file mode 100644 index e15e8c6176..0000000000 --- a/pkg/targetallocator/reconcile/configmap_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" -) - -func TestDesiredConfigMap(t *testing.T) { - t.Run("should return expected config map", func(t *testing.T) { - expectedLables := map[string]string{ - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-targetallocator", - "app.kubernetes.io/name": "test-targetallocator", - } - - expectedData := map[string]string{ - "targetallocator.yaml": `config: - scrape_configs: - job_name: otel-collector - scrape_interval: 10s - static_configs: - - targets: - - 0.0.0.0:8888 - - 0.0.0.0:9999 -label_selector: - app.kubernetes.io/instance: default.test - app.kubernetes.io/managed-by: opentelemetry-operator -`, - } - - actual, err := desiredConfigMap(context.Background(), params()) - assert.NoError(t, err) - - assert.Equal(t, "test-targetallocator", actual.Name) - assert.Equal(t, expectedLables, actual.Labels) - assert.Equal(t, expectedData, actual.Data) - - }) - -} - -func TestExpectedConfigMap(t *testing.T) { - param := params() - t.Run("should create config map", func(t *testing.T) { - configMap, err := desiredConfigMap(context.Background(), param) - assert.NoError(t, err) - err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - }) - - t.Run("should update config map", func(t *testing.T) { - - newParam := Params{ - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Ports: []v1.ServicePort{{ - Name: "web", - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 80, - }, - NodePort: 0, - }}, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - }, - Config: "", - }, - }, - Scheme: testScheme, - Log: logger, - } - cm, err := desiredConfigMap(context.Background(), newParam) - assert.EqualError(t, err, "no receivers available as part of the configuration") - createObjectIfNotExists(t, "test-targetallocator", &cm) - - configMap, err := desiredConfigMap(context.Background(), param) - assert.NoError(t, err) - err = expectedConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}, true) - assert.NoError(t, err) - - actual := v1.ConfigMap{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - - config, err := adapters.ConfigFromString(param.Instance.Spec.Config) - assert.NoError(t, err) - - parmConfig, err := ta.ConfigToPromConfig(config) - assert.NoError(t, err) - - taConfig := make(map[interface{}]interface{}) - taConfig["label_selector"] = map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - } - taConfig["config"] = parmConfig - taConfigYAML, _ := yaml.Marshal(taConfig) - - assert.Equal(t, string(taConfigYAML), actual.Data["targetallocator.yaml"]) - }) - - t.Run("should delete config map", func(t *testing.T) { - - deletecm := v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-delete-targetallocator", - Namespace: "default", - Labels: map[string]string{ - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }, - }, - } - createObjectIfNotExists(t, "test-delete-targetallocator", &deletecm) - - exists, _ := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.True(t, exists) - - configMap, err := desiredConfigMap(context.Background(), param) - assert.NoError(t, err) - err = deleteConfigMaps(context.Background(), param, []v1.ConfigMap{configMap}) - assert.NoError(t, err) - - exists, _ = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.False(t, exists) - }) -} diff --git a/pkg/targetallocator/reconcile/deployment.go b/pkg/targetallocator/reconcile/deployment.go deleted file mode 100644 index a0161645f9..0000000000 --- a/pkg/targetallocator/reconcile/deployment.go +++ /dev/null @@ -1,141 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - "reflect" - - appsv1 "k8s.io/api/apps/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete - -// Deployments reconciles the deployment(s) required for the instance in the current context. -func Deployments(ctx context.Context, params Params) error { - desired := []appsv1.Deployment{} - - if IsAllocatorEnabled(params) { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) - } - - // first, handle the create/update parts - if err := expectedDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected deployments: %v", err) - } - - // then, delete the extra objects - if err := deleteDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) - } - - return nil -} - -func expectedDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &appsv1.Deployment{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } else if !deploymentImageChanged(&desired, existing) { - continue - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - - updated.Spec = desired.Spec - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - - params.Log.V(2).Info("applied", "deployment.name", desired.Name, "deployment.namespace", desired.Namespace) - } - - return nil -} - -func deleteDeployments(ctx context.Context, params Params, expected []appsv1.Deployment) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &appsv1.DeploymentList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "deployment.name", existing.Name, "deployment.namespace", existing.Namespace) - } - } - - return nil -} - -func deploymentImageChanged(desired *appsv1.Deployment, actual *appsv1.Deployment) bool { - return !reflect.DeepEqual(desired.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) -} diff --git a/pkg/targetallocator/reconcile/deployment_test.go b/pkg/targetallocator/reconcile/deployment_test.go deleted file mode 100644 index 135a7759a3..0000000000 --- a/pkg/targetallocator/reconcile/deployment_test.go +++ /dev/null @@ -1,258 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestExpectedDeployments(t *testing.T) { - param := params() - expectedDeploy := targetallocator.Deployment(param.Config, logger, param.Instance) - - t.Run("should create deployment", func(t *testing.T) { - err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) - - t.Run("should not create deployment when otel collector mode is not StatefulSet", func(t *testing.T) { - modes := []v1alpha1.Mode{v1alpha1.ModeDaemonSet, v1alpha1.ModeDeployment, v1alpha1.ModeSidecar} - - for _, mode := range modes { - newParam := Params{ - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: mode, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - }, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - - `, - }, - }, - Scheme: testScheme, - Log: logger, - } - expected := []v1.Deployment{} - if newParam.Instance.Spec.Mode == v1alpha1.ModeStatefulSet { - expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) - } - - assert.Len(t, expected, 0) - } - }) - - t.Run("should not create deployment when targetallocator is not enabled", func(t *testing.T) { - newParam := Params{ - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - - `, - }, - }, - Scheme: testScheme, - Log: logger, - } - expected := []v1.Deployment{} - if newParam.Instance.Spec.TargetAllocator.Enabled { - expected = append(expected, targetallocator.Deployment(newParam.Config, newParam.Log, newParam.Instance)) - } - - assert.Len(t, expected, 0) - }) - - t.Run("should not update deployment container when the config is updated", func(t *testing.T) { - ctx := context.Background() - createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) - orgUID := expectedDeploy.OwnerReferences[0].UID - - updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) - - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) - assert.Equal(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) - assert.Equal(t, int32(1), *actual.Spec.Replicas) - }) - - t.Run("should update deployment container when the container image is updated", func(t *testing.T) { - ctx := context.Background() - createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) - orgUID := expectedDeploy.OwnerReferences[0].UID - - updatedParam := newParams("test/test-img") - updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) - - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) - assert.NotEqual(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) - assert.Equal(t, int32(1), *actual.Spec.Replicas) - }) - - t.Run("should delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "test.targetallocator", - "app.kubernetes.io/managed-by": "opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Labels = labels - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, - }, - }, - } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.False(t, exists) - - }) - - t.Run("should not delete deployment", func(t *testing.T) { - labels := map[string]string{ - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/managed-by": "helm-opentelemetry-operator", - } - deploy := v1.Deployment{} - deploy.Name = "dummy" - deploy.Namespace = "default" - deploy.Spec = v1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "dummy", - Image: "busybox", - }}, - }, - }, - } - createObjectIfNotExists(t, "dummy", &deploy) - - err := deleteDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) - assert.NoError(t, err) - - actual := v1.Deployment{} - exists, _ := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "dummy"}) - - assert.True(t, exists) - - }) -} diff --git a/pkg/targetallocator/reconcile/params.go b/pkg/targetallocator/reconcile/params.go deleted file mode 100644 index e9e6fa3dbf..0000000000 --- a/pkg/targetallocator/reconcile/params.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 reconcile - -import ( - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" -) - -// Params holds the reconciliation-specific parameters. -type Params struct { - Config config.Config - Client client.Client - Instance v1alpha1.OpenTelemetryCollector - Log logr.Logger - Scheme *runtime.Scheme -} diff --git a/pkg/targetallocator/reconcile/service.go b/pkg/targetallocator/reconcile/service.go deleted file mode 100644 index 3dc2993076..0000000000 --- a/pkg/targetallocator/reconcile/service.go +++ /dev/null @@ -1,160 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete - -// Services reconciles the service(s) required for the instance in the current context. -func Services(ctx context.Context, params Params) error { - desired := []corev1.Service{} - - if IsAllocatorEnabled(params) { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } - desired = append(desired, desiredService(params)) - } - - // first, handle the create/update parts - if err := expectedServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) - } - - // then, delete the extra objects - if err := deleteServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) - } - - return nil -} - -func desiredService(params Params) corev1.Service { - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: naming.TAService(params.Instance), - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: 443, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} - -func expectedServices(ctx context.Context, params Params, expected []corev1.Service) error { - for _, obj := range expected { - desired := obj - - if err := controllerutil.SetControllerReference(¶ms.Instance, &desired, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - existing := &corev1.Service{} - nns := types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name} - err := params.Client.Get(ctx, nns, existing) - if err != nil && k8serrors.IsNotFound(err) { - if err := params.Client.Create(ctx, &desired); err != nil { - return fmt.Errorf("failed to create: %w", err) - } - params.Log.V(2).Info("created", "service.name", desired.Name, "service.namespace", desired.Namespace) - continue - } else if err != nil { - return fmt.Errorf("failed to get: %w", err) - } - - // it exists already, merge the two if the end result isn't identical to the existing one - updated := existing.DeepCopy() - if updated.Labels == nil { - updated.Labels = map[string]string{} - } - updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - - for k, v := range desired.ObjectMeta.Labels { - updated.ObjectMeta.Labels[k] = v - } - updated.Spec.Ports = desired.Spec.Ports - - patch := client.MergeFrom(existing) - - if err := params.Client.Patch(ctx, updated, patch); err != nil { - return fmt.Errorf("failed to apply changes: %w", err) - } - - params.Log.V(2).Info("applied", "service.name", desired.Name, "service.namespace", desired.Namespace) - } - - return nil -} - -func deleteServices(ctx context.Context, params Params, expected []corev1.Service) error { - opts := []client.ListOption{ - client.InNamespace(params.Instance.Namespace), - client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Name, "targetallocator"), - "app.kubernetes.io/managed-by": "opentelemetry-operator", - }), - } - list := &corev1.ServiceList{} - if err := params.Client.List(ctx, list, opts...); err != nil { - return fmt.Errorf("failed to list: %w", err) - } - - for i := range list.Items { - existing := list.Items[i] - del := true - for _, keep := range expected { - if keep.Name == existing.Name && keep.Namespace == existing.Namespace { - del = false - } - } - - if del { - if err := params.Client.Delete(ctx, &existing); err != nil { - return fmt.Errorf("failed to delete: %w", err) - } - params.Log.V(2).Info("deleted", "service.name", existing.Name, "service.namespace", existing.Namespace) - } - } - - return nil -} diff --git a/pkg/targetallocator/reconcile/service_test.go b/pkg/targetallocator/reconcile/service_test.go deleted file mode 100644 index aa1672a11f..0000000000 --- a/pkg/targetallocator/reconcile/service_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// 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 reconcile - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/open-telemetry/opentelemetry-operator/pkg/naming" - "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" -) - -func TestDesiredService(t *testing.T) { - t.Run("should return service with default port", func(t *testing.T) { - expected := service("test-targetallocator") - actual := desiredService(params()) - - assert.Equal(t, expected, actual) - }) - -} - -func TestExpectedServices(t *testing.T) { - t.Run("should create the service", func(t *testing.T) { - err := expectedServices(context.Background(), params(), []corev1.Service{service("targetallocator")}) - assert.NoError(t, err) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "targetallocator"}) - - assert.NoError(t, err) - assert.True(t, exists) - - }) -} - -func TestDeleteServices(t *testing.T) { - t.Run("should delete excess services", func(t *testing.T) { - deleteService := service("test-delete-targetallocator", 8888) - createObjectIfNotExists(t, "test-delete-targetallocator", &deleteService) - - exists, err := populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.True(t, exists) - - err = deleteServices(context.Background(), params(), []corev1.Service{desiredService(params())}) - assert.NoError(t, err) - - exists, err = populateObjectIfExists(t, &corev1.Service{}, types.NamespacedName{Namespace: "default", Name: "test-delete-targetallocator"}) - assert.NoError(t, err) - assert.False(t, exists) - - }) -} - -func service(name string, portOpt ...int32) corev1.Service { - port := int32(443) - if len(portOpt) > 0 { - port = portOpt[0] - } - params := params() - labels := targetallocator.Labels(params.Instance) - labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) - - selector := targetallocator.Labels(params.Instance) - selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) - - return corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Instance.Namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Selector: selector, - Ports: []corev1.ServicePort{{ - Name: "targetallocation", - Port: port, - TargetPort: intstr.FromInt(443), - }}, - }, - } -} diff --git a/pkg/targetallocator/reconcile/suite_test.go b/pkg/targetallocator/reconcile/suite_test.go deleted file mode 100644 index cef4205192..0000000000 --- a/pkg/targetallocator/reconcile/suite_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// 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 reconcile - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - "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/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" - "github.com/open-telemetry/opentelemetry-operator/internal/config" -) - -var k8sClient client.Client -var testEnv *envtest.Environment -var testScheme *runtime.Scheme = scheme.Scheme -var logger = logf.Log.WithName("unit-tests") - -var instanceUID = uuid.NewUUID() - -func TestMain(m *testing.M) { - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, - } - - cfg, err := testEnv.Start() - if err != nil { - fmt.Printf("failed to start testEnv: %v", err) - os.Exit(1) - } - - if err := v1alpha1.AddToScheme(testScheme); err != nil { - fmt.Printf("failed to register scheme: %v", err) - os.Exit(1) - } - // +kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) - if err != nil { - fmt.Printf("failed to setup a Kubernetes client: %v", err) - os.Exit(1) - } - - code := m.Run() - - err = testEnv.Stop() - if err != nil { - fmt.Printf("failed to stop testEnv: %v", err) - os.Exit(1) - } - - os.Exit(code) -} - -func params() Params { - replicas := int32(1) - configYAML, err := ioutil.ReadFile("suite_test.yaml") - if err != nil { - fmt.Printf("Error getting yaml file: %v", err) - } - return Params{ - Config: config.New(), - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Ports: []v1.ServicePort{{ - Name: "web", - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 80, - }, - NodePort: 0, - }}, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - }, - Replicas: &replicas, - Config: string(configYAML), - }, - }, - Scheme: testScheme, - Log: logger, - } -} - -func newParams(containerImage ...string) Params { - replicas := int32(1) - configYAML, err := ioutil.ReadFile("suite_test.yaml") - if err != nil { - fmt.Printf("Error getting yaml file: %v", err) - } - - cfg := config.New() - defaultContainerImage := cfg.TargetAllocatorImage() - if len(containerImage) > 0 && len(containerImage[0]) > 0 { - defaultContainerImage = containerImage[0] - } - - return Params{ - Config: cfg, - Client: k8sClient, - Instance: v1alpha1.OpenTelemetryCollector{ - TypeMeta: metav1.TypeMeta{ - Kind: "opentelemetry.io", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - UID: instanceUID, - }, - Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: v1alpha1.ModeStatefulSet, - Ports: []v1.ServicePort{{ - Name: "web", - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 80, - }, - NodePort: 0, - }}, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ - Enabled: true, - Image: defaultContainerImage, - }, - Replicas: &replicas, - Config: string(configYAML), - }, - }, - Scheme: testScheme, - Log: logger, - } -} - -func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { - tb.Helper() - err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) - if errors.IsNotFound(err) { - err := k8sClient.Create(context.Background(), - object) - assert.NoError(tb, err) - } -} - -func populateObjectIfExists(t testing.TB, object client.Object, namespacedName types.NamespacedName) (bool, error) { - t.Helper() - err := k8sClient.Get(context.Background(), namespacedName, object) - if errors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - return true, nil - -} From 888ae315347d205c638bafea481757dd68452e4a Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Sun, 8 Aug 2021 20:20:14 -0700 Subject: [PATCH 24/30] Added additional label for collector pod selection and removed test-step --- pkg/collector/reconcile/configmap.go | 1 + pkg/collector/reconcile/configmap_test.go | 2 ++ tests/e2e/smoke-targetallocator/03-install.yaml | 5 ----- tests/e2e/targetallocator-features/03-install.yaml | 5 ----- 4 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 tests/e2e/smoke-targetallocator/03-install.yaml delete mode 100644 tests/e2e/targetallocator-features/03-install.yaml diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index 8db554ba53..50e6f59bbd 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -93,6 +93,7 @@ func desiredTAConfigMap(_ context.Context, params Params) (corev1.ConfigMap, err taConfig["label_selector"] = map[string]string{ "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", } taConfig["config"] = promConfig taConfigYAML, err := yaml.Marshal(taConfig) diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index 1d05e04034..3eab10c7ad 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -90,6 +90,7 @@ service: - 0.0.0.0:8888 - 0.0.0.0:9999 label_selector: + app.kubernetes.io/component: opentelemetry-collector app.kubernetes.io/instance: default.test app.kubernetes.io/managed-by: opentelemetry-operator `, @@ -219,6 +220,7 @@ func TestExpectedConfigMap(t *testing.T) { taConfig["label_selector"] = map[string]string{ "app.kubernetes.io/instance": "default.test", "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", } taConfig["config"] = parmConfig taConfigYAML, _ := yaml.Marshal(taConfig) diff --git a/tests/e2e/smoke-targetallocator/03-install.yaml b/tests/e2e/smoke-targetallocator/03-install.yaml deleted file mode 100644 index 49a5c5c58c..0000000000 --- a/tests/e2e/smoke-targetallocator/03-install.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl -n $NAMESPACE delete rolebinding default-view-$NAMESPACE - command: kubectl -n $NAMESPACE delete role pod-view \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/03-install.yaml b/tests/e2e/targetallocator-features/03-install.yaml deleted file mode 100644 index 49a5c5c58c..0000000000 --- a/tests/e2e/targetallocator-features/03-install.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl -n $NAMESPACE delete rolebinding default-view-$NAMESPACE - command: kubectl -n $NAMESPACE delete role pod-view \ No newline at end of file From 11d3c87246c65f263762507bc82d6b9be53ffe98 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Mon, 9 Aug 2021 04:19:51 -0700 Subject: [PATCH 25/30] Minor changes --- api/v1alpha1/opentelemetrycollector_webhook.go | 2 +- pkg/collector/reconcile/deployment_test.go | 11 +++++++---- pkg/collector/reconcile/suite_test.go | 6 +++--- ...nfig_to_promConfig.go => config_to_prom_config.go} | 0 ...omConfig_test.go => config_to_prom_config_test.go} | 0 5 files changed, 11 insertions(+), 8 deletions(-) rename pkg/targetallocator/adapters/{config_to_promConfig.go => config_to_prom_config.go} (100%) rename pkg/targetallocator/adapters/{config_to_promConfig_test.go => config_to_prom_config_test.go} (100%) diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index e25437d344..2ed41c6bef 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -92,7 +92,7 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error { } // validate target allocation - if r.Spec.TargetAllocator.Enabled && !(r.Spec.Mode == ModeStatefulSet) { + if r.Spec.TargetAllocator.Enabled && r.Spec.Mode != ModeStatefulSet { return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the target allocation deployment", r.Spec.Mode) } diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go index d677704e5d..50648fb83a 100644 --- a/pkg/collector/reconcile/deployment_test.go +++ b/pkg/collector/reconcile/deployment_test.go @@ -121,9 +121,11 @@ func TestExpectedDeployments(t *testing.T) { createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) orgUID := expectedDeploy.OwnerReferences[0].UID - updatedDeploy := targetallocator.Deployment(newParams().Config, logger, param.Instance) + updatedParam, err := newParams("test/test-img") + assert.NoError(t, err) + updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, param.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + err = expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} @@ -141,10 +143,11 @@ func TestExpectedDeployments(t *testing.T) { createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) orgUID := expectedDeploy.OwnerReferences[0].UID - updatedParam := newParams("test/test-img") + updatedParam, err := newParams("test/test-img") + assert.NoError(t, err) updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) - err := expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + err = expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) actual := v1.Deployment{} diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go index a9e09b7d9b..be802e679f 100644 --- a/pkg/collector/reconcile/suite_test.go +++ b/pkg/collector/reconcile/suite_test.go @@ -120,11 +120,11 @@ func params() Params { } } -func newParams(containerImage ...string) Params { +func newParams(containerImage ...string) (Params, error) { replicas := int32(1) configYAML, err := ioutil.ReadFile("test.yaml") if err != nil { - fmt.Printf("Error getting yaml file: %v", err) + return Params{}, fmt.Errorf("Error getting yaml file: %v", err) } cfg := config.New() @@ -167,7 +167,7 @@ func newParams(containerImage ...string) Params { }, Scheme: testScheme, Log: logger, - } + }, nil } func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { diff --git a/pkg/targetallocator/adapters/config_to_promConfig.go b/pkg/targetallocator/adapters/config_to_prom_config.go similarity index 100% rename from pkg/targetallocator/adapters/config_to_promConfig.go rename to pkg/targetallocator/adapters/config_to_prom_config.go diff --git a/pkg/targetallocator/adapters/config_to_promConfig_test.go b/pkg/targetallocator/adapters/config_to_prom_config_test.go similarity index 100% rename from pkg/targetallocator/adapters/config_to_promConfig_test.go rename to pkg/targetallocator/adapters/config_to_prom_config_test.go From a7764b97a208aa268864f298dd81268584407344 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Tue, 10 Aug 2021 20:20:32 -0700 Subject: [PATCH 26/30] Added version management & Minor fixes --- .github/workflows/publish-images.yaml | 2 ++ Dockerfile | 3 ++- Makefile | 5 +++-- internal/config/main.go | 4 ++-- internal/version/main.go | 23 ++++++++++++++++++---- internal/version/main_test.go | 15 ++++++++++++++ main.go | 1 + pkg/collector/reconcile/configmap.go | 4 ++-- pkg/collector/reconcile/configmap_test.go | 8 ++++---- pkg/collector/reconcile/deployment_test.go | 16 +++++++++------ pkg/collector/reconcile/service.go | 4 ++-- pkg/collector/reconcile/suite_test.go | 8 ++------ versions.txt | 3 +++ 13 files changed, 67 insertions(+), 29 deletions(-) diff --git a/.github/workflows/publish-images.yaml b/.github/workflows/publish-images.yaml index 1d0e012f1b..d81713c05f 100644 --- a/.github/workflows/publish-images.yaml +++ b/.github/workflows/publish-images.yaml @@ -24,6 +24,7 @@ jobs: - name: Set env vars for the job run: | grep -v '\#' versions.txt | grep opentelemetry-collector | awk -F= '{print "OTELCOL_VERSION="$2}' >> $GITHUB_ENV + grep -v '\#' versions.txt | grep targetallocator | awk -F= '{print "TARGETALLOCATOR_VERSION="$2}' >> $GITHUB_ENV echo "VERSION_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV echo "VERSION=$(git describe --tags | sed 's/^v//')" >> $GITHUB_ENV @@ -72,5 +73,6 @@ jobs: VERSION=${{ env.VERSION }} VERSION_DATE=${{ env.VERSION_DATE }} OTELCOL_VERSION=${{ env.OTELCOL_VERSION }} + TARGETALLOCATOR_VERSION=${{ env.TARGETALLOCATOR_VERSION }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/Dockerfile b/Dockerfile index bde3f4b0d5..b056cb2c28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,9 +21,10 @@ ARG VERSION_PKG ARG VERSION ARG VERSION_DATE ARG OTELCOL_VERSION +ARG TARGETALLOCATOR_VERSION # Build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION}" -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION}" -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index 7d52c2abe4..82915bb0e4 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ VERSION_DATE ?= $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION_PKG ?= "github.com/open-telemetry/opentelemetry-operator/internal/version" OTELCOL_VERSION ?= "$(shell grep -v '\#' versions.txt | grep opentelemetry-collector | awk -F= '{print $$2}')" OPERATOR_VERSION ?= "$(shell grep -v '\#' versions.txt | grep operator | awk -F= '{print $$2}')" -LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION}" +TARGETALLOCATOR_VERSION ?= "$(shell grep -v '\#' versions.txt | grep targetallocator | awk -F= '{print $$2}')" +LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION}" # Image URL to use all building/pushing image targets IMG_PREFIX ?= quay.io/${USER} @@ -122,7 +123,7 @@ set-test-image-vars: # Build the container image, used only for local dev purposes container: - docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} . + docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} --build-arg TARGETALLOCATOR_VERSION=${TARGETALLOCATOR_VERSION} . # Push the container image, used only for local dev purposes container-push: diff --git a/internal/config/main.go b/internal/config/main.go index 49c49e9545..3a08f4d8b7 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -75,8 +75,8 @@ func New(opts ...Option) Config { } if len(o.targetAllocatorImage) == 0 { - // will be replcaed with target allocator image once approved and available - o.targetAllocatorImage = "raul9595/otel-loadbalancer:0.0.1" + // will be replaced with target allocator image once approved and available + o.targetAllocatorImage = fmt.Sprintf("raul9595/otel-loadbalancer:%s", o.version.TargetAllocator) } return Config{ diff --git a/internal/version/main.go b/internal/version/main.go index c0080a4b2a..9966a3f6a1 100644 --- a/internal/version/main.go +++ b/internal/version/main.go @@ -21,9 +21,10 @@ import ( ) var ( - version string - buildDate string - otelCol string + version string + buildDate string + otelCol string + targetAllocator string ) // Version holds this Operator's version as well as the version of some of the components it uses. @@ -32,6 +33,7 @@ type Version struct { BuildDate string `json:"build-date"` OpenTelemetryCollector string `json:"opentelemetry-collector-version"` Go string `json:"go-version"` + TargetAllocator string `json:"target-allocator-version"` } // Get returns the Version object with the relevant information. @@ -41,16 +43,18 @@ func Get() Version { BuildDate: buildDate, OpenTelemetryCollector: OpenTelemetryCollector(), Go: runtime.Version(), + TargetAllocator: TargetAllocator(), } } func (v Version) String() string { return fmt.Sprintf( - "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v')", + "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v', TargetAllocator='%v')", v.Operator, v.BuildDate, v.OpenTelemetryCollector, v.Go, + v.TargetAllocator, ) } @@ -64,3 +68,14 @@ func OpenTelemetryCollector() string { // fallback value, useful for tests return "0.0.0" } + +// TargetAllocator returns the default TargetAllocator to use when no versions are specified via CLI or configuration. +func TargetAllocator() string { + if len(targetAllocator) > 0 { + // this should always be set, as it's specified during the build + return targetAllocator + } + + // fallback value, useful for tests + return "0.0.0" +} diff --git a/internal/version/main_test.go b/internal/version/main_test.go index ec9a028605..7855991957 100644 --- a/internal/version/main_test.go +++ b/internal/version/main_test.go @@ -34,3 +34,18 @@ func TestVersionFromBuild(t *testing.T) { assert.Equal(t, otelCol, OpenTelemetryCollector()) assert.Contains(t, Get().String(), otelCol) } + +func TestTargetAllocatorFallbackVersion(t *testing.T) { + assert.Equal(t, "0.0.0", TargetAllocator()) +} + +func TestTargetAllocatorVersionFromBuild(t *testing.T) { + // prepare + targetAllocator = "0.0.2" // set during the build + defer func() { + targetAllocator = "" + }() + + assert.Equal(t, targetAllocator, TargetAllocator()) + assert.Contains(t, Get().String(), targetAllocator) +} diff --git a/main.go b/main.go index 0e2aa7ee9b..194dc00438 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,7 @@ func main() { logger.Info("Starting the OpenTelemetry Operator", "opentelemetry-operator", v.Operator, "opentelemetry-collector", v.OpenTelemetryCollector, + "opentelemetry-targetallocator", v.TargetAllocator, "build-date", v.BuildDate, "go-version", v.Go, "go-arch", runtime.GOARCH, diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index 50e6f59bbd..9bb78e9461 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -41,7 +41,7 @@ func ConfigMaps(ctx context.Context, params Params) error { } if params.Instance.Spec.TargetAllocator.Enabled { - cm, err := desiredTAConfigMap(ctx, params) + cm, err := desiredTAConfigMap(params) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } @@ -79,7 +79,7 @@ func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { } } -func desiredTAConfigMap(_ context.Context, params Params) (corev1.ConfigMap, error) { +func desiredTAConfigMap(params Params) (corev1.ConfigMap, error) { name := naming.TAConfigMap(params.Instance) labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index 3eab10c7ad..15d71e56d5 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -96,7 +96,7 @@ label_selector: `, } - actual, err := desiredTAConfigMap(context.Background(), params()) + actual, err := desiredTAConfigMap(params()) assert.NoError(t, err) assert.Equal(t, "test-targetallocator", actual.Name) @@ -109,7 +109,7 @@ label_selector: func TestExpectedConfigMap(t *testing.T) { t.Run("should create collector and target allocator config maps", func(t *testing.T) { - configMap, err := desiredTAConfigMap(context.Background(), params()) + configMap, err := desiredTAConfigMap(params()) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params()), configMap}, true) assert.NoError(t, err) @@ -194,11 +194,11 @@ func TestExpectedConfigMap(t *testing.T) { Scheme: testScheme, Log: logger, } - cm, err := desiredTAConfigMap(context.Background(), param) + cm, err := desiredTAConfigMap(param) assert.EqualError(t, err, "no receivers available as part of the configuration") createObjectIfNotExists(t, "test-targetallocator", &cm) - configMap, err := desiredTAConfigMap(context.Background(), params()) + configMap, err := desiredTAConfigMap(params()) assert.NoError(t, err) err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{configMap}, true) assert.NoError(t, err) diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go index 50648fb83a..a8f2ca34ac 100644 --- a/pkg/collector/reconcile/deployment_test.go +++ b/pkg/collector/reconcile/deployment_test.go @@ -118,8 +118,8 @@ func TestExpectedDeployments(t *testing.T) { t.Run("should not update target allocator deployment when the config is updated", func(t *testing.T) { ctx := context.Background() - createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) - orgUID := expectedDeploy.OwnerReferences[0].UID + createObjectIfNotExists(t, "test-targetallocator", &expectedTADeploy) + orgUID := expectedTADeploy.OwnerReferences[0].UID updatedParam, err := newParams("test/test-img") assert.NoError(t, err) @@ -133,15 +133,17 @@ func TestExpectedDeployments(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) + t.Log(expectedTADeploy.Spec.Template.Spec.Containers[0].Image) + t.Log(actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) - assert.Equal(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + assert.Equal(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, int32(1), *actual.Spec.Replicas) }) t.Run("should update target allocator deployment when the container image is updated", func(t *testing.T) { ctx := context.Background() - createObjectIfNotExists(t, "test-targetallocator", &expectedDeploy) - orgUID := expectedDeploy.OwnerReferences[0].UID + createObjectIfNotExists(t, "test-targetallocator", &expectedTADeploy) + orgUID := expectedTADeploy.OwnerReferences[0].UID updatedParam, err := newParams("test/test-img") assert.NoError(t, err) @@ -156,7 +158,9 @@ func TestExpectedDeployments(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) - assert.NotEqual(t, expectedDeploy.Spec.Template.Spec.Containers[0], actual.Spec.Template.Spec.Containers[0]) + t.Log(expectedTADeploy.Spec.Template.Spec.Containers[0].Image) + t.Log(actual.Spec.Template.Spec.Containers[0].Image) + assert.NotEqual(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, int32(1), *actual.Spec.Replicas) }) diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index 0a7faf3dd5..8863bc73cd 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -148,8 +148,8 @@ func desiredTAService(params Params) corev1.Service { Selector: selector, Ports: []corev1.ServicePort{{ Name: "targetallocation", - Port: 443, - TargetPort: intstr.FromInt(443), + Port: 80, + TargetPort: intstr.FromInt(8080), }}, }, } diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go index be802e679f..434ba36c15 100644 --- a/pkg/collector/reconcile/suite_test.go +++ b/pkg/collector/reconcile/suite_test.go @@ -120,7 +120,7 @@ func params() Params { } } -func newParams(containerImage ...string) (Params, error) { +func newParams(containerImage string) (Params, error) { replicas := int32(1) configYAML, err := ioutil.ReadFile("test.yaml") if err != nil { @@ -128,10 +128,6 @@ func newParams(containerImage ...string) (Params, error) { } cfg := config.New() - defaultContainerImage := cfg.TargetAllocatorImage() - if len(containerImage) > 0 && len(containerImage[0]) > 0 { - defaultContainerImage = containerImage[0] - } return Params{ Config: cfg, @@ -159,7 +155,7 @@ func newParams(containerImage ...string) (Params, error) { }}, TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ Enabled: true, - Image: defaultContainerImage, + Image: containerImage, }, Replicas: &replicas, Config: string(configYAML), diff --git a/versions.txt b/versions.txt index 0ed4e12bad..efc8a1110c 100644 --- a/versions.txt +++ b/versions.txt @@ -6,3 +6,6 @@ opentelemetry-collector=0.31.0 # Represents the current release of the OpenTelemetry Operator. operator=0.31.0 + +# Represents the current release of the Target Allocator. +targetallocator=0.1.0 From 33cb04253bb223613f4a26e2b36ce52a960a912f Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Wed, 11 Aug 2021 07:16:45 -0700 Subject: [PATCH 27/30] Updated docker image to quay.io image --- internal/config/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/config/main.go b/internal/config/main.go index 3a08f4d8b7..d5f2be2d4e 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -75,8 +75,7 @@ func New(opts ...Option) Config { } if len(o.targetAllocatorImage) == 0 { - // will be replaced with target allocator image once approved and available - o.targetAllocatorImage = fmt.Sprintf("raul9595/otel-loadbalancer:%s", o.version.TargetAllocator) + o.targetAllocatorImage = fmt.Sprintf("quay.io/opentelemetry/target-allocator:%s", o.version.TargetAllocator) } return Config{ From 3d37148696742ed1a2cda7c23357834b1bc803f5 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Wed, 11 Aug 2021 10:34:44 -0700 Subject: [PATCH 28/30] Added validation in webhook to validate prometheus config --- .../opentelemetrycollector_webhook.go | 10 +++++++ pkg/collector/reconcile/configmap.go | 3 +- pkg/collector/reconcile/configmap_test.go | 6 +--- pkg/collector/reconcile/deployment.go | 4 --- pkg/collector/reconcile/helper.go | 29 ------------------- pkg/collector/reconcile/service.go | 4 --- .../adapters/config_to_prom_config.go | 9 +++++- .../adapters/config_to_prom_config_test.go | 16 ++-------- 8 files changed, 23 insertions(+), 58 deletions(-) delete mode 100644 pkg/collector/reconcile/helper.go diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index 2ed41c6bef..361e0c7004 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -21,6 +21,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" + + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) // log is for logging in this package. @@ -96,5 +98,13 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error { return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the target allocation deployment", r.Spec.Mode) } + // validate Prometheus config for target allocation + if r.Spec.TargetAllocator.Enabled { + _, err := ta.ConfigToPromConfig(r.Spec.Config) + if err != nil { + return fmt.Errorf("the OpenTelemetry Spec configuration is incorrect, %s", err) + } + } + return nil } diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index 9bb78e9461..cb57d9c45e 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -30,6 +30,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete @@ -84,7 +85,7 @@ func desiredTAConfigMap(params Params) (corev1.ConfigMap, error) { labels := targetallocator.Labels(params.Instance) labels["app.kubernetes.io/name"] = name - promConfig, err := GetPromConfig(params) + promConfig, err := ta.ConfigToPromConfig(params.Instance.Spec.Config) if err != nil { return corev1.ConfigMap{}, err } diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index 15d71e56d5..d9319c6560 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -28,7 +28,6 @@ import ( "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) @@ -210,10 +209,7 @@ func TestExpectedConfigMap(t *testing.T) { assert.True(t, exists) assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) - config, err := adapters.ConfigFromString(params().Instance.Spec.Config) - assert.NoError(t, err) - - parmConfig, err := ta.ConfigToPromConfig(config) + parmConfig, err := ta.ConfigToPromConfig(params().Instance.Spec.Config) assert.NoError(t, err) taConfig := make(map[interface{}]interface{}) diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go index a46b1d2410..9a4cba39db 100644 --- a/pkg/collector/reconcile/deployment.go +++ b/pkg/collector/reconcile/deployment.go @@ -38,10 +38,6 @@ func Deployments(ctx context.Context, params Params) error { } if params.Instance.Spec.TargetAllocator.Enabled { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) } diff --git a/pkg/collector/reconcile/helper.go b/pkg/collector/reconcile/helper.go deleted file mode 100644 index 06ea1db6a4..0000000000 --- a/pkg/collector/reconcile/helper.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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 reconcile - -import ( - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" - ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" -) - -func GetPromConfig(params Params) (map[interface{}]interface{}, error) { - config, err := adapters.ConfigFromString(params.Instance.Spec.Config) - if err != nil { - return nil, err - } - - return ta.ConfigToPromConfig(config) -} diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index 8863bc73cd..d50760470a 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -51,10 +51,6 @@ func Services(ctx context.Context, params Params) error { } if params.Instance.Spec.TargetAllocator.Enabled { - _, err := GetPromConfig(params) - if err != nil { - return fmt.Errorf("failed to parse Prometheus config: %v", err) - } desired = append(desired, desiredTAService(params)) } diff --git a/pkg/targetallocator/adapters/config_to_prom_config.go b/pkg/targetallocator/adapters/config_to_prom_config.go index eb34f2dfc8..b4ba5f4d59 100644 --- a/pkg/targetallocator/adapters/config_to_prom_config.go +++ b/pkg/targetallocator/adapters/config_to_prom_config.go @@ -16,6 +16,8 @@ package adapters import ( "fmt" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ) func errorNoComponent(component string) error { @@ -27,7 +29,12 @@ func errorNotAMap(component string) error { } // ConfigToPromConfig converts the incoming configuration object into a the Prometheus receiver config. -func ConfigToPromConfig(config map[interface{}]interface{}) (map[interface{}]interface{}, error) { +func ConfigToPromConfig(cfg string) (map[interface{}]interface{}, error) { + config, err := adapters.ConfigFromString(cfg) + if err != nil { + return nil, err + } + receiversProperty, ok := config["receivers"] if !ok { return nil, errorNoComponent("receivers") diff --git a/pkg/targetallocator/adapters/config_to_prom_config_test.go b/pkg/targetallocator/adapters/config_to_prom_config_test.go index 208c9989c7..ba5cc2978a 100644 --- a/pkg/targetallocator/adapters/config_to_prom_config_test.go +++ b/pkg/targetallocator/adapters/config_to_prom_config_test.go @@ -20,9 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) @@ -49,13 +47,8 @@ func TestExtractPromConfigFromConfig(t *testing.T) { }, } - // prepare - config, err := adapters.ConfigFromString(configStr) - require.NoError(t, err) - require.NotEmpty(t, config) - // test - promConfig, err := ta.ConfigToPromConfig(config) + promConfig, err := ta.ConfigToPromConfig(configStr) assert.NoError(t, err) // verify @@ -76,13 +69,8 @@ func TestExtractPromConfigFromNullConfig(t *testing.T) { endpoint: 0.0.0.0:15268 ` - // prepare - config, err := adapters.ConfigFromString(configStr) - require.NoError(t, err) - require.NotEmpty(t, config) - // test - promConfig, err := ta.ConfigToPromConfig(config) + promConfig, err := ta.ConfigToPromConfig(configStr) assert.Equal(t, err, fmt.Errorf("%s property in the configuration doesn't contain valid %s", "prometheusConfig", "prometheusConfig")) // verify From a4968257e812ddba69068a29b965674c8c455c48 Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Wed, 11 Aug 2021 14:53:36 -0400 Subject: [PATCH 29/30] Update api/v1alpha1/opentelemetrycollector_webhook.go Co-authored-by: Anthony Mirabella --- api/v1alpha1/opentelemetrycollector_webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index 361e0c7004..77499d11da 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -102,7 +102,7 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error { if r.Spec.TargetAllocator.Enabled { _, err := ta.ConfigToPromConfig(r.Spec.Config) if err != nil { - return fmt.Errorf("the OpenTelemetry Spec configuration is incorrect, %s", err) + return fmt.Errorf("the OpenTelemetry Spec Prometheus configuration is incorrect, %s", err) } } From 98acb2fc8ed3793051e76e8b21648f696053a9bd Mon Sep 17 00:00:00 2001 From: Rahul Varma Date: Thu, 12 Aug 2021 05:35:53 -0700 Subject: [PATCH 30/30] Updated struct name & minor changes --- api/v1alpha1/opentelemetrycollector_types.go | 6 +++--- api/v1alpha1/zz_generated.deepcopy.go | 8 ++++---- pkg/collector/adapters/config_to_ports.go | 2 +- pkg/collector/reconcile/configmap.go | 6 +++--- pkg/collector/reconcile/configmap_test.go | 2 +- pkg/collector/reconcile/daemonset.go | 4 ++-- pkg/collector/reconcile/deployment.go | 4 ++-- pkg/collector/reconcile/deployment_test.go | 7 ++----- pkg/collector/reconcile/service.go | 4 ++-- pkg/collector/reconcile/serviceaccount.go | 4 ++-- pkg/collector/reconcile/statefulset.go | 4 ++-- pkg/collector/reconcile/suite_test.go | 4 ++-- pkg/targetallocator/container_test.go | 4 ++-- 13 files changed, 28 insertions(+), 31 deletions(-) diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index 77172ef012..63921453a2 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -44,7 +44,7 @@ type OpenTelemetryCollectorSpec struct { // TargetAllocator indicates a value which determines whether to spawn a target allocation resource or not. // +optional // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true - TargetAllocator OpenTelemetryTargetAllocator `json:"targetAllocator,omitempty"` + TargetAllocator OpenTelemetryTargetAllocatorSpec `json:"targetAllocator,omitempty"` // Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) // +optional @@ -121,8 +121,8 @@ type OpenTelemetryCollectorStatus struct { Messages []string `json:"messages,omitempty"` } -// OpenTelemetryTargetAllocator defines the configurations for the Prometheus target allocator. -type OpenTelemetryTargetAllocator struct { +// OpenTelemetryTargetAllocatorSpec defines the configurations for the Prometheus target allocator. +type OpenTelemetryTargetAllocatorSpec struct { // Enabled indicates whether to use a target allocation mechanism for Prometheus targets or not. // +optional Enabled bool `json:"enabled,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d29db7cd6a..9a0fffd24f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -179,16 +179,16 @@ func (in *OpenTelemetryCollectorStatus) DeepCopy() *OpenTelemetryCollectorStatus } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OpenTelemetryTargetAllocator) DeepCopyInto(out *OpenTelemetryTargetAllocator) { +func (in *OpenTelemetryTargetAllocatorSpec) DeepCopyInto(out *OpenTelemetryTargetAllocatorSpec) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryTargetAllocator. -func (in *OpenTelemetryTargetAllocator) DeepCopy() *OpenTelemetryTargetAllocator { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryTargetAllocatorSpec. +func (in *OpenTelemetryTargetAllocatorSpec) DeepCopy() *OpenTelemetryTargetAllocatorSpec { if in == nil { return nil } - out := new(OpenTelemetryTargetAllocator) + out := new(OpenTelemetryTargetAllocatorSpec) in.DeepCopyInto(out) return out } diff --git a/pkg/collector/adapters/config_to_ports.go b/pkg/collector/adapters/config_to_ports.go index 70f12a388f..a3fc2822cb 100644 --- a/pkg/collector/adapters/config_to_ports.go +++ b/pkg/collector/adapters/config_to_ports.go @@ -73,7 +73,7 @@ func ConfigToReceiverPorts(logger logr.Logger, config map[interface{}]interface{ // should we break the process and return an error, or just ignore this faulty parser // and let the other parsers add their ports to the service? right now, the best // option seems to be to log the failures and move on, instead of failing them all - logger.Error(err, "parser for '%s' has returned an error: %v", rcvrName, err) + logger.Error(err, "parser for '%s' has returned an error: %w", rcvrName, err) continue } diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index cb57d9c45e..c628165dbe 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -44,19 +44,19 @@ func ConfigMaps(ctx context.Context, params Params) error { if params.Instance.Spec.TargetAllocator.Enabled { cm, err := desiredTAConfigMap(params) if err != nil { - return fmt.Errorf("failed to parse config: %v", err) + return fmt.Errorf("failed to parse config: %w", err) } desired = append(desired, cm) } // first, handle the create/update parts if err := expectedConfigMaps(ctx, params, desired, true); err != nil { - return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) + return fmt.Errorf("failed to reconcile the expected configmaps: %w", err) } // then, delete the extra objects if err := deleteConfigMaps(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the configmaps to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index d9319c6560..01747b0828 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -184,7 +184,7 @@ func TestExpectedConfigMap(t *testing.T) { }, NodePort: 0, }}, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ Enabled: true, }, Config: "", diff --git a/pkg/collector/reconcile/daemonset.go b/pkg/collector/reconcile/daemonset.go index fa6a1a6f82..8acbfa2229 100644 --- a/pkg/collector/reconcile/daemonset.go +++ b/pkg/collector/reconcile/daemonset.go @@ -38,12 +38,12 @@ func DaemonSets(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedDaemonSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected daemon sets: %v", err) + return fmt.Errorf("failed to reconcile the expected daemon sets: %w", err) } // then, delete the extra objects if err := deleteDaemonSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the daemon sets to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the daemon sets to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go index 9a4cba39db..8176390948 100644 --- a/pkg/collector/reconcile/deployment.go +++ b/pkg/collector/reconcile/deployment.go @@ -43,12 +43,12 @@ func Deployments(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected deployments: %v", err) + return fmt.Errorf("failed to reconcile the expected deployments: %w", err) } // then, delete the extra objects if err := deleteDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the deployments to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go index a8f2ca34ac..668cb93191 100644 --- a/pkg/collector/reconcile/deployment_test.go +++ b/pkg/collector/reconcile/deployment_test.go @@ -116,7 +116,7 @@ func TestExpectedDeployments(t *testing.T) { assert.Equal(t, int32(2), *actual.Spec.Replicas) }) - t.Run("should not update target allocator deployment when the config is updated", func(t *testing.T) { + t.Run("should not update target allocator deployment when the container image is not updated", func(t *testing.T) { ctx := context.Background() createObjectIfNotExists(t, "test-targetallocator", &expectedTADeploy) orgUID := expectedTADeploy.OwnerReferences[0].UID @@ -124,6 +124,7 @@ func TestExpectedDeployments(t *testing.T) { updatedParam, err := newParams("test/test-img") assert.NoError(t, err) updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, param.Instance) + *updatedDeploy.Spec.Replicas = int32(3) err = expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) assert.NoError(t, err) @@ -133,8 +134,6 @@ func TestExpectedDeployments(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) - t.Log(expectedTADeploy.Spec.Template.Spec.Containers[0].Image) - t.Log(actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) assert.Equal(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, int32(1), *actual.Spec.Replicas) @@ -158,8 +157,6 @@ func TestExpectedDeployments(t *testing.T) { assert.NoError(t, err) assert.True(t, exists) assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) - t.Log(expectedTADeploy.Spec.Template.Spec.Containers[0].Image) - t.Log(actual.Spec.Template.Spec.Containers[0].Image) assert.NotEqual(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, int32(1), *actual.Spec.Replicas) }) diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index d50760470a..b1c9239441 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -56,12 +56,12 @@ func Services(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) + return fmt.Errorf("failed to reconcile the expected services: %w", err) } // then, delete the extra objects if err := deleteServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the services to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/serviceaccount.go b/pkg/collector/reconcile/serviceaccount.go index 9bdbaf5448..ecf140b3dc 100644 --- a/pkg/collector/reconcile/serviceaccount.go +++ b/pkg/collector/reconcile/serviceaccount.go @@ -39,12 +39,12 @@ func ServiceAccounts(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedServiceAccounts(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected service accounts: %v", err) + return fmt.Errorf("failed to reconcile the expected service accounts: %w", err) } // then, delete the extra objects if err := deleteServiceAccounts(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the service accounts to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the service accounts to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/statefulset.go b/pkg/collector/reconcile/statefulset.go index 0468586986..e1ab648b11 100644 --- a/pkg/collector/reconcile/statefulset.go +++ b/pkg/collector/reconcile/statefulset.go @@ -39,12 +39,12 @@ func StatefulSets(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedStatefulSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected stateful sets: %v", err) + return fmt.Errorf("failed to reconcile the expected stateful sets: %w", err) } // then, delete the extra objects if err := deleteStatefulSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the stateful sets to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the stateful sets to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go index 434ba36c15..7fbb84c52d 100644 --- a/pkg/collector/reconcile/suite_test.go +++ b/pkg/collector/reconcile/suite_test.go @@ -124,7 +124,7 @@ func newParams(containerImage string) (Params, error) { replicas := int32(1) configYAML, err := ioutil.ReadFile("test.yaml") if err != nil { - return Params{}, fmt.Errorf("Error getting yaml file: %v", err) + return Params{}, fmt.Errorf("Error getting yaml file: %w", err) } cfg := config.New() @@ -153,7 +153,7 @@ func newParams(containerImage string) (Params, error) { }, NodePort: 0, }}, - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ Enabled: true, Image: containerImage, }, diff --git a/pkg/targetallocator/container_test.go b/pkg/targetallocator/container_test.go index 61b130e988..70314f3b69 100644 --- a/pkg/targetallocator/container_test.go +++ b/pkg/targetallocator/container_test.go @@ -43,7 +43,7 @@ func TestContainerWithImageOverridden(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ Spec: v1alpha1.OpenTelemetryCollectorSpec{ - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ Enabled: true, Image: "overridden-image", }, @@ -62,7 +62,7 @@ func TestContainerVolumes(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ Spec: v1alpha1.OpenTelemetryCollectorSpec{ - TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ Enabled: true, Image: "default-image", },