Skip to content

Commit 9559ac8

Browse files
committed
Generate TargetAllocator CR from Collector CR
1 parent 5b59a10 commit 9559ac8

File tree

7 files changed

+376
-21
lines changed

7 files changed

+376
-21
lines changed

apis/v1beta1/targetallocator_types.go

+51-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
package v1beta1
1818

1919
import (
20+
"encoding/json"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
)
2224

2325
// TargetAllocatorSpec defines the desired state of TargetAllocator.
2426
type TargetAllocatorSpec struct {
2527
// Common defines fields that are common to all OpenTelemetry CRD workloads.
2628
OpenTelemetryCommonFields `json:",inline"`
29+
// CollectorSelector is the selector for Collector Pods the target allocator will allocate targets to.
30+
CollectorSelector metav1.LabelSelector `json:"collectorSelector,omitempty"`
2731
// AllocationStrategy determines which strategy the target allocator should use for allocation.
2832
// The current options are least-weighted and consistent-hashing. The default option is consistent-hashing
2933
// +optional
@@ -35,8 +39,13 @@ type TargetAllocatorSpec struct {
3539
// +optional
3640
// +kubebuilder:default:=relabel-config
3741
FilterStrategy TargetAllocatorFilterStrategy `json:"filterStrategy,omitempty"`
38-
// ServiceAccount indicates the name of an existing service account to use with this instance. When set,
39-
// the operator will not automatically create a ServiceAccount for the TargetAllocator.
42+
// ScrapeConfigs define static Prometheus scrape configurations for the target allocator.
43+
// To use dynamic configurations from ServiceMonitors and PodMonitors, see the PrometheusCR section.
44+
// For the exact format, see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config.
45+
// +optional
46+
// +listType=atomic
47+
ScrapeConfigs []ScrapeConfig `json:"scrapeConfigs,omitempty"`
48+
// PrometheusCR defines the configuration for the retrieval of PrometheusOperator CRDs ( servicemonitor.monitoring.coreos.com/v1 and podmonitor.monitoring.coreos.com/v1 ).
4049
// +optional
4150
PrometheusCR TargetAllocatorPrometheusCR `json:"prometheusCR,omitempty"`
4251
}
@@ -76,6 +85,46 @@ type TargetAllocatorStatus struct {
7685
Image string `json:"image,omitempty"`
7786
}
7887

88+
// ScrapeConfig is a Prometheus scrape config definition.
89+
type ScrapeConfig map[string]interface{}
90+
91+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
92+
func (in *ScrapeConfig) DeepCopyInto(out *ScrapeConfig) {
93+
*out = make(map[string]interface{}, len(*in))
94+
for key, val := range *in {
95+
(*out)[key] = val
96+
}
97+
}
98+
99+
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScrapeConfig.
100+
func (in *ScrapeConfig) DeepCopy() *ScrapeConfig {
101+
if in == nil {
102+
return nil
103+
}
104+
out := new(ScrapeConfig)
105+
in.DeepCopyInto(out)
106+
return out
107+
}
108+
109+
var _ json.Marshaler = &ScrapeConfig{}
110+
var _ json.Unmarshaler = &ScrapeConfig{}
111+
112+
// UnmarshalJSON implements an alternative parser for this field.
113+
func (c *ScrapeConfig) UnmarshalJSON(b []byte) error {
114+
if err := json.Unmarshal(b, c); err != nil {
115+
return err
116+
}
117+
return nil
118+
}
119+
120+
// MarshalJSON specifies how to convert this object into JSON.
121+
func (c *ScrapeConfig) MarshalJSON() ([]byte, error) {
122+
if c == nil {
123+
return []byte("{}"), nil
124+
}
125+
return json.Marshal(c)
126+
}
127+
79128
//+kubebuilder:object:root=true
80129
//+kubebuilder:subresource:status
81130

apis/v1beta1/zz_generated.deepcopy.go

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controllers/opentelemetrycollector_controller.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,24 @@ func (r *OpenTelemetryCollectorReconciler) getParams(instance v1alpha1.OpenTelem
134134
if err != nil {
135135
return manifests.Params{}, err
136136
}
137-
return manifests.Params{
137+
params := manifests.Params{
138138
Config: r.config,
139139
Client: r.Client,
140140
OtelCol: otelCol,
141141
Log: r.log,
142142
Scheme: r.scheme,
143143
Recorder: r.recorder,
144-
}, nil
144+
}
145+
146+
// generate the target allocator CR from the collector CR
147+
targetAllocator, err := collector.TargetAllocator(params)
148+
if err != nil {
149+
return params, err
150+
}
151+
if targetAllocator != nil {
152+
params.TargetAllocator = *targetAllocator
153+
}
154+
return params, nil
145155
}
146156

147157
// NewReconciler creates a new reconciler for OpenTelemetryCollector objects.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 collector
16+
17+
import (
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
20+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
21+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests"
22+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils"
23+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator/adapters"
24+
)
25+
26+
// TargetAllocator builds the TargetAllocator CR for the given instance.
27+
func TargetAllocator(params manifests.Params) (*v1beta1.TargetAllocator, error) {
28+
29+
taSpec := params.OtelCol.Spec.TargetAllocator
30+
if !taSpec.Enabled {
31+
return nil, nil
32+
}
33+
34+
collectorSelector := metav1.LabelSelector{
35+
MatchLabels: manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, ComponentOpenTelemetryCollector),
36+
}
37+
38+
configStr, err := params.OtelCol.Spec.Config.Yaml()
39+
if err != nil {
40+
return nil, err
41+
}
42+
scrapeConfigs, err := getScrapeConfigs(configStr)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
return &v1beta1.TargetAllocator{
48+
ObjectMeta: metav1.ObjectMeta{
49+
Name: params.OtelCol.Name,
50+
Namespace: params.OtelCol.Namespace,
51+
Annotations: params.OtelCol.Annotations,
52+
Labels: params.OtelCol.Labels,
53+
},
54+
Spec: v1beta1.TargetAllocatorSpec{
55+
OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{
56+
Replicas: taSpec.Replicas,
57+
NodeSelector: taSpec.NodeSelector,
58+
Resources: taSpec.Resources,
59+
ServiceAccount: taSpec.ServiceAccount,
60+
SecurityContext: taSpec.SecurityContext,
61+
PodSecurityContext: taSpec.PodSecurityContext,
62+
Image: taSpec.Image,
63+
Affinity: taSpec.Affinity,
64+
TopologySpreadConstraints: taSpec.TopologySpreadConstraints,
65+
Tolerations: taSpec.Tolerations,
66+
Env: taSpec.Env,
67+
PodAnnotations: params.OtelCol.Spec.PodAnnotations,
68+
},
69+
CollectorSelector: collectorSelector,
70+
AllocationStrategy: taSpec.AllocationStrategy,
71+
FilterStrategy: taSpec.FilterStrategy,
72+
ScrapeConfigs: scrapeConfigs,
73+
PrometheusCR: taSpec.PrometheusCR,
74+
},
75+
}, nil
76+
}
77+
78+
func getScrapeConfigs(otelcolConfig string) ([]v1beta1.ScrapeConfig, error) {
79+
// Collector supports environment variable substitution, but the TA does not.
80+
// TA Scrape Configs should have a single "$", as it does not support env var substitution
81+
prometheusReceiverConfig, err := adapters.UnescapeDollarSignsInPromConfig(otelcolConfig)
82+
if err != nil {
83+
return nil, err
84+
}
85+
86+
scrapeConfigs, err := adapters.GetScrapeConfigsFromPromConfig(prometheusReceiverConfig)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
v1beta1scrapeConfigs := make([]v1beta1.ScrapeConfig, len(scrapeConfigs))
92+
93+
for i, config := range scrapeConfigs {
94+
v1beta1scrapeConfigs[i] = config
95+
}
96+
97+
return v1beta1scrapeConfigs, nil
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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 collector
16+
17+
import (
18+
"fmt"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
25+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
26+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests"
27+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils"
28+
)
29+
30+
func TestTargetAllocator(t *testing.T) {
31+
objectMetadata := metav1.ObjectMeta{
32+
Name: "name",
33+
Namespace: "namespace",
34+
Annotations: map[string]string{
35+
"annotation_key": "annotation_value",
36+
},
37+
Labels: map[string]string{
38+
"label_key": "label_value",
39+
},
40+
}
41+
otelcolConfig := v1beta1.Config{
42+
Receivers: v1beta1.AnyConfig{
43+
Object: map[string]interface{}{
44+
"prometheus": map[string]any{
45+
"config": map[string]any{
46+
"scrape_configs": []any{},
47+
},
48+
},
49+
},
50+
},
51+
}
52+
53+
testCases := []struct {
54+
name string
55+
input v1beta1.OpenTelemetryCollector
56+
want *v1beta1.TargetAllocator
57+
wantErr error
58+
}{
59+
{
60+
name: "disabled",
61+
input: v1beta1.OpenTelemetryCollector{
62+
Spec: v1beta1.OpenTelemetryCollectorSpec{
63+
TargetAllocator: v1beta1.TargetAllocatorEmbedded{
64+
Enabled: false,
65+
},
66+
},
67+
},
68+
want: nil,
69+
},
70+
{
71+
name: "metadata",
72+
input: v1beta1.OpenTelemetryCollector{
73+
ObjectMeta: objectMetadata,
74+
Spec: v1beta1.OpenTelemetryCollectorSpec{
75+
Config: otelcolConfig,
76+
TargetAllocator: v1beta1.TargetAllocatorEmbedded{
77+
Enabled: true,
78+
},
79+
},
80+
},
81+
want: &v1beta1.TargetAllocator{
82+
ObjectMeta: objectMetadata,
83+
Spec: v1beta1.TargetAllocatorSpec{
84+
CollectorSelector: metav1.LabelSelector{
85+
MatchLabels: manifestutils.SelectorLabels(objectMetadata, ComponentOpenTelemetryCollector),
86+
},
87+
ScrapeConfigs: []v1beta1.ScrapeConfig{},
88+
},
89+
},
90+
},
91+
}
92+
93+
for _, testCase := range testCases {
94+
testCase := testCase
95+
t.Run(testCase.name, func(t *testing.T) {
96+
params := manifests.Params{
97+
OtelCol: testCase.input,
98+
}
99+
actual, err := TargetAllocator(params)
100+
assert.Equal(t, testCase.wantErr, err)
101+
assert.Equal(t, testCase.want, actual)
102+
})
103+
}
104+
}
105+
106+
func TestGetScrapeConfigs(t *testing.T) {
107+
testCases := []struct {
108+
name string
109+
input v1beta1.Config
110+
want []v1beta1.ScrapeConfig
111+
wantErr error
112+
}{
113+
{
114+
name: "empty scrape configs list",
115+
input: v1beta1.Config{
116+
Receivers: v1beta1.AnyConfig{
117+
Object: map[string]interface{}{
118+
"prometheus": map[string]any{
119+
"config": map[string]any{
120+
"scrape_configs": []any{},
121+
},
122+
},
123+
},
124+
},
125+
},
126+
want: []v1beta1.ScrapeConfig{},
127+
},
128+
{
129+
name: "no scrape configs key",
130+
input: v1beta1.Config{
131+
Receivers: v1beta1.AnyConfig{
132+
Object: map[string]interface{}{
133+
"prometheus": map[string]any{
134+
"config": map[string]any{},
135+
},
136+
},
137+
},
138+
},
139+
wantErr: fmt.Errorf("no scrape_configs available as part of the configuration"),
140+
},
141+
{
142+
name: "one scrape config",
143+
input: v1beta1.Config{
144+
Receivers: v1beta1.AnyConfig{
145+
Object: map[string]interface{}{
146+
"prometheus": map[string]any{
147+
"config": map[string]any{
148+
"scrape_configs": []any{
149+
map[string]any{
150+
"job": "somejob",
151+
},
152+
},
153+
},
154+
},
155+
},
156+
},
157+
},
158+
want: []v1beta1.ScrapeConfig{
159+
{
160+
"job": "somejob",
161+
},
162+
},
163+
},
164+
}
165+
166+
for _, testCase := range testCases {
167+
testCase := testCase
168+
t.Run(testCase.name, func(t *testing.T) {
169+
configStr, err := testCase.input.Yaml()
170+
require.NoError(t, err)
171+
actual, err := getScrapeConfigs(configStr)
172+
assert.Equal(t, testCase.wantErr, err)
173+
assert.Equal(t, testCase.want, actual)
174+
})
175+
}
176+
}

0 commit comments

Comments
 (0)