Skip to content

Commit 3e5ae20

Browse files
Merge branch 'main' into chore/improving-set-targets
2 parents 28880ab + 5eefae8 commit 3e5ae20

11 files changed

+851
-111
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: bug_fix
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: collector
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Fix deletion of optional resources for OpenTelemetryCollector CRs
9+
10+
# One or more tracking issues related to the change
11+
issues: [3454]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

controllers/common.go

+20-6
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121

2222
"github.com/go-logr/logr"
2323
rbacv1 "k8s.io/api/rbac/v1"
24+
apimeta "k8s.io/apimachinery/pkg/api/meta"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2626
"k8s.io/apimachinery/pkg/runtime"
2727
"k8s.io/apimachinery/pkg/types"
2828
"k8s.io/client-go/util/retry"
@@ -119,18 +119,32 @@ func BuildTargetAllocator(params targetallocator.Params) ([]client.Object, error
119119
// getList queries the Kubernetes API to list the requested resource, setting the list l of type T.
120120
func getList[T client.Object](ctx context.Context, cl client.Client, l T, options ...client.ListOption) (map[types.UID]client.Object, error) {
121121
ownedObjects := map[types.UID]client.Object{}
122-
list := &unstructured.UnstructuredList{}
123122
gvk, err := apiutil.GVKForObject(l, cl.Scheme())
124123
if err != nil {
125124
return nil, err
126125
}
127-
list.SetGroupVersionKind(gvk)
128-
err = cl.List(ctx, list, options...)
126+
gvk.Kind = fmt.Sprintf("%sList", gvk.Kind)
127+
list, err := cl.Scheme().New(gvk)
128+
if err != nil {
129+
return nil, fmt.Errorf("unable to list objects of type %s: %w", gvk.Kind, err)
130+
}
131+
132+
objList := list.(client.ObjectList)
133+
134+
err = cl.List(ctx, objList, options...)
129135
if err != nil {
130136
return ownedObjects, fmt.Errorf("error listing %T: %w", l, err)
131137
}
132-
for i := range list.Items {
133-
ownedObjects[list.Items[i].GetUID()] = &list.Items[i]
138+
objs, err := apimeta.ExtractList(objList)
139+
if err != nil {
140+
return ownedObjects, fmt.Errorf("error listing %T: %w", l, err)
141+
}
142+
for i := range objs {
143+
typedObj, ok := objs[i].(T)
144+
if !ok {
145+
return ownedObjects, fmt.Errorf("error listing %T: %w", l, err)
146+
}
147+
ownedObjects[typedObj.GetUID()] = typedObj
134148
}
135149
return ownedObjects, nil
136150
}

controllers/opentelemetrycollector_controller.go

+99-66
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package controllers
1717

1818
import (
1919
"context"
20-
"fmt"
2120
"sort"
2221

2322
"github.com/go-logr/logr"
@@ -30,12 +29,14 @@ import (
3029
policyV1 "k8s.io/api/policy/v1"
3130
rbacv1 "k8s.io/api/rbac/v1"
3231
apierrors "k8s.io/apimachinery/pkg/api/errors"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3333
"k8s.io/apimachinery/pkg/labels"
3434
"k8s.io/apimachinery/pkg/runtime"
3535
"k8s.io/apimachinery/pkg/types"
3636
"k8s.io/client-go/tools/record"
3737
ctrl "sigs.k8s.io/controller-runtime"
3838
"sigs.k8s.io/controller-runtime/pkg/client"
39+
"sigs.k8s.io/controller-runtime/pkg/cluster"
3940
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4041

4142
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
@@ -53,6 +54,8 @@ import (
5354
"github.com/open-telemetry/opentelemetry-operator/pkg/featuregate"
5455
)
5556

57+
const resourceOwnerKey = ".metadata.owner"
58+
5659
var (
5760
ownedClusterObjectTypes = []client.Object{
5861
&rbacv1.ClusterRole{},
@@ -82,51 +85,42 @@ type Params struct {
8285

8386
func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Context, params manifests.Params) (map[types.UID]client.Object, error) {
8487
ownedObjects := map[types.UID]client.Object{}
85-
ownedObjectTypes := []client.Object{
86-
&autoscalingv2.HorizontalPodAutoscaler{},
87-
&networkingv1.Ingress{},
88-
&policyV1.PodDisruptionBudget{},
89-
}
90-
listOps := &client.ListOptions{
91-
Namespace: params.OtelCol.Namespace,
92-
LabelSelector: labels.SelectorFromSet(manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, collector.ComponentOpenTelemetryCollector)),
93-
}
94-
if featuregate.PrometheusOperatorIsAvailable.IsEnabled() && r.config.PrometheusCRAvailability() == prometheus.Available {
95-
ownedObjectTypes = append(ownedObjectTypes,
96-
&monitoringv1.ServiceMonitor{},
97-
&monitoringv1.PodMonitor{},
98-
)
99-
}
100-
if params.Config.OpenShiftRoutesAvailability() == openshift.RoutesAvailable {
101-
ownedObjectTypes = append(ownedObjectTypes, &routev1.Route{})
88+
collectorConfigMaps := []*corev1.ConfigMap{}
89+
ownedObjectTypes := r.GetOwnedResourceTypes()
90+
listOpts := []client.ListOption{
91+
client.InNamespace(params.OtelCol.Namespace),
92+
client.MatchingFields{resourceOwnerKey: params.OtelCol.Name},
10293
}
10394
for _, objectType := range ownedObjectTypes {
104-
objs, err := getList(ctx, r, objectType, listOps)
95+
objs, err := getList(ctx, r, objectType, listOpts...)
10596
if err != nil {
10697
return nil, err
10798
}
10899
for uid, object := range objs {
109100
ownedObjects[uid] = object
110101
}
111-
}
112-
if params.Config.CreateRBACPermissions() == rbac.Available {
113-
objs, err := r.findClusterRoleObjects(ctx, params)
114-
if err != nil {
115-
return nil, err
116-
}
117-
for uid, object := range objs {
118-
ownedObjects[uid] = object
102+
// save Collector ConfigMaps into a separate slice, we need to do additional filtering on them
103+
switch objectType.(type) {
104+
case *corev1.ConfigMap:
105+
for _, object := range objs {
106+
if !featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() && object.GetLabels()["app.kubernetes.io/component"] != "opentelemetry-collector" {
107+
// we only apply this to collector ConfigMaps
108+
continue
109+
}
110+
configMap := object.(*corev1.ConfigMap)
111+
collectorConfigMaps = append(collectorConfigMaps, configMap)
112+
}
113+
default:
119114
}
120115
}
121116

122-
configMapList := &corev1.ConfigMapList{}
123-
err := r.List(ctx, configMapList, listOps)
124-
if err != nil {
125-
return nil, fmt.Errorf("error listing ConfigMaps: %w", err)
126-
}
127-
ownedConfigMaps := r.getConfigMapsToRemove(params.OtelCol.Spec.ConfigVersions, configMapList)
128-
for i := range ownedConfigMaps {
129-
ownedObjects[ownedConfigMaps[i].GetUID()] = &ownedConfigMaps[i]
117+
// at this point we don't know if the most recent ConfigMap will still be the most recent after reconciliation, or
118+
// if a new one will be created. We keep one additional ConfigMap to account for this. The next reconciliation that
119+
// doesn't spawn a new ConfigMap will delete the extra one we kept here.
120+
configVersionsToKeep := max(params.OtelCol.Spec.ConfigVersions, 1) + 1
121+
configMapsToKeep := getCollectorConfigMapsToKeep(configVersionsToKeep, collectorConfigMaps)
122+
for _, configMap := range configMapsToKeep {
123+
delete(ownedObjects, configMap.GetUID())
130124
}
131125

132126
return ownedObjects, nil
@@ -138,7 +132,8 @@ func (r *OpenTelemetryCollectorReconciler) findClusterRoleObjects(ctx context.Co
138132
// Remove cluster roles and bindings.
139133
// Users might switch off the RBAC creation feature on the operator which should remove existing RBAC.
140134
listOpsCluster := &client.ListOptions{
141-
LabelSelector: labels.SelectorFromSet(manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, collector.ComponentOpenTelemetryCollector)),
135+
LabelSelector: labels.SelectorFromSet(
136+
manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, collector.ComponentOpenTelemetryCollector)),
142137
}
143138
for _, objectType := range ownedClusterObjectTypes {
144139
objs, err := getList(ctx, r, objectType, listOpsCluster)
@@ -152,25 +147,21 @@ func (r *OpenTelemetryCollectorReconciler) findClusterRoleObjects(ctx context.Co
152147
return ownedObjects, nil
153148
}
154149

155-
// getConfigMapsToRemove returns a list of ConfigMaps to remove based on the number of ConfigMaps to keep.
156-
// It keeps the newest ConfigMap, the `configVersionsToKeep` next newest ConfigMaps, and returns the remainder.
157-
func (r *OpenTelemetryCollectorReconciler) getConfigMapsToRemove(configVersionsToKeep int, configMapList *corev1.ConfigMapList) []corev1.ConfigMap {
150+
// getCollectorConfigMapsToKeep gets ConfigMaps the controller would normally delete, but which we want to keep around
151+
// anyway. This is part of a feature to keep around previous ConfigMap versions to make rollbacks easier.
152+
// Fundamentally, this just sorts by time created and picks configVersionsToKeep latest ones.
153+
func getCollectorConfigMapsToKeep(configVersionsToKeep int, configMaps []*corev1.ConfigMap) []*corev1.ConfigMap {
158154
configVersionsToKeep = max(1, configVersionsToKeep)
159-
ownedConfigMaps := []corev1.ConfigMap{}
160-
sort.Slice(configMapList.Items, func(i, j int) bool {
161-
iTime := configMapList.Items[i].GetCreationTimestamp().Time
162-
jTime := configMapList.Items[j].GetCreationTimestamp().Time
155+
sort.Slice(configMaps, func(i, j int) bool {
156+
iTime := configMaps[i].GetCreationTimestamp().Time
157+
jTime := configMaps[j].GetCreationTimestamp().Time
163158
// sort the ConfigMaps newest to oldest
164159
return iTime.After(jTime)
165160
})
166161

167-
for i := range configMapList.Items {
168-
if i > configVersionsToKeep {
169-
ownedConfigMaps = append(ownedConfigMaps, configMapList.Items[i])
170-
}
171-
}
172-
173-
return ownedConfigMaps
162+
configMapsToKeep := min(configVersionsToKeep, len(configMaps))
163+
// return the first configVersionsToKeep items
164+
return configMaps[:configMapsToKeep]
174165
}
175166

176167
func (r *OpenTelemetryCollectorReconciler) GetParams(ctx context.Context, instance v1beta1.OpenTelemetryCollector) (manifests.Params, error) {
@@ -310,32 +301,74 @@ func (r *OpenTelemetryCollectorReconciler) Reconcile(ctx context.Context, req ct
310301

311302
// SetupWithManager tells the manager what our controller is interested in.
312303
func (r *OpenTelemetryCollectorReconciler) SetupWithManager(mgr ctrl.Manager) error {
304+
err := r.SetupCaches(mgr)
305+
if err != nil {
306+
return err
307+
}
308+
309+
ownedResources := r.GetOwnedResourceTypes()
313310
builder := ctrl.NewControllerManagedBy(mgr).
314-
For(&v1beta1.OpenTelemetryCollector{}).
315-
Owns(&corev1.ConfigMap{}).
316-
Owns(&corev1.ServiceAccount{}).
317-
Owns(&corev1.Service{}).
318-
Owns(&appsv1.Deployment{}).
319-
Owns(&appsv1.DaemonSet{}).
320-
Owns(&appsv1.StatefulSet{}).
321-
Owns(&networkingv1.Ingress{}).
322-
Owns(&autoscalingv2.HorizontalPodAutoscaler{}).
323-
Owns(&policyV1.PodDisruptionBudget{})
311+
For(&v1beta1.OpenTelemetryCollector{})
312+
313+
for _, resource := range ownedResources {
314+
builder.Owns(resource)
315+
}
316+
317+
return builder.Complete(r)
318+
}
319+
320+
// SetupCaches sets up caching and indexing for our controller.
321+
func (r *OpenTelemetryCollectorReconciler) SetupCaches(cluster cluster.Cluster) error {
322+
ownedResources := r.GetOwnedResourceTypes()
323+
for _, resource := range ownedResources {
324+
if err := cluster.GetCache().IndexField(context.Background(), resource, resourceOwnerKey, func(rawObj client.Object) []string {
325+
owner := metav1.GetControllerOf(rawObj)
326+
if owner == nil {
327+
return nil
328+
}
329+
// make sure it's an OpenTelemetryCollector
330+
if owner.Kind != "OpenTelemetryCollector" {
331+
return nil
332+
}
333+
334+
return []string{owner.Name}
335+
}); err != nil {
336+
return err
337+
}
338+
}
339+
return nil
340+
}
341+
342+
// GetOwnedResourceTypes returns all the resource types the controller can own. Even though this method returns an array
343+
// of client.Object, these are (empty) example structs rather than actual resources.
344+
func (r *OpenTelemetryCollectorReconciler) GetOwnedResourceTypes() []client.Object {
345+
ownedResources := []client.Object{
346+
&corev1.ConfigMap{},
347+
&corev1.ServiceAccount{},
348+
&corev1.Service{},
349+
&appsv1.Deployment{},
350+
&appsv1.DaemonSet{},
351+
&appsv1.StatefulSet{},
352+
&networkingv1.Ingress{},
353+
&autoscalingv2.HorizontalPodAutoscaler{},
354+
&policyV1.PodDisruptionBudget{},
355+
}
324356

325357
if r.config.CreateRBACPermissions() == rbac.Available {
326-
builder.Owns(&rbacv1.ClusterRoleBinding{})
327-
builder.Owns(&rbacv1.ClusterRole{})
358+
ownedResources = append(ownedResources, &rbacv1.ClusterRole{})
359+
ownedResources = append(ownedResources, &rbacv1.ClusterRoleBinding{})
328360
}
329361

330362
if featuregate.PrometheusOperatorIsAvailable.IsEnabled() && r.config.PrometheusCRAvailability() == prometheus.Available {
331-
builder.Owns(&monitoringv1.ServiceMonitor{})
332-
builder.Owns(&monitoringv1.PodMonitor{})
363+
ownedResources = append(ownedResources, &monitoringv1.PodMonitor{})
364+
ownedResources = append(ownedResources, &monitoringv1.ServiceMonitor{})
333365
}
366+
334367
if r.config.OpenShiftRoutesAvailability() == openshift.RoutesAvailable {
335-
builder.Owns(&routev1.Route{})
368+
ownedResources = append(ownedResources, &routev1.Route{})
336369
}
337370

338-
return builder.Complete(r)
371+
return ownedResources
339372
}
340373

341374
const collectorFinalizer = "opentelemetrycollector.opentelemetry.io/finalizer"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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
16+
17+
import (
18+
"testing"
19+
"time"
20+
21+
"github.com/stretchr/testify/assert"
22+
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
func TestGetCollectorConfigMapsToKeep(t *testing.T) {
27+
now := time.Now()
28+
testCases := []struct {
29+
name string
30+
versionsToKeep int
31+
input []*corev1.ConfigMap
32+
output []*corev1.ConfigMap
33+
}{
34+
{
35+
name: "no configmaps",
36+
input: []*corev1.ConfigMap{},
37+
output: []*corev1.ConfigMap{},
38+
},
39+
{
40+
name: "one configmap",
41+
input: []*corev1.ConfigMap{
42+
{},
43+
},
44+
output: []*corev1.ConfigMap{
45+
{},
46+
},
47+
},
48+
{
49+
name: "two configmaps, keep one",
50+
input: []*corev1.ConfigMap{
51+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now}}},
52+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Second)}}},
53+
},
54+
output: []*corev1.ConfigMap{
55+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Second)}}},
56+
},
57+
},
58+
{
59+
name: "three configmaps, keep two",
60+
versionsToKeep: 2,
61+
input: []*corev1.ConfigMap{
62+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now}}},
63+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Second)}}},
64+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Minute)}}},
65+
},
66+
output: []*corev1.ConfigMap{
67+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Minute)}}},
68+
{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: now.Add(time.Second)}}},
69+
},
70+
},
71+
}
72+
for _, tc := range testCases {
73+
t.Run(tc.name, func(t *testing.T) {
74+
actualOutput := getCollectorConfigMapsToKeep(tc.versionsToKeep, tc.input)
75+
assert.Equal(t, tc.output, actualOutput)
76+
})
77+
}
78+
}

0 commit comments

Comments
 (0)