From 6dbb17f604ad44e2f8d5ee615bd2699a242d1b22 Mon Sep 17 00:00:00 2001 From: Ahmed Mezghani <38987709+ahmed-mez@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:10:49 +0100 Subject: [PATCH] pkg/clusteragent/admission: add unit tests (#15044) --- .../admission/common/lib_config.go | 12 ++ .../admission/common/lib_config_test.go | 154 ++++++++++++++++++ .../admission/patch/file_provider.go | 8 +- .../admission/patch/patch_request_test.go | 93 +++++++++++ pkg/clusteragent/admission/patch/provider.go | 6 +- .../admission/patch/rc_provider.go | 4 +- .../admission/patch/rc_provider_test.go | 64 ++++++++ pkg/clusteragent/admission/patch/start.go | 2 +- 8 files changed, 329 insertions(+), 14 deletions(-) create mode 100644 pkg/clusteragent/admission/common/lib_config_test.go create mode 100644 pkg/clusteragent/admission/patch/patch_request_test.go create mode 100644 pkg/clusteragent/admission/patch/rc_provider_test.go diff --git a/pkg/clusteragent/admission/common/lib_config.go b/pkg/clusteragent/admission/common/lib_config.go index 48bf926d1017a..d04de05eaaa57 100644 --- a/pkg/clusteragent/admission/common/lib_config.go +++ b/pkg/clusteragent/admission/common/lib_config.go @@ -59,6 +59,18 @@ type TracingHeaderTagEntry struct { // ToEnvs converts the config fields into environment variables func (lc LibConfig) ToEnvs() []corev1.EnvVar { var envs []corev1.EnvVar + if lc.ServiceName != nil { + envs = append(envs, corev1.EnvVar{ + Name: "DD_SERVICE", + Value: *lc.ServiceName, + }) + } + if lc.Env != nil { + envs = append(envs, corev1.EnvVar{ + Name: "DD_ENV", + Value: *lc.Env, + }) + } if val, defined := checkFormatVal(lc.Tracing); defined { envs = append(envs, corev1.EnvVar{ Name: "DD_TRACE_ENABLED", diff --git a/pkg/clusteragent/admission/common/lib_config_test.go b/pkg/clusteragent/admission/common/lib_config_test.go new file mode 100644 index 0000000000000..f177c9c5096e9 --- /dev/null +++ b/pkg/clusteragent/admission/common/lib_config_test.go @@ -0,0 +1,154 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver +// +build kubeapiserver + +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" +) + +func TestLibConfig_ToEnvs(t *testing.T) { + type fields struct { + ServiceName *string + Env *string + Tracing *bool + LogInjection *bool + HealthMetrics *bool + RuntimeMetrics *bool + TracingSamplingRate *float64 + TracingRateLimit *int + TracingTags []string + + /* + TracingServiceMapping []TracingServiceMapEntry + TracingAgentTimeout *int + TracingHeaderTags []TracingHeaderTagEntry + TracingPartialFlushMinSpans *int + TracingDebug *bool + TracingLogLevel *string + TracingMethods []string + TracingPropagationStyleInject []string + TracingPropagationStyleExtract []string + */ + } + tests := []struct { + name string + fields fields + want []corev1.EnvVar + }{ + { + name: "all", + fields: fields{ + ServiceName: ptr("svc"), + Env: ptr("dev"), + Tracing: ptr(true), + LogInjection: ptr(true), + HealthMetrics: ptr(true), + RuntimeMetrics: ptr(true), + TracingSamplingRate: ptr(0.5), + TracingRateLimit: ptr(50), + TracingTags: []string{"k1:v1", "k2:v2"}, + }, + want: []corev1.EnvVar{ + { + Name: "DD_SERVICE", + Value: "svc", + }, + { + Name: "DD_ENV", + Value: "dev", + }, + { + Name: "DD_TRACE_ENABLED", + Value: "true", + }, + { + Name: "DD_LOGS_INJECTION", + Value: "true", + }, + { + Name: "DD_TRACE_HEALTH_METRICS_ENABLED", + Value: "true", + }, + { + Name: "DD_RUNTIME_METRICS_ENABLED", + Value: "true", + }, + { + Name: "DD_TRACE_SAMPLE_RATE", + Value: "0.50", + }, + { + Name: "DD_TRACE_RATE_LIMIT", + Value: "50", + }, + { + Name: "DD_TAGS", + Value: "k1:v1,k2:v2", + }, + }, + }, + { + name: "only service and env", + fields: fields{ + ServiceName: ptr("svc"), + Env: ptr("dev"), + }, + want: []corev1.EnvVar{ + { + Name: "DD_SERVICE", + Value: "svc", + }, + { + Name: "DD_ENV", + Value: "dev", + }, + }, + }, + { + name: "empty", + fields: fields{}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lc := LibConfig{ + ServiceName: tt.fields.ServiceName, + Env: tt.fields.Env, + Tracing: tt.fields.Tracing, + LogInjection: tt.fields.LogInjection, + HealthMetrics: tt.fields.HealthMetrics, + RuntimeMetrics: tt.fields.RuntimeMetrics, + TracingSamplingRate: tt.fields.TracingSamplingRate, + TracingRateLimit: tt.fields.TracingRateLimit, + TracingTags: tt.fields.TracingTags, + + /* + TracingServiceMapping: tt.fields.TracingServiceMapping, + TracingAgentTimeout: tt.fields.TracingAgentTimeout, + TracingHeaderTags: tt.fields.TracingHeaderTags, + TracingPartialFlushMinSpans: tt.fields.TracingPartialFlushMinSpans, + TracingDebug: tt.fields.TracingDebug, + TracingLogLevel: tt.fields.TracingLogLevel, + TracingMethods: tt.fields.TracingMethods, + TracingPropagationStyleInject: tt.fields.TracingPropagationStyleInject, + TracingPropagationStyleExtract: tt.fields.TracingPropagationStyleExtract, + */ + } + require.EqualValues(t, tt.want, lc.ToEnvs()) + }) + } +} + +func ptr[T int | bool | string | float64](val T) *T { + return &val +} diff --git a/pkg/clusteragent/admission/patch/file_provider.go b/pkg/clusteragent/admission/patch/file_provider.go index 79edbacc9e94c..efa7b6ab6a73d 100644 --- a/pkg/clusteragent/admission/patch/file_provider.go +++ b/pkg/clusteragent/admission/patch/file_provider.go @@ -20,7 +20,6 @@ import ( type filePatchProvider struct { file string pollInterval time.Duration - isLeader func() bool subscribers map[TargetObjKind]chan PatchRequest lastSuccessfulRefresh time.Time clusterName string @@ -28,11 +27,10 @@ type filePatchProvider struct { var _ patchProvider = &filePatchProvider{} -func newfileProvider(isLeaderFunc func() bool, clusterName string) *filePatchProvider { +func newfileProvider(clusterName string) *filePatchProvider { return &filePatchProvider{ file: "/etc/datadog-agent/auto-instru.json", pollInterval: 15 * time.Second, - isLeader: isLeaderFunc, subscribers: make(map[TargetObjKind]chan PatchRequest), clusterName: clusterName, } @@ -61,10 +59,6 @@ func (fpp *filePatchProvider) start(stopCh <-chan struct{}) { } func (fpp *filePatchProvider) refresh() error { - if !fpp.isLeader() { - log.Debug("Not leader, skipping") - return nil - } requests, err := fpp.poll() if err != nil { return err diff --git a/pkg/clusteragent/admission/patch/patch_request_test.go b/pkg/clusteragent/admission/patch/patch_request_test.go new file mode 100644 index 0000000000000..df972089db4c8 --- /dev/null +++ b/pkg/clusteragent/admission/patch/patch_request_test.go @@ -0,0 +1,93 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver +// +build kubeapiserver + +package patch + +import ( + "testing" + + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" + "github.com/stretchr/testify/require" +) + +func TestPatchRequestValidate(t *testing.T) { + tests := []struct { + name string + LibConfig common.LibConfig + K8sTarget K8sTarget + clusterName string + valid bool + }{ + { + name: "valid", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "cluster", Kind: "deployment", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: true, + }, + { + name: "empty version", + LibConfig: common.LibConfig{Language: "lang"}, + K8sTarget: K8sTarget{Cluster: "cluster", Kind: "deployment", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "empty language", + LibConfig: common.LibConfig{Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "cluster", Kind: "deployment", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "empty cluster", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Kind: "deployment", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "wrong cluster", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "wrong-cluster", Kind: "deployment", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "empty kind", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "cluster", Name: "name", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "empty name", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "cluster", Kind: "deployment", Namespace: "ns"}, + clusterName: "cluster", + valid: false, + }, + { + name: "empty namesapce", + LibConfig: common.LibConfig{Language: "lang", Version: "latest"}, + K8sTarget: K8sTarget{Cluster: "cluster", Kind: "deployment", Name: "name"}, + clusterName: "cluster", + valid: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pr := PatchRequest{ + LibConfig: tt.LibConfig, + K8sTarget: tt.K8sTarget, + } + err := pr.Validate(tt.clusterName) + require.True(t, (err == nil) == tt.valid) + }) + } +} diff --git a/pkg/clusteragent/admission/patch/provider.go b/pkg/clusteragent/admission/patch/provider.go index b8b21e463f240..43fcef4589bb6 100644 --- a/pkg/clusteragent/admission/patch/provider.go +++ b/pkg/clusteragent/admission/patch/provider.go @@ -20,13 +20,13 @@ type patchProvider interface { subscribe(kind TargetObjKind) chan PatchRequest } -func newPatchProvider(rcClient *remote.Client, isLeaderFunc func() bool, clusterName string) (patchProvider, error) { +func newPatchProvider(rcClient *remote.Client, clusterName string) (patchProvider, error) { if config.Datadog.GetBool("remote_configuration.enabled") { - return newRemoteConfigProvider(rcClient, isLeaderFunc, clusterName) + return newRemoteConfigProvider(rcClient, clusterName) } if config.Datadog.GetBool("admission_controller.auto_instrumentation.patcher.fallback_to_file_provider") { // Use the file config provider for e2e testing only (it replaces RC as a source of configs) - return newfileProvider(isLeaderFunc, clusterName), nil + return newfileProvider(clusterName), nil } return nil, errors.New("remote config is disabled") } diff --git a/pkg/clusteragent/admission/patch/rc_provider.go b/pkg/clusteragent/admission/patch/rc_provider.go index 10fc4b67663b9..103b0b8d077a0 100644 --- a/pkg/clusteragent/admission/patch/rc_provider.go +++ b/pkg/clusteragent/admission/patch/rc_provider.go @@ -20,20 +20,18 @@ import ( // remoteConfigProvider consumes tracing configs from RC and delivers them to the patcher type remoteConfigProvider struct { client *remote.Client - isLeader func() bool subscribers map[TargetObjKind]chan PatchRequest clusterName string } var _ patchProvider = &remoteConfigProvider{} -func newRemoteConfigProvider(client *remote.Client, isLeaderFunc func() bool, clusterName string) (*remoteConfigProvider, error) { +func newRemoteConfigProvider(client *remote.Client, clusterName string) (*remoteConfigProvider, error) { if client == nil { return nil, errors.New("remote config client not initialized") } return &remoteConfigProvider{ client: client, - isLeader: isLeaderFunc, subscribers: make(map[TargetObjKind]chan PatchRequest), clusterName: clusterName, }, nil diff --git a/pkg/clusteragent/admission/patch/rc_provider_test.go b/pkg/clusteragent/admission/patch/rc_provider_test.go new file mode 100644 index 0000000000000..bf0a26cfd72e9 --- /dev/null +++ b/pkg/clusteragent/admission/patch/rc_provider_test.go @@ -0,0 +1,64 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver +// +build kubeapiserver + +package patch + +import ( + "fmt" + "testing" + + "github.com/DataDog/datadog-agent/pkg/config/remote" + "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" + "github.com/stretchr/testify/require" +) + +func TestProcess(t *testing.T) { + genConfig := func(cluster, kind string) []byte { + base := ` +{ + "id": "17945471932432318983", + "revision": 1673513604823158800, + "schema_version": "v1.0.0", + "action": "enable", + "lib_config": { + "library_language": "java", + "library_version": "latest" + }, + "k8s_target": { + "cluster": "%s", + "kind": "%s", + "name": "my-java-app", + "namespace": "default" + } +} +` + return []byte(fmt.Sprintf(base, cluster, kind)) + } + rcp, err := newRemoteConfigProvider(&remote.Client{}, "dev") + require.NoError(t, err) + notifs := rcp.subscribe(KindDeployment) + in := map[string]state.APMTracingConfig{ + "path1": {Config: genConfig("dev", "deployment")}, // valid config + "path2": {Config: []byte("invalid")}, // invalid json + "path3": {Config: genConfig("dev", "wrong")}, // kind mismatch + "path4": {Config: genConfig("wrong", "deployment")}, // cluster mismatch + } + rcp.process(in) + require.Len(t, notifs, 1) + pr := <-notifs + require.Equal(t, "17945471932432318983", pr.ID) + require.Equal(t, int64(1673513604823158800), pr.Revision) + require.Equal(t, "v1.0.0", pr.SchemaVersion) + require.Equal(t, "java", pr.LibConfig.Language) + require.Equal(t, "latest", pr.LibConfig.Version) + require.Equal(t, "dev", pr.K8sTarget.Cluster) + require.Equal(t, KindDeployment, pr.K8sTarget.Kind) + require.Equal(t, "my-java-app", pr.K8sTarget.Name) + require.Equal(t, "default", pr.K8sTarget.Namespace) + require.Len(t, notifs, 0) +} diff --git a/pkg/clusteragent/admission/patch/start.go b/pkg/clusteragent/admission/patch/start.go index 29a246e27b072..b3bef5321931b 100644 --- a/pkg/clusteragent/admission/patch/start.go +++ b/pkg/clusteragent/admission/patch/start.go @@ -27,7 +27,7 @@ type ControllerContext struct { // StartControllers starts the patch controllers func StartControllers(ctx ControllerContext) error { log.Info("Starting patch controllers") - provider, err := newPatchProvider(ctx.RcClient, ctx.IsLeaderFunc, ctx.ClusterName) + provider, err := newPatchProvider(ctx.RcClient, ctx.ClusterName) if err != nil { return err }