diff --git a/cmd/main.go b/cmd/main.go index a8608c4aaa..1ec3ada543 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -139,6 +139,7 @@ type options struct { untaintControllerEnabled bool untaintControllerWaitForCSIDriver bool rolloutOnConfigMapChangeEnabled bool + defaultDataPlaneLinuxEnabled bool // Secret Backend options secretBackendCommand string @@ -188,6 +189,7 @@ func (opts *options) Parse() { "When true (requires --untaintControllerEnabled), the Untaint controller removes the startup taint only after both the node Agent and Datadog CSI node-server pods are Ready. Requires Pod watch coverage of CSI namespaces (DD_CSIDRIVER_WATCH_NAMESPACE).") flag.BoolVar(&opts.rolloutOnConfigMapChangeEnabled, "rolloutOnConfigMapChangeEnabled", true, "Automatically roll out Agent/Cluster Agent/Cluster Check Runner/OTel Agent Gateway workloads when a ConfigMap referenced by their pod template changes content out-of-band") + flag.BoolVar(&opts.defaultDataPlaneLinuxEnabled, "defaultDataPlaneLinuxEnabled", false, "Enable the Agent Data Plane by default on Linux") // DatadogAgentInternal flag.BoolVar(&opts.createControllerRevisions, "createControllerRevisions", false, "Enable creation of ControllerRevision snapshots on each DDA spec change") @@ -223,6 +225,7 @@ func (opts *options) Parse() { boolEnv(&opts.untaintControllerWaitForCSIDriver, "DD_UNTAINT_CONTROLLER_WAIT_FOR_CSI_DRIVER"), boolEnv(&opts.createControllerRevisions, "DD_CREATE_CONTROLLER_REVISIONS"), boolEnv(&opts.rolloutOnConfigMapChangeEnabled, "DD_ROLLOUT_ON_CONFIGMAP_CHANGE_ENABLED"), + boolEnv(&opts.defaultDataPlaneLinuxEnabled, "DD_DEFAULT_DATA_PLANE_LINUX_ENABLED"), }) // Parsing flags @@ -501,6 +504,7 @@ func run(opts *options) error { UntaintControllerEnabled: opts.untaintControllerEnabled, UntaintControllerWaitForCSIDriver: opts.untaintControllerWaitForCSIDriver, RolloutOnConfigMapChangeEnabled: opts.rolloutOnConfigMapChangeEnabled, + DefaultDataPlaneLinuxEnabled: opts.defaultDataPlaneLinuxEnabled, ClusterProviderDetector: providerDetector, } diff --git a/cmd/main_test.go b/cmd/main_test.go index cb1c898a77..27ef881e0f 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -65,6 +65,15 @@ func TestOperatorManagedAgentInstallationEnabled(t *testing.T) { } } +func TestOptionsParse_DefaultDataPlaneLinuxEnabled(t *testing.T) { + resetCommandLine(t) + + var opts options + opts.Parse() + + require.False(t, opts.defaultDataPlaneLinuxEnabled) +} + func TestOptionsParse_EnvOverridesDefaults(t *testing.T) { resetCommandLine(t) t.Setenv("DD_METRICS_ADDR", ":9090") @@ -81,6 +90,7 @@ func TestOptionsParse_EnvOverridesDefaults(t *testing.T) { t.Setenv("DD_UNTAINT_CONTROLLER_WAIT_FOR_CSI_DRIVER", "true") t.Setenv("DD_CREATE_CONTROLLER_REVISIONS", "true") t.Setenv("DD_MANAGED_AGENT_INSTALLATION_ENABLED", "true") + t.Setenv("DD_DEFAULT_DATA_PLANE_LINUX_ENABLED", "true") var opts options opts.Parse() @@ -99,6 +109,7 @@ func TestOptionsParse_EnvOverridesDefaults(t *testing.T) { require.True(t, opts.untaintControllerWaitForCSIDriver) require.True(t, opts.createControllerRevisions) require.True(t, opts.managedAgentInstallationEnabled) + require.True(t, opts.defaultDataPlaneLinuxEnabled) } func TestOptionsParse_CLIOverridesEnv(t *testing.T) { @@ -111,6 +122,7 @@ func TestOptionsParse_CLIOverridesEnv(t *testing.T) { "-datadogGenericResourceRequeuePeriod=2m30s", "-leader-election-lease-duration=2m", "-untaintControllerWaitForCSIDriver=false", + "-defaultDataPlaneLinuxEnabled=false", ) t.Setenv("DD_METRICS_ADDR", ":9090") t.Setenv("DD_METRICS_SECURE", "true") @@ -120,6 +132,7 @@ func TestOptionsParse_CLIOverridesEnv(t *testing.T) { t.Setenv("DD_GENERIC_RESOURCE_REQUEUE_PERIOD", "5m") t.Setenv("DD_LEADER_ELECTION_LEASE_DURATION", "90s") t.Setenv("DD_UNTAINT_CONTROLLER_WAIT_FOR_CSI_DRIVER", "true") + t.Setenv("DD_DEFAULT_DATA_PLANE_LINUX_ENABLED", "true") var opts options opts.Parse() @@ -132,6 +145,7 @@ func TestOptionsParse_CLIOverridesEnv(t *testing.T) { require.Equal(t, 150*time.Second, opts.datadogGenericResourceRequeuePeriod) require.Equal(t, 2*time.Minute, opts.leaderElectionLeaseDuration) require.False(t, opts.untaintControllerWaitForCSIDriver) + require.False(t, opts.defaultDataPlaneLinuxEnabled) } func TestOptionsParse_InvalidEnvLeavesDefault(t *testing.T) { @@ -141,6 +155,7 @@ func TestOptionsParse_InvalidEnvLeavesDefault(t *testing.T) { t.Setenv("DD_GENERIC_RESOURCE_REQUEUE_PERIOD", "120") t.Setenv("DD_LEADER_ELECTION_LEASE_DURATION", "not-a-duration") t.Setenv("DD_MONITOR_CONTROLLER_ENABLED", "not-a-boolean-meaning-string") + t.Setenv("DD_DEFAULT_DATA_PLANE_LINUX_ENABLED", "not-a-boolean-meaning-string") var opts options opts.Parse() @@ -150,6 +165,7 @@ func TestOptionsParse_InvalidEnvLeavesDefault(t *testing.T) { require.Equal(t, defaultDatadogGenericResourceRequeuePeriod, opts.datadogGenericResourceRequeuePeriod) require.Equal(t, 60*time.Second, opts.leaderElectionLeaseDuration) require.False(t, opts.datadogMonitorEnabled) + require.False(t, opts.defaultDataPlaneLinuxEnabled) } func resetCommandLine(t *testing.T, args ...string) { diff --git a/docs/installation.md b/docs/installation.md index 38fe31d420..6244b63e8a 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -191,6 +191,7 @@ Other operator startup options can also be configured via environment variable: | DDGR max concurrent reconciles | `--datadogGenericResourceMaxConcurrentReconciles` | `DD_GENERIC_RESOURCE_MAX_CONCURRENT_RECONCILES` | `1` | | DDGR requeue period | `--datadogGenericResourceRequeuePeriod` | `DD_GENERIC_RESOURCE_REQUEUE_PERIOD` | `60s` | | Controller revisions | `--createControllerRevisions` | `DD_CREATE_CONTROLLER_REVISIONS` | `false` | +| Linux Data Plane default | `--defaultDataPlaneLinuxEnabled` | `DD_DEFAULT_DATA_PLANE_LINUX_ENABLED` | `false` | The leader election toggle (`--enable-leader-election`), pprof (`--pprof`), log options (`--loglevel`, `--logEncoder`), secret backend options diff --git a/internal/controller/datadogagent/feature/dataplane/feature.go b/internal/controller/datadogagent/feature/dataplane/feature.go index e4ac76af88..5e7075323c 100644 --- a/internal/controller/datadogagent/feature/dataplane/feature.go +++ b/internal/controller/datadogagent/feature/dataplane/feature.go @@ -29,6 +29,7 @@ func buildDataPlaneFeature(options *feature.Options) feature.Feature { if options != nil { f.logger = options.Logger + f.defaultEnabled = options.DefaultDataPlaneEnabled } return f @@ -37,6 +38,7 @@ func buildDataPlaneFeature(options *feature.Options) feature.Feature { type dataPlaneFeature struct { logger logr.Logger + defaultEnabled bool enabled bool dogstatsdEnabled bool } @@ -49,11 +51,12 @@ func (f *dataPlaneFeature) ID() feature.IDType { // Configure is used to configure the feature from a v2alpha1.DatadogAgent instance. func (f *dataPlaneFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgentSpec, _ *v2alpha1.RemoteConfigConfiguration) feature.RequiredComponents { // Check if the deprecated annotation is being used, and log a warning if so. - if featureutils.HasFeatureEnableAnnotation(dda, featureutils.EnableADPAnnotation) { + if featureutils.HasFeatureEnableAnnotation(dda, featureutils.EnableADPAnnotation) || + featureutils.HasFeatureDisableAnnotation(dda, featureutils.EnableADPAnnotation) { f.logger.Info("DEPRECATION WARNING: annotation 'agent.datadoghq.com/adp-enabled' is deprecated; use 'spec.features.dataPlane.enabled' instead") } - f.enabled = featureutils.IsDataPlaneEnabled(dda, ddaSpec) + f.enabled = featureutils.IsDataPlaneEnabled(dda, ddaSpec, f.defaultEnabled) f.dogstatsdEnabled = featureutils.IsDataPlaneDogstatsdEnabled(ddaSpec) var reqComp feature.RequiredComponents @@ -84,40 +87,40 @@ func (f *dataPlaneFeature) ManageClusterAgent(managers feature.PodTemplateManage // if SingleContainerStrategy is enabled and can be used with the configured feature set. // It should do nothing if the feature doesn't need to configure it. func (f *dataPlaneFeature) ManageSingleContainerNodeAgent(managers feature.PodTemplateManagers) error { - return f.ManageNodeAgent(managers) + return f.manageNodeAgent(managers, apicommon.UnprivilegedSingleAgentContainerName, apicommon.UnprivilegedSingleAgentContainerName) } // ManageNodeAgent allows a feature to configure the Node Agent's corev1.PodTemplateSpec // It should do nothing if the feature doesn't need to configure it. func (f *dataPlaneFeature) ManageNodeAgent(managers feature.PodTemplateManagers) error { - // We set the relevant configuration on the Core Agent specifically, which trickles down to the Data Plane when it - // queries the Core Agent for its configuration. - // - // It is also used to influence the Core Agent in terms of what it chooses to run itself or allow to be delegated to - // the data plane. - if f.enabled { - // When Data Plane is enabled, we signal this to the Core Agent by setting an environment variable. - managers.EnvVar().AddEnvVarToContainer(apicommon.CoreAgentContainerName, &corev1.EnvVar{ - Name: common.DDDataPlaneEnabled, - Value: "true", - }) + return f.manageNodeAgent(managers, apicommon.CoreAgentContainerName, apicommon.AgentDataPlaneContainerName) +} - // Configure the ADP container to fetch configuration from the Core Agent. - managers.EnvVar().AddEnvVarToContainer(apicommon.AgentDataPlaneContainerName, &corev1.EnvVar{ - Name: common.DDDataPlaneRemoteAgentEnabled, - Value: "true", - }) - managers.EnvVar().AddEnvVarToContainer(apicommon.AgentDataPlaneContainerName, &corev1.EnvVar{ - Name: common.DDDataPlaneUseNewConfigStreamEndpoint, +func (f *dataPlaneFeature) manageNodeAgent(managers feature.PodTemplateManagers, coreContainer, dataPlaneContainer apicommon.AgentContainerName) error { + if !f.enabled { + return nil + } + + // Core Agent delegates the selected pipelines to ADP. In the single-container strategy, + // both processes inherit these settings from the shared container environment. + managers.EnvVar().AddEnvVarToContainer(coreContainer, &corev1.EnvVar{ + Name: common.DDDataPlaneEnabled, + Value: "true", + }) + managers.EnvVar().AddEnvVarToContainer(dataPlaneContainer, &corev1.EnvVar{ + Name: common.DDDataPlaneRemoteAgentEnabled, + Value: "true", + }) + managers.EnvVar().AddEnvVarToContainer(dataPlaneContainer, &corev1.EnvVar{ + Name: common.DDDataPlaneUseNewConfigStreamEndpoint, + Value: "true", + }) + + if f.dogstatsdEnabled { + managers.EnvVar().AddEnvVarToContainer(coreContainer, &corev1.EnvVar{ + Name: common.DDDataPlaneDogstatsdEnabled, Value: "true", }) - - if f.dogstatsdEnabled { - managers.EnvVar().AddEnvVarToContainer(apicommon.CoreAgentContainerName, &corev1.EnvVar{ - Name: common.DDDataPlaneDogstatsdEnabled, - Value: "true", - }) - } } return nil diff --git a/internal/controller/datadogagent/feature/dataplane/feature_test.go b/internal/controller/datadogagent/feature/dataplane/feature_test.go index c300e3b0ce..1a17d783ec 100644 --- a/internal/controller/datadogagent/feature/dataplane/feature_test.go +++ b/internal/controller/datadogagent/feature/dataplane/feature_test.go @@ -8,7 +8,11 @@ package dataplane import ( "testing" + "github.com/go-logr/zapr" "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" corev1 "k8s.io/api/core/v1" apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" @@ -29,6 +33,14 @@ func Test_dataPlaneFeature(t *testing.T) { Name: common.DDDataPlaneDogstatsdEnabled, Value: "true", } + dataPlaneRemoteAgentEnabledEnvVar := &corev1.EnvVar{ + Name: common.DDDataPlaneRemoteAgentEnabled, + Value: "true", + } + dataPlaneUseNewConfigStreamEndpointEnvVar := &corev1.EnvVar{ + Name: common.DDDataPlaneUseNewConfigStreamEndpoint, + Value: "true", + } tests := test.FeatureTestSuite{ { @@ -46,12 +58,83 @@ func Test_dataPlaneFeature(t *testing.T) { ), }, { - Name: "data plane disabled (forced via annotation)", + Name: "data plane enabled by feature default", + DDA: testutils.NewDatadogAgentBuilder(). + WithNodeAgentImage("agent:7.83.0-rc.5"). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + agentEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.CoreAgentContainerName] + assert.Contains(t, agentEnvVars, dataPlaneEnabledEnvVar, "DD_DATA_PLANE_ENABLED should be set when Data Plane is enabled by the feature default") + + adpEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.AgentDataPlaneContainerName] + assert.Contains(t, adpEnvVars, dataPlaneRemoteAgentEnabledEnvVar, "DD_DATA_PLANE_REMOTE_AGENT_ENABLED should be set on Agent Data Plane when Data Plane is enabled by the feature default") + assert.Contains(t, adpEnvVars, dataPlaneUseNewConfigStreamEndpointEnvVar, "DD_DATA_PLANE_USE_NEW_CONFIG_STREAM_ENDPOINT should be set on Agent Data Plane when Data Plane is enabled by the feature default") + + dda := testutils.NewDatadogAgentBuilder().WithNodeAgentImage("agent:7.83.0-rc.5").BuildWithDefaults() + requiredComponents := buildDataPlaneFeature(&feature.Options{DefaultDataPlaneEnabled: true}).Configure(dda, &dda.Spec, dda.Status.RemoteConfigConfiguration) + assert.Contains(t, requiredComponents.Agent.Containers, apicommon.AgentDataPlaneContainerName, "Agent Data Plane should be a required Agent component when Data Plane is enabled by the feature default") + }, + ), + }, + { + Name: "data plane enabled by feature default with single container strategy", + DDA: testutils.NewDatadogAgentBuilder(). + WithSingleContainerStrategy(true). + WithDataPlaneDogstatsdEnabled(true). + WithNodeAgentImage("agent:7.83.0-rc.5"). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + singleAgentEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.UnprivilegedSingleAgentContainerName] + assert.ElementsMatch(t, []*corev1.EnvVar{ + dataPlaneEnabledEnvVar, + dataPlaneDogstatsdEnabledEnvVar, + dataPlaneRemoteAgentEnabledEnvVar, + dataPlaneUseNewConfigStreamEndpointEnvVar, + }, singleAgentEnvVars, "all Data Plane interaction flags should be set on the shared single Agent container") + assert.Empty(t, mgr.EnvVarMgr.EnvVarsByC[apicommon.CoreAgentContainerName], "single container strategy should not mutate the Core Agent container") + assert.Empty(t, mgr.EnvVarMgr.EnvVarsByC[apicommon.AgentDataPlaneContainerName], "single container strategy should not mutate the Agent Data Plane container") + }, + ), + }, + { + Name: "data plane explicitly disabled via CRD overrides feature default", + DDA: testutils.NewDatadogAgentBuilder(). + WithDataPlaneEnabled(false). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: false, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + agentEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.CoreAgentContainerName] + assert.NotContains(t, agentEnvVars, dataPlaneEnabledEnvVar, "DD_DATA_PLANE_ENABLED should not be set when the CRD explicitly disables Data Plane") + }, + ), + }, + { + Name: "data plane disabled via legacy annotation overrides feature default", DDA: testutils.NewDatadogAgentBuilder(). WithAnnotations(map[string]string{ utils.EnableADPAnnotation: "false", }). BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, WantConfigure: false, Agent: test.NewDefaultComponentTest().WithWantFunc( func(t testing.TB, mgrInterface feature.PodTemplateManagers) { @@ -61,6 +144,26 @@ func Test_dataPlaneFeature(t *testing.T) { }, ), }, + { + Name: "data plane CRD enable overrides legacy disable annotation", + DDA: testutils.NewDatadogAgentBuilder(). + WithDataPlaneEnabled(true). + WithAnnotations(map[string]string{ + utils.EnableADPAnnotation: "false", + }). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + agentEnvVars := mgr.EnvVarMgr.EnvVarsByC[apicommon.CoreAgentContainerName] + assert.Contains(t, agentEnvVars, dataPlaneEnabledEnvVar, "DD_DATA_PLANE_ENABLED should be set when the CRD explicitly enables Data Plane") + }, + ), + }, { Name: "data plane enabled via annotation (deprecated)", DDA: testutils.NewDatadogAgentBuilder(). @@ -128,3 +231,37 @@ func Test_dataPlaneFeature(t *testing.T) { tests.Run(t, buildDataPlaneFeature) } + +func TestDataPlaneFeatureManageSingleContainerNodeAgent(t *testing.T) { + managers := fake.NewPodTemplateManagers(t, corev1.PodTemplateSpec{}) + feature := &dataPlaneFeature{enabled: true, dogstatsdEnabled: true} + + assert.NoError(t, feature.ManageSingleContainerNodeAgent(managers)) + assert.ElementsMatch(t, []*corev1.EnvVar{ + {Name: common.DDDataPlaneEnabled, Value: "true"}, + {Name: common.DDDataPlaneRemoteAgentEnabled, Value: "true"}, + {Name: common.DDDataPlaneUseNewConfigStreamEndpoint, Value: "true"}, + {Name: common.DDDataPlaneDogstatsdEnabled, Value: "true"}, + }, managers.EnvVarMgr.EnvVarsByC[apicommon.UnprivilegedSingleAgentContainerName]) + + disabledManagers := fake.NewPodTemplateManagers(t, corev1.PodTemplateSpec{}) + disabledFeature := &dataPlaneFeature{} + assert.NoError(t, disabledFeature.ManageSingleContainerNodeAgent(disabledManagers)) + assert.Empty(t, disabledManagers.EnvVarMgr.EnvVarsByC[apicommon.UnprivilegedSingleAgentContainerName]) +} + +func TestDataPlaneFeatureLogsLegacyAnnotationDeprecation(t *testing.T) { + for _, annotationValue := range []string{"true", "false"} { + t.Run(annotationValue, func(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + feature := buildDataPlaneFeature(&feature.Options{Logger: zapr.NewLogger(zap.New(core))}).(*dataPlaneFeature) + dda := testutils.NewDatadogAgentBuilder(). + WithAnnotations(map[string]string{utils.EnableADPAnnotation: annotationValue}). + BuildWithDefaults() + + feature.Configure(dda, &dda.Spec, nil) + + assert.Len(t, logs.FilterMessage("DEPRECATION WARNING: annotation 'agent.datadoghq.com/adp-enabled' is deprecated; use 'spec.features.dataPlane.enabled' instead").All(), 1) + }) + } +} diff --git a/internal/controller/datadogagent/feature/dogstatsd/feature.go b/internal/controller/datadogagent/feature/dogstatsd/feature.go index 1c689c282c..2147a16175 100644 --- a/internal/controller/datadogagent/feature/dogstatsd/feature.go +++ b/internal/controller/datadogagent/feature/dogstatsd/feature.go @@ -44,6 +44,7 @@ func buildDogstatsdFeature(options *feature.Options) feature.Feature { if options != nil { dogstatsdFeat.logger = options.Logger + dogstatsdFeat.defaultDataPlaneEnabled = options.DefaultDataPlaneEnabled } return dogstatsdFeat @@ -61,6 +62,7 @@ type dogstatsdFeature struct { tagCardinality string mapperProfiles *v2alpha1.CustomConfig + defaultDataPlaneEnabled bool dataPlaneEnabled bool dataPlaneDogstatsdEnabled bool agentSupportsADPDelegation bool @@ -114,7 +116,7 @@ func (f *dogstatsdFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.Datado // non-local traffic from hostPort/local-service exposure like APM. f.nonLocalTraffic = apiutils.BoolValue(dogstatsd.NonLocalTraffic) - f.dataPlaneEnabled = featureutils.IsDataPlaneEnabled(dda, ddaSpec) + f.dataPlaneEnabled = featureutils.IsDataPlaneEnabled(dda, ddaSpec, f.defaultDataPlaneEnabled) f.dataPlaneDogstatsdEnabled = featureutils.IsDataPlaneDogstatsdEnabled(ddaSpec) f.agentSupportsADPDelegation = featureutils.AgentSupportsADPDogstatsdDelegation(ddaSpec) @@ -271,10 +273,10 @@ func (f *dogstatsdFeature) manageNodeAgent(containerName apicommon.AgentContaine Name: DDDogstatsdNonLocalTraffic, Value: strconv.FormatBool(f.nonLocalTraffic), }) - // When ADP is handling DogStatsD, the UDP port binding must go to the ADP container so that - // ADP binds port 8125 (and owns the HostPort). The Core Agent must not bind it to avoid conflicts. + // In the optimized strategy, ADP owns the UDP port binding when it handles DogStatsD. + // In the single-container strategy, Core and ADP share the input container and its port. portContainerName := containerName - if f.dataPlaneEnabled && f.dataPlaneDogstatsdEnabled { + if f.dataPlaneEnabled && f.dataPlaneDogstatsdEnabled && containerName != apicommon.UnprivilegedSingleAgentContainerName { portContainerName = apicommon.AgentDataPlaneContainerName } managers.Port().AddPortToContainer(portContainerName, dogstatsdPort) diff --git a/internal/controller/datadogagent/feature/dogstatsd/feature_test.go b/internal/controller/datadogagent/feature/dogstatsd/feature_test.go index 31c41c44c4..42972f4a23 100644 --- a/internal/controller/datadogagent/feature/dogstatsd/feature_test.go +++ b/internal/controller/datadogagent/feature/dogstatsd/feature_test.go @@ -507,6 +507,71 @@ func Test_DogstatsdFeature_Configure(t *testing.T) { }, ), }, + { + Name: "data plane feature default routes DogStatsD UDP host port to ADP", + DDA: testutils.NewDefaultDatadogAgentBuilder(). + WithDogstatsdHostPortEnabled(true). + WithNodeAgentImage("agent:7.83.0-rc.5"). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + + adpPorts := mgr.PortMgr.PortsByC[apicommon.AgentDataPlaneContainerName] + assert.NotEmpty(t, adpPorts, "ADP container should have the DogStatsD UDP host port when Data Plane is enabled by the feature default") + + coreAgentPorts := mgr.PortMgr.PortsByC[apicommon.CoreAgentContainerName] + assert.Empty(t, coreAgentPorts, "Core Agent container should not have the DogStatsD UDP host port when Data Plane is enabled by the feature default") + }, + ), + }, + { + Name: "data plane feature default keeps DogStatsD UDP host port on the single Agent container", + DDA: testutils.NewDefaultDatadogAgentBuilder(). + WithSingleContainerStrategy(true). + WithDataPlaneDogstatsdEnabled(true). + WithDogstatsdHostPortEnabled(true). + WithNodeAgentImage("agent:7.83.0-rc.5"). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + assert.ElementsMatch(t, getWantHostPorts(), mgr.PortMgr.PortsByC[apicommon.UnprivilegedSingleAgentContainerName], "single Agent container should own the DogStatsD UDP host port") + assert.Empty(t, mgr.PortMgr.PortsByC[apicommon.AgentDataPlaneContainerName], "Agent Data Plane container should not own the DogStatsD UDP host port in single container strategy") + assert.Empty(t, mgr.PortMgr.PortsByC[apicommon.CoreAgentContainerName], "Core Agent container should not own the DogStatsD UDP host port in single container strategy") + }, + ), + }, + { + Name: "data plane explicit CRD disable keeps DogStatsD UDP host port on Core Agent", + DDA: testutils.NewDefaultDatadogAgentBuilder(). + WithDataPlaneEnabled(false). + WithDogstatsdHostPortEnabled(true). + BuildWithDefaults(), + FeatureOptions: &feature.Options{ + DefaultDataPlaneEnabled: true, + }, + WantConfigure: true, + Agent: test.NewDefaultComponentTest().WithWantFunc( + func(t testing.TB, mgrInterface feature.PodTemplateManagers) { + mgr := mgrInterface.(*fake.PodTemplateManagers) + + adpPorts := mgr.PortMgr.PortsByC[apicommon.AgentDataPlaneContainerName] + assert.Empty(t, adpPorts, "ADP container should not have the DogStatsD UDP host port when the CRD explicitly disables Data Plane") + + coreAgentPorts := mgr.PortMgr.PortsByC[apicommon.CoreAgentContainerName] + assert.NotEmpty(t, coreAgentPorts, "Core Agent container should have the DogStatsD UDP host port when the CRD explicitly disables Data Plane") + }, + ), + }, { Name: "data plane + dogstatsd + host port enabled - UDP port binding on ADP container, not Core Agent", DDA: testutils.NewDefaultDatadogAgentBuilder(). diff --git a/internal/controller/datadogagent/feature/test/factory_test.go b/internal/controller/datadogagent/feature/test/factory_test.go index 5af413f553..f31d78215f 100644 --- a/internal/controller/datadogagent/feature/test/factory_test.go +++ b/internal/controller/datadogagent/feature/test/factory_test.go @@ -67,6 +67,28 @@ func TestBuilder(t *testing.T) { common.PrivateActionRunnerContainerName: false, }, }, + { + name: "Data Plane enabled by feature options with single container strategy, 1 single container", + dda: testutils.NewDatadogAgentBuilder(). + WithSingleContainerStrategy(true). + WithNodeAgentImage("agent:7.83.0-rc.5"). + BuildWithDefaults(), + featureOptions: feature.Options{ + DefaultDataPlaneEnabled: true, + }, + wantAgentContainer: map[common.AgentContainerName]bool{ + common.UnprivilegedSingleAgentContainerName: true, + common.CoreAgentContainerName: false, + common.ProcessAgentContainerName: false, + common.TraceAgentContainerName: false, + common.SystemProbeContainerName: false, + common.SecurityAgentContainerName: false, + common.OtelAgent: false, + common.HostProfiler: false, + common.AgentDataPlaneContainerName: false, + common.PrivateActionRunnerContainerName: false, + }, + }, { name: "APM enabled, 2 agents", dda: testutils.NewDatadogAgentBuilder(). diff --git a/internal/controller/datadogagent/feature/types.go b/internal/controller/datadogagent/feature/types.go index b480a4c573..9d3e66806a 100644 --- a/internal/controller/datadogagent/feature/types.go +++ b/internal/controller/datadogagent/feature/types.go @@ -192,6 +192,7 @@ type Options struct { // The operator's own ClusterRole only holds csidrivers permissions when this is // true, so features must not grant permissions to other components unless it is set. DatadogCSIDriverEnabled bool + DefaultDataPlaneEnabled bool } // BuildFunc function type used by each Feature during its factory registration. diff --git a/internal/controller/datadogagent/feature/utils/utils.go b/internal/controller/datadogagent/feature/utils/utils.go index 27bd2b6853..dd914acb9e 100644 --- a/internal/controller/datadogagent/feature/utils/utils.go +++ b/internal/controller/datadogagent/feature/utils/utils.go @@ -25,9 +25,12 @@ const ( // DogStatsD when data_plane.enabled and data_plane.dogstatsd.enabled are both true. Below this // version the Operator must set DD_USE_DOGSTATSD=false explicitly to avoid a bind conflict. ADPDogstatsdDelegationMinVersion = "7.75.0-0" - EnableADPAnnotation = "agent.datadoghq.com/adp-enabled" - EnableFineGrainedKubeletAuthz = "agent.datadoghq.com/fine-grained-kubelet-authorization-enabled" - EnableHostProfilerAnnotation = "agent.datadoghq.com/host-profiler-enabled" + // DefaultDataPlaneMinAgentVersion is the minimum Agent version that receives the Operator-level Data Plane default. + // Explicit Data Plane configuration remains available for older versions. + DefaultDataPlaneMinAgentVersion = "7.83.0-0" + EnableADPAnnotation = "agent.datadoghq.com/adp-enabled" + EnableFineGrainedKubeletAuthz = "agent.datadoghq.com/fine-grained-kubelet-authorization-enabled" + EnableHostProfilerAnnotation = "agent.datadoghq.com/host-profiler-enabled" // EnableHostProfilerSeccompAnnotation controls whether the host-profiler applies its localhost // seccomp profile (and the init container that installs it on the node). Defaults to enabled; // set to "false" to disable both the seccomp profile and its setup init container. @@ -112,20 +115,30 @@ func AgentSupportsADPDogstatsdDelegation(ddaSpec *v2alpha1.DatadogAgentSpec) boo } // IsDataPlaneEnabled returns true if the Data Plane is enabled. -// CRD configuration takes precedence over the annotation. -// If the annotation is used, a deprecation warning is logged. -func IsDataPlaneEnabled(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgentSpec) bool { - // CRD takes precedence +// CRD configuration takes precedence over the legacy annotation, which takes precedence over defaultEnabled. +func IsDataPlaneEnabled(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgentSpec, defaultEnabled bool) bool { + // CRD takes precedence. if ddaSpec.Features != nil && ddaSpec.Features.DataPlane != nil && ddaSpec.Features.DataPlane.Enabled != nil { return *ddaSpec.Features.DataPlane.Enabled } - // Fall back to annotation + // Fall back to the legacy annotation before applying the Operator default. + if HasFeatureDisableAnnotation(dda, EnableADPAnnotation) { + return false + } if HasFeatureEnableAnnotation(dda, EnableADPAnnotation) { return true } - return false + return defaultEnabled && AgentSupportsDefaultDataPlane(ddaSpec) +} + +// AgentSupportsDefaultDataPlane returns whether the selected Agent image supports the Operator-level Data Plane default. +func AgentSupportsDefaultDataPlane(ddaSpec *v2alpha1.DatadogAgentSpec) bool { + if nodeAgent, ok := ddaSpec.Override[v2alpha1.NodeAgentComponentName]; ok && nodeAgent != nil && nodeAgent.Image != nil { + return utils.IsAboveMinVersion(common.GetAgentVersionFromImage(*nodeAgent.Image), DefaultDataPlaneMinAgentVersion, nil) + } + return utils.IsAboveMinVersion(images.AgentLatestVersion, DefaultDataPlaneMinAgentVersion, nil) } // IsDataPlaneDogstatsdEnabled returns true if the Data Plane should handle DogStatsD. diff --git a/internal/controller/datadogagent/feature/utils/utils_test.go b/internal/controller/datadogagent/feature/utils/utils_test.go index d8c91a52f8..d82768262d 100644 --- a/internal/controller/datadogagent/feature/utils/utils_test.go +++ b/internal/controller/datadogagent/feature/utils/utils_test.go @@ -10,12 +10,62 @@ import ( "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/fake" ) +func TestIsDataPlaneEnabled(t *testing.T) { + withDataPlaneEnabled := func(enabled bool) *v2alpha1.DatadogAgentSpec { + return &v2alpha1.DatadogAgentSpec{ + Features: &v2alpha1.DatadogFeatures{ + DataPlane: &v2alpha1.DataPlaneFeatureConfig{Enabled: ptr.To(enabled)}, + }, + } + } + withNodeAgentImage := func(tag string) *v2alpha1.DatadogAgentSpec { + return &v2alpha1.DatadogAgentSpec{ + Override: map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ + v2alpha1.NodeAgentComponentName: { + Image: &v2alpha1.AgentImageConfig{Name: "agent", Tag: tag}, + }, + }, + } + } + + for _, tt := range []struct { + name string + annotations map[string]string + spec *v2alpha1.DatadogAgentSpec + defaultEnabled bool + want bool + }{ + {name: "runtime default disabled", spec: &v2alpha1.DatadogAgentSpec{}}, + {name: "runtime default does not enable current default Agent image below minimum version", spec: &v2alpha1.DatadogAgentSpec{}, defaultEnabled: true}, + {name: "runtime default does not enable older pinned Agent image", spec: withNodeAgentImage("7.81.0"), defaultEnabled: true}, + {name: "runtime default enables compatible pinned Agent image", spec: withNodeAgentImage("7.83.0"), defaultEnabled: true, want: true}, + {name: "explicit CRD enable overrides incompatible pinned Agent image", spec: func() *v2alpha1.DatadogAgentSpec { + spec := withNodeAgentImage("7.81.0") + spec.Features = &v2alpha1.DatadogFeatures{DataPlane: &v2alpha1.DataPlaneFeatureConfig{Enabled: ptr.To(true)}} + return spec + }(), defaultEnabled: true, want: true}, + {name: "legacy enable annotation", annotations: map[string]string{EnableADPAnnotation: "true"}, spec: &v2alpha1.DatadogAgentSpec{}, want: true}, + {name: "legacy disable annotation overrides runtime default", annotations: map[string]string{EnableADPAnnotation: "false"}, spec: &v2alpha1.DatadogAgentSpec{}, defaultEnabled: true}, + {name: "CRD enable overrides legacy disable", annotations: map[string]string{EnableADPAnnotation: "false"}, spec: withDataPlaneEnabled(true), defaultEnabled: true, want: true}, + {name: "CRD disable overrides legacy enable", annotations: map[string]string{EnableADPAnnotation: "true"}, spec: withDataPlaneEnabled(false), defaultEnabled: true}, + } { + t.Run(tt.name, func(t *testing.T) { + dda := &v2alpha1.DatadogAgent{ObjectMeta: metav1.ObjectMeta{Annotations: tt.annotations}} + assert.Equal(t, tt.want, IsDataPlaneEnabled(dda, tt.spec, tt.defaultEnabled)) + }) + } +} + func TestEnableConfigSyncForDirectSend(t *testing.T) { containers := []apicommon.AgentContainerName{ apicommon.CoreAgentContainerName, diff --git a/internal/controller/datadogagentinternal/controller.go b/internal/controller/datadogagentinternal/controller.go index baa4dbb6f2..525a24afb1 100644 --- a/internal/controller/datadogagentinternal/controller.go +++ b/internal/controller/datadogagentinternal/controller.go @@ -70,6 +70,7 @@ type ReconcilerOptions struct { UntaintControllerEnabled bool DatadogCSIDriverEnabled bool RolloutOnConfigMapChangeEnabled bool + DefaultDataPlaneLinuxEnabled bool APIReader client.Reader } @@ -128,12 +129,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, ddai *v1alpha1.DatadogAgentI return resp, err } -func (r *Reconciler) reconcilerOptionsToFeatureOptions(ctx context.Context) *feature.Options { +func (r *Reconciler) reconcilerOptionsToFeatureOptions(ctx context.Context, ddai *v1alpha1.DatadogAgentInternal) *feature.Options { + windowsProfile := isDDAILabeledWithProfile(ddai) && ddai.GetAnnotations()[kubernetes.ProviderAnnotationKey] == kubernetes.WindowsProvider + return &feature.Options{ Logger: ctrl.LoggerFrom(ctx), Client: r.apiReader, PlatformInfo: r.platformInfo, DatadogCSIDriverEnabled: r.options.DatadogCSIDriverEnabled, + DefaultDataPlaneEnabled: r.options.DefaultDataPlaneLinuxEnabled && !windowsProfile, } } diff --git a/internal/controller/datadogagentinternal/controller_test.go b/internal/controller/datadogagentinternal/controller_test.go index b64530eb22..a18591bdca 100644 --- a/internal/controller/datadogagentinternal/controller_test.go +++ b/internal/controller/datadogagentinternal/controller_test.go @@ -6,17 +6,74 @@ package datadogagentinternal import ( + "context" "reflect" "slices" + "testing" + "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" + "github.com/DataDog/datadog-operator/pkg/constants" "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" + "github.com/DataDog/datadog-operator/pkg/kubernetes" "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) +func TestDefaultDataPlaneEnabled(t *testing.T) { + tests := []struct { + name string + enabled bool + ddai *v1alpha1.DatadogAgentInternal + want bool + }{ + { + name: "ordinary DDAI uses enabled runtime option", + enabled: true, + ddai: &v1alpha1.DatadogAgentInternal{}, + want: true, + }, + { + name: "ordinary DDAI uses disabled runtime option", + enabled: false, + ddai: &v1alpha1.DatadogAgentInternal{}, + want: false, + }, + { + name: "Windows profile disables the default", + enabled: true, + ddai: &v1alpha1.DatadogAgentInternal{ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{constants.ProfileLabelKey: "windows"}, + Annotations: map[string]string{ + kubernetes.ProviderAnnotationKey: kubernetes.WindowsProvider, + }, + }}, + want: false, + }, + { + name: "unprofiled DDAI ignores Windows annotation", + enabled: true, + ddai: &v1alpha1.DatadogAgentInternal{ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + kubernetes.ProviderAnnotationKey: kubernetes.WindowsProvider, + }, + }}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Reconciler{options: ReconcilerOptions{DefaultDataPlaneLinuxEnabled: tt.enabled}} + assert.Equal(t, tt.want, r.reconcilerOptionsToFeatureOptions(context.Background(), tt.ddai).DefaultDataPlaneEnabled) + }) + } +} + func containsEnv(slice []corev1.EnvVar, name, value string) bool { for _, element := range slice { if element.Name == name && element.Value == value { diff --git a/internal/controller/datadogagentinternal/reconcile.go b/internal/controller/datadogagentinternal/reconcile.go index 858a865a9f..5d0132a035 100644 --- a/internal/controller/datadogagentinternal/reconcile.go +++ b/internal/controller/datadogagentinternal/reconcile.go @@ -59,7 +59,7 @@ func (r *Reconciler) reconcileInstance(ctx context.Context, instance *v1alpha1.D newStatus := instance.Status.DeepCopy() now := metav1.NewTime(time.Now()) - configuredFeatures, enabledFeatures, requiredComponents, unsupportedFeatures := feature.BuildFeatures(instance, &instance.Spec, instance.Status.RemoteConfigConfiguration, r.reconcilerOptionsToFeatureOptions(ctx)) + configuredFeatures, enabledFeatures, requiredComponents, unsupportedFeatures := feature.BuildFeatures(instance, &instance.Spec, instance.Status.RemoteConfigConfiguration, r.reconcilerOptionsToFeatureOptions(ctx, instance)) // update list of enabled features for metrics forwarder r.updateMetricsForwardersFeatures(instance, enabledFeatures) diff --git a/internal/controller/setup.go b/internal/controller/setup.go index 6eaa0492a9..1a16e2eb5c 100644 --- a/internal/controller/setup.go +++ b/internal/controller/setup.go @@ -57,6 +57,7 @@ type SetupOptions struct { UntaintControllerEnabled bool UntaintControllerWaitForCSIDriver bool RolloutOnConfigMapChangeEnabled bool + DefaultDataPlaneLinuxEnabled bool ClusterProviderDetector datadogagent.ProviderReader } @@ -136,6 +137,7 @@ func startDatadogAgentInternal(logger logr.Logger, mgr manager.Manager, pInfo ku UntaintControllerEnabled: options.UntaintControllerEnabled, DatadogCSIDriverEnabled: options.DatadogCSIDriverEnabled, RolloutOnConfigMapChangeEnabled: options.RolloutOnConfigMapChangeEnabled, + DefaultDataPlaneLinuxEnabled: options.DefaultDataPlaneLinuxEnabled, APIReader: mgr.GetAPIReader(), }, }).SetupWithManager(mgr, metricForwardersMgr) diff --git a/test/e2e/manifests/datadog-agent-gke-autopilot.yaml b/test/e2e/manifests/datadog-agent-gke-autopilot.yaml index 52d1b685ec..4e8d14c856 100644 --- a/test/e2e/manifests/datadog-agent-gke-autopilot.yaml +++ b/test/e2e/manifests/datadog-agent-gke-autopilot.yaml @@ -8,6 +8,12 @@ metadata: annotations: experimental.agent.datadoghq.com/autopilot: "true" spec: + override: + nodeAgent: + labels: + cloud.google.com/matching-allowlist: datadog-datadog-daemonset-exemption-v1.0.5 + image: + tag: 7.83.0-rc.5 features: logCollection: enabled: true diff --git a/test/e2e/manifests/dogstatsd/datadog-agent-dsd-udp-single-adp-default.yaml b/test/e2e/manifests/dogstatsd/datadog-agent-dsd-udp-single-adp-default.yaml new file mode 100644 index 0000000000..574e8a1f33 --- /dev/null +++ b/test/e2e/manifests/dogstatsd/datadog-agent-dsd-udp-single-adp-default.yaml @@ -0,0 +1,26 @@ +apiVersion: datadoghq.com/v2alpha1 +kind: DatadogAgent +metadata: + namespace: e2e-operator + labels: + agent.datadoghq.com/e2e-test: datadog-agent-dsd-udp-single-adp-default +spec: + global: + containerStrategy: single + registry: public.ecr.aws/datadog + kubelet: + tlsVerify: false + override: + nodeAgent: + image: + tag: 7.83.0-rc.5 + features: + # Service Discovery requires the privileged System Probe container, which is incompatible with this strategy. + serviceDiscovery: + enabled: false + dogstatsd: + hostPortConfig: + enabled: true + hostPort: 8125 + unixDomainSocketConfig: + enabled: false diff --git a/test/e2e/tests/k8s_suite/gke_autopilot_test.go b/test/e2e/tests/k8s_suite/gke_autopilot_test.go index 55e1c546be..327c70f66b 100644 --- a/test/e2e/tests/k8s_suite/gke_autopilot_test.go +++ b/test/e2e/tests/k8s_suite/gke_autopilot_test.go @@ -24,6 +24,9 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" ) const ( @@ -46,12 +49,14 @@ rbac: serviceAccount: create: false name: datadog-operator-e2e-controller-manager +env: + - name: DD_DEFAULT_DATA_PLANE_LINUX_ENABLED + value: "true" `), } ddaConfigPath, err := common.GetAbsPath(filepath.Join(common.ManifestsPath, "datadog-agent-gke-autopilot.yaml")) require.NoError(t, err) - ddaOptions := []agentwithoperatorparams.Option{ agentwithoperatorparams.WithNamespace(common.NamespaceName), agentwithoperatorparams.WithDDAConfig(agentwithoperatorparams.DDAConfig{ @@ -96,17 +101,34 @@ func (s *gkeAutopilotSuite) TestAutopilotDDA() { s.logClusterAndNodeVersions() }) + s.Run("Verify Autopilot Agent DaemonSet creation", func() { + s.Assert().EventuallyWithT(func(c *assert.CollectT) { + _, err := s.Env().KubernetesCluster.Client().AppsV1().DaemonSets(common.NamespaceName).Get( + context.TODO(), gkeAutopilotDDAName+"-agent", metav1.GetOptions{}, + ) + assert.NoError(c, err) + }, 2*time.Minute, 10*time.Second, "GKE Autopilot rejected or did not create the Agent DaemonSet") + }) + if s.T().Failed() { + s.logADPWorkloadDiagnostics(gkeAutopilotDDAName, gkeAutopilotAgentSelector) + return + } + s.Run("Verify Autopilot Agent", func() { s.Assert().EventuallyWithT(func(c *assert.CollectT) { utils.VerifyAgentPods(s.T(), c, common.NamespaceName, s.Env().KubernetesCluster.Client(), gkeAutopilotAgentSelector) utils.VerifyNumPodsForSelector(s.T(), c, common.NamespaceName, s.Env().KubernetesCluster.Client(), 1, common.ClusterAgentSelector+",agent.datadoghq.com/name="+gkeAutopilotDDAName) - agentPods, err := s.runningAgentPods() + agentPods, err := s.runningAgentPods(gkeAutopilotAgentSelector) assert.NoError(c, err) assert.NotEmpty(c, agentPods) for _, pod := range agentPods { assertContainerPresent(c, pod, systemProbeContainerName) + assertContainerPresent(c, pod, adpContainerName) + assertContainerHasEnvVar(c, pod, coreAgentContainerName, "DD_DATA_PLANE_ENABLED", "true") + assertContainerHasEnvVar(c, pod, adpContainerName, "DD_DATA_PLANE_REMOTE_AGENT_ENABLED", "true") + assertContainerHasEnvVar(c, pod, adpContainerName, "DD_DATA_PLANE_USE_NEW_CONFIG_STREAM_ENDPOINT", "true") } s.verifyAPIMetrics(c) @@ -114,6 +136,28 @@ func (s *gkeAutopilotSuite) TestAutopilotDDA() { s.verifyAPIConnections(c) }, 15*time.Minute, 30*time.Second, "could not validate GKE Autopilot Agent in time") }) + if s.T().Failed() { + s.logADPWorkloadDiagnostics(gkeAutopilotDDAName, gkeAutopilotAgentSelector) + return + } + + s.Run("Verify Autopilot Agent with ADP explicitly disabled", func() { + require.NoError(s.T(), s.setDDADataPlaneEnabled(gkeAutopilotDDAName, false)) + + s.Assert().EventuallyWithT(func(c *assert.CollectT) { + utils.VerifyAgentPods(s.T(), c, common.NamespaceName, s.Env().KubernetesCluster.Client(), gkeAutopilotAgentSelector) + + agentPods, err := s.runningAgentPods(gkeAutopilotAgentSelector) + assert.NoError(c, err) + assert.NotEmpty(c, agentPods) + for _, pod := range agentPods { + assertContainerAbsent(c, pod, adpContainerName) + } + }, 15*time.Minute, 30*time.Second, "could not validate GKE Autopilot Agent with ADP disabled in time") + }) + if s.T().Failed() { + s.logADPWorkloadDiagnostics(gkeAutopilotDDAName, gkeAutopilotAgentSelector) + } } func (s *gkeAutopilotSuite) logClusterAndNodeVersions() { @@ -171,9 +215,9 @@ func selectedNodeLabels(labels map[string]string) string { return strings.Join(selected, ",") } -func (s *gkeAutopilotSuite) runningAgentPods() ([]corev1.Pod, error) { +func (s *gkeAutopilotSuite) runningAgentPods(selector string) ([]corev1.Pod, error) { agentPods, err := s.Env().KubernetesCluster.Client().CoreV1().Pods(common.NamespaceName).List(context.TODO(), metav1.ListOptions{ - LabelSelector: gkeAutopilotAgentSelector, + LabelSelector: selector, FieldSelector: "status.phase=Running", }) if err != nil { @@ -183,6 +227,58 @@ func (s *gkeAutopilotSuite) runningAgentPods() ([]corev1.Pod, error) { return agentPods.Items, nil } +func (s *gkeAutopilotSuite) setDDADataPlaneEnabled(ddaName string, enabled bool) error { + client, err := dynamic.NewForConfig(s.Env().KubernetesCluster.KubernetesClient.K8sConfig) + if err != nil { + return err + } + + resource := client.Resource(schema.GroupVersionResource{Group: "datadoghq.com", Version: "v2alpha1", Resource: "datadogagents"}).Namespace(common.NamespaceName) + dda, err := resource.Get(context.TODO(), ddaName, metav1.GetOptions{}) + if err != nil { + return err + } + if err := unstructured.SetNestedField(dda.Object, enabled, "spec", "features", "dataPlane", "enabled"); err != nil { + return err + } + _, err = resource.Update(context.TODO(), dda, metav1.UpdateOptions{}) + return err +} + +func (s *gkeAutopilotSuite) logADPWorkloadDiagnostics(ddaName, agentSelector string) { + pods, err := s.Env().KubernetesCluster.Client().CoreV1().Pods(common.NamespaceName).List(context.TODO(), metav1.ListOptions{LabelSelector: agentSelector}) + if err != nil { + s.T().Logf("could not list Agent pods for %s: %v", ddaName, err) + } else if len(pods.Items) == 0 { + s.T().Logf("no Agent pods found for %s", ddaName) + } else { + for _, pod := range pods.Items { + s.T().Logf("Agent pod for %s: name=%s node=%s phase=%s reason=%s message=%s conditions=%v init=%v containers=%v", ddaName, pod.Name, pod.Spec.NodeName, pod.Status.Phase, pod.Status.Reason, pod.Status.Message, pod.Status.Conditions, pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses) + } + } + + client, err := dynamic.NewForConfig(s.Env().KubernetesCluster.KubernetesClient.K8sConfig) + if err != nil { + s.T().Logf("could not create dynamic client for %s: %v", ddaName, err) + return + } + ddai, err := client.Resource(schema.GroupVersionResource{Group: "datadoghq.com", Version: "v1alpha1", Resource: "datadogagentinternals"}).Namespace(common.NamespaceName).Get(context.TODO(), ddaName, metav1.GetOptions{}) + if err != nil { + s.T().Logf("could not get DatadogAgentInternal for %s: %v", ddaName, err) + return + } + status, found, err := unstructured.NestedMap(ddai.Object, "status") + if err != nil { + s.T().Logf("could not read DatadogAgentInternal status for %s: %v", ddaName, err) + return + } + if !found { + s.T().Logf("DatadogAgentInternal for %s has no status", ddaName) + return + } + s.T().Logf("DatadogAgentInternal status for %s: %v", ddaName, status) +} + func (s *gkeAutopilotSuite) verifyAPIMetrics(c *assert.CollectT) { metricNames, err := s.Env().FakeIntake.Client().GetMetricNames() assert.NoError(c, err) diff --git a/test/e2e/tests/k8s_suite/k8s_suite_test.go b/test/e2e/tests/k8s_suite/k8s_suite_test.go index 32c4e89da5..90cedbcf4c 100644 --- a/test/e2e/tests/k8s_suite/k8s_suite_test.go +++ b/test/e2e/tests/k8s_suite/k8s_suite_test.go @@ -477,6 +477,56 @@ serviceAccount: }, 3*time.Minute, 10*time.Second, "DSD UDP with ADP: metrics not received by fakeintake") }) + // --- Subtest: DSD UDP, single-container ADP enabled by the Operator default --- + s.T().Run("Single-container DSD UDP uses the Operator ADP default", func(t *testing.T) { + ddaConfigPath, err := common.GetAbsPath(filepath.Join(common.ManifestsPath, "dogstatsd", "datadog-agent-dsd-udp-single-adp-default.yaml")) + assert.NoError(s.T(), err) + senderPath, err := common.GetAbsPath(filepath.Join(common.ManifestsPath, "dogstatsd", "dsd-udp-sender.yaml")) + assert.NoError(s.T(), err) + + ddaOpts := append([]agentwithoperatorparams.Option{ + agentwithoperatorparams.WithDDAConfig(agentwithoperatorparams.DDAConfig{ + Name: "dda-dsd-udp-single-adp-default", + YamlFilePath: ddaConfigPath, + }), + }, defaultDDAOpts...) + + operatorOpts := append([]operatorparams.Option{}, defaultOperatorOpts...) + operatorOpts = append(operatorOpts, operatorparams.WithHelmValues(`env: + - name: DD_DEFAULT_DATA_PLANE_LINUX_ENABLED + value: "true" +`)) + + provisionerOpts := []provisioners.KubernetesProvisionerOption{ + provisioners.WithTestName("e2e-operator-dsd-udp-single-adp-default"), + provisioners.WithOperatorOptions(operatorOpts...), + provisioners.WithDDAOptions(ddaOpts...), + provisioners.WithYAMLWorkload(provisioners.YAMLWorkload{Name: "dsd-udp-sender", Path: senderPath}), + provisioners.WithLocal(s.local), + } + applyDDA("e2e-operator-dsd-udp-single-adp-default", provisionerOpts) + + err = s.Env().FakeIntake.Client().FlushServerAndResetAggregators() + s.Assert().NoError(err) + + agentSelector := common.NodeAgentSelector + ",agent.datadoghq.com/name=dda-dsd-udp-single-adp-default" + + s.Assert().EventuallyWithTf(func(c *assert.CollectT) { + utils.VerifyAgentPods(s.T(), c, common.NamespaceName, s.Env().KubernetesCluster.Client(), agentSelector) + + pods, err := s.Env().KubernetesCluster.Client().CoreV1().Pods(common.NamespaceName).List( + context.TODO(), metav1.ListOptions{LabelSelector: agentSelector}) + if !assert.NoError(c, err) || !assert.Len(c, pods.Items, 1, "expected one single-container Agent pod") { + return + } + s.assertSingleContainerADPRuntime(c, pods.Items[0]) + }, 5*time.Minute, 15*time.Second, "single-container DSD UDP ADP default runtime verification failed") + + s.Assert().EventuallyWithTf(func(c *assert.CollectT) { + s.verifyDSDMetrics(c, "e2e.dsd.udp.counter") + }, 3*time.Minute, 10*time.Second, "single-container DSD UDP ADP default metrics not received by fakeintake") + }) + // --- Subtest: DSD UDS, ADP disabled --- s.T().Run("DSD UDS without ADP", func(t *testing.T) { ddaConfigPath, err := common.GetAbsPath(filepath.Join(common.ManifestsPath, "dogstatsd", "datadog-agent-dsd-uds.yaml")) @@ -666,6 +716,33 @@ func assertContainerDoesNotHaveHostPort(c *assert.CollectT, pod corev1.Pod, cont } } +func (s *k8sSuite) assertSingleContainerADPRuntime(c *assert.CollectT, pod corev1.Pod) { + if !assert.Len(c, pod.Spec.Containers, 1, "expected one Agent container") { + return + } + + const containerName = "unprivileged-single-agent" + assertContainerPresent(c, pod, containerName) + for _, env := range []string{ + "DD_DATA_PLANE_ENABLED", + "DD_DATA_PLANE_DOGSTATSD_ENABLED", + "DD_DATA_PLANE_REMOTE_AGENT_ENABLED", + "DD_DATA_PLANE_USE_NEW_CONFIG_STREAM_ENDPOINT", + } { + assertContainerHasEnvVar(c, pod, containerName, env, "true") + } + assertContainerHasUDPHostPort(c, pod, containerName, dsdPort) + + socketOutput, socketErr, err := s.Env().KubernetesCluster.KubernetesClient.PodExec( + common.NamespaceName, pod.Name, containerName, []string{"sh", "-c", "ss -lunp 'sport = :8125'"}) + if !assert.NoErrorf(c, err, "could not inspect UDP/8125 with ss: %s", strings.TrimSpace(strings.Join([]string{socketOutput, socketErr}, "\n"))) { + return + } + socketDetails := strings.TrimSpace(strings.Join([]string{socketOutput, socketErr}, "\n")) + assert.Contains(c, socketDetails, ":8125", "ss did not report UDP/8125") + assert.Contains(c, socketDetails, "agent-data-plan", "ss did not identify agent-data-plane as the UDP/8125 owner") +} + func assertContainerHasEnvVar(c *assert.CollectT, pod corev1.Pod, containerName, envName, envValue string) { container := findContainer(pod, containerName) if !assert.NotNilf(c, container, "container %q not found in pod %s", containerName, pod.Name) {