diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4aeee98ec3dbe2..960a32659e6fb9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -776,6 +776,7 @@ /test/new-e2e/tests/windows @DataDog/windows-products @DataDog/windows-products /test/new-e2e/tests/apm @DataDog/agent-apm /test/new-e2e/tests/remote-config @DataDog/remote-config +/test/new-e2e/tests/ssi @DataDog/injection-platform /test/new-e2e/tests/fleet @DataDog/fleet @DataDog/windows-products /test/new-e2e/tests/installer @DataDog/fleet @DataDog/windows-products /test/new-e2e/tests/installer/script @DataDog/fleet @DataDog/data-jobs-monitoring diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b0c2fb0b57903..9a3aeb4894ba5c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -968,6 +968,22 @@ workflow: compare_to: $COMPARE_TO_BRANCH when: on_success +.on_ssi_or_e2e_changes: + - !reference [.on_e2e_main_release_or_rc] + - changes: + paths: + - pkg/clusteragent/admission/mutate/autoinstrumentation/**/* + - pkg/clusteragent/admission/mutate/common/**/* + - pkg/clusteragent/admission/mutate/config/**/* + - pkg/clusteragent/admission/mutate/tagsfromlabels/**/* + - pkg/clusteragent/admission/common/**/* + - pkg/clusteragent/admission/controllers/webhook/**/* + - comp/core/workloadmeta/collectors/internal/kubeapiserver/**/* + - comp/languagedetection/**/* + - test/new-e2e/tests/ssi/**/* + compare_to: $COMPARE_TO_BRANCH + when: on_success + .on_windows_service_or_e2e_changes: - !reference [.on_e2e_main_release_or_rc] - changes: diff --git a/.gitlab/e2e/e2e.yml b/.gitlab/e2e/e2e.yml index 56a3a65ee5b230..e0fcc25fc71479 100644 --- a/.gitlab/e2e/e2e.yml +++ b/.gitlab/e2e/e2e.yml @@ -955,6 +955,20 @@ new-e2e-otel: TEAM: otel ON_NIGHTLY_FIPS: "true" +new-e2e-ssi: + extends: .new_e2e_template + rules: + - !reference [.on_ssi_or_e2e_changes] + - !reference [.manual] + needs: + - !reference [.needs_new_e2e_template] + - qa_dca + - qa_agent + - qa_agent_full + variables: + TARGETS: ./tests/ssi + TEAM: injection-platform + .new-e2e_package_signing: variables: TARGETS: ./tests/agent-platform/package-signing diff --git a/test/e2e-framework/scenarios/aws/kindvm/run.go b/test/e2e-framework/scenarios/aws/kindvm/run.go index 27527b8e6a5cdf..c5e729ef09f2f6 100644 --- a/test/e2e-framework/scenarios/aws/kindvm/run.go +++ b/test/e2e-framework/scenarios/aws/kindvm/run.go @@ -185,6 +185,7 @@ func RunWithEnv(ctx *pulumi.Context, awsEnv resAws.Environment, env *environment if err != nil { return err } + dependsOnDDAgent = utils.PulumiDependsOn(operatorComp) } if params.deployDogstatsd { @@ -236,12 +237,6 @@ func RunWithEnv(ctx *pulumi.Context, awsEnv resAws.Environment, env *environment if _, err := cpustress.K8sAppDefinition(&awsEnv, kubeProvider, "workload-cpustress"); err != nil { return err } - for _, appFunc := range params.depWorkloadAppFuncs { - _, err := appFunc(&awsEnv, kubeProvider, dependsOnDDAgent) - if err != nil { - return err - } - } } if params.deployArgoRollout { @@ -250,6 +245,16 @@ func RunWithEnv(ctx *pulumi.Context, awsEnv resAws.Environment, env *environment } } } + + if dependsOnDDAgent != nil { + for _, appFunc := range params.depWorkloadAppFuncs { + _, err := appFunc(&awsEnv, kubeProvider, dependsOnDDAgent) + if err != nil { + return err + } + } + } + for _, appFunc := range params.workloadAppFuncs { _, err := appFunc(&awsEnv, kubeProvider) if err != nil { diff --git a/test/new-e2e/go.mod b/test/new-e2e/go.mod index fd397f2b0aa0ef..020a42cc5343ee 100644 --- a/test/new-e2e/go.mod +++ b/test/new-e2e/go.mod @@ -220,6 +220,7 @@ require ( github.com/DataDog/datadog-agent/comp/otelcol/ddflareextension/types v0.65.0-devel github.com/DataDog/datadog-agent/pkg/metrics v0.73.0-rc.9 github.com/DataDog/datadog-agent/pkg/networkpath/payload v0.0.0-20250128160050-7ac9ccd58c07 + github.com/DataDog/datadog-agent/pkg/ssi/testutils v0.0.0-00010101000000-000000000000 github.com/DataDog/datadog-agent/pkg/trace v0.73.0-rc.9 github.com/DataDog/datadog-go/v5 v5.8.2 github.com/DataDog/dd-trace-go/v2 v2.4.1 diff --git a/test/new-e2e/tests/ssi/doc.go b/test/new-e2e/tests/ssi/doc.go new file mode 100644 index 00000000000000..7c11212b62b4e6 --- /dev/null +++ b/test/new-e2e/tests/ssi/doc.go @@ -0,0 +1,8 @@ +// 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. + +// Package ssi provides end to end tests for Single Step Instrumentation. It focuses on user configuration and how that +// impacts targeting in Kubernetes. +package ssi diff --git a/test/new-e2e/tests/ssi/local_sdk_injection_test.go b/test/new-e2e/tests/ssi/local_sdk_injection_test.go new file mode 100644 index 00000000000000..0976598d376b48 --- /dev/null +++ b/test/new-e2e/tests/ssi/local_sdk_injection_test.go @@ -0,0 +1,98 @@ +// 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. +package ssi + +import ( + "os" + "testing" + "time" + + "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/pkg/ssi/testutils" + "github.com/DataDog/datadog-agent/test/e2e-framework/common/config" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/apps/singlestep" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/kubernetesagentparams" + compkube "github.com/DataDog/datadog-agent/test/e2e-framework/components/kubernetes" + "github.com/DataDog/datadog-agent/test/e2e-framework/scenarios/aws/kindvm" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/e2e" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/environments" + provkindvm "github.com/DataDog/datadog-agent/test/e2e-framework/testing/provisioners/aws/kubernetes/kindvm" +) + +type localSDKInjectionSuite struct { + e2e.BaseSuite[environments.Kubernetes] +} + +func TestLocalSDKInjectionSuite(t *testing.T) { + helmValues, err := os.ReadFile("testdata/local_sdk_injection.yaml") + require.NoError(t, err, "Could not open helm values file for test") + e2e.Run(t, &localSDKInjectionSuite{}, e2e.WithProvisioner(provkindvm.Provisioner( + provkindvm.WithRunOptions( + kindvm.WithAgentDependentWorkloadApp(func(e config.Env, kubeProvider *kubernetes.Provider, dependsOnAgent pulumi.ResourceOption) (*compkube.Workload, error) { + return singlestep.Scenario(e, kubeProvider, "local-sdk-injection", []singlestep.Namespace{ + { + Name: "application", + Apps: []singlestep.App{ + { + Name: DefaultAppName, + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + PodLabels: map[string]string{ + "admission.datadoghq.com/enabled": "true", + "tags.datadoghq.com/service": DefaultAppName, + }, + PodAnnotations: map[string]string{ + "admission.datadoghq.com/python-lib.version": "v3.18.1", + }, + }, + { + Name: "expect-no-injection", + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + }, + }, + }, + }, dependsOnAgent) + }), + kindvm.WithAgentOptions(kubernetesagentparams.WithHelmValues(string(helmValues))), + ), + ))) +} + +func (v *localSDKInjectionSuite) TestClusterAgentInstalled() { + FindPodInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "datadog", "cluster-agent") +} + +func (v *localSDKInjectionSuite) TestExpectInjection() { + // Get clients. + intake := v.Env().FakeIntake.Client() + k8s := v.Env().KubernetesCluster.Client() + + // Ensure the pod was injected. + pod := FindPodInNamespace(v.T(), k8s, "application", DefaultAppName) + podValidator := testutils.NewPodValidator(pod) + podValidator.RequireInjection(v.T(), DefaultExpectedContainers) + podValidator.RequireLibraryVersions(v.T(), map[string]string{ + "python": "v3.18.1", + }) + podValidator.RequireInjectorVersion(v.T(), "0.52.0") + + // Ensure the service has traces. + require.Eventually(v.T(), func() bool { + traces := FindTracesForService(v.T(), intake, DefaultAppName) + return len(traces) != 0 + }, 1*time.Minute, 10*time.Second, "did not find any traces at intake for DD_SERVICE %s", DefaultAppName) +} + +func (v *localSDKInjectionSuite) TestExpectNoInjection() { + pod := FindPodInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "application", "expect-no-injection") + podValidator := testutils.NewPodValidator(pod) + podValidator.RequireNoInjection(v.T()) +} diff --git a/test/new-e2e/tests/ssi/namespace_selection_test.go b/test/new-e2e/tests/ssi/namespace_selection_test.go new file mode 100644 index 00000000000000..83bb42ff91343d --- /dev/null +++ b/test/new-e2e/tests/ssi/namespace_selection_test.go @@ -0,0 +1,99 @@ +// 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. + +package ssi + +import ( + "os" + "testing" + "time" + + "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/pkg/ssi/testutils" + "github.com/DataDog/datadog-agent/test/e2e-framework/common/config" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/apps/singlestep" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/kubernetesagentparams" + compkube "github.com/DataDog/datadog-agent/test/e2e-framework/components/kubernetes" + "github.com/DataDog/datadog-agent/test/e2e-framework/scenarios/aws/kindvm" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/e2e" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/environments" + provkindvm "github.com/DataDog/datadog-agent/test/e2e-framework/testing/provisioners/aws/kubernetes/kindvm" +) + +type namespaceSelectionSuite struct { + e2e.BaseSuite[environments.Kubernetes] +} + +func TestNamespaceSelectionSuite(t *testing.T) { + helmValues, err := os.ReadFile("testdata/namespace_selection.yaml") + require.NoError(t, err, "Could not open helm values file for test") + e2e.Run(t, &namespaceSelectionSuite{}, e2e.WithProvisioner(provkindvm.Provisioner( + provkindvm.WithRunOptions( + kindvm.WithAgentDependentWorkloadApp(func(e config.Env, kubeProvider *kubernetes.Provider, dependsOnAgent pulumi.ResourceOption) (*compkube.Workload, error) { + return singlestep.Scenario(e, kubeProvider, "namespace-selection", []singlestep.Namespace{ + { + Name: "expect-injection", + Apps: []singlestep.App{ + { + Name: DefaultAppName, + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + }, + }, + }, + { + Name: "expect-no-injection", + Apps: []singlestep.App{ + { + Name: DefaultAppName, + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + }, + }, + }, + }, dependsOnAgent) + }), + kindvm.WithAgentOptions(kubernetesagentparams.WithHelmValues(string(helmValues))), + ), + ))) +} + +func (v *namespaceSelectionSuite) TestClusterAgentInstalled() { + FindPodInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "datadog", "cluster-agent") +} + +func (v *namespaceSelectionSuite) TestExpectInjection() { + // Get clients. + intake := v.Env().FakeIntake.Client() + k8s := v.Env().KubernetesCluster.Client() + + // Ensure the pod was injected. + pod := FindPodInNamespace(v.T(), k8s, "expect-injection", DefaultAppName) + podValidator := testutils.NewPodValidator(pod) + podValidator.RequireInjection(v.T(), DefaultExpectedContainers) + podValidator.RequireLibraryVersions(v.T(), map[string]string{ + "python": "v3.18.1", + }) + podValidator.RequireInjectorVersion(v.T(), "0.52.0") + + // Ensure the service has traces. + require.Eventually(v.T(), func() bool { + traces := FindTracesForService(v.T(), intake, DefaultAppName) + return len(traces) != 0 + }, 1*time.Minute, 10*time.Second, "did not find any traces at intake for DD_SERVICE %s", DefaultAppName) +} + +func (v *namespaceSelectionSuite) TestExpectNoInjection() { + pods := GetPodsInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "expect-no-injection") + for _, pod := range pods { + podValidator := testutils.NewPodValidator(&pod) + podValidator.RequireNoInjection(v.T()) + } +} diff --git a/test/new-e2e/tests/ssi/testdata/local_sdk_injection.yaml b/test/new-e2e/tests/ssi/testdata/local_sdk_injection.yaml new file mode 100644 index 00000000000000..71633ade32eafc --- /dev/null +++ b/test/new-e2e/tests/ssi/testdata/local_sdk_injection.yaml @@ -0,0 +1,11 @@ +--- +clusterAgent: + admissionController: + configMode: "hostip" +datadog: + apm: + instrumentation: + enabled: false + enabledNamespaces: [] + injector: + imageTag: "0.52.0" diff --git a/test/new-e2e/tests/ssi/testdata/namespace_selection.yaml b/test/new-e2e/tests/ssi/testdata/namespace_selection.yaml new file mode 100644 index 00000000000000..714f83ca0efbd3 --- /dev/null +++ b/test/new-e2e/tests/ssi/testdata/namespace_selection.yaml @@ -0,0 +1,14 @@ +--- +clusterAgent: + admissionController: + configMode: "hostip" +datadog: + apm: + instrumentation: + enabled: true + injector: + imageTag: "0.52.0" + enabledNamespaces: + - "expect-injection" + libVersions: + python: "v3.18.1" diff --git a/test/new-e2e/tests/ssi/testdata/workload_selection.yaml b/test/new-e2e/tests/ssi/testdata/workload_selection.yaml new file mode 100644 index 00000000000000..dc8b3cf06c4c50 --- /dev/null +++ b/test/new-e2e/tests/ssi/testdata/workload_selection.yaml @@ -0,0 +1,24 @@ +--- +clusterAgent: + admissionController: + configMode: "hostip" +datadog: + apm: + instrumentation: + enabled: true + injector: + imageTag: "0.52.0" + enabledNamespaces: [] + targets: + - name: "python-apps" + podSelector: + matchLabels: + language: "python" + namespaceSelector: + matchLabels: + injection: "yes" + ddTraceVersions: + python: "v3.18.1" + ddTraceConfigs: + - name: "DD_PROFILING_ENABLED" + value: "true" diff --git a/test/new-e2e/tests/ssi/utils.go b/test/new-e2e/tests/ssi/utils.go new file mode 100644 index 00000000000000..86fde94432b029 --- /dev/null +++ b/test/new-e2e/tests/ssi/utils.go @@ -0,0 +1,66 @@ +// 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. + +package ssi + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubeClient "k8s.io/client-go/kubernetes" + + "github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace" + fakeintake "github.com/DataDog/datadog-agent/test/fakeintake/client" +) + +const DefaultAppName = "test-app" + +var DefaultExpectedContainers = []string{DefaultAppName} + +func GetPodsInNamespace(t *testing.T, client kubeClient.Interface, namespace string) []corev1.Pod { + res, err := client.CoreV1().Pods(namespace).List(context.Background(), v1.ListOptions{}) + require.NoError(t, err, "received an error fetching pods") + return res.Items +} + +func FindPodInNamespace(t *testing.T, client kubeClient.Interface, namespace string, appName string) *corev1.Pod { + pods := GetPodsInNamespace(t, client, namespace) + for _, pod := range pods { + if strings.Contains(pod.Name, appName) { + return &pod + } + } + require.NoError(t, fmt.Errorf("did not find pod with app name %s in namespace %s", appName, namespace)) + return nil +} + +func FindTracesForService(t *testing.T, intake *fakeintake.Client, serviceName string) []*trace.TracerPayload { + filtered := []*trace.TracerPayload{} + serviceNameTag := "service:" + serviceName + + payloads, err := intake.GetTraces() + require.NoError(t, err, "got error fetching traces from fake intake") + for _, payload := range payloads { + for _, trace := range payload.TracerPayloads { + extracted, ok := trace.Tags["_dd.tags.container"] + if !ok { + continue + } + tags := strings.Split(extracted, ",") + for _, tag := range tags { + if tag == serviceNameTag { + filtered = append(filtered, trace) + } + } + } + } + + return filtered +} diff --git a/test/new-e2e/tests/ssi/workload_selection_test.go b/test/new-e2e/tests/ssi/workload_selection_test.go new file mode 100644 index 00000000000000..23fbe15bcefd2b --- /dev/null +++ b/test/new-e2e/tests/ssi/workload_selection_test.go @@ -0,0 +1,98 @@ +// 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. + +package ssi + +import ( + "os" + "testing" + "time" + + "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/pkg/ssi/testutils" + "github.com/DataDog/datadog-agent/test/e2e-framework/common/config" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/apps/singlestep" + "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/kubernetesagentparams" + compkube "github.com/DataDog/datadog-agent/test/e2e-framework/components/kubernetes" + "github.com/DataDog/datadog-agent/test/e2e-framework/scenarios/aws/kindvm" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/e2e" + "github.com/DataDog/datadog-agent/test/e2e-framework/testing/environments" + provkindvm "github.com/DataDog/datadog-agent/test/e2e-framework/testing/provisioners/aws/kubernetes/kindvm" +) + +type workloadSelectionSuite struct { + e2e.BaseSuite[environments.Kubernetes] +} + +func TestWorkloadSelectionSuite(t *testing.T) { + helmValues, err := os.ReadFile("testdata/workload_selection.yaml") + require.NoError(t, err, "Could not open helm values file for test") + e2e.Run(t, &workloadSelectionSuite{}, e2e.WithProvisioner(provkindvm.Provisioner( + provkindvm.WithRunOptions( + kindvm.WithAgentDependentWorkloadApp(func(e config.Env, kubeProvider *kubernetes.Provider, dependsOnAgent pulumi.ResourceOption) (*compkube.Workload, error) { + return singlestep.Scenario(e, kubeProvider, "workload-selection", []singlestep.Namespace{ + { + Name: "targeted-namespace", + Labels: map[string]string{ + "injection": "yes", + }, + Apps: []singlestep.App{ + { + Name: DefaultAppName, + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + PodLabels: map[string]string{ + "language": "python", + }, + }, + { + Name: "expect-no-injection", + Image: "gcr.io/datadoghq/injector-dev/python", + Version: "d425e7df", + Port: 8080, + }, + }, + }, + }, dependsOnAgent) + }), + kindvm.WithAgentOptions(kubernetesagentparams.WithHelmValues(string(helmValues))), + ), + ))) +} + +func (v *workloadSelectionSuite) TestClusterAgentInstalled() { + FindPodInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "datadog", "cluster-agent") +} + +func (v *workloadSelectionSuite) TestExpectInjection() { + // Get clients. + intake := v.Env().FakeIntake.Client() + k8s := v.Env().KubernetesCluster.Client() + + // Ensure the pod was injected. + pod := FindPodInNamespace(v.T(), k8s, "targeted-namespace", DefaultAppName) + podValidator := testutils.NewPodValidator(pod) + podValidator.RequireInjection(v.T(), DefaultExpectedContainers) + podValidator.RequireLibraryVersions(v.T(), map[string]string{ + "python": "v3.18.1", + }) + podValidator.RequireInjectorVersion(v.T(), "0.52.0") + + // Ensure the service has traces. + require.Eventually(v.T(), func() bool { + traces := FindTracesForService(v.T(), intake, DefaultAppName) + return len(traces) != 0 + }, 1*time.Minute, 10*time.Second, "did not find any traces at intake for DD_SERVICE %s", DefaultAppName) +} + +func (v *workloadSelectionSuite) TestExpectNoInjection() { + pod := FindPodInNamespace(v.T(), v.Env().KubernetesCluster.Client(), "targeted-namespace", "expect-no-injection") + podValidator := testutils.NewPodValidator(pod) + podValidator.RequireNoInjection(v.T()) +}