Skip to content

Commit 5279433

Browse files
committed
Generate TargetAllocator CR from Collector CR
1 parent 94c8420 commit 5279433

File tree

7 files changed

+344
-22
lines changed

7 files changed

+344
-22
lines changed

apis/v1alpha2/targetallocator_types.go

+51-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
package v1alpha2
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
}
@@ -82,6 +91,46 @@ type TargetAllocatorStatus struct {
8291
Messages []string `json:"messages,omitempty"`
8392
}
8493

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

apis/v1alpha2/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

+17-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
3535
"github.com/open-telemetry/opentelemetry-operator/internal/config"
3636
"github.com/open-telemetry/opentelemetry-operator/internal/manifests"
37+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector"
3738
collectorStatus "github.com/open-telemetry/opentelemetry-operator/internal/status/collector"
3839
"github.com/open-telemetry/opentelemetry-operator/pkg/featuregate"
3940
)
@@ -56,15 +57,25 @@ type Params struct {
5657
Config config.Config
5758
}
5859

59-
func (r *OpenTelemetryCollectorReconciler) getParams(instance v1alpha1.OpenTelemetryCollector) manifests.Params {
60-
return manifests.Params{
60+
func (r *OpenTelemetryCollectorReconciler) getParams(instance v1alpha1.OpenTelemetryCollector) (manifests.Params, error) {
61+
params := manifests.Params{
6162
Config: r.config,
6263
Client: r.Client,
6364
OtelCol: instance,
6465
Log: r.log,
6566
Scheme: r.scheme,
6667
Recorder: r.recorder,
6768
}
69+
70+
// generate the target allocator CR from the collector CR
71+
targetAllocator, err := collector.TargetAllocator(params)
72+
if err != nil {
73+
return params, err
74+
}
75+
if targetAllocator != nil {
76+
params.TargetAllocator = *targetAllocator
77+
}
78+
return params, nil
6879
}
6980

7081
// NewReconciler creates a new reconciler for OpenTelemetryCollector objects.
@@ -119,7 +130,10 @@ func (r *OpenTelemetryCollectorReconciler) Reconcile(ctx context.Context, req ct
119130
return ctrl.Result{}, nil
120131
}
121132

122-
params := r.getParams(instance)
133+
params, buildErr := r.getParams(instance)
134+
if buildErr != nil {
135+
return ctrl.Result{}, buildErr
136+
}
123137

124138
desiredObjects, buildErr := BuildCollector(params)
125139
if buildErr != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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/v1alpha1"
21+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha2"
22+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests"
23+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils"
24+
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/targetallocator/adapters"
25+
)
26+
27+
// TargetAllocator builds the TargetAllocator CR for the given instance.
28+
func TargetAllocator(params manifests.Params) (*v1alpha2.TargetAllocator, error) {
29+
30+
taSpec := params.OtelCol.Spec.TargetAllocator
31+
if !taSpec.Enabled {
32+
return nil, nil
33+
}
34+
35+
collectorSelector := metav1.LabelSelector{
36+
MatchLabels: manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, ComponentOpenTelemetryCollector),
37+
}
38+
scrapeConfigs, err := getScrapeConfigs(params.OtelCol.Spec.Config)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return &v1alpha2.TargetAllocator{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Name: params.OtelCol.Name,
46+
Namespace: params.OtelCol.Namespace,
47+
Annotations: params.OtelCol.Annotations,
48+
Labels: params.OtelCol.Labels,
49+
},
50+
Spec: v1alpha2.TargetAllocatorSpec{
51+
OpenTelemetryCommonFields: v1alpha2.OpenTelemetryCommonFields{
52+
Replicas: taSpec.Replicas,
53+
NodeSelector: taSpec.NodeSelector,
54+
Resources: taSpec.Resources,
55+
ServiceAccount: taSpec.ServiceAccount,
56+
SecurityContext: taSpec.SecurityContext,
57+
PodSecurityContext: taSpec.PodSecurityContext,
58+
Image: taSpec.Image,
59+
Affinity: taSpec.Affinity,
60+
TopologySpreadConstraints: taSpec.TopologySpreadConstraints,
61+
Tolerations: taSpec.Tolerations,
62+
Env: taSpec.Env,
63+
PodAnnotations: params.OtelCol.Spec.PodAnnotations,
64+
},
65+
CollectorSelector: collectorSelector,
66+
AllocationStrategy: v1alpha2.TargetAllocatorAllocationStrategy(taSpec.AllocationStrategy),
67+
FilterStrategy: v1alpha2.TargetAllocatorFilterStrategy(taSpec.FilterStrategy),
68+
ScrapeConfigs: scrapeConfigs,
69+
PrometheusCR: TargetAllocatorPrometheusCR(taSpec.PrometheusCR),
70+
},
71+
}, nil
72+
}
73+
74+
// TargetAllocatorPrometheusCR converts v1alpha1 PrometheusCR subresource to v1alpha2.
75+
func TargetAllocatorPrometheusCR(promCR v1alpha1.OpenTelemetryTargetAllocatorPrometheusCR) v1alpha2.TargetAllocatorPrometheusCR {
76+
v2promCR := v1alpha2.TargetAllocatorPrometheusCR{
77+
Enabled: promCR.Enabled,
78+
ScrapeInterval: promCR.ScrapeInterval,
79+
}
80+
if promCR.ServiceMonitorSelector != nil {
81+
v2promCR.ServiceMonitorSelector = &metav1.LabelSelector{
82+
MatchLabels: promCR.ServiceMonitorSelector,
83+
}
84+
}
85+
if promCR.PodMonitorSelector != nil {
86+
v2promCR.PodMonitorSelector = &metav1.LabelSelector{
87+
MatchLabels: promCR.PodMonitorSelector,
88+
}
89+
}
90+
return v2promCR
91+
}
92+
93+
func getScrapeConfigs(otelcolConfig string) ([]v1alpha2.ScrapeConfig, error) {
94+
// Collector supports environment variable substitution, but the TA does not.
95+
// TA Scrape Configs should have a single "$", as it does not support env var substitution
96+
prometheusReceiverConfig, err := adapters.UnescapeDollarSignsInPromConfig(otelcolConfig)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
scrapeConfigs, err := adapters.GetScrapeConfigsFromPromConfig(prometheusReceiverConfig)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
v1alpha2scrapeConfigs := make([]v1alpha2.ScrapeConfig, len(scrapeConfigs))
107+
108+
for i, config := range scrapeConfigs {
109+
v1alpha2scrapeConfigs[i] = config
110+
}
111+
112+
return v1alpha2scrapeConfigs, nil
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
"testing"
19+
"time"
20+
21+
"github.com/stretchr/testify/assert"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
24+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
25+
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha2"
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 := `
42+
receivers:
43+
prometheus:
44+
config:
45+
scrape_configs: []
46+
`
47+
testCases := []struct {
48+
name string
49+
input v1alpha1.OpenTelemetryCollector
50+
want *v1alpha2.TargetAllocator
51+
wantErr error
52+
}{
53+
{
54+
name: "disabled",
55+
input: v1alpha1.OpenTelemetryCollector{
56+
Spec: v1alpha1.OpenTelemetryCollectorSpec{
57+
TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{
58+
Enabled: false,
59+
},
60+
},
61+
},
62+
want: nil,
63+
},
64+
{
65+
name: "metadata",
66+
input: v1alpha1.OpenTelemetryCollector{
67+
ObjectMeta: objectMetadata,
68+
Spec: v1alpha1.OpenTelemetryCollectorSpec{
69+
Config: otelcolConfig,
70+
TargetAllocator: v1alpha1.OpenTelemetryTargetAllocator{
71+
Enabled: true,
72+
},
73+
},
74+
},
75+
want: &v1alpha2.TargetAllocator{
76+
ObjectMeta: objectMetadata,
77+
Spec: v1alpha2.TargetAllocatorSpec{
78+
CollectorSelector: metav1.LabelSelector{
79+
MatchLabels: manifestutils.SelectorLabels(objectMetadata, ComponentOpenTelemetryCollector),
80+
},
81+
ScrapeConfigs: []v1alpha2.ScrapeConfig{},
82+
},
83+
},
84+
},
85+
}
86+
87+
for _, testCase := range testCases {
88+
testCase := testCase
89+
t.Run(testCase.name, func(t *testing.T) {
90+
params := manifests.Params{
91+
OtelCol: testCase.input,
92+
}
93+
actual, err := TargetAllocator(params)
94+
assert.Equal(t, testCase.wantErr, err)
95+
assert.Equal(t, testCase.want, actual)
96+
})
97+
}
98+
}
99+
100+
func TestTargetAllocatorPrometheusCR(t *testing.T) {
101+
v1PrometheusCR := v1alpha1.OpenTelemetryTargetAllocatorPrometheusCR{
102+
Enabled: true,
103+
ScrapeInterval: &metav1.Duration{Duration: time.Second * 15},
104+
PodMonitorSelector: map[string]string{
105+
"podkey": "value",
106+
},
107+
ServiceMonitorSelector: map[string]string{
108+
"servicekey": "value",
109+
},
110+
}
111+
expected := v1alpha2.TargetAllocatorPrometheusCR{
112+
Enabled: v1PrometheusCR.Enabled,
113+
ScrapeInterval: v1PrometheusCR.ScrapeInterval,
114+
PodMonitorSelector: &metav1.LabelSelector{
115+
MatchLabels: v1PrometheusCR.PodMonitorSelector,
116+
},
117+
ServiceMonitorSelector: &metav1.LabelSelector{
118+
MatchLabels: v1PrometheusCR.ServiceMonitorSelector,
119+
},
120+
}
121+
actual := TargetAllocatorPrometheusCR(v1PrometheusCR)
122+
assert.Equal(t, expected, actual)
123+
}

0 commit comments

Comments
 (0)