Skip to content

Commit

Permalink
[target allocator] Restart target allocator when config changes (#1889)
Browse files Browse the repository at this point in the history
* Move target allocator ConfigMap to the targetallocator package

* Restart target allocator when config changes

* Use the right collector selector labels for TA
  • Loading branch information
Mikołaj Świątek committed Jul 13, 2023
1 parent 02b4d30 commit ecebb74
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 162 deletions.
16 changes: 16 additions & 0 deletions .chloggen/feat_restart-targetallocator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. operator, target allocator, github action)
component: target allocator

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Restart target allocator when its configuration changes

# One or more tracking issues related to the change
issues: [1882]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
2 changes: 1 addition & 1 deletion hack/modify-test-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

SED_BIN=${SED_BIN:-sed}

${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/smoke-targetallocator/00-install.yaml
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/smoke-targetallocator/*.yaml
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/targetallocator-features/00-install.yaml
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/prometheus-config-validation/*.yaml
70 changes: 1 addition & 69 deletions pkg/collector/reconcile/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,17 @@ import (
"context"
"fmt"
"reflect"
"strings"

"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/apis/v1alpha1"
"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
Expand All @@ -44,7 +40,7 @@ func ConfigMaps(ctx context.Context, params Params) error {
}

if params.Instance.Spec.TargetAllocator.Enabled {
cm, err := desiredTAConfigMap(params)
cm, err := targetallocator.ConfigMap(params.Instance)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
Expand Down Expand Up @@ -86,70 +82,6 @@ func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap {
}
}

func desiredTAConfigMap(params Params) (corev1.ConfigMap, error) {
name := naming.TAConfigMap(params.Instance)
version := strings.Split(params.Instance.Spec.Image, ":")
labels := targetallocator.Labels(params.Instance, name)
if len(version) > 1 {
labels["app.kubernetes.io/version"] = version[len(version)-1]
} else {
labels["app.kubernetes.io/version"] = "latest"
}

// Collector supports environment variable substitution, but the TA does not.
// TA ConfigMap should have a single "$", as it does not support env var substitution
prometheusReceiverConfig, err := ta.UnescapeDollarSignsInPromConfig(params.Instance.Spec.Config)
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",
"app.kubernetes.io/component": "opentelemetry-collector",
}
// We only take the "config" from the returned object, if it's present
if prometheusConfig, ok := prometheusReceiverConfig["config"]; ok {
taConfig["config"] = prometheusConfig
}

if len(params.Instance.Spec.TargetAllocator.AllocationStrategy) > 0 {
taConfig["allocation_strategy"] = params.Instance.Spec.TargetAllocator.AllocationStrategy
} else {
taConfig["allocation_strategy"] = v1alpha1.OpenTelemetryTargetAllocatorAllocationStrategyLeastWeighted
}

if len(params.Instance.Spec.TargetAllocator.FilterStrategy) > 0 {
taConfig["filter_strategy"] = params.Instance.Spec.TargetAllocator.FilterStrategy
}

if params.Instance.Spec.TargetAllocator.PrometheusCR.ServiceMonitorSelector != nil {
taConfig["service_monitor_selector"] = &params.Instance.Spec.TargetAllocator.PrometheusCR.ServiceMonitorSelector
}

if params.Instance.Spec.TargetAllocator.PrometheusCR.PodMonitorSelector != nil {
taConfig["pod_monitor_selector"] = &params.Instance.Spec.TargetAllocator.PrometheusCR.PodMonitorSelector
}

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
Expand Down
80 changes: 5 additions & 75 deletions pkg/collector/reconcile/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
"github.com/open-telemetry/opentelemetry-operator/internal/config"
"github.com/open-telemetry/opentelemetry-operator/pkg/featuregate"
"github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator"
ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters"
)

Expand Down Expand Up @@ -213,80 +214,11 @@ service:

})

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": `allocation_strategy: least-weighted
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/component: opentelemetry-collector
app.kubernetes.io/instance: default.test
app.kubernetes.io/managed-by: opentelemetry-operator
`,
}

actual, err := desiredTAConfigMap(params())
assert.NoError(t, err)

assert.Equal(t, "test-targetallocator", actual.Name)
assert.Equal(t, expectedLables, actual.Labels)
assert.Equal(t, expectedData, actual.Data)

})
t.Run("should return expected target allocator config map with label selectors", func(t *testing.T) {
expectedLables["app.kubernetes.io/component"] = "opentelemetry-targetallocator"
expectedLables["app.kubernetes.io/name"] = "test-targetallocator"

expectedData := map[string]string{
"targetallocator.yaml": `allocation_strategy: least-weighted
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/component: opentelemetry-collector
app.kubernetes.io/instance: default.test
app.kubernetes.io/managed-by: opentelemetry-operator
pod_monitor_selector:
release: test
service_monitor_selector:
release: test
`,
}
p := params()
p.Instance.Spec.TargetAllocator.PrometheusCR.PodMonitorSelector = map[string]string{
"release": "test",
}
p.Instance.Spec.TargetAllocator.PrometheusCR.ServiceMonitorSelector = map[string]string{
"release": "test",
}
actual, err := desiredTAConfigMap(p)
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 collector and target allocator config maps", func(t *testing.T) {
configMap, err := desiredTAConfigMap(params())
configMap, err := targetallocator.ConfigMap(params().Instance)
assert.NoError(t, err)
err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params()), configMap}, true)
assert.NoError(t, err)
Expand Down Expand Up @@ -340,7 +272,6 @@ func TestExpectedConfigMap(t *testing.T) {
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",
Expand Down Expand Up @@ -368,14 +299,12 @@ func TestExpectedConfigMap(t *testing.T) {
Config: "",
},
},
Scheme: testScheme,
Log: logger,
}
cm, err := desiredTAConfigMap(param)
cm, err := targetallocator.ConfigMap(param.Instance)
assert.EqualError(t, err, "no receivers available as part of the configuration")
createObjectIfNotExists(t, "test-targetallocator", &cm)

configMap, err := desiredTAConfigMap(params())
configMap, err := targetallocator.ConfigMap(params().Instance)
assert.NoError(t, err)
err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{configMap}, true)
assert.NoError(t, err)
Expand All @@ -395,6 +324,7 @@ func TestExpectedConfigMap(t *testing.T) {
"app.kubernetes.io/instance": "default.test",
"app.kubernetes.io/managed-by": "opentelemetry-operator",
"app.kubernetes.io/component": "opentelemetry-collector",
"app.kubernetes.io/part-of": "opentelemetry",
}
taConfig["config"] = promConfig["config"]
taConfig["allocation_strategy"] = "least-weighted"
Expand Down
49 changes: 49 additions & 0 deletions pkg/targetallocator/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 targetallocator

import (
"crypto/sha256"
"fmt"

v1 "k8s.io/api/core/v1"

"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
)

const configMapHashAnnotationKey = "opentelemetry-targetallocator-config/hash"

// Annotations returns the annotations for the TargetAllocator Pod.
func Annotations(instance v1alpha1.OpenTelemetryCollector) map[string]string {
// Make a copy of PodAnnotations to be safe
annotations := make(map[string]string, len(instance.Spec.PodAnnotations))
for key, value := range instance.Spec.PodAnnotations {
annotations[key] = value
}

configMap, err := ConfigMap(instance)
if err == nil {
annotations[configMapHashAnnotationKey] = getConfigMapSHA(configMap)
}

return annotations
}

// getConfigMapSHA returns the hash of the content of the TA ConfigMap.
func getConfigMapSHA(configMap v1.ConfigMap) string {
configBytes := configMap.BinaryData[targetAllocatorFilename]
h := sha256.Sum256(configBytes)
return fmt.Sprintf("%x", h)
}
46 changes: 46 additions & 0 deletions pkg/targetallocator/annotations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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 targetallocator

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPodAnnotations(t *testing.T) {
instance := collectorInstance()
instance.Spec.PodAnnotations = map[string]string{
"key": "value",
}
annotations := Annotations(instance)
assert.Subset(t, annotations, instance.Spec.PodAnnotations)
}

func TestConfigMapHash(t *testing.T) {
instance := collectorInstance()
annotations := Annotations(instance)
require.Contains(t, annotations, configMapHashAnnotationKey)
cmHash := annotations[configMapHashAnnotationKey]
assert.Len(t, cmHash, 64)
}

func TestInvalidConfigNoHash(t *testing.T) {
instance := collectorInstance()
instance.Spec.Config = ""
annotations := Annotations(instance)
require.NotContains(t, annotations, configMapHashAnnotationKey)
}
Loading

0 comments on commit ecebb74

Please sign in to comment.