From e9999a271012e97a168ae4709eeb105eafc3b94f Mon Sep 17 00:00:00 2001 From: Nicolas Schweitzer Date: Tue, 19 Nov 2024 13:40:04 +0100 Subject: [PATCH] sync container tests from main --- test/fakeintake/client/client.go | 47 +- test/new-e2e/tests/containers/base_test.go | 153 +++++- .../tests/containers/dump_cluster_state.go | 62 ++- test/new-e2e/tests/containers/ecs_test.go | 115 ++++- test/new-e2e/tests/containers/eks_test.go | 39 +- test/new-e2e/tests/containers/k8s_test.go | 484 ++++++++++++++++-- test/new-e2e/tests/containers/kindvm_test.go | 39 +- test/new-e2e/tests/containers/main_test.go | 8 +- test/new-e2e/tests/containers/utils.go | 12 +- test/new-e2e/tests/containers/utils_test.go | 22 +- 10 files changed, 894 insertions(+), 87 deletions(-) diff --git a/test/fakeintake/client/client.go b/test/fakeintake/client/client.go index 00562e56830b82..d14f034701e01f 100644 --- a/test/fakeintake/client/client.go +++ b/test/fakeintake/client/client.go @@ -80,7 +80,7 @@ const ( // ErrNoFlareAvailable is returned when no flare is available var ErrNoFlareAvailable = errors.New("no flare available") -//nolint:revive // TODO(APL) Fix revive linter +// Client tbd type Client struct { fakeIntakeURL string @@ -355,6 +355,39 @@ func (c *Client) FilterMetrics(name string, options ...MatchOpt[*aggregator.Metr return filteredMetrics, nil } +// filterPayload returns payloads matching any [MatchOpt](#MatchOpt) options +func filterPayload[T aggregator.PayloadItem](payloads []T, options ...MatchOpt[T]) ([]T, error) { + // apply filters one after the other + filteredPayloads := make([]T, 0, len(payloads)) + for _, payload := range payloads { + matchCount := 0 + for _, matchOpt := range options { + isMatch, err := matchOpt(payload) + if err != nil { + return nil, err + } + if !isMatch { + break + } + matchCount++ + } + if matchCount == len(options) { + filteredPayloads = append(filteredPayloads, payload) + } + } + return filteredPayloads, nil +} + +// FilterCheckRuns fetches fakeintake on `/api/v1/check_run` endpoint and returns +// metrics matching `name` and any [MatchOpt](#MatchOpt) options +func (c *Client) FilterCheckRuns(name string, options ...MatchOpt[*aggregator.CheckRun]) ([]*aggregator.CheckRun, error) { + checkRuns, err := c.GetCheckRun(name) + if err != nil { + return nil, err + } + return filterPayload(checkRuns, options...) +} + // WithTags filters by `tags` func WithTags[P aggregator.PayloadItem](tags []string) MatchOpt[P] { return func(payload P) (bool, error) { @@ -401,9 +434,7 @@ func WithMetricValueLowerThan(maxValue float64) MatchOpt[*aggregator.MetricSerie } } -// WithMetricValueLowerThan filters metrics with values higher than `minValue` -// -//nolint:revive // TODO(APL) Fix revive linter +// WithMetricValueHigherThan filters metrics with values higher than `minValue` func WithMetricValueHigherThan(minValue float64) MatchOpt[*aggregator.MetricSeries] { return func(metric *aggregator.MetricSeries) (bool, error) { for _, point := range metric.Points { @@ -424,10 +455,8 @@ func (c *Client) getLog(service string) ([]*aggregator.Log, error) { return c.logAggregator.GetPayloadsByName(service), nil } -// GetLogNames fetches fakeintake on `/api/v2/logs` endpoint and returns +// GetLogServiceNames fetches fakeintake on `/api/v2/logs` endpoint and returns // all received log service names -// -//nolint:revive // TODO(APL) Fix revive linter func (c *Client) GetLogServiceNames() ([]string, error) { err := c.getLogs() if err != nil { @@ -500,10 +529,8 @@ func (c *Client) GetCheckRunNames() ([]string, error) { return c.checkRunAggregator.GetNames(), nil } -// FilterLogs fetches fakeintake on `/api/v1/check_run` endpoint, unpackage payloads and returns +// GetCheckRun fetches fakeintake on `/api/v1/check_run` endpoint, unpackage payloads and returns // checks matching `name` -// -//nolint:revive // TODO(APL) Fix revive linter func (c *Client) GetCheckRun(name string) ([]*aggregator.CheckRun, error) { err := c.getCheckRuns() if err != nil { diff --git a/test/new-e2e/tests/containers/base_test.go b/test/new-e2e/tests/containers/base_test.go index da16d5b9ac21fd..84bce8ea70197c 100644 --- a/test/new-e2e/tests/containers/base_test.go +++ b/test/new-e2e/tests/containers/base_test.go @@ -19,6 +19,7 @@ import ( "gopkg.in/zorkian/go-datadog-api.v2" "github.com/DataDog/agent-payload/v5/gogen" + "github.com/DataDog/datadog-agent/pkg/util/pointer" "github.com/DataDog/datadog-agent/test/fakeintake/aggregator" fakeintake "github.com/DataDog/datadog-agent/test/fakeintake/client" @@ -50,9 +51,18 @@ func (suite *baseSuite) TearDownSuite() { suite.endTime = time.Now() } +func (suite *baseSuite) BeforeTest(suiteName, testName string) { + suite.T().Logf("START %s/%s %s", suiteName, testName, time.Now()) +} + +func (suite *baseSuite) AfterTest(suiteName, testName string) { + suite.T().Logf("FINISH %s/%s %s", suiteName, testName, time.Now()) +} + type testMetricArgs struct { - Filter testMetricFilterArgs - Expect testMetricExpectArgs + Filter testMetricFilterArgs + Expect testMetricExpectArgs + Optional testMetricExpectArgs } type testMetricFilterArgs struct { @@ -97,6 +107,11 @@ func (suite *baseSuite) testMetric(args *testMetricArgs) { expectedTags = lo.Map(*args.Expect.Tags, func(tag string, _ int) *regexp.Regexp { return regexp.MustCompile(tag) }) } + var optionalTags []*regexp.Regexp + if args.Optional.Tags != nil { + optionalTags = lo.Map(*args.Optional.Tags, func(tag string, _ int) *regexp.Regexp { return regexp.MustCompile(tag) }) + } + sendEvent := func(alertType, text string) { formattedArgs, err := yaml.Marshal(args) suite.Require().NoError(err) @@ -156,13 +171,13 @@ func (suite *baseSuite) testMetric(args *testMetricArgs) { } }() - filterTags := lo.Map(args.Filter.Tags, func(tag string, _ int) *regexp.Regexp { + regexTags := lo.Map(args.Filter.Tags, func(tag string, _ int) *regexp.Regexp { return regexp.MustCompile(tag) }) metrics, err := suite.Fakeintake.FilterMetrics( args.Filter.Name, - fakeintake.WithMatchingTags[*aggregator.MetricSeries](filterTags), + fakeintake.WithMatchingTags[*aggregator.MetricSeries](regexTags), ) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to query fake intake") { @@ -175,7 +190,7 @@ func (suite *baseSuite) testMetric(args *testMetricArgs) { // Check tags if expectedTags != nil { - err := assertTags(metrics[len(metrics)-1].GetTags(), expectedTags, args.Expect.AcceptUnexpectedTags) + err := assertTags(metrics[len(metrics)-1].GetTags(), expectedTags, optionalTags, args.Expect.AcceptUnexpectedTags) assert.NoErrorf(c, err, "Tags mismatch on `%s`", prettyMetricQuery) } @@ -285,9 +300,13 @@ func (suite *baseSuite) testLog(args *testLogArgs) { } }() + regexTags := lo.Map(args.Filter.Tags, func(tag string, _ int) *regexp.Regexp { + return regexp.MustCompile(tag) + }) + logs, err := suite.Fakeintake.FilterLogs( args.Filter.Service, - fakeintake.WithTags[*aggregator.Log](args.Filter.Tags), + fakeintake.WithMatchingTags[*aggregator.Log](regexTags), ) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to query fake intake") { @@ -300,7 +319,7 @@ func (suite *baseSuite) testLog(args *testLogArgs) { // Check tags if expectedTags != nil { - err := assertTags(logs[len(logs)-1].GetTags(), expectedTags, false) + err := assertTags(logs[len(logs)-1].GetTags(), expectedTags, []*regexp.Regexp{}, false) assert.NoErrorf(c, err, "Tags mismatch on `%s`", prettyLogQuery) } @@ -316,3 +335,123 @@ func (suite *baseSuite) testLog(args *testLogArgs) { }, 2*time.Minute, 10*time.Second, "Failed finding `%s` with proper tags and message", prettyLogQuery) }) } + +type testCheckRunArgs struct { + Filter testCheckRunFilterArgs + Expect testCheckRunExpectArgs + Optional testCheckRunExpectArgs +} + +type testCheckRunFilterArgs struct { + Name string + // Tags are used to filter the checkRun + // Regexes are supported + Tags []string +} + +type testCheckRunExpectArgs struct { + // Tags are the tags expected to be present + // Regexes are supported + Tags *[]string + AcceptUnexpectedTags bool +} + +func (suite *baseSuite) testCheckRun(args *testCheckRunArgs) { + prettyCheckRunQuery := fmt.Sprintf("%s{%s}", args.Filter.Name, strings.Join(args.Filter.Tags, ",")) + + suite.Run("checkRun "+prettyCheckRunQuery, func() { + var expectedTags []*regexp.Regexp + if args.Expect.Tags != nil { + expectedTags = lo.Map(*args.Expect.Tags, func(tag string, _ int) *regexp.Regexp { return regexp.MustCompile(tag) }) + } + + var optionalTags []*regexp.Regexp + if args.Optional.Tags != nil { + optionalTags = lo.Map(*args.Optional.Tags, func(tag string, _ int) *regexp.Regexp { return regexp.MustCompile(tag) }) + } + + sendEvent := func(alertType, text string) { + formattedArgs, err := yaml.Marshal(args) + suite.Require().NoError(err) + + tags := lo.Map(args.Filter.Tags, func(tag string, _ int) string { + return "filter_tag_" + tag + }) + + if _, err := suite.datadogClient.PostEvent(&datadog.Event{ + Title: pointer.Ptr(fmt.Sprintf("testCheckRun %s", prettyCheckRunQuery)), + Text: pointer.Ptr(fmt.Sprintf(`%%%%%% +### Result + +`+"```"+` +%s +`+"```"+` + +### Query + +`+"```"+` +%s +`+"```"+` + %%%%%%`, text, formattedArgs)), + AlertType: &alertType, + Tags: append([]string{ + "app:agent-new-e2e-tests-containers", + "cluster_name:" + suite.clusterName, + "check_run:" + args.Filter.Name, + "test:" + suite.T().Name(), + }, tags...), + }); err != nil { + suite.T().Logf("Failed to post event: %s", err) + } + } + + defer func() { + if suite.T().Failed() { + sendEvent("error", fmt.Sprintf("Failed finding %s with proper tags and value", prettyCheckRunQuery)) + } else { + sendEvent("success", "All good!") + } + }() + + suite.EventuallyWithTf(func(collect *assert.CollectT) { + c := &myCollectT{ + CollectT: collect, + errors: []error{}, + } + // To enforce the use of myCollectT instead + collect = nil //nolint:ineffassign + + defer func() { + if len(c.errors) == 0 { + sendEvent("success", "All good!") + } else { + sendEvent("warning", errors.Join(c.errors...).Error()) + } + }() + + regexTags := lo.Map(args.Filter.Tags, func(tag string, _ int) *regexp.Regexp { + return regexp.MustCompile(tag) + }) + + checkRuns, err := suite.Fakeintake.FilterCheckRuns( + args.Filter.Name, + fakeintake.WithMatchingTags[*aggregator.CheckRun](regexTags), + ) + // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged + if !assert.NoErrorf(c, err, "Failed to query fake intake") { + return + } + // Can be replaced by require.NoEmptyf(…) once https://github.com/stretchr/testify/pull/1481 is merged + if !assert.NotEmptyf(c, checkRuns, "No `%s` checkRun yet", prettyCheckRunQuery) { + return + } + + // Check tags + if expectedTags != nil { + err := assertTags(checkRuns[len(checkRuns)-1].GetTags(), expectedTags, optionalTags, args.Expect.AcceptUnexpectedTags) + assert.NoErrorf(c, err, "Tags mismatch on `%s`", prettyCheckRunQuery) + } + + }, 2*time.Minute, 10*time.Second, "Failed finding `%s` with proper tags and value", prettyCheckRunQuery) + }) +} diff --git a/test/new-e2e/tests/containers/dump_cluster_state.go b/test/new-e2e/tests/containers/dump_cluster_state.go index 7e8a5d9f6ba59c..ddf546d889d093 100644 --- a/test/new-e2e/tests/containers/dump_cluster_state.go +++ b/test/new-e2e/tests/containers/dump_cluster_state.go @@ -25,8 +25,11 @@ import ( awsekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" kubectlget "k8s.io/kubectl/pkg/cmd/get" @@ -163,11 +166,18 @@ func dumpKindClusterState(ctx context.Context, name string) (ret string) { auth = append(auth, ssh.PublicKeys(signer)) } - sshClient, err := ssh.Dial("tcp", *instanceIP+":22", &ssh.ClientConfig{ - User: "ubuntu", - Auth: auth, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - }) + var sshClient *ssh.Client + err = nil + for _, user := range []string{"ec2-user", "ubuntu"} { + sshClient, err = ssh.Dial("tcp", *instanceIP+":22", &ssh.ClientConfig{ + User: user, + Auth: auth, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err == nil { + break + } + } if err != nil { fmt.Fprintf(&out, "Failed to dial SSH server %s: %v\n", *instanceIP, err) return @@ -286,4 +296,46 @@ func dumpK8sClusterState(ctx context.Context, kubeconfig *clientcmdapi.Config, o fmt.Fprintf(out, "Failed to execute Get command: %v\n", err) return } + + // Get the logs of containers that have restarted + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigFile.Name()) + if err != nil { + fmt.Fprintf(out, "Failed to build Kubernetes config: %v\n", err) + return + } + k8sClient, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Fprintf(out, "Failed to create Kubernetes client: %v\n", err) + return + } + + pods, err := k8sClient.CoreV1().Pods("").List(ctx, metav1.ListOptions{}) + if err != nil { + fmt.Fprintf(out, "Failed to list pods: %v\n", err) + return + } + + for _, pod := range pods.Items { + for _, containerStatus := range pod.Status.ContainerStatuses { + if containerStatus.RestartCount > 0 { + fmt.Fprintf(out, "\nLOGS FOR POD %s/%s CONTAINER %s:\n", pod.Namespace, pod.Name, containerStatus.Name) + logs, err := k8sClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{ + Container: containerStatus.Name, + Previous: true, + // TailLines: pointer.Ptr(int64(100)), + }).Stream(ctx) + if err != nil { + fmt.Fprintf(out, "Failed to get logs: %v\n", err) + continue + } + defer logs.Close() + + _, err = io.Copy(out, logs) + if err != nil { + fmt.Fprintf(out, "Failed to copy logs: %v\n", err) + continue + } + } + } + } } diff --git a/test/new-e2e/tests/containers/ecs_test.go b/test/new-e2e/tests/containers/ecs_test.go index 7d7a22acc27dad..9498aa9414deb6 100644 --- a/test/new-e2e/tests/containers/ecs_test.go +++ b/test/new-e2e/tests/containers/ecs_test.go @@ -63,7 +63,12 @@ func (suite *ecsSuite) SetupSuite() { "ddtestworkload:deploy": auto.ConfigValue{Value: "true"}, } - _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure(ctx, "ecs-cluster", ecs.Run, infra.WithConfigMap(stackConfig)) + _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure( + ctx, + "ecs-cluster", + ecs.Run, + infra.WithConfigMap(stackConfig), + ) suite.Require().NoError(err) fakeintake := &components.FakeIntake{} @@ -187,7 +192,7 @@ func (suite *ecsSuite) Test00UpAndRunning() { } } } - }, 5*time.Minute, 10*time.Second, "Not all tasks became ready in time.") + }, 15*time.Minute, 10*time.Second, "Not all tasks became ready in time.") }) } @@ -201,6 +206,7 @@ func (suite *ecsSuite) TestNginxECS() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-nginx-ec2-`, @@ -208,12 +214,14 @@ func (suite *ecsSuite) TestNginxECS() { `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:nginx$`, `^ecs_launch_type:ec2$`, - `^git.commit.sha:`, // org.opencontainers.image.revision docker image label + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-nginx-ec2$`, + `^git.commit.sha:`, // org.opencontainers.image.revision docker image label `^git.repository_url:https://github.com/DataDog/test-infra-definitions$`, // org.opencontainers.image.source docker image label `^image_id:sha256:`, `^image_name:ghcr.io/datadog/apps-nginx-server$`, `^image_tag:main$`, `^nginx_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, + `^region:us-east-1$`, `^short_image:apps-nginx-server$`, `^task_arn:`, `^task_family:.*-nginx-ec2$`, @@ -227,10 +235,11 @@ func (suite *ecsSuite) TestNginxECS() { suite.testLog(&testLogArgs{ Filter: testLogFilterArgs{ Service: "apps-nginx-server", - Tags: []string{"ecs_launch_type:ec2"}, + Tags: []string{"^ecs_launch_type:ec2$"}, }, Expect: testLogExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-nginx-ec2-`, @@ -238,11 +247,13 @@ func (suite *ecsSuite) TestNginxECS() { `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:nginx$`, `^ecs_launch_type:ec2$`, - `^git.commit.sha:`, // org.opencontainers.image.revision docker image label + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-nginx-ec2$`, + `^git.commit.sha:`, // org.opencontainers.image.revision docker image label `^git.repository_url:https://github.com/DataDog/test-infra-definitions$`, // org.opencontainers.image.source docker image label `^image_id:sha256:`, `^image_name:ghcr.io/datadog/apps-nginx-server$`, `^image_tag:main$`, + `^region:us-east-1$`, `^short_image:apps-nginx-server$`, `^task_arn:arn:`, `^task_family:.*-nginx-ec2$`, @@ -264,16 +275,19 @@ func (suite *ecsSuite) TestRedisECS() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-redis-ec2-`, `^docker_image:public.ecr.aws/docker/library/redis:latest$`, `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:redis$`, + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-redis-ec2$`, `^ecs_launch_type:ec2$`, `^image_id:sha256:`, `^image_name:public.ecr.aws/docker/library/redis$`, `^image_tag:latest$`, + `^region:us-east-1$`, `^short_image:redis$`, `^task_arn:`, `^task_family:.*-redis-ec2$`, @@ -287,10 +301,11 @@ func (suite *ecsSuite) TestRedisECS() { suite.testLog(&testLogArgs{ Filter: testLogFilterArgs{ Service: "redis", - Tags: []string{"ecs_launch_type:ec2"}, + Tags: []string{"^ecs_launch_type:ec2$"}, }, Expect: testLogExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-redis-ec2-`, @@ -298,9 +313,11 @@ func (suite *ecsSuite) TestRedisECS() { `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:redis$`, `^ecs_launch_type:ec2$`, + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-redis-ec2$`, `^image_id:sha256:`, `^image_name:public.ecr.aws/docker/library/redis$`, `^image_tag:latest$`, + `^region:us-east-1$`, `^short_image:redis$`, `^task_arn:arn:`, `^task_family:.*-redis-ec2$`, @@ -322,6 +339,7 @@ func (suite *ecsSuite) TestNginxFargate() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^availability_zone:`, `^availability-zone:`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, @@ -356,6 +374,7 @@ func (suite *ecsSuite) TestRedisFargate() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^availability_zone:`, `^availability-zone:`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, @@ -379,6 +398,75 @@ func (suite *ecsSuite) TestRedisFargate() { }) } +func (suite *ecsSuite) TestWindowsFargate() { + suite.testCheckRun(&testCheckRunArgs{ + Filter: testCheckRunFilterArgs{ + Name: "http.can_connect", + Tags: []string{ + "^ecs_launch_type:fargate$", + "^container_name:aspnetsample$", + }, + }, + Expect: testCheckRunExpectArgs{ + Tags: &[]string{ + `^aws_account:\d{12}$`, + `^availability_zone:`, + `^availability-zone:`, + `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, + `^container_id:`, + `^container_name:aspnetsample$`, + `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, + `^ecs_container_name:aspnetsample$`, + `^ecs_launch_type:fargate$`, + `^image_id:sha256:`, + `^image_name:mcr.microsoft.com/dotnet/samples$`, + `^image_tag:aspnetapp-nanoserver-ltsc2022$`, + `^region:us-east-1$`, + `^short_image:samples$`, + `^task_arn:`, + `^task_family:.*-aspnet-fg$`, + `^task_name:.*-aspnet-fg*`, + `^task_version:[[:digit:]]+$`, + `^url:`, + }, + AcceptUnexpectedTags: true, + }, + }) + + // Test container check + suite.testMetric(&testMetricArgs{ + Filter: testMetricFilterArgs{ + Name: "container.cpu.usage", + Tags: []string{ + "^ecs_container_name:aspnetsample$", + }, + }, + Expect: testMetricExpectArgs{ + Tags: &[]string{ + `^aws_account:\d{12}$`, + `^availability_zone:`, + `^availability-zone:`, + `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, + `^container_id:`, + `^container_name:aspnetsample$`, + `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, + `^ecs_container_name:aspnetsample$`, + `^ecs_launch_type:fargate$`, + `^image_id:sha256:`, + `^image_name:mcr.microsoft.com/dotnet/samples$`, + `^image_tag:aspnetapp-nanoserver-ltsc2022$`, + `^region:us-east-1$`, + `^runtime:ecsfargate$`, + `^short_image:samples$`, + `^task_arn:`, + `^task_family:.*-aspnet-fg$`, + `^task_name:.*-aspnet-fg*`, + `^task_version:[[:digit:]]+$`, + }, + }, + }) +} + func (suite *ecsSuite) TestCPU() { // Test CPU metrics suite.testMetric(&testMetricArgs{ @@ -390,17 +478,20 @@ func (suite *ecsSuite) TestCPU() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-stress-ng-ec2-`, `^docker_image:ghcr.io/colinianking/stress-ng:409201de7458c639c68088d28ec8270ef599fe47$`, `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:stress-ng$`, + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-stress-ng$`, `^git.commit.sha:`, `^git.repository_url:https://github.com/ColinIanKing/stress-ng$`, `^image_id:sha256:`, `^image_name:ghcr.io/colinianking/stress-ng$`, `^image_tag:409201de7458c639c68088d28ec8270ef599fe47$`, + `^region:us-east-1$`, `^runtime:docker$`, `^short_image:stress-ng$`, `^task_arn:`, @@ -434,17 +525,20 @@ func (suite *ecsSuite) testDogstatsd(taskName string) { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-` + regexp.QuoteMeta(taskName) + `-ec2-`, `^docker_image:ghcr.io/datadog/apps-dogstatsd:main$`, `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:dogstatsd$`, - `^git.commit.sha:`, // org.opencontainers.image.revision docker image label + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-dogstatsd-ud[ps]$`, + `^git.commit.sha:`, // org.opencontainers.image.revision docker image label `^git.repository_url:https://github.com/DataDog/test-infra-definitions$`, // org.opencontainers.image.source docker image label `^image_id:sha256:`, `^image_name:ghcr.io/datadog/apps-dogstatsd$`, `^image_tag:main$`, + `^region:us-east-1$`, `^series:`, `^short_image:apps-dogstatsd$`, `^task_arn:`, @@ -464,18 +558,21 @@ func (suite *ecsSuite) TestPrometheus() { }, Expect: testMetricExpectArgs{ Tags: &[]string{ + `^aws_account:\d{12}$`, `^cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^container_id:`, `^container_name:ecs-.*-prometheus-ec2-`, `^docker_image:ghcr.io/datadog/apps-prometheus:main$`, `^ecs_cluster_name:` + regexp.QuoteMeta(suite.ecsClusterName) + `$`, `^ecs_container_name:prometheus$`, + `^ecs_service:` + regexp.QuoteMeta(strings.TrimSuffix(suite.ecsClusterName, "-ecs")) + `-prometheus$`, `^endpoint:http://.*:8080/metrics$`, - `^git.commit.sha:`, // org.opencontainers.image.revision docker image label + `^git.commit.sha:`, // org.opencontainers.image.revision docker image label `^git.repository_url:https://github.com/DataDog/test-infra-definitions$`, // org.opencontainers.image.source docker image label `^image_id:sha256:`, `^image_name:ghcr.io/datadog/apps-prometheus$`, `^image_tag:main$`, + `^region:us-east-1$`, `^series:`, `^short_image:apps-prometheus$`, `^task_arn:`, @@ -528,7 +625,7 @@ func (suite *ecsSuite) testTrace(taskName string) { regexp.MustCompile(`^task_family:.*-` + regexp.QuoteMeta(taskName) + `-ec2$`), regexp.MustCompile(`^task_name:.*-` + regexp.QuoteMeta(taskName) + `-ec2$`), regexp.MustCompile(`^task_version:[[:digit:]]+$`), - }, false) + }, []*regexp.Regexp{}, false) if err == nil { break } diff --git a/test/new-e2e/tests/containers/eks_test.go b/test/new-e2e/tests/containers/eks_test.go index 16182842b0be8b..a5e32051418ad2 100644 --- a/test/new-e2e/tests/containers/eks_test.go +++ b/test/new-e2e/tests/containers/eks_test.go @@ -10,10 +10,12 @@ import ( "encoding/json" "testing" + "github.com/DataDog/test-infra-definitions/scenarios/aws/eks" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/components" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/runner" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/runner/parameters" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/infra" - "github.com/DataDog/test-infra-definitions/scenarios/aws/eks" "github.com/pulumi/pulumi/sdk/v3/go/auto" "github.com/stretchr/testify/suite" @@ -22,10 +24,16 @@ import ( type eksSuite struct { k8sSuite + initOnly bool } func TestEKSSuite(t *testing.T) { - suite.Run(t, &eksSuite{}) + var initOnly bool + initOnlyParam, err := runner.GetProfile().ParamStore().GetBoolWithDefault(parameters.InitOnly, false) + if err == nil { + initOnly = initOnlyParam + } + suite.Run(t, &eksSuite{initOnly: initOnly}) } func (suite *eksSuite) SetupSuite() { @@ -38,7 +46,13 @@ func (suite *eksSuite) SetupSuite() { "dddogstatsd:deploy": auto.ConfigValue{Value: "false"}, } - _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure(ctx, "eks-cluster", eks.Run, infra.WithConfigMap(stackConfig)) + _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure( + ctx, + "eks-cluster", + eks.Run, + infra.WithConfigMap(stackConfig), + ) + if !suite.Assert().NoError(err) { stackName, err := infra.GetStackManager().GetPulumiStackName("eks-cluster") suite.Require().NoError(err) @@ -49,6 +63,10 @@ func (suite *eksSuite) SetupSuite() { suite.T().FailNow() } + if suite.initOnly { + suite.T().Skip("E2E_INIT_ONLY is set, skipping tests") + } + fakeintake := &components.FakeIntake{} fiSerialized, err := json.Marshal(stackOutput.Outputs["dd-Fakeintake-aws-ecs"].Value) suite.Require().NoError(err) @@ -57,7 +75,7 @@ func (suite *eksSuite) SetupSuite() { suite.Fakeintake = fakeintake.Client() kubeCluster := &components.KubernetesCluster{} - kubeSerialized, err := json.Marshal(stackOutput.Outputs["dd-Cluster-aws-eks"].Value) + kubeSerialized, err := json.Marshal(stackOutput.Outputs["dd-Cluster-eks"].Value) suite.Require().NoError(err) suite.Require().NoError(kubeCluster.Import(kubeSerialized, &kubeCluster)) suite.Require().NoError(kubeCluster.Init(suite)) @@ -66,13 +84,22 @@ func (suite *eksSuite) SetupSuite() { suite.K8sConfig, err = clientcmd.RESTConfigFromKubeConfig([]byte(kubeCluster.KubeConfig)) suite.Require().NoError(err) - suite.AgentLinuxHelmInstallName = stackOutput.Outputs["agent-linux-helm-install-name"].Value.(string) - suite.AgentWindowsHelmInstallName = stackOutput.Outputs["agent-windows-helm-install-name"].Value.(string) + kubernetesAgent := &components.KubernetesAgent{} + kubernetesAgentSerialized, err := json.Marshal(stackOutput.Outputs["dd-KubernetesAgent-aws-datadog-agent"].Value) + suite.Require().NoError(err) + suite.Require().NoError(kubernetesAgent.Import(kubernetesAgentSerialized, &kubernetesAgent)) + + suite.KubernetesAgentRef = kubernetesAgent suite.k8sSuite.SetupSuite() } func (suite *eksSuite) TearDownSuite() { + if suite.initOnly { + suite.T().Logf("E2E_INIT_ONLY is set, skipping deletion") + return + } + suite.k8sSuite.TearDownSuite() ctx := context.Background() diff --git a/test/new-e2e/tests/containers/k8s_test.go b/test/new-e2e/tests/containers/k8s_test.go index 72489032df046b..3629b348da837c 100644 --- a/test/new-e2e/tests/containers/k8s_test.go +++ b/test/new-e2e/tests/containers/k8s_test.go @@ -7,6 +7,8 @@ package containers import ( "context" + "encoding/json" + "errors" "fmt" "regexp" "strings" @@ -14,10 +16,12 @@ import ( "github.com/DataDog/agent-payload/v5/cyclonedx_v1_4" "github.com/DataDog/agent-payload/v5/sbom" + "gopkg.in/zorkian/go-datadog-api.v2" + "github.com/DataDog/datadog-agent/pkg/util/pointer" "github.com/DataDog/datadog-agent/test/fakeintake/aggregator" fakeintake "github.com/DataDog/datadog-agent/test/fakeintake/client" - "gopkg.in/zorkian/go-datadog-api.v2" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/components" "github.com/fatih/color" "github.com/samber/lo" @@ -51,6 +55,7 @@ type k8sSuite struct { KubeClusterName string AgentLinuxHelmInstallName string AgentWindowsHelmInstallName string + KubernetesAgentRef *components.KubernetesAgent K8sConfig *restclient.Config K8sClient kubernetes.Interface @@ -92,7 +97,7 @@ func (suite *k8sSuite) TearDownSuite() { // The 00 in Test00UpAndRunning is here to guarantee that this test, waiting for the agent pods to be ready, // is run first. func (suite *k8sSuite) Test00UpAndRunning() { - suite.testUpAndRunning(5 * time.Minute) + suite.testUpAndRunning(10 * time.Minute) } // An agent restart (because of a health probe failure or because of a OOM kill for ex.) @@ -130,7 +135,7 @@ func (suite *k8sSuite) testUpAndRunning(waitFor time.Duration) { } linuxPods, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", suite.AgentLinuxHelmInstallName+"-datadog").String(), + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.LinuxNodeAgent.LabelSelectors["app"]).String(), }) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to list Linux datadog agent pods") { @@ -138,7 +143,7 @@ func (suite *k8sSuite) testUpAndRunning(waitFor time.Duration) { } windowsPods, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", suite.AgentWindowsHelmInstallName+"-datadog").String(), + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.WindowsNodeAgent.LabelSelectors["app"]).String(), }) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to list Windows datadog agent pods") { @@ -146,7 +151,7 @@ func (suite *k8sSuite) testUpAndRunning(waitFor time.Duration) { } clusterAgentPods, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", suite.AgentLinuxHelmInstallName+"-datadog-cluster-agent").String(), + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.LinuxClusterAgent.LabelSelectors["app"]).String(), }) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to list datadog cluster agent pods") { @@ -154,7 +159,7 @@ func (suite *k8sSuite) testUpAndRunning(waitFor time.Duration) { } clusterChecksPods, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", suite.AgentLinuxHelmInstallName+"-datadog-clusterchecks").String(), + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.LinuxClusterChecks.LabelSelectors["app"]).String(), }) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to list datadog cluster checks runner pods") { @@ -187,6 +192,23 @@ func (suite *k8sSuite) testUpAndRunning(waitFor time.Duration) { }) } +func (suite *k8sSuite) TestAdmissionControllerWebhooksExist() { + ctx := context.Background() + expectedWebhookName := "datadog-webhook" + + suite.Run("agent registered mutating webhook configuration", func() { + mutatingConfig, err := suite.K8sClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, expectedWebhookName, metav1.GetOptions{}) + suite.Require().NoError(err) + suite.NotNilf(mutatingConfig, "None of the mutating webhook configurations have the name '%s'", expectedWebhookName) + }) + + suite.Run("agent registered validating webhook configuration", func() { + validatingConfig, err := suite.K8sClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, expectedWebhookName, metav1.GetOptions{}) + suite.Require().NoError(err) + suite.NotNilf(validatingConfig, "None of the validating webhook configurations have the name '%s'", expectedWebhookName) + }) +} + func (suite *k8sSuite) TestVersion() { ctx := context.Background() versionExtractor := regexp.MustCompile(`Commit: ([[:xdigit:]]+)`) @@ -198,22 +220,22 @@ func (suite *k8sSuite) TestVersion() { }{ { "Linux agent", - suite.AgentLinuxHelmInstallName + "-datadog", + suite.KubernetesAgentRef.LinuxNodeAgent.LabelSelectors["app"], "agent", }, { "Windows agent", - suite.AgentWindowsHelmInstallName + "-datadog", + suite.KubernetesAgentRef.WindowsNodeAgent.LabelSelectors["app"], "agent", }, { "cluster agent", - suite.AgentLinuxHelmInstallName + "-datadog-cluster-agent", + suite.KubernetesAgentRef.LinuxClusterAgent.LabelSelectors["app"], "cluster-agent", }, { "cluster checks", - suite.AgentLinuxHelmInstallName + "-datadog-clusterchecks", + suite.KubernetesAgentRef.LinuxClusterChecks.LabelSelectors["app"], "agent", }, } { @@ -245,6 +267,178 @@ func (suite *k8sSuite) TestVersion() { } } +func (suite *k8sSuite) TestCLI() { + suite.Run("agent CLI", func() { suite.testAgentCLI() }) + suite.Run("cluster agent CLI", func() { suite.testClusterAgentCLI() }) +} + +func (suite *k8sSuite) testAgentCLI() { + ctx := context.Background() + + pod, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.LinuxNodeAgent.LabelSelectors["app"]).String(), + Limit: 1, + }) + suite.Require().NoError(err) + suite.Require().Len(pod.Items, 1) + + suite.Run("agent status", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"agent", "status"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `agent status` should be empty") + suite.Contains(stdout, "Collector") + suite.Contains(stdout, "Running Checks") + suite.Contains(stdout, "Instance ID: container [OK]") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent status --json", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"env", "DD_LOG_LEVEL=off", "agent", "status", "--json"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `agent status` should be empty") + if !suite.Truef(json.Valid([]byte(stdout)), "Output of `agent status --json` isn’t valid JSON") { + var blob interface{} + err := json.Unmarshal([]byte(stdout), &blob) + suite.NoError(err) + } + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent checkconfig", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"agent", "checkconfig"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `agent checkconfig` should be empty") + suite.Contains(stdout, "=== container check ===") + suite.Contains(stdout, "Config for instance ID: container:") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent check -r container", func() { + var stdout string + suite.EventuallyWithT(func(c *assert.CollectT) { + stdout, _, err = suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"agent", "check", "-t", "3", "container", "--table", "--delay", "1000", "--pause", "5000"}) + // Can be replaced by require.NoError(…) once https://github.com/stretchr/testify/pull/1481 is merged + if !assert.NoError(c, err) { + return + } + matched, err := regexp.MatchString(`container\.memory\.usage\s+gauge\s+\d+\s+\d+`, stdout) + if assert.NoError(c, err) { + assert.Truef(c, matched, "Output of `agent check -r container` doesn’t contain the expected metric") + } + }, 2*time.Minute, 1*time.Second) + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent check -r container --json", func() { + var stdout string + suite.EventuallyWithT(func(c *assert.CollectT) { + stdout, _, err = suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"env", "DD_LOG_LEVEL=off", "agent", "check", "-r", "container", "--table", "--delay", "1000", "--json"}) + // Can be replaced by require.NoError(…) once https://github.com/stretchr/testify/pull/1481 is merged + if !assert.NoError(c, err) { + return + } + if !assert.Truef(c, json.Valid([]byte(stdout)), "Output of `agent check -r container --json` isn’t valid JSON") { + var blob interface{} + err := json.Unmarshal([]byte(stdout), &blob) + assert.NoError(c, err) + } + }, 2*time.Minute, 1*time.Second) + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent workload-list", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"agent", "workload-list", "-v"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `agent workload-list` should be empty") + suite.Contains(stdout, "=== Entity container sources(merged):[node_orchestrator runtime] id: ") + suite.Contains(stdout, "=== Entity kubernetes_pod sources(merged):[cluster_orchestrator node_orchestrator] id: ") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("agent tagger-list", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "agent", []string{"agent", "tagger-list"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `agent tagger-list` should be empty") + suite.Contains(stdout, "=== Entity container_id://") + suite.Contains(stdout, "=== Entity kubernetes_pod_uid://") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) +} + +func (suite *k8sSuite) testClusterAgentCLI() { + ctx := context.Background() + + pod, err := suite.K8sClient.CoreV1().Pods("datadog").List(ctx, metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector("app", suite.KubernetesAgentRef.LinuxClusterAgent.LabelSelectors["app"]).String(), + Limit: 1, + }) + suite.Require().NoError(err) + suite.Require().Len(pod.Items, 1) + + suite.Run("cluster-agent status", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "cluster-agent", []string{"datadog-cluster-agent", "status"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `datadog-cluster-agent status` should be empty") + suite.Contains(stdout, "Collector") + suite.Contains(stdout, "Running Checks") + suite.Contains(stdout, "kubernetes_state_core") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("cluster-agent status --json", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "cluster-agent", []string{"env", "DD_LOG_LEVEL=off", "datadog-cluster-agent", "status", "--json"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `datadog-cluster-agent status` should be empty") + if !suite.Truef(json.Valid([]byte(stdout)), "Output of `datadog-cluster-agent status --json` isn’t valid JSON") { + var blob interface{} + err := json.Unmarshal([]byte(stdout), &blob) + suite.NoError(err) + } + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("cluster-agent checkconfig", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "cluster-agent", []string{"datadog-cluster-agent", "checkconfig"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `datadog-cluster-agent checkconfig` should be empty") + suite.Contains(stdout, "=== kubernetes_state_core check ===") + suite.Contains(stdout, "Config for instance ID: kubernetes_state_core:") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) + + suite.Run("cluster-agent clusterchecks", func() { + stdout, stderr, err := suite.podExec("datadog", pod.Items[0].Name, "cluster-agent", []string{"datadog-cluster-agent", "clusterchecks"}) + suite.Require().NoError(err) + suite.Empty(stderr, "Standard error of `datadog-cluster-agent clusterchecks` should be empty") + suite.Contains(stdout, "agents reporting ===") + suite.Contains(stdout, "===== Checks on ") + suite.Contains(stdout, "=== helm check ===") + if suite.T().Failed() { + suite.T().Log(stdout) + } + }) +} + func (suite *k8sSuite) TestNginx() { // `nginx` check is configured via AD annotation on pods // Test it is properly scheduled @@ -273,6 +467,8 @@ func (suite *k8sSuite) TestNginx() { `^pod_name:nginx-[[:alnum:]]+-[[:alnum:]]+$`, `^pod_phase:running$`, `^short_image:apps-nginx-server$`, + `^email:team-container-platform@datadoghq.com$`, + `^team:contp$`, }, AcceptUnexpectedTags: true, }, @@ -346,6 +542,8 @@ func (suite *k8sSuite) TestNginx() { `^pod_name:nginx-[[:alnum:]]+-[[:alnum:]]+$`, `^pod_phase:running$`, `^short_image:apps-nginx-server$`, + `^email:team-container-platform@datadoghq.com$`, + `^team:contp$`, }, Message: `GET / HTTP/1\.1`, }, @@ -446,6 +644,7 @@ func (suite *k8sSuite) TestRedis() { } func (suite *k8sSuite) TestCPU() { + // TODO: https://datadoghq.atlassian.net/browse/CONTINT-4143 // Test CPU metrics suite.testMetric(&testMetricArgs{ Filter: testMetricFilterArgs{ @@ -638,10 +837,6 @@ func (suite *k8sSuite) testDogstatsdPodUID(kubeNamespace string) { } func (suite *k8sSuite) testDogstatsdContainerID(kubeNamespace, kubeDeployment string) { - if kubeDeployment == kubeDeploymentDogstatsdUDPOrigin { - // CONTINT-3934: This test is flaky, skip it for now - suite.T().Skipf("Skipping test for %s/%s as it is currently flaky", kubeNamespace, kubeDeployment) - } suite.testMetric(&testMetricArgs{ Filter: testMetricFilterArgs{ Name: "custom.metric", @@ -713,20 +908,63 @@ func (suite *k8sSuite) TestPrometheus() { }) } -func (suite *k8sSuite) TestAdmissionController() { +func (suite *k8sSuite) TestAdmissionControllerWithoutAPMInjection() { + suite.testAdmissionControllerPod("workload-mutated", "mutated", "", false) +} + +func (suite *k8sSuite) TestAdmissionControllerWithLibraryAnnotation() { + suite.testAdmissionControllerPod("workload-mutated-lib-injection", "mutated-with-lib-annotation", "python", false) +} + +func (suite *k8sSuite) TestAdmissionControllerWithAutoDetectedLanguage() { + suite.testAdmissionControllerPod("workload-mutated-lib-injection", "mutated-with-auto-detected-language", "python", true) +} + +func (suite *k8sSuite) testAdmissionControllerPod(namespace string, name string, language string, languageShouldBeAutoDetected bool) { ctx := context.Background() + // When the language should be auto-detected, we need to wait for the + // deployment to be created and the annotation with the languages to be set + // by the Cluster Agent so that we can be sure that in the next restart the + // libraries for the detected language are injected + if languageShouldBeAutoDetected { + suite.Require().EventuallyWithTf(func(c *assert.CollectT) { + deployment, err := suite.K8sClient.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + if !assert.NoError(c, err) { + return + } + + detectedLangsLabelIsSet := false + detectedLangsAnnotationRegex := regexp.MustCompile(`^internal\.dd\.datadoghq\.com/.*\.detected_langs$`) + for annotation := range deployment.Annotations { + if detectedLangsAnnotationRegex.Match([]byte(annotation)) { + detectedLangsLabelIsSet = true + break + } + } + assert.True(c, detectedLangsLabelIsSet) + }, 5*time.Minute, 10*time.Second, "The deployment with name %s in namespace %s does not exist or does not have the auto detected languages annotation", name, namespace) + } + + // Record old pod, so we can be sure we are not looking at the incorrect one after deletion + oldPods, err := suite.K8sClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector("app", name).String(), + }) + suite.Require().NoError(err) + suite.Require().Len(oldPods.Items, 1) + oldPod := oldPods.Items[0] + // Delete the pod to ensure it is recreated after the admission controller is deployed - err := suite.K8sClient.CoreV1().Pods("workload-mutated").DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", "mutated").String(), + err = suite.K8sClient.CoreV1().Pods(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector("app", name).String(), }) suite.Require().NoError(err) // Wait for the fresh pod to be created var pod corev1.Pod suite.Require().EventuallyWithTf(func(c *assert.CollectT) { - pods, err := suite.K8sClient.CoreV1().Pods("workload-mutated").List(ctx, metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector("app", "mutated").String(), + pods, err := suite.K8sClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector("app", name).String(), }) if !assert.NoError(c, err) { return @@ -735,7 +973,10 @@ func (suite *k8sSuite) TestAdmissionController() { return } pod = pods.Items[0] - }, 2*time.Minute, 10*time.Second, "Failed to witness the creation of a pod in workload-mutated") + if !assert.NotEqual(c, oldPod.Name, pod.Name) { + return + } + }, 2*time.Minute, 10*time.Second, "Failed to witness the creation of pod with name %s in namespace %s", name, namespace) suite.Require().Len(pod.Spec.Containers, 1) @@ -756,7 +997,7 @@ func (suite *k8sSuite) TestAdmissionController() { suite.Equal("e2e", env["DD_ENV"]) } if suite.Contains(env, "DD_SERVICE") { - suite.Equal("mutated", env["DD_SERVICE"]) + suite.Equal(name, env["DD_SERVICE"]) } if suite.Contains(env, "DD_VERSION") { suite.Equal("v0.0.1", env["DD_VERSION"]) @@ -770,22 +1011,97 @@ func (suite *k8sSuite) TestAdmissionController() { } } + volumesMarkedAsSafeToEvict := strings.Split( + pod.Annotations["cluster-autoscaler.kubernetes.io/safe-to-evict-local-volumes"], ",", + ) + if suite.Contains(hostPathVolumes, "datadog") { suite.Equal("/var/run/datadog", hostPathVolumes["datadog"].Path) + suite.Contains(volumesMarkedAsSafeToEvict, "datadog") } - volumeMounts := make(map[string]string) + volumeMounts := make(map[string][]string) for _, volumeMount := range pod.Spec.Containers[0].VolumeMounts { - volumeMounts[volumeMount.Name] = volumeMount.MountPath + volumeMounts[volumeMount.Name] = append(volumeMounts[volumeMount.Name], volumeMount.MountPath) } if suite.Contains(volumeMounts, "datadog") { - suite.Equal("/var/run/datadog", volumeMounts["datadog"]) + suite.ElementsMatch([]string{"/var/run/datadog"}, volumeMounts["datadog"]) + } + + switch language { + // APM supports several languages, but for now all the test apps are Python + case "python": + emptyDirVolumes := make(map[string]*corev1.EmptyDirVolumeSource) + for _, volume := range pod.Spec.Volumes { + if volume.EmptyDir != nil { + emptyDirVolumes[volume.Name] = volume.EmptyDir + } + } + + if suite.Contains(emptyDirVolumes, "datadog-auto-instrumentation") { + suite.Contains(volumesMarkedAsSafeToEvict, "datadog-auto-instrumentation") + } + + if suite.Contains(emptyDirVolumes, "datadog-auto-instrumentation-etc") { + suite.Contains(volumesMarkedAsSafeToEvict, "datadog-auto-instrumentation-etc") + } + + if suite.Contains(volumeMounts, "datadog-auto-instrumentation") { + suite.ElementsMatch([]string{ + "/opt/datadog-packages/datadog-apm-inject", + "/opt/datadog/apm/library", + }, volumeMounts["datadog-auto-instrumentation"]) + } } + } func (suite *k8sSuite) TestContainerImage() { - suite.EventuallyWithTf(func(c *assert.CollectT) { + sendEvent := func(alertType, text string) { + if _, err := suite.datadogClient.PostEvent(&datadog.Event{ + Title: pointer.Ptr(suite.T().Name()), + Text: pointer.Ptr(fmt.Sprintf(`%%%%%% +`+"```"+` +%s +`+"```"+` + %%%%%%`, text)), + AlertType: &alertType, + Tags: []string{ + "app:agent-new-e2e-tests-containers", + "cluster_name:" + suite.clusterName, + "contimage:ghcr.io/datadog/apps-nginx-server", + "test:" + suite.T().Name(), + }, + }); err != nil { + suite.T().Logf("Failed to post event: %s", err) + } + } + + defer func() { + if suite.T().Failed() { + sendEvent("error", "Failed finding the `ghcr.io/datadog/apps-nginx-server` container image payload with proper tags") + } else { + sendEvent("success", "All good!") + } + }() + + suite.EventuallyWithTf(func(collect *assert.CollectT) { + c := &myCollectT{ + CollectT: collect, + errors: []error{}, + } + // To enforce the use of myCollectT instead + collect = nil //nolint:ineffassign + + defer func() { + if len(c.errors) == 0 { + sendEvent("success", "All good!") + } else { + sendEvent("warning", errors.Join(c.errors...).Error()) + } + }() + images, err := suite.Fakeintake.FilterContainerImages("ghcr.io/datadog/apps-nginx-server") // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to query fake intake") { @@ -806,13 +1122,56 @@ func (suite *k8sSuite) TestContainerImage() { regexp.MustCompile(`^os_name:linux$`), regexp.MustCompile(`^short_image:apps-nginx-server$`), } - err = assertTags(images[len(images)-1].GetTags(), expectedTags, false) + err = assertTags(images[len(images)-1].GetTags(), expectedTags, []*regexp.Regexp{}, false) assert.NoErrorf(c, err, "Tags mismatch") }, 2*time.Minute, 10*time.Second, "Failed finding the container image payload") } func (suite *k8sSuite) TestSBOM() { - suite.EventuallyWithTf(func(c *assert.CollectT) { + sendEvent := func(alertType, text string) { + if _, err := suite.datadogClient.PostEvent(&datadog.Event{ + Title: pointer.Ptr(suite.T().Name()), + Text: pointer.Ptr(fmt.Sprintf(`%%%%%% +`+"```"+` +%s +`+"```"+` + %%%%%%`, text)), + AlertType: &alertType, + Tags: []string{ + "app:agent-new-e2e-tests-containers", + "cluster_name:" + suite.clusterName, + "sbom:ghcr.io/datadog/apps-nginx-server", + "test:" + suite.T().Name(), + }, + }); err != nil { + suite.T().Logf("Failed to post event: %s", err) + } + } + + defer func() { + if suite.T().Failed() { + sendEvent("error", "Failed finding the `ghcr.io/datadog/apps-nginx-server` SBOM payload with proper tags") + } else { + sendEvent("success", "All good!") + } + }() + + suite.EventuallyWithTf(func(collect *assert.CollectT) { + c := &myCollectT{ + CollectT: collect, + errors: []error{}, + } + // To enforce the use of myCollectT instead + collect = nil //nolint:ineffassign + + defer func() { + if len(c.errors) == 0 { + sendEvent("success", "All good!") + } else { + sendEvent("warning", errors.Join(c.errors...).Error()) + } + }() + sbomIDs, err := suite.Fakeintake.GetSBOMIDs() // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to query fake intake") { @@ -875,7 +1234,7 @@ func (suite *k8sSuite) TestSBOM() { regexp.MustCompile(`^os_name:linux$`), regexp.MustCompile(`^short_image:apps-nginx-server$`), } - err = assertTags(image.GetTags(), expectedTags, false) + err = assertTags(image.GetTags(), expectedTags, []*regexp.Regexp{}, false) assert.NoErrorf(c, err, "Tags mismatch") properties := lo.Associate(image.GetCyclonedx().Metadata.Component.Properties, func(property *cyclonedx_v1_4.Property) (string, string) { @@ -894,27 +1253,76 @@ func (suite *k8sSuite) TestSBOM() { } func (suite *k8sSuite) TestContainerLifecycleEvents() { + sendEvent := func(alertType, text string) { + if _, err := suite.datadogClient.PostEvent(&datadog.Event{ + Title: pointer.Ptr(suite.T().Name()), + Text: pointer.Ptr(fmt.Sprintf(`%%%%%% +`+"```"+` +%s +`+"```"+` + %%%%%%`, text)), + AlertType: &alertType, + Tags: []string{ + "app:agent-new-e2e-tests-containers", + "cluster_name:" + suite.clusterName, + "contlcycle:ghcr.io/datadog/apps-nginx-server", + "test:" + suite.T().Name(), + }, + }); err != nil { + suite.T().Logf("Failed to post event: %s", err) + } + } + + defer func() { + if suite.T().Failed() { + sendEvent("error", "Failed finding the `ghcr.io/datadog/apps-nginx-server` container lifecycle event") + } else { + sendEvent("success", "All good!") + } + }() + var nginxPod corev1.Pod suite.Require().EventuallyWithTf(func(c *assert.CollectT) { pods, err := suite.K8sClient.CoreV1().Pods("workload-nginx").List(context.Background(), metav1.ListOptions{ LabelSelector: fields.OneTermEqualSelector("app", "nginx").String(), + FieldSelector: fields.OneTermEqualSelector("status.phase", "Running").String(), }) // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to list nginx pods") { return } - if !assert.NotEmptyf(c, pods, "Failed to find an nginx pod") { + if !assert.NotEmptyf(c, pods.Items, "Failed to find an nginx pod") { return } - nginxPod = pods.Items[0] + // Choose the oldest pod. + // If we choose a pod that is too recent, there is a risk that we delete a pod that hasn’t been seen by the agent yet. + // So that no pod lifecycle event is sent. + nginxPod = lo.MinBy(pods.Items, func(item corev1.Pod, min corev1.Pod) bool { + return item.Status.StartTime.Before(min.Status.StartTime) + }) }, 1*time.Minute, 10*time.Second, "Failed to find an nginx pod") err := suite.K8sClient.CoreV1().Pods("workload-nginx").Delete(context.Background(), nginxPod.Name, metav1.DeleteOptions{}) suite.Require().NoError(err) - suite.EventuallyWithTf(func(c *assert.CollectT) { + suite.EventuallyWithTf(func(collect *assert.CollectT) { + c := &myCollectT{ + CollectT: collect, + errors: []error{}, + } + // To enforce the use of myCollectT instead + collect = nil //nolint:ineffassign + + defer func() { + if len(c.errors) == 0 { + sendEvent("success", "All good!") + } else { + sendEvent("warning", errors.Join(c.errors...).Error()) + } + }() + events, err := suite.Fakeintake.GetContainerLifecycleEvents() // Can be replaced by require.NoErrorf(…) once https://github.com/stretchr/testify/pull/1481 is merged if !assert.NoErrorf(c, err, "Failed to query fake intake") { @@ -932,8 +1340,8 @@ func (suite *k8sSuite) TestContainerLifecycleEvents() { } } - assert.Truef(c, foundPodEvent, "Failed to find the pod lifecycle event") - }, 2*time.Minute, 10*time.Second, "Failed to find the container lifecycle events") + assert.Truef(c, foundPodEvent, "Failed to find the pod lifecycle event for pod %s/%s", nginxPod.Namespace, nginxPod.Name) + }, 2*time.Minute, 10*time.Second, "Failed to find the pod lifecycle event for pod %s/%s", nginxPod.Namespace, nginxPod.Name) } func (suite *k8sSuite) testHPA(namespace, deployment string) { @@ -1024,7 +1432,9 @@ func (suite *k8sSuite) testHPA(namespace, deployment string) { }) } -func (suite *k8sSuite) podExec(namespace, pod, container string, cmd []string) (stdout, stderr string, err error) { +type podExecOption func(*corev1.PodExecOptions) + +func (suite *k8sSuite) podExec(namespace, pod, container string, cmd []string, podOptions ...podExecOption) (stdout, stderr string, err error) { req := suite.K8sClient.CoreV1().RESTClient().Post().Resource("pods").Namespace(namespace).Name(pod).SubResource("exec") option := &corev1.PodExecOptions{ Stdin: false, @@ -1035,6 +1445,10 @@ func (suite *k8sSuite) podExec(namespace, pod, container string, cmd []string) ( Command: cmd, } + for _, podOption := range podOptions { + podOption(option) + } + req.VersionedParams( option, scheme.ParameterCodec, @@ -1100,7 +1514,7 @@ func (suite *k8sSuite) testTrace(kubeDeployment string) { regexp.MustCompile(`^pod_name:` + kubeDeployment + `-[[:alnum:]]+-[[:alnum:]]+$`), regexp.MustCompile(`^pod_phase:running$`), regexp.MustCompile(`^short_image:apps-tracegen$`), - }, false) + }, []*regexp.Regexp{}, false) if err == nil { break } diff --git a/test/new-e2e/tests/containers/kindvm_test.go b/test/new-e2e/tests/containers/kindvm_test.go index a1ca2513e5cbc8..425b075e8fc184 100644 --- a/test/new-e2e/tests/containers/kindvm_test.go +++ b/test/new-e2e/tests/containers/kindvm_test.go @@ -10,10 +10,11 @@ import ( "encoding/json" "testing" + "github.com/DataDog/test-infra-definitions/scenarios/aws/kindvm" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/components" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/runner" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/infra" - "github.com/DataDog/test-infra-definitions/scenarios/aws/kindvm" "github.com/pulumi/pulumi/sdk/v3/go/auto" "github.com/stretchr/testify/suite" @@ -32,13 +33,19 @@ func (suite *kindSuite) SetupSuite() { ctx := context.Background() stackConfig := runner.ConfigMap{ - "ddagent:deploy": auto.ConfigValue{Value: "true"}, - "ddagent:fakeintake": auto.ConfigValue{Value: "true"}, - "ddtestworkload:deploy": auto.ConfigValue{Value: "true"}, - "dddogstatsd:deploy": auto.ConfigValue{Value: "false"}, + "ddinfra:aws/defaultInstanceType": auto.ConfigValue{Value: "t3.xlarge"}, + "ddagent:deploy": auto.ConfigValue{Value: "true"}, + "ddagent:fakeintake": auto.ConfigValue{Value: "true"}, + "ddtestworkload:deploy": auto.ConfigValue{Value: "true"}, + "dddogstatsd:deploy": auto.ConfigValue{Value: "false"}, } - _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure(ctx, "kind-cluster", kindvm.Run, infra.WithConfigMap(stackConfig)) + _, stackOutput, err := infra.GetStackManager().GetStackNoDeleteOnFailure( + ctx, + "kind-cluster", + kindvm.Run, + infra.WithConfigMap(stackConfig), + ) if !suite.Assert().NoError(err) { stackName, err := infra.GetStackManager().GetPulumiStackName("kind-cluster") suite.Require().NoError(err) @@ -66,8 +73,11 @@ func (suite *kindSuite) SetupSuite() { suite.K8sConfig, err = clientcmd.RESTConfigFromKubeConfig([]byte(kubeCluster.KubeConfig)) suite.Require().NoError(err) - suite.AgentLinuxHelmInstallName = stackOutput.Outputs["agent-linux-helm-install-name"].Value.(string) - suite.AgentWindowsHelmInstallName = "none" + kubernetesAgent := &components.KubernetesAgent{} + kubernetesAgentSerialized, err := json.Marshal(stackOutput.Outputs["dd-KubernetesAgent-aws-datadog-agent"].Value) + suite.Require().NoError(err) + suite.Require().NoError(kubernetesAgent.Import(kubernetesAgentSerialized, &kubernetesAgent)) + suite.KubernetesAgentRef = kubernetesAgent suite.k8sSuite.SetupSuite() } @@ -98,7 +108,7 @@ func (suite *kindSuite) TestControlPlane() { `^dry_run:$`, `^group:`, `^image_id:`, - `^image_name:registry.k8s.io/kube-apiserver$`, + `^image_name:(?:k8s\.gcr\.io|registry\.k8s\.io)/kube-apiserver$`, `^image_tag:v1\.`, `^kube_container_name:kube-apiserver$`, `^kube_namespace:kube-system$`, @@ -110,10 +120,15 @@ func (suite *kindSuite) TestControlPlane() { `^scope:(?:|cluster|namespace|resource)$`, `^short_image:kube-apiserver$`, `^subresource:`, - `^verb:(?:APPLY|DELETE|GET|LIST|PATCH|POST|PUT|PATCH)$`, + `^verb:(?:APPLY|DELETE|GET|LIST|PATCH|POST|PUT|PATCH|WATCH|TOTAL)$`, `^version:`, }, }, + Optional: testMetricExpectArgs{ + Tags: &[]string{ + `^contentType:`, + }, + }, }) // Test `kube_controller_manager` check is properly working @@ -127,7 +142,7 @@ func (suite *kindSuite) TestControlPlane() { `^container_name:kube-controller-manager$`, `^display_container_name:kube-controller-manager_kube-controller-manager-.*-control-plane$`, `^image_id:`, - `^image_name:registry.k8s.io/kube-controller-manager$`, + `^image_name:(?:k8s\.gcr\.io|registry\.k8s\.io)/kube-controller-manager$`, `^image_tag:v1\.`, `^kube_container_name:kube-controller-manager$`, `^kube_namespace:kube-system$`, @@ -152,7 +167,7 @@ func (suite *kindSuite) TestControlPlane() { `^container_name:kube-scheduler$`, `^display_container_name:kube-scheduler_kube-scheduler-.*-control-plane$`, `^image_id:`, - `^image_name:registry.k8s.io/kube-scheduler$`, + `^image_name:(?:k8s\.gcr\.io|registry\.k8s\.io)/kube-scheduler$`, `^image_tag:v1\.`, `^kube_container_name:kube-scheduler$`, `^kube_namespace:kube-system$`, diff --git a/test/new-e2e/tests/containers/main_test.go b/test/new-e2e/tests/containers/main_test.go index dd3e39da50f9b8..005e6e439b6472 100644 --- a/test/new-e2e/tests/containers/main_test.go +++ b/test/new-e2e/tests/containers/main_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/runner" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/runner/parameters" "github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/infra" ) @@ -20,7 +21,12 @@ var keepStacks = flag.Bool("keep-stacks", false, "Do not destroy the Pulumi stac func TestMain(m *testing.M) { code := m.Run() - if runner.GetProfile().AllowDevMode() && *keepStacks { + initOnly, err := runner.GetProfile().ParamStore().GetBoolWithDefault(parameters.InitOnly, false) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + + if runner.GetProfile().AllowDevMode() && *keepStacks || initOnly { fmt.Fprintln(os.Stderr, "Keeping stacks") } else { fmt.Fprintln(os.Stderr, "Cleaning up stacks") diff --git a/test/new-e2e/tests/containers/utils.go b/test/new-e2e/tests/containers/utils.go index c66d0ad918ee28..3cc1528525040a 100644 --- a/test/new-e2e/tests/containers/utils.go +++ b/test/new-e2e/tests/containers/utils.go @@ -14,7 +14,7 @@ import ( "github.com/samber/lo" ) -func assertTags(actualTags []string, expectedTags []*regexp.Regexp, acceptUnexpectedTags bool) error { +func assertTags(actualTags []string, expectedTags []*regexp.Regexp, optionalTags []*regexp.Regexp, acceptUnexpectedTags bool) error { missingTags := make([]*regexp.Regexp, len(expectedTags)) copy(missingTags, expectedTags) unexpectedTags := []string{} @@ -29,6 +29,16 @@ func assertTags(actualTags []string, expectedTags []*regexp.Regexp, acceptUnexpe break } } + + if !found { + for _, optionalTag := range optionalTags { + if optionalTag.MatchString(actualTag) { + found = true + break + } + } + } + if !found { unexpectedTags = append(unexpectedTags, actualTag) } diff --git a/test/new-e2e/tests/containers/utils_test.go b/test/new-e2e/tests/containers/utils_test.go index daff0171fe99f9..9118a9339f9702 100644 --- a/test/new-e2e/tests/containers/utils_test.go +++ b/test/new-e2e/tests/containers/utils_test.go @@ -17,6 +17,7 @@ func TestAssertTags(t *testing.T) { name string actualTags []string expectedTags []*regexp.Regexp + optionalTags []*regexp.Regexp acceptUnexpectedTags bool expectedOutput string }{ @@ -98,11 +99,30 @@ func TestAssertTags(t *testing.T) { }, expectedOutput: "unexpected tags: qux:qux, quux:quux\nmissing tags: ^grault:, ^corge:", }, + { + name: "Optional tags", + actualTags: []string{ + "foo:foo", + "bar:bar", + "baz:baz", + "qux:qux", + }, + expectedTags: []*regexp.Regexp{ + regexp.MustCompile(`^foo:foo$`), + regexp.MustCompile(`^bar:`), + regexp.MustCompile(`:baz$`), + }, + optionalTags: []*regexp.Regexp{ + regexp.MustCompile(`^qux:qux$`), + regexp.MustCompile(`^krak:krak$`), + }, + expectedOutput: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - output := assertTags(tt.actualTags, tt.expectedTags, tt.acceptUnexpectedTags) + output := assertTags(tt.actualTags, tt.expectedTags, tt.optionalTags, tt.acceptUnexpectedTags) if output != nil || tt.expectedOutput != "" { assert.EqualError(t, output, tt.expectedOutput) }