Skip to content

Commit 80bf880

Browse files
committed
Add controller for TargetAllocator CR
1 parent e203cbc commit 80bf880

15 files changed

+462
-35
lines changed

apis/v1alpha1/targetallocator_types.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
)
2222

2323
func init() {
24-
v1beta1.SchemeBuilder.Register(&TargetAllocator{}, &TargetAllocatorList{})
24+
SchemeBuilder.Register(&TargetAllocator{}, &TargetAllocatorList{})
2525
}
2626

2727
//+kubebuilder:object:root=true
28+
//+kubebuilder:storageversion
2829
//+kubebuilder:subresource:status
2930

3031
// TargetAllocator is the Schema for the targetallocators API.

bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ metadata:
9999
categories: Logging & Tracing,Monitoring
100100
certified: "false"
101101
containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator
102-
createdAt: "2024-09-05T15:16:50Z"
102+
createdAt: "2024-09-09T16:20:28Z"
103103
description: Provides the OpenTelemetry components, including the Collector
104104
operators.operatorframework.io/builder: operator-sdk-v1.29.0
105105
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3

bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ metadata:
9999
categories: Logging & Tracing,Monitoring
100100
certified: "false"
101101
containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator
102-
createdAt: "2024-09-05T15:16:58Z"
102+
createdAt: "2024-09-09T16:20:31Z"
103103
description: Provides the OpenTelemetry components, including the Collector
104104
operators.operatorframework.io/builder: operator-sdk-v1.29.0
105105
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3

controllers/common.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func BuildCollector(params manifests.Params) ([]client.Object, error) {
6767
Recorder: params.Recorder,
6868
Log: params.Log,
6969
Config: params.Config,
70-
Collector: params.OtelCol,
70+
Collector: &params.OtelCol,
7171
TargetAllocator: *params.TargetAllocator,
7272
}
7373
taResources, err := BuildTargetAllocator(taParams)

controllers/suite_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ func TestMain(m *testing.M) {
182182
fmt.Printf("failed to SetupWebhookWithManager: %v", err)
183183
os.Exit(1)
184184
}
185+
if err = v1alpha1.SetupTargetAllocatorWebhook(mgr, config.New(), reviewer); err != nil {
186+
fmt.Printf("failed to SetupWebhookWithManager: %v", err)
187+
os.Exit(1)
188+
}
185189

186190
if err = v1alpha1.SetupOpAMPBridgeWebhook(mgr, config.New()); err != nil {
187191
fmt.Printf("failed to SetupWebhookWithManager: %v", err)
+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package controllers contains the main controller, where the reconciliation starts.
16+
package controllers
17+
18+
import (
19+
"context"
20+
21+
"github.com/go-logr/logr"
22+
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
23+
appsv1 "k8s.io/api/apps/v1"
24+
corev1 "k8s.io/api/core/v1"
25+
policyV1 "k8s.io/api/policy/v1"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/client-go/tools/record"
29+
ctrl "sigs.k8s.io/controller-runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
32+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
33+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
34+
"github.com/open-telemetry/opentelemetry-operator/internal/config"
35+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator"
36+
taStatus "github.com/open-telemetry/opentelemetry-operator/internal/status/targetallocator"
37+
"github.com/open-telemetry/opentelemetry-operator/pkg/featuregate"
38+
)
39+
40+
// TargetAllocatorReconciler reconciles a TargetAllocator object.
41+
type TargetAllocatorReconciler struct {
42+
client.Client
43+
recorder record.EventRecorder
44+
scheme *runtime.Scheme
45+
log logr.Logger
46+
config config.Config
47+
}
48+
49+
// TargetAllocatorReconcilerParams is the set of options to build a new TargetAllocatorReconciler.
50+
type TargetAllocatorReconcilerParams struct {
51+
client.Client
52+
Recorder record.EventRecorder
53+
Scheme *runtime.Scheme
54+
Log logr.Logger
55+
Config config.Config
56+
}
57+
58+
func (r *TargetAllocatorReconciler) getParams(instance v1alpha1.TargetAllocator) targetallocator.Params {
59+
p := targetallocator.Params{
60+
Config: r.config,
61+
Client: r.Client,
62+
Log: r.log,
63+
Scheme: r.scheme,
64+
Recorder: r.recorder,
65+
TargetAllocator: instance,
66+
}
67+
68+
return p
69+
}
70+
71+
// NewTargetAllocatorReconciler creates a new reconciler for TargetAllocator objects.
72+
func NewTargetAllocatorReconciler(
73+
client client.Client,
74+
scheme *runtime.Scheme,
75+
recorder record.EventRecorder,
76+
config config.Config,
77+
logger logr.Logger,
78+
) *TargetAllocatorReconciler {
79+
return &TargetAllocatorReconciler{
80+
Client: client,
81+
log: logger,
82+
scheme: scheme,
83+
config: config,
84+
recorder: recorder,
85+
}
86+
}
87+
88+
// TODO: Uncomment the lines below after enabling the TA controller in main.go
89+
// // +kubebuilder:rbac:groups="",resources=pods;configmaps;services;serviceaccounts;persistentvolumeclaims;persistentvolumes,verbs=get;list;watch;create;update;patch;delete
90+
// // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
91+
// // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
92+
// // +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
93+
// // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;podmonitors,verbs=get;list;watch;create;update;patch;delete
94+
// // +kubebuilder:rbac:groups=opentelemetry.io,resources=opentelemetrycollectors,verbs=get;list;watch;update;patch
95+
// // +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators,verbs=get;list;watch;update;patch
96+
// // +kubebuilder:rbac:groups=opentelemetry.io,resources=targetallocators/status,verbs=get;update;patch
97+
98+
// Reconcile the current state of a TargetAllocator resource with the desired state.
99+
func (r *TargetAllocatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
100+
log := r.log.WithValues("targetallocator", req.NamespacedName)
101+
102+
var instance v1alpha1.TargetAllocator
103+
if err := r.Client.Get(ctx, req.NamespacedName, &instance); err != nil {
104+
if !apierrors.IsNotFound(err) {
105+
log.Error(err, "unable to fetch TargetAllocator")
106+
}
107+
108+
// we'll ignore not-found errors, since they can't be fixed by an immediate
109+
// requeue (we'll need to wait for a new notification), and we can get them
110+
// on deleted requests.
111+
return ctrl.Result{}, client.IgnoreNotFound(err)
112+
}
113+
// We have a deletion, short circuit and let the deletion happen
114+
if deletionTimestamp := instance.GetDeletionTimestamp(); deletionTimestamp != nil {
115+
return ctrl.Result{}, nil
116+
}
117+
118+
if instance.Spec.ManagementState == v1beta1.ManagementStateUnmanaged {
119+
log.Info("Skipping reconciliation for unmanaged TargetAllocator resource", "name", req.String())
120+
// Stop requeueing for unmanaged TargetAllocator custom resources
121+
return ctrl.Result{}, nil
122+
}
123+
124+
params := r.getParams(instance)
125+
desiredObjects, buildErr := BuildTargetAllocator(params)
126+
if buildErr != nil {
127+
return ctrl.Result{}, buildErr
128+
}
129+
130+
err := reconcileDesiredObjects(ctx, r.Client, log, &params.TargetAllocator, params.Scheme, desiredObjects, nil)
131+
return taStatus.HandleReconcileStatus(ctx, log, params, err)
132+
}
133+
134+
// SetupWithManager tells the manager what our controller is interested in.
135+
func (r *TargetAllocatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
136+
builder := ctrl.NewControllerManagedBy(mgr).
137+
For(&v1alpha1.TargetAllocator{}).
138+
Owns(&corev1.ConfigMap{}).
139+
Owns(&corev1.ServiceAccount{}).
140+
Owns(&corev1.Service{}).
141+
Owns(&appsv1.Deployment{}).
142+
Owns(&corev1.PersistentVolume{}).
143+
Owns(&corev1.PersistentVolumeClaim{}).
144+
Owns(&policyV1.PodDisruptionBudget{})
145+
146+
if featuregate.PrometheusOperatorIsAvailable.IsEnabled() {
147+
builder.Owns(&monitoringv1.ServiceMonitor{})
148+
builder.Owns(&monitoringv1.PodMonitor{})
149+
}
150+
151+
return builder.Complete(r)
152+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package controllers_test
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
24+
appsv1 "k8s.io/api/apps/v1"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
"k8s.io/client-go/tools/record"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
logf "sigs.k8s.io/controller-runtime/pkg/log"
31+
k8sreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
32+
33+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
34+
"github.com/open-telemetry/opentelemetry-operator/controllers"
35+
"github.com/open-telemetry/opentelemetry-operator/internal/config"
36+
)
37+
38+
var testLogger = logf.Log.WithName("opamp-bridge-controller-unit-tests")
39+
40+
func TestNewObjectsOnReconciliation_TargetAllocator(t *testing.T) {
41+
// prepare
42+
cfg := config.New(
43+
config.WithTargetAllocatorImage("default-ta"),
44+
)
45+
nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"}
46+
reconciler := controllers.NewTargetAllocatorReconciler(
47+
k8sClient,
48+
testScheme,
49+
record.NewFakeRecorder(10),
50+
cfg,
51+
testLogger,
52+
)
53+
created := &v1alpha1.TargetAllocator{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: nsn.Name,
56+
Namespace: nsn.Namespace,
57+
},
58+
Spec: v1alpha1.TargetAllocatorSpec{},
59+
}
60+
err := k8sClient.Create(context.Background(), created)
61+
require.NoError(t, err)
62+
63+
// test
64+
req := k8sreconcile.Request{
65+
NamespacedName: nsn,
66+
}
67+
_, err = reconciler.Reconcile(context.Background(), req)
68+
69+
// verify
70+
require.NoError(t, err)
71+
72+
// the base query for the underlying objects
73+
opts := []client.ListOption{
74+
client.InNamespace(nsn.Namespace),
75+
client.MatchingLabels(map[string]string{
76+
"app.kubernetes.io/instance": fmt.Sprintf("%s.%s", nsn.Namespace, nsn.Name),
77+
"app.kubernetes.io/managed-by": "opentelemetry-operator",
78+
"app.kubernetes.io/component": "opentelemetry-targetallocator",
79+
}),
80+
}
81+
82+
// verify that we have at least one object for each of the types we create
83+
// whether we have the right ones is up to the specific tests for each type
84+
{
85+
list := &corev1.ConfigMapList{}
86+
err = k8sClient.List(context.Background(), list, opts...)
87+
assert.NoError(t, err)
88+
assert.NotEmpty(t, list.Items)
89+
}
90+
{
91+
list := &corev1.ServiceAccountList{}
92+
err = k8sClient.List(context.Background(), list, opts...)
93+
assert.NoError(t, err)
94+
assert.NotEmpty(t, list.Items)
95+
}
96+
{
97+
list := &corev1.ServiceList{}
98+
err = k8sClient.List(context.Background(), list, opts...)
99+
assert.NoError(t, err)
100+
assert.NotEmpty(t, list.Items)
101+
}
102+
{
103+
list := &appsv1.DeploymentList{}
104+
err = k8sClient.List(context.Background(), list, opts...)
105+
assert.NoError(t, err)
106+
assert.NotEmpty(t, list.Items)
107+
}
108+
// cleanup
109+
require.NoError(t, k8sClient.Delete(context.Background(), created))
110+
}
111+
112+
func TestSkipWhenInstanceDoesNotExist_TargetAllocator(t *testing.T) {
113+
// prepare
114+
cfg := config.New()
115+
nsn := types.NamespacedName{Name: "non-existing-my-instance", Namespace: "default"}
116+
reconciler := controllers.NewTargetAllocatorReconciler(
117+
k8sClient,
118+
testScheme,
119+
record.NewFakeRecorder(10),
120+
cfg,
121+
testLogger,
122+
)
123+
124+
// test
125+
req := k8sreconcile.Request{
126+
NamespacedName: nsn,
127+
}
128+
_, err := reconciler.Reconcile(context.Background(), req)
129+
130+
// verify
131+
assert.NoError(t, err)
132+
}

internal/manifests/targetallocator/configmap.go

+27-12
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,38 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) {
3838
taSpec := instance.Spec
3939

4040
taConfig := make(map[interface{}]interface{})
41-
42-
taConfig["collector_selector"] = metav1.LabelSelector{
43-
MatchLabels: manifestutils.SelectorLabels(params.Collector.ObjectMeta, collector.ComponentOpenTelemetryCollector),
44-
}
45-
4641
// Set config if global or scrape configs set
4742
config := map[string]interface{}{}
48-
globalConfig, err := getGlobalConfig(taSpec.GlobalConfig, params.Collector.Spec.Config)
49-
if err != nil {
50-
return nil, err
43+
var (
44+
globalConfig map[string]any
45+
scrapeConfigs []v1beta1.AnyConfig
46+
collectorSelector *metav1.LabelSelector
47+
err error
48+
)
49+
if params.Collector != nil {
50+
collectorSelector = &metav1.LabelSelector{
51+
MatchLabels: manifestutils.SelectorLabels(params.Collector.ObjectMeta, collector.ComponentOpenTelemetryCollector),
52+
}
53+
54+
globalConfig, err = getGlobalConfig(taSpec.GlobalConfig, params.Collector.Spec.Config)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
scrapeConfigs, err = getScrapeConfigs(taSpec.ScrapeConfigs, params.Collector.Spec.Config)
60+
if err != nil {
61+
return nil, err
62+
}
63+
} else { // if there's no collector, just use what's in the TargetAllocator CR
64+
collectorSelector = nil
65+
globalConfig = taSpec.GlobalConfig.Object
66+
scrapeConfigs = taSpec.ScrapeConfigs
5167
}
68+
5269
if len(globalConfig) > 0 {
5370
config["global"] = globalConfig
5471
}
5572

56-
scrapeConfigs, err := getScrapeConfigs(taSpec.ScrapeConfigs, params.Collector.Spec.Config)
57-
if err != nil {
58-
return nil, err
59-
}
6073
if len(scrapeConfigs) > 0 {
6174
config["scrape_configs"] = scrapeConfigs
6275
}
@@ -65,6 +78,8 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) {
6578
taConfig["config"] = config
6679
}
6780

81+
taConfig["collector_selector"] = collectorSelector
82+
6883
if len(taSpec.AllocationStrategy) > 0 {
6984
taConfig["allocation_strategy"] = taSpec.AllocationStrategy
7085
} else {

0 commit comments

Comments
 (0)