diff --git a/api/datadoghq/v1alpha1/datadogagentprofile_validation.go b/api/datadoghq/v1alpha1/datadogagentprofile_validation.go index a32b905257..7001bab241 100644 --- a/api/datadoghq/v1alpha1/datadogagentprofile_validation.go +++ b/api/datadoghq/v1alpha1/datadogagentprofile_validation.go @@ -14,11 +14,11 @@ import ( ) // ValidateDatadogAgentProfileSpec is used to check if a DatadogAgentProfileSpec is valid -func ValidateDatadogAgentProfileSpec(spec *DatadogAgentProfileSpec, datadogAgentInternalEnabled bool) error { +func ValidateDatadogAgentProfileSpec(spec *DatadogAgentProfileSpec) error { if err := validateProfileAffinity(spec.ProfileAffinity); err != nil { return err } - if err := validateConfig(spec.Config, datadogAgentInternalEnabled); err != nil { + if err := validateConfig(spec.Config); err != nil { return err } @@ -39,20 +39,17 @@ func validateProfileAffinity(profileAffinity *ProfileAffinity) error { return nil } -func validateConfig(spec *v2alpha1.DatadogAgentSpec, datadogAgentInternalEnabled bool) error { +func validateConfig(spec *v2alpha1.DatadogAgentSpec) error { if spec == nil { return undefinedError("config") } - if err := validateFeatures(spec.Features, datadogAgentInternalEnabled); err != nil { + if err := validateFeatures(spec.Features); err != nil { return err } // global is not supported if spec.Global != nil { return unsupportedError("global") } - if !datadogAgentInternalEnabled && spec.Override == nil { - return undefinedError("config override") - } for component, override := range spec.Override { if err := validateOverride(component, override); err != nil { return err @@ -62,13 +59,10 @@ func validateConfig(spec *v2alpha1.DatadogAgentSpec, datadogAgentInternalEnabled return nil } -func validateFeatures(features *v2alpha1.DatadogFeatures, datadogAgentInternalEnabled bool) error { +func validateFeatures(features *v2alpha1.DatadogFeatures) error { if features == nil { return nil } - if !datadogAgentInternalEnabled { - return fmt.Errorf("the 'features' field is only supported when DatadogAgentInternal is enabled") - } // Only GPU feature is currently supported in DatadogAgentProfile context. // Remove supported features from the `unsupportedFeatures` array. diff --git a/api/datadoghq/v1alpha1/datadogagentprofile_validation_test.go b/api/datadoghq/v1alpha1/datadogagentprofile_validation_test.go index 8eabe3129d..d2af2a8fa0 100644 --- a/api/datadoghq/v1alpha1/datadogagentprofile_validation_test.go +++ b/api/datadoghq/v1alpha1/datadogagentprofile_validation_test.go @@ -155,101 +155,70 @@ func TestIsValidDatadogAgentProfile(t *testing.T) { }, }, } - invalidFeaturesNoDDAI := &DatadogAgentProfileSpec{ - ProfileAffinity: basicProfileAffinity, - Config: &v2alpha1.DatadogAgentSpec{ - Features: &v2alpha1.DatadogFeatures{ - GPU: &v2alpha1.GPUFeatureConfig{ - Enabled: ptr.To(true), - }, - }, - }, - } - testCases := []struct { - name string - spec *DatadogAgentProfileSpec - datadogAgentInternalEnabled bool - wantErr string + name string + spec *DatadogAgentProfileSpec + wantErr string }{ { - name: "valid dap", - spec: valid, - datadogAgentInternalEnabled: true, - }, - { - name: "valid dap, resources specified in one container only", - spec: validResourceOverrideInOneContainerOnly, - datadogAgentInternalEnabled: true, + name: "valid dap", + spec: valid, }, { - name: "invalid component override", - spec: invalidComponentOverride, - datadogAgentInternalEnabled: true, - wantErr: "component node selector override is not supported", + name: "valid dap, resources specified in one container only", + spec: validResourceOverrideInOneContainerOnly, }, { - name: "invalid container override", - spec: invalidContainerOverride, - datadogAgentInternalEnabled: true, - wantErr: "container command override is not supported", + name: "invalid component override", + spec: invalidComponentOverride, + wantErr: "component node selector override is not supported", }, { - name: "missing override when ddai disabled", - spec: missingOverride, - datadogAgentInternalEnabled: false, - wantErr: "config override must be defined", + name: "invalid container override", + spec: invalidContainerOverride, + wantErr: "container command override is not supported", }, { - name: "missing config", - spec: missingConfig, - datadogAgentInternalEnabled: true, - wantErr: "config must be defined", + name: "missing override is valid", + spec: missingOverride, }, { - name: "missing node selector requirement", - spec: missingNSR, - datadogAgentInternalEnabled: true, - wantErr: "profileNodeAffinity must have at least 1 requirement", + name: "missing config", + spec: missingConfig, + wantErr: "config must be defined", }, { - name: "missing profile node affinity", - spec: missingNodeAffinity, - datadogAgentInternalEnabled: true, - wantErr: "profileNodeAffinity must be defined", + name: "missing node selector requirement", + spec: missingNSR, + wantErr: "profileNodeAffinity must have at least 1 requirement", }, { - name: "missing profile affinity", - spec: missingProfileAffinity, - datadogAgentInternalEnabled: true, - wantErr: "profileAffinity must be defined", + name: "missing profile node affinity", + spec: missingNodeAffinity, + wantErr: "profileNodeAffinity must be defined", }, { - name: "gpu feature override", - spec: validGPUFeature, - datadogAgentInternalEnabled: true, + name: "missing profile affinity", + spec: missingProfileAffinity, + wantErr: "profileAffinity must be defined", }, { - name: "valid dap with features only when ddai enabled", - spec: validFeaturesNoOverride, - datadogAgentInternalEnabled: true, + name: "gpu feature override", + spec: validGPUFeature, }, { - name: "dap with unsupported feature when ddai enabled", - spec: invalidFeatures, - datadogAgentInternalEnabled: true, - wantErr: "npm override is not supported", + name: "valid dap with features only, no override", + spec: validFeaturesNoOverride, }, { - name: "features not supported when ddai disabled", - spec: invalidFeaturesNoDDAI, - datadogAgentInternalEnabled: false, - wantErr: "the 'features' field is only supported when DatadogAgentInternal is enabled", + name: "dap with unsupported feature", + spec: invalidFeatures, + wantErr: "npm override is not supported", }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - result := ValidateDatadogAgentProfileSpec(test.spec, test.datadogAgentInternalEnabled) + result := ValidateDatadogAgentProfileSpec(test.spec) if test.wantErr != "" { assert.EqualError(t, result, test.wantErr) } else { diff --git a/cmd/main.go b/cmd/main.go index 44d1212b74..cb7b987e43 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -135,7 +135,6 @@ type options struct { edsSlowStartAdditiveIncrease string supportCilium bool datadogAgentEnabled bool - datadogAgentInternalEnabled bool createControllerRevisions bool datadogMonitorEnabled bool datadogSLOEnabled bool @@ -189,7 +188,6 @@ func (opts *options) Parse() { flag.BoolVar(&opts.datadogCSIDriverEnabled, "datadogCSIDriverEnabled", false, "Enable the DatadogCSIDriver controller") // DatadogAgentInternal - flag.BoolVar(&opts.datadogAgentInternalEnabled, "datadogAgentInternalEnabled", true, "Enable the DatadogAgentInternal controller") flag.BoolVar(&opts.createControllerRevisions, "createControllerRevisions", false, "Enable creation of ControllerRevision snapshots on each DDA spec change") // ExtendedDaemonset configuration @@ -235,10 +233,6 @@ func run(opts *options) error { } version.PrintVersionLogs(setupLog) - if !opts.datadogAgentInternalEnabled { - setupLog.Error(nil, "[WARNING] DatadogAgentInternal controller is disabled. This flag will be removed in Operator v1.27 and enabling DatadogAgentInternal will be required.") - } - // submits the maximum go routine setting as a metric metrics.MaxGoroutines.Set(float64(opts.maximumGoroutines)) @@ -292,7 +286,6 @@ func run(opts *options) error { RetryPeriod: &retryPeriod, Cache: config.CacheOptions(setupLog, config.WatchOptions{ DatadogAgentEnabled: opts.datadogAgentEnabled, - DatadogAgentInternalEnabled: opts.datadogAgentInternalEnabled, DatadogMonitorEnabled: opts.datadogMonitorEnabled, DatadogSLOEnabled: opts.datadogSLOEnabled, DatadogAgentProfileEnabled: opts.datadogAgentProfileEnabled, @@ -322,6 +315,9 @@ func run(opts *options) error { // Reader interface as returned from mgr.GetAPIReader() reads directly from API server bypassing cache and informer initialization. credsManager := config.NewCredentialManagerWithDecryptor(mgr.GetAPIReader(), secrets.NewSecretBackend()) creds, err := credsManager.GetCredentials() + if err != nil { + setupLog.Error(err, "Unable to get credentials") + } if opts.secretRefreshInterval > 0 && opts.secretBackendCommand == "" { setupLog.Error(nil, "secretRefreshInterval is set but secretBackendCommand is not configured") @@ -346,29 +342,13 @@ func run(opts *options) error { } if opts.remoteUpdatesEnabled { - if rcErr := setupFleetDaemon(setupLog, mgr, rcUpdater.Client(), opts.createControllerRevisions && opts.datadogAgentInternalEnabled); rcErr != nil { + if rcErr := setupFleetDaemon(setupLog, mgr, rcUpdater.Client(), opts.createControllerRevisions && opts.datadogAgentEnabled); rcErr != nil { setupErrorf(setupLog, rcErr, "Unable to setup Fleet daemon") } } }() } - // Cleanup leftover DatadogAgentInternal resources if DDAI controller is disabled - if opts.datadogAgentEnabled && !opts.datadogAgentInternalEnabled { - go func() { - // Block until this controller manager is elected leader and controllers are set up - <-mgr.Elected() - - // Wait a bit more to ensure reconciliation has had a chance to patch ownerRefs - time.Sleep(60 * time.Second) - - setupLog.Info("Starting cleanup of DatadogAgentInternal resources") - if err = controller.CleanupDatadogAgentInternalResources(setupLog, restConfig); err != nil { - setupLog.Error(err, "Failed to cleanup DatadogAgentInternal resources") - } - }() - } - options := controller.SetupOptions{ SupportExtendedDaemonset: controller.ExtendedDaemonsetOptions{ Enabled: opts.supportExtendedDaemonset, @@ -386,8 +366,7 @@ func run(opts *options) error { SupportCilium: opts.supportCilium, CredsManager: credsManager, DatadogAgentEnabled: opts.datadogAgentEnabled, - DatadogAgentInternalEnabled: opts.datadogAgentInternalEnabled, - CreateControllerRevisions: opts.createControllerRevisions && opts.datadogAgentInternalEnabled, // Only enable if DDAI is also enabled. + CreateControllerRevisions: opts.createControllerRevisions && opts.datadogAgentEnabled, DatadogMonitorEnabled: opts.datadogMonitorEnabled, DatadogSLOEnabled: opts.datadogSLOEnabled, OperatorMetricsEnabled: opts.operatorMetricsEnabled, @@ -633,15 +612,16 @@ func setupAndStartOperatorMetadataForwarder(logger logr.Logger, client client.Re DatadogSLOEnabled: options.datadogSLOEnabled, DatadogGenericResourceEnabled: options.datadogGenericResourceEnabled, DatadogAgentProfileEnabled: options.datadogAgentProfileEnabled, - DatadogAgentInternalEnabled: options.datadogAgentInternalEnabled, - LeaderElectionEnabled: options.enableLeaderElection, - ExtendedDaemonSetEnabled: options.supportExtendedDaemonset, - RemoteConfigEnabled: options.remoteConfigEnabled, - RemoteUpdatesEnabled: options.remoteUpdatesEnabled, - IntrospectionEnabled: options.introspectionEnabled, - ConfigDDURL: os.Getenv(constants.DDURL), - ConfigDDSite: os.Getenv(constants.DDSite), - ResourceCounts: make(map[string]int), + // Since v1.27, DDAI is always tied to DDA — no separate flag. Kept in telemetry for metric continuity. + DatadogAgentInternalEnabled: options.datadogAgentEnabled, + LeaderElectionEnabled: options.enableLeaderElection, + ExtendedDaemonSetEnabled: options.supportExtendedDaemonset, + RemoteConfigEnabled: options.remoteConfigEnabled, + RemoteUpdatesEnabled: options.remoteUpdatesEnabled, + IntrospectionEnabled: options.introspectionEnabled, + ConfigDDURL: os.Getenv(constants.DDURL), + ConfigDDSite: os.Getenv(constants.DDSite), + ResourceCounts: make(map[string]int), } omf.Start() @@ -655,8 +635,9 @@ func setupAndStartCRDMetadataForwarder(logger logr.Logger, client client.Reader, version.GetVersion(), credsManager, metadata.EnabledCRDKindsConfig{ - DatadogAgentEnabled: options.datadogAgentEnabled, - DatadogAgentInternalEnabled: options.datadogAgentInternalEnabled, + DatadogAgentEnabled: options.datadogAgentEnabled, + // Since v1.27, DDAI is always tied to DDA — no separate flag. Kept in telemetry for metric continuity. + DatadogAgentInternalEnabled: options.datadogAgentEnabled, DatadogAgentProfileEnabled: options.datadogAgentProfileEnabled, }, ) diff --git a/internal/controller/datadogagent/controller.go b/internal/controller/datadogagent/controller.go index daa3bd16d4..fe75e172e4 100644 --- a/internal/controller/datadogagent/controller.go +++ b/internal/controller/datadogagent/controller.go @@ -18,7 +18,6 @@ import ( "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" componentagent "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/agent" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" "github.com/DataDog/datadog-operator/pkg/kubernetes" @@ -70,14 +69,13 @@ const ( // ReconcilerOptions provides options read from command line type ReconcilerOptions struct { - ExtendedDaemonsetOptions componentagent.ExtendedDaemonsetOptions - SupportCilium bool - OperatorMetricsEnabled bool - IntrospectionEnabled bool - DatadogAgentProfileEnabled bool - DatadogAgentInternalEnabled bool - DatadogCSIDriverEnabled bool - CreateControllerRevisions bool + ExtendedDaemonsetOptions componentagent.ExtendedDaemonsetOptions + SupportCilium bool + OperatorMetricsEnabled bool + IntrospectionEnabled bool + DatadogAgentProfileEnabled bool + DatadogCSIDriverEnabled bool + CreateControllerRevisions bool // ExperimentTimeout overrides ExperimentDefaultTimeout. Zero means use the default. ExperimentTimeout time.Duration } @@ -134,12 +132,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, dda *v2alpha1.DatadogAgent) return resp, err } -func reconcilerOptionsToFeatureOptions(opts *ReconcilerOptions, logger logr.Logger) *feature.Options { - return &feature.Options{ - Logger: logger, - } -} - // metricsForwarderProcessError convert the reconciler errors into metrics if metrics forwarder is enabled func (r *Reconciler) metricsForwarderProcessError(dda *v2alpha1.DatadogAgent, err error) { if r.options.OperatorMetricsEnabled { diff --git a/internal/controller/datadogagent/controller_reconcile_agent.go b/internal/controller/datadogagent/controller_reconcile_agent.go index 466d5a1457..141a57c915 100644 --- a/internal/controller/datadogagent/controller_reconcile_agent.go +++ b/internal/controller/datadogagent/controller_reconcile_agent.go @@ -8,321 +8,21 @@ package datadogagent import ( "context" "maps" - "time" - edsv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" - "github.com/go-logr/logr" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - utilerrors "k8s.io/apimachinery/pkg/util/errors" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" - datadoghqv2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - apiutils "github.com/DataDog/datadog-operator/api/utils" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component" - componentagent "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/agent" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/experimental" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/global" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/object" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/override" "github.com/DataDog/datadog-operator/pkg/agentprofile" - "github.com/DataDog/datadog-operator/pkg/condition" "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" - "github.com/DataDog/datadog-operator/pkg/helm" "github.com/DataDog/datadog-operator/pkg/kubernetes" ) -func (r *Reconciler) reconcileV2Agent(logger logr.Logger, requiredComponents feature.RequiredComponents, features []feature.Feature, - dda *datadoghqv2alpha1.DatadogAgent, resourcesManager feature.ResourceManagers, newStatus *datadoghqv2alpha1.DatadogAgentStatus, - provider string, providerList map[string]struct{}, profile *v1alpha1.DatadogAgentProfile) (reconcile.Result, error) { - var result reconcile.Result - var eds *edsv1alpha1.ExtendedDaemonSet - var daemonset *appsv1.DaemonSet - var podManagers feature.PodTemplateManagers - - daemonsetLogger := logger.WithValues("component", datadoghqv2alpha1.NodeAgentComponentName) - - // requiredComponents needs to be taken into account in case a feature(s) changes and - // a requiredComponent becomes disabled, in addition to taking into account override.Disabled - disabledByOverride := false - - agentEnabled := requiredComponents.Agent.IsEnabled() - singleContainerStrategyEnabled := requiredComponents.Agent.SingleContainerStrategyEnabled() - // TODO: remove this once reconcileV2 is removed - instanceName := GetAgentInstanceLabelValue(dda, profile) - - // When EDS is enabled and there are profiles defined, we only create an - // EDS for the default profile, for the other profiles we create - // DaemonSets. - // This is to make deployments simpler. With multiple EDS there would be - // multiple canaries, etc. - if (r.options.ExtendedDaemonsetOptions.Enabled && !r.options.DatadogAgentProfileEnabled) || (r.options.ExtendedDaemonsetOptions.Enabled && - r.options.DatadogAgentProfileEnabled && agentprofile.IsDefaultProfile(profile.Namespace, profile.Name)) { - // Start by creating the Default Agent extendeddaemonset - eds = componentagent.NewDefaultAgentExtendedDaemonset(dda, &r.options.ExtendedDaemonsetOptions, requiredComponents.Agent) - podManagers = feature.NewPodTemplateManagers(&eds.Spec.Template) - - // Set Global setting on the default extendeddaemonset - global.ApplyGlobalSettingsNodeAgent(logger, podManagers, dda.GetObjectMeta(), &dda.Spec, resourcesManager, singleContainerStrategyEnabled, requiredComponents) - - // Apply features changes on the Deployment.Spec.Template - for _, feat := range features { - if errFeat := feat.ManageNodeAgent(podManagers, provider); errFeat != nil { - return result, errFeat - } - } - - // If Override is defined for the node agent component, apply the override on the PodTemplateSpec, it will cascade to container. - var componentOverrides []*datadoghqv2alpha1.DatadogAgentComponentOverride - if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.NodeAgentComponentName]; ok { - componentOverrides = append(componentOverrides, componentOverride) - } - - if r.options.DatadogAgentProfileEnabled { - // Apply overrides from profiles after override from manifest, so they can override what's defined in the DDA. - overrideFromProfile := agentprofile.OverrideFromProfile(profile, false) - componentOverrides = append(componentOverrides, &overrideFromProfile) - } - - if r.options.IntrospectionEnabled { - // use the last name override in the list to generate a provider-specific name - overrideName := eds.Name - for _, componentOverride := range componentOverrides { - if componentOverride.Name != nil && *componentOverride.Name != "" { - overrideName = *componentOverride.Name - } - } - overrideFromProvider := kubernetes.ComponentOverrideFromProvider(overrideName, provider, providerList) - componentOverrides = append(componentOverrides, &overrideFromProvider) - } else { - eds.Labels[constants.MD5AgentDeploymentProviderLabelKey] = kubernetes.LegacyProvider - } - - for _, componentOverride := range componentOverrides { - if apiutils.BoolValue(componentOverride.Disabled) { - disabledByOverride = true - } - override.PodTemplateSpec(logger, podManagers, componentOverride, datadoghqv2alpha1.NodeAgentComponentName, dda.Name) - override.ExtendedDaemonSet(eds, componentOverride) - } - - experimental.ApplyExperimentalOverrides(logger, dda, podManagers) - - if disabledByOverride { - if agentEnabled { - // The override supersedes what's set in requiredComponents; update status to reflect the conflict - condition.UpdateDatadogAgentStatusConditions( - newStatus, - metav1.NewTime(time.Now()), - common.OverrideReconcileConflictConditionType, - metav1.ConditionTrue, - "OverrideConflict", - "Agent component is set to disabled", - true, - ) - } - if err := r.deleteV2ExtendedDaemonSet(daemonsetLogger, dda, eds, newStatus); err != nil { - return reconcile.Result{}, err - } - return reconcile.Result{}, nil - } - - if errs := global.ValidateFIPSVersions(podManagers); len(errs) > 0 { - return result, utilerrors.NewAggregate(errs) - } - - return r.createOrUpdateExtendedDaemonset(daemonsetLogger, dda, eds, newStatus, updateEDSStatusV2WithAgent) - } - - // Start by creating the Default Agent daemonset - daemonset = componentagent.NewDefaultAgentDaemonset(dda, &r.options.ExtendedDaemonsetOptions, requiredComponents.Agent, instanceName) - podManagers = feature.NewPodTemplateManagers(&daemonset.Spec.Template) - - // Check if this operator daemonset should have migration label (after Helm migration completed) - if helm.IsHelmMigration(dda) { - dsList := appsv1.DaemonSetList{} - if err := r.client.List(context.TODO(), &dsList, client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "Helm", - apicommon.AgentDeploymentNameLabelKey: "datadog", - }); err == nil && len(dsList.Items) == 0 { - nsName := types.NamespacedName{ - Name: daemonset.GetName(), - Namespace: daemonset.GetNamespace(), - } - existingDaemonset := &appsv1.DaemonSet{} - if err := r.client.Get(context.TODO(), nsName, existingDaemonset); err != nil { - if daemonset.Labels == nil { - daemonset.Labels = make(map[string]string) - } - daemonset.Labels[constants.MD5AgentDeploymentMigratedLabelKey] = "true" - logger.Info("Adding migration label to new operator daemonset as Helm migration has completed") - } - } - } - // Set Global setting on the default daemonset - global.ApplyGlobalSettingsNodeAgent(logger, podManagers, dda.GetObjectMeta(), &dda.Spec, resourcesManager, singleContainerStrategyEnabled, requiredComponents) - - // Apply features changes on the Deployment.Spec.Template - for _, feat := range features { - if singleContainerStrategyEnabled { - if errFeat := feat.ManageSingleContainerNodeAgent(podManagers, provider); errFeat != nil { - return result, errFeat - } - } else { - if errFeat := feat.ManageNodeAgent(podManagers, provider); errFeat != nil { - return result, errFeat - } - } - } - - // If Override is defined for the node agent component, apply the override on the PodTemplateSpec, it will cascade to container. - var componentOverrides []*datadoghqv2alpha1.DatadogAgentComponentOverride - if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.NodeAgentComponentName]; ok { - componentOverrides = append(componentOverrides, componentOverride) - } - - if r.options.DatadogAgentProfileEnabled { - // Apply overrides from profiles after override from manifest, so they can override what's defined in the DDA. - overrideFromProfile := agentprofile.OverrideFromProfile(profile, useV3Metadata(dda)) - componentOverrides = append(componentOverrides, &overrideFromProfile) - } - - if r.options.IntrospectionEnabled { - // use the last name override in the list to generate a provider-specific name - overrideName := daemonset.Name - for _, componentOverride := range componentOverrides { - if componentOverride.Name != nil && *componentOverride.Name != "" { - overrideName = *componentOverride.Name - } - } - overrideFromProvider := kubernetes.ComponentOverrideFromProvider(overrideName, provider, providerList) - componentOverrides = append(componentOverrides, &overrideFromProvider) - } else { - daemonset.Labels[constants.MD5AgentDeploymentProviderLabelKey] = kubernetes.LegacyProvider - } - - for _, componentOverride := range componentOverrides { - if apiutils.BoolValue(componentOverride.Disabled) { - disabledByOverride = true - } - override.PodTemplateSpec(logger, podManagers, componentOverride, datadoghqv2alpha1.NodeAgentComponentName, dda.Name) - override.DaemonSet(daemonset, componentOverride) - } - - experimental.ApplyExperimentalOverrides(logger, dda, podManagers) - - if disabledByOverride { - if agentEnabled { - // The override supersedes what's set in requiredComponents; update status to reflect the conflict - condition.UpdateDatadogAgentStatusConditions( - newStatus, - metav1.NewTime(time.Now()), - common.OverrideReconcileConflictConditionType, - metav1.ConditionTrue, - "OverrideConflict", - "Agent component is set to disabled", - true, - ) - } - if err := r.deleteV2DaemonSet(daemonsetLogger, dda, daemonset, newStatus); err != nil { - return reconcile.Result{}, err - } - deleteStatusWithAgent(newStatus) - return reconcile.Result{}, nil - } - - if errs := global.ValidateFIPSVersions(podManagers); len(errs) > 0 { - return result, utilerrors.NewAggregate(errs) - } - - return r.createOrUpdateDaemonset(daemonsetLogger, dda, daemonset, newStatus, updateDSStatusV2WithAgent, profile) -} - -func updateDSStatusV2WithAgent(dsName string, ds *appsv1.DaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) { - newStatus.AgentList = condition.UpdateDaemonSetStatus(dsName, ds, newStatus.AgentList, &updateTime) - condition.UpdateDatadogAgentStatusConditions(newStatus, updateTime, common.AgentReconcileConditionType, status, reason, message, true) - newStatus.Agent = condition.UpdateCombinedDaemonSetStatus(newStatus.AgentList) -} - -func updateEDSStatusV2WithAgent(eds *edsv1alpha1.ExtendedDaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) { - newStatus.AgentList = condition.UpdateExtendedDaemonSetStatus(eds, newStatus.AgentList, &updateTime) - condition.UpdateDatadogAgentStatusConditions(newStatus, updateTime, common.AgentReconcileConditionType, status, reason, message, true) - newStatus.Agent = condition.UpdateCombinedDaemonSetStatus(newStatus.AgentList) -} - -func (r *Reconciler) deleteV2DaemonSet(logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, ds *appsv1.DaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus) error { - err := r.client.Delete(context.TODO(), ds) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err - } - logger.Info("Delete DaemonSet", "daemonSet.Namespace", ds.Namespace, "daemonSet.Name", ds.Name) - event := buildEventInfo(ds.Name, ds.Namespace, kubernetes.DaemonSetKind, datadog.DeletionEvent) - r.recordEvent(dda, event) - removeStaleStatus(newStatus, ds.Name) - - return nil -} - -func (r *Reconciler) deleteV2ExtendedDaemonSet(logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, eds *edsv1alpha1.ExtendedDaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus) error { - err := r.client.Delete(context.TODO(), eds) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err - } - logger.Info("Delete DaemonSet", "extendedDaemonSet.Namespace", eds.Namespace, "extendedDaemonSet.Name", eds.Name) - event := buildEventInfo(eds.Name, eds.Namespace, kubernetes.ExtendedDaemonSetKind, datadog.DeletionEvent) - r.recordEvent(dda, event) - removeStaleStatus(newStatus, eds.Name) - - return nil -} - -func deleteStatusWithAgent(newStatus *datadoghqv2alpha1.DatadogAgentStatus) { - newStatus.Agent = nil - condition.DeleteDatadogAgentStatusCondition(newStatus, common.AgentReconcileConditionType) -} - -// removeStaleStatus removes a DaemonSet's status from a DatadogAgent's -// status based on the DaemonSet's name -func removeStaleStatus(ddaStatus *datadoghqv2alpha1.DatadogAgentStatus, name string) { - if ddaStatus != nil { - for i, dsStatus := range ddaStatus.AgentList { - if dsStatus.DaemonsetName == name { - newStatus := make([]*datadoghqv2alpha1.DaemonSetStatus, 0, len(ddaStatus.AgentList)-1) - newStatus = append(newStatus, ddaStatus.AgentList[:i]...) - ddaStatus.AgentList = append(newStatus, ddaStatus.AgentList[i+1:]...) - } - } - } -} - -func (r *Reconciler) handleProfiles(ctx context.Context, profilesByNode map[string]types.NamespacedName, ddaNamespace string) error { - if err := r.labelNodesWithProfiles(ctx, profilesByNode); err != nil { - return err - } - - if err := r.cleanupPodsForProfilesThatNoLongerApply(ctx, profilesByNode, ddaNamespace); err != nil { - return err - } - - return nil -} - // labelNodesWithProfiles sets the "agent.datadoghq.com/datadogagentprofile" label only in // the nodes where a profile is applied func (r *Reconciler) labelNodesWithProfiles(ctx context.Context, profilesByNode map[string]types.NamespacedName) error { @@ -444,75 +144,6 @@ func (r *Reconciler) cleanupPodsForProfilesThatNoLongerApply(ctx context.Context return nil } -// cleanupExtraneousDaemonSets deletes DSs/EDSs that no longer apply. -// Use cases include deleting old DSs/EDSs when: -// - a DaemonSet's name is changed using node overrides -// - introspection is disabled or enabled -// - a profile is deleted -func (r *Reconciler) cleanupExtraneousDaemonSets(ctx context.Context, logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, newStatus *datadoghqv2alpha1.DatadogAgentStatus, - providerList map[string]struct{}, profiles []v1alpha1.DatadogAgentProfile) error { - matchLabels := client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: object.NewPartOfLabelValue(dda).String(), - } - - dsName := component.GetDaemonSetNameFromDatadogAgent(dda, &dda.Spec) - - validDaemonSetNames, validExtendedDaemonSetNames := r.getValidDaemonSetNames(dsName, providerList, profiles, useV3Metadata(dda)) - // log computed valid names for debugging - vd := make([]string, 0, len(validDaemonSetNames)) - for n := range validDaemonSetNames { - vd = append(vd, n) - } - veds := make([]string, 0, len(validExtendedDaemonSetNames)) - for n := range validExtendedDaemonSetNames { - veds = append(veds, n) - } - - // Safety guard: if no valid names could be computed, skip cleanup to avoid - // deleting all DaemonSets during transient cache hiccups (e.g., empty provider list). - if len(validDaemonSetNames) == 0 && len(validExtendedDaemonSetNames) == 0 { - logger.Info("Skipping cleanup of DaemonSets: no valid names computed", "providersLen", len(providerList), "profilesLen", len(profiles)) - return nil - } - - // Only the default profile uses an EDS when profiles are enabled - // Multiple EDSs can be created with introspection - if r.options.ExtendedDaemonsetOptions.Enabled { - edsList := edsv1alpha1.ExtendedDaemonSetList{} - if err := r.client.List(ctx, &edsList, matchLabels); err != nil { - return err - } - logger.V(1).Info("Listed ExtendedDaemonSets for cleanup", "count", len(edsList.Items)) - for _, eds := range edsList.Items { - if _, ok := validExtendedDaemonSetNames[eds.Name]; !ok { - logger.Info("Candidate ExtendedDaemonSet deletion", "name", eds.Name) - if err := r.deleteV2ExtendedDaemonSet(logger, dda, &eds, newStatus); err != nil { - return err - } - } - } - } - - daemonSetList := appsv1.DaemonSetList{} - if err := r.client.List(ctx, &daemonSetList, matchLabels); err != nil { - return err - } - - logger.V(1).Info("Listed DaemonSets for cleanup", "count", len(daemonSetList.Items)) - for _, daemonSet := range daemonSetList.Items { - if _, ok := validDaemonSetNames[daemonSet.Name]; !ok { - logger.Info("Candidate DaemonSet deletion", "name", daemonSet.Name) - if err := r.deleteV2DaemonSet(logger, dda, &daemonSet, newStatus); err != nil { - return err - } - } - } - - return nil -} - // getValidDaemonSetNames generates a list of valid DS and EDS names func (r *Reconciler) getValidDaemonSetNames(dsName string, providerList map[string]struct{}, profiles []v1alpha1.DatadogAgentProfile, useV3Metadata bool) (map[string]struct{}, map[string]struct{}) { validDaemonSetNames := map[string]struct{}{} diff --git a/internal/controller/datadogagent/controller_reconcile_agent_test.go b/internal/controller/datadogagent/controller_reconcile_agent_test.go index 73dcdde854..a163dcb2d9 100644 --- a/internal/controller/datadogagent/controller_reconcile_agent_test.go +++ b/internal/controller/datadogagent/controller_reconcile_agent_test.go @@ -15,18 +15,14 @@ import ( "github.com/DataDog/datadog-operator/pkg/constants" "github.com/DataDog/datadog-operator/pkg/kubernetes" - edsdatadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" ) const defaultProvider = kubernetes.DefaultProvider @@ -364,1244 +360,6 @@ func Test_getDaemonSetNameFromDatadogAgent(t *testing.T) { }) } } - -func Test_cleanupExtraneousDaemonSets(t *testing.T) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - _ = edsdatadoghqv1alpha1.AddToScheme(sch) - ctx := context.Background() - - testCases := []struct { - name string - description string - existingAgents []client.Object - introspectionEnabled bool - profilesEnabled bool - edsEnabled bool - providerList map[string]struct{} - profiles []v1alpha1.DatadogAgentProfile - wantDS *appsv1.DaemonSetList - wantEDS *edsdatadoghqv1alpha1.ExtendedDaemonSetList - }{ - { - name: "no unused ds, introspection disabled, profiles disabled", - description: "DS `dda-foo-agent` should not be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: false, - profilesEnabled: false, - edsEnabled: false, - providerList: map[string]struct{}{}, - profiles: []v1alpha1.DatadogAgentProfile{}, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "no unused eds, introspection disabled, profiles disabled", - description: "EDS `dda-foo-agent` should not be deleted", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: false, - profilesEnabled: false, - edsEnabled: true, - providerList: map[string]struct{}{}, - profiles: []v1alpha1.DatadogAgentProfile{}, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{}, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - }, - { - name: "no unused ds, introspection enabled, profiles enabled", - description: "DS `profile-1-agent-gke-cos` should not be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: false, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "no unused eds, introspection enabled, profiles enabled", - description: "EDS `dda-foo-agent-gke-cos` should not be deleted. The EDS name comes from the default profile", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{}, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - }, - { - name: "multiple unused ds, introspection enabled, profiles enabled", - description: "All DS except `profile-1-agent-gke-cos` should be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }}, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: false, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "multiple unused eds, introspection enabled, profiles enabled", - description: "All but EDS `dda-foo-agent-gke-cos` and DS `profile-1-agent-gke-cos` should be deleted", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - }, - { - name: "multiple unused ds, introspection enabled, profiles disabled", - description: "All DS except `dda-foo-agent-gke-cos` should be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }}, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }}, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: false, - edsEnabled: false, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "multiple unused eds, introspection enabled, profiles disabled", - description: "All but EDS `dda-foo-agent-gke-cos` should be deleted", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: false, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{}, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - }, - { - name: "multiple unused ds, introspection disabled, profiles enabled", - description: "DS `profile-1-agent-gke-cos` should be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }}, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: false, - profilesEnabled: true, - edsEnabled: false, - providerList: map[string]struct{}{ - kubernetes.LegacyProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "default", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "multiple unused eds, introspection disabled, profiles enabled", - description: "All but EDS `dda-foo-agent` and DS `profile-1-agent` should be deleted", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - introspectionEnabled: false, - profilesEnabled: true, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - ResourceVersion: "999", - }, - }, - }, - }, - }, - { - name: "DSs are not created by the operator (do not have the expected labels) and should not be removed", - description: "No DSs should be deleted", - existingAgents: []client.Object{ - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: false, - providerList: map[string]struct{}{ - kubernetes.LegacyProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "default", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "EDSs are not created by the operator (do not have the expected labels) and should not be removed", - description: "Nothing should be deleted", - existingAgents: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - }, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - }, - }, - }, - }, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - ResourceVersion: "999", - }, - }, - }, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent", - Namespace: "ns-1", - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1-agent-gke-cos", - Namespace: "ns-1", - Labels: map[string]string{ - constants.MD5AgentDeploymentProviderLabelKey: gkeCosProvider, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }, - ResourceVersion: "999", - }, - }, - }, - }, - }, - { - name: "no existing ds, introspection enabled, profiles enabled", - description: "DS list should be empty (nothing to delete)", - existingAgents: []client.Object{}, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: false, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{}, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - { - name: "no existing eds, introspection enabled, profiles enabled", - description: "DS and EDS list should be empty (nothing to delete)", - existingAgents: []client.Object{}, - introspectionEnabled: true, - profilesEnabled: true, - edsEnabled: true, - providerList: map[string]struct{}{ - gkeCosProvider: {}, - }, - profiles: []v1alpha1.DatadogAgentProfile{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "profile-1", - Namespace: "ns-1", - }, - }, - }, - wantDS: &appsv1.DaemonSetList{ - Items: []appsv1.DaemonSet{}, - }, - wantEDS: &edsdatadoghqv1alpha1.ExtendedDaemonSetList{ - Items: []edsdatadoghqv1alpha1.ExtendedDaemonSet{}, - }, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(tt.existingAgents...).Build() - logger := logf.Log.WithName("test_cleanupExtraneousDaemonSets") - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "test_cleanupExtraneousDaemonSets"}) - - r := &Reconciler{ - client: fakeClient, - log: logger, - recorder: recorder, - options: ReconcilerOptions{ - IntrospectionEnabled: tt.introspectionEnabled, - DatadogAgentProfileEnabled: tt.profilesEnabled, - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: tt.edsEnabled, - }, - }, - } - - dda := datadoghqv2alpha1.DatadogAgent{ - TypeMeta: metav1.TypeMeta{ - Kind: "DatadogAgent", - APIVersion: "datadoghq.com/v2alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo", - Namespace: "ns-1", - }, - } - ddaStatus := datadoghqv2alpha1.DatadogAgentStatus{} - - err := r.cleanupExtraneousDaemonSets(ctx, logger, &dda, &ddaStatus, tt.providerList, tt.profiles) - assert.NoError(t, err) - - dsList := &appsv1.DaemonSetList{} - edsList := &edsdatadoghqv1alpha1.ExtendedDaemonSetList{} - - err = fakeClient.List(ctx, dsList) - assert.NoError(t, err) - err = fakeClient.List(ctx, edsList) - assert.NoError(t, err) - - assert.Equal(t, tt.wantDS, dsList) - assert.Equal(t, tt.wantEDS, edsList) - }) - } -} - -func Test_removeStaleStatus(t *testing.T) { - testCases := []struct { - name string - ddaStatus *datadoghqv2alpha1.DatadogAgentStatus - dsName string - wantStatus *datadoghqv2alpha1.DatadogAgentStatus - }{ - { - name: "no status to delete", - ddaStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{ - { - Desired: 1, - Current: 1, - Ready: 1, - Available: 1, - DaemonsetName: "foo", - }, - }, - }, - dsName: "bar", - wantStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{ - { - Desired: 1, - Current: 1, - Ready: 1, - Available: 1, - DaemonsetName: "foo", - }, - }, - }, - }, - { - name: "delete status", - ddaStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{ - { - Desired: 1, - Current: 1, - Ready: 1, - Available: 1, - DaemonsetName: "foo", - }, - { - Desired: 2, - Current: 2, - Ready: 1, - Available: 1, - UpToDate: 1, - DaemonsetName: "bar", - }, - }, - }, - dsName: "bar", - wantStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{ - { - Desired: 1, - Current: 1, - Ready: 1, - Available: 1, - DaemonsetName: "foo", - }, - }, - }, - }, - { - name: "delete only status", - ddaStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{ - { - Desired: 2, - Current: 2, - Ready: 1, - Available: 1, - UpToDate: 1, - DaemonsetName: "bar", - }, - }, - }, - dsName: "bar", - wantStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{}, - }, - }, - { - name: "agent status is empty", - ddaStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{}, - }, - dsName: "bar", - wantStatus: &datadoghqv2alpha1.DatadogAgentStatus{ - AgentList: []*datadoghqv2alpha1.DaemonSetStatus{}, - }, - }, - { - name: "dda status is empty", - ddaStatus: &datadoghqv2alpha1.DatadogAgentStatus{}, - dsName: "bar", - wantStatus: &datadoghqv2alpha1.DatadogAgentStatus{}, - }, - { - name: "status is nil", - ddaStatus: nil, - dsName: "bar", - wantStatus: nil, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - removeStaleStatus(tt.ddaStatus, tt.dsName) - assert.Equal(t, tt.wantStatus, tt.ddaStatus) - }) - } -} - func Test_labelNodesWithProfiles(t *testing.T) { sch := runtime.NewScheme() _ = scheme.AddToScheme(sch) diff --git a/internal/controller/datadogagent/controller_reconcile_ccr_test.go b/internal/controller/datadogagent/controller_reconcile_ccr_test.go deleted file mode 100644 index c7ba9fdc09..0000000000 --- a/internal/controller/datadogagent/controller_reconcile_ccr_test.go +++ /dev/null @@ -1,273 +0,0 @@ -package datadogagent - -import ( - "context" - "testing" - - "k8s.io/utils/ptr" - - apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" - datadoghqv2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/kubernetes" - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_getDeploymentNameFromCCR(t *testing.T) { - testCases := []struct { - name string - dda *datadoghqv2alpha1.DatadogAgent - wantDeploymentName string - }{ - { - name: "ccr no override", - dda: &datadoghqv2alpha1.DatadogAgent{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, - wantDeploymentName: "foo-cluster-checks-runner", - }, - { - name: "ccr override with no name override", - dda: &datadoghqv2alpha1.DatadogAgent{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: datadoghqv2alpha1.DatadogAgentSpec{ - Override: map[datadoghqv2alpha1.ComponentName]*datadoghqv2alpha1.DatadogAgentComponentOverride{ - datadoghqv2alpha1.ClusterAgentComponentName: { - Replicas: ptr.To[int32](10), - }, - }, - }, - }, - wantDeploymentName: "foo-cluster-checks-runner", - }, - { - name: "ccr override with name override", - dda: &datadoghqv2alpha1.DatadogAgent{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: datadoghqv2alpha1.DatadogAgentSpec{ - Override: map[datadoghqv2alpha1.ComponentName]*datadoghqv2alpha1.DatadogAgentComponentOverride{ - datadoghqv2alpha1.ClusterChecksRunnerComponentName: { - Name: ptr.To("bar"), - Replicas: ptr.To[int32](10), - }, - }, - }, - }, - wantDeploymentName: "bar", - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - deploymentName := getDeploymentNameFromCCR(tt.dda) - assert.Equal(t, tt.wantDeploymentName, deploymentName) - }) - } -} - -func Test_cleanupOldCCRDeployments(t *testing.T) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - ctx := context.Background() - - testCases := []struct { - name string - description string - existingAgents []client.Object - wantDeployment *appsv1.DeploymentList - }{ - { - name: "no unused CCR deployments", - description: "DCA deployment `dda-foo-cluster-checks-runner` should not be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - }, - { - name: "multiple unused CCR deployments", - description: "all deployments except `dda-foo-cluster-checks-runner` should be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo-ccr", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-ccr", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - }, - { - name: "deployments are not created by the operator (do not have the expected labels) and should not be removed", - description: "No deployments should be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - Namespace: "ns-1", - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-one-cluster-checks-runner", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-two-cluster-checks-runner", - Namespace: "ns-1", - Labels: map[string]string{ - "bar": "foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-one-cluster-checks-runner", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-two-cluster-checks-runner", - Namespace: "ns-1", - Labels: map[string]string{ - "bar": "foo", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-checks-runner", - Namespace: "ns-1", - ResourceVersion: "999", - }, - }, - }, - }, - }, - } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(tt.existingAgents...).Build() - logger := logf.Log.WithName("Test_cleanupOldCCRDeployments") - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "Test_cleanupOldCCRDeployments"}) - - r := &Reconciler{ - client: fakeClient, - log: logger, - recorder: recorder, - } - - dda := datadoghqv2alpha1.DatadogAgent{ - TypeMeta: metav1.TypeMeta{ - Kind: "DatadogAgent", - APIVersion: "datadoghq.com/v2alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo", - Namespace: "ns-1", - }, - } - - err := r.cleanupOldCCRDeployments(ctx, logger, &dda) - assert.NoError(t, err) - - deploymentList := &appsv1.DeploymentList{} - - err = fakeClient.List(ctx, deploymentList) - assert.NoError(t, err) - - assert.Equal(t, tt.wantDeployment, deploymentList) - }) - } -} diff --git a/internal/controller/datadogagent/controller_reconcile_dca_test.go b/internal/controller/datadogagent/controller_reconcile_dca_test.go deleted file mode 100644 index 832f258ad9..0000000000 --- a/internal/controller/datadogagent/controller_reconcile_dca_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package datadogagent - -import ( - "context" - "testing" - - apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" - datadoghqv2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/defaults" - "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/kubernetes" - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_cleanupOldDCADeployments(t *testing.T) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - ctx := context.Background() - - testCases := []struct { - name string - description string - existingAgents []client.Object - wantDeployment *appsv1.DeploymentList - }{ - { - name: "no unused DCA deployments", - description: "DCA deployment `dda-foo-cluster-agent` should not be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - }, - { - name: "multiple unused DCA deployments", - description: "all deployments except `dda-foo-cluster-agent` should be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo-dca", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-dca", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - ResourceVersion: "999", - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: "ns--1-dda--foo", - }, - }, - }, - }, - }, - }, - { - name: "deployments are not created by the operator (do not have the expected labels) and should not be removed", - description: "No deployments should be deleted", - existingAgents: []client.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - Namespace: "ns-1", - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-one-cluster-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-two-cluster-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "bar": "foo", - }, - }, - }, - }, - wantDeployment: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-one-cluster-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "foo": "bar", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "datadog-test-two-cluster-agent", - Namespace: "ns-1", - Labels: map[string]string{ - "bar": "foo", - }, - ResourceVersion: "999", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo-cluster-agent", - Namespace: "ns-1", - ResourceVersion: "999", - }, - }, - }, - }, - }, - } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(tt.existingAgents...).Build() - logger := logf.Log.WithName("Test_cleanupOldDCADeployments") - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "Test_cleanupOldDCADeployments"}) - - r := &Reconciler{ - client: fakeClient, - log: logger, - recorder: recorder, - } - instance := &datadoghqv2alpha1.DatadogAgent{} - instanceCopy := instance.DeepCopy() - defaults.DefaultDatadogAgentSpec(&instanceCopy.Spec) - - dda := datadoghqv2alpha1.DatadogAgent{ - TypeMeta: metav1.TypeMeta{ - Kind: "DatadogAgent", - APIVersion: "datadoghq.com/v2alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dda-foo", - Namespace: "ns-1", - }, - } - - err := r.cleanupOldDCADeployments(ctx, logger, &dda) - assert.NoError(t, err) - - deploymentList := &appsv1.DeploymentList{} - - err = fakeClient.List(ctx, deploymentList) - assert.NoError(t, err) - - assert.Equal(t, tt.wantDeployment, deploymentList) - }) - } -} diff --git a/internal/controller/datadogagent/controller_reconcile_deployment_test.go b/internal/controller/datadogagent/controller_reconcile_deployment_test.go deleted file mode 100644 index 2f60ea41ef..0000000000 --- a/internal/controller/datadogagent/controller_reconcile_deployment_test.go +++ /dev/null @@ -1,602 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016-present Datadog, Inc. - -package datadogagent - -import ( - "context" - "fmt" - "testing" - "time" - - "k8s.io/utils/ptr" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" - "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" - "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" - agenttestutils "github.com/DataDog/datadog-operator/internal/controller/datadogagent/testutils" - "github.com/DataDog/datadog-operator/pkg/kubernetes" - "github.com/DataDog/datadog-operator/pkg/testutils" -) - -// TestDeploymentReconciliationDifferences tests the accidental differences between DCA and CCR reconciliation logic -// by running full reconcile cycles and verifying component-specific behavior -func TestDeploymentReconciliationDifferences(t *testing.T) { - const resourcesName = "test-dda" - const resourcesNamespace = "test-namespace" - - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "TestDeploymentReconciliation"}) - forwarders := dummyManager{} - - logf.SetLogger(zap.New(zap.UseDevMode(true))) - logger := logf.Log.WithName("TestDeploymentReconciliation") - s := agenttestutils.TestScheme() - defaultRequeueDuration := 15 * time.Second - - // Load CRD from config folder - crd, err := getDDAICRDFromConfig(s) - assert.NoError(t, err) - - tests := []struct { - name string - client client.Client - scheme *runtime.Scheme - platformInfo kubernetes.PlatformInfo - recorder record.EventRecorder - loadFunc func(c client.Client) *v2alpha1.DatadogAgent - want reconcile.Result - wantErr bool - wantFunc func(t *testing.T, c client.Client) error - description string - }{ - // Test 1: Override Conflict Detection - Both components should set OverrideConflictCondition - { - name: "DCA override conflict detection via full reconcile", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - Build() - // But disable via override to create conflict - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Create(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify override conflict condition is set - dda := &v2alpha1.DatadogAgent{} - err := c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - found := false - for _, cond := range dda.Status.Conditions { - if cond.Type == common.OverrideReconcileConflictConditionType && - cond.Status == metav1.ConditionTrue && - cond.Reason == "OverrideConflict" { - found = true - assert.Contains(t, cond.Message, "clusterAgent component is set to disabled") - break - } - } - assert.True(t, found, "Expected override conflict condition for DCA") - - // Verify no DCA deployment exists - deploymentList := &appsv1.DeploymentList{} - err = c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - for _, deployment := range deploymentList.Items { - assert.NotContains(t, deployment.Name, "cluster-agent", "DCA deployment should not exist when disabled via override") - } - return nil - }, - description: "DCA should detect override conflict and set appropriate condition", - }, - { - name: "CCR override conflict detection via full reconcile", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithClusterChecksUseCLCEnabled(true). // Enable CCR in features - Build() - // But disable CCR via override to create conflict - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterChecksRunnerComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Create(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - dda := &v2alpha1.DatadogAgent{} - err := c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - found := false - for _, cond := range dda.Status.Conditions { - if cond.Type == common.OverrideReconcileConflictConditionType && - cond.Status == metav1.ConditionTrue && - cond.Reason == "OverrideConflict" { - found = true - assert.Contains(t, cond.Message, "clusterChecksRunner component is set to disabled") - break - } - } - assert.True(t, found, "Expected override conflict condition for CCR") - - // Verify DCA deployment exists but CCR does not - deploymentList := &appsv1.DeploymentList{} - err = c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - dcaFound := false - for _, deployment := range deploymentList.Items { - if deployment.Labels[apicommon.AgentDeploymentComponentLabelKey] == "cluster-agent" { - dcaFound = true - } - assert.NotContains(t, deployment.Name, "cluster-checks-runner", "CCR deployment should not exist when disabled via override") - } - assert.True(t, dcaFound, "DCA deployment should exist") - return nil - }, - description: "CCR should detect override conflict and set appropriate condition", - }, - - // Test 2: Component Dependency Logic - CCR depends on DCA being enabled - { - name: "CCR dependency check - DCA disabled", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithClusterChecksUseCLCEnabled(true). // Enable CCR in features - Build() - // But disable DCA - CCR should be cleaned up due to dependency - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Create(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify neither DCA nor CCR deployments exist - deploymentList := &appsv1.DeploymentList{} - err := c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - for _, deployment := range deploymentList.Items { - assert.NotContains(t, deployment.Name, "cluster-agent", "DCA deployment should not exist when disabled") - assert.NotContains(t, deployment.Name, "cluster-checks-runner", "CCR deployment should not exist when DCA is disabled") - } - - // Verify status reflects cleanup - dda := &v2alpha1.DatadogAgent{} - err = c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // controller_reconcile_v2.go line 207 adds generated token regardless of whether the component is disabled - assert.NotNil(t, dda.Status.ClusterAgent, "DCA status should be nil when disabled") - assert.Equal(t, dda.Status.ClusterAgent.State, "", "DCA status is empty when disabled") - assert.Equal(t, dda.Status.ClusterAgent.Status, "", "DCA status is empty when disabled") - - assert.Nil(t, dda.Status.ClusterChecksRunner, "CCR status should be nil when DCA dependency is disabled") - return nil - }, - description: "CCR should be cleaned up when DCA dependency is disabled", - }, - - // Test 3: Cleanup Status Handling - Verify status is properly cleaned up - { - name: "Deployment cleanup and status handling", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - // First create DDA with both components enabled - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithClusterChecksUseCLCEnabled(true). - Build() - _ = c.Create(context.TODO(), dda) - - // Simulate first reconcile by creating deployments - dcaDeployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-cluster-agent", resourcesName), - Namespace: resourcesNamespace, - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: "cluster-agent", - }, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "test"}}}, - }, - }, - } - _ = c.Create(context.TODO(), dcaDeployment) - - // Now disable both components to test cleanup - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - v2alpha1.ClusterChecksRunnerComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Update(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify deployments are cleaned up - deploymentList := &appsv1.DeploymentList{} - err := c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - for _, deployment := range deploymentList.Items { - assert.NotContains(t, deployment.Name, "cluster-agent", "DCA deployment should be cleaned up") - assert.NotContains(t, deployment.Name, "cluster-checks-runner", "CCR deployment should be cleaned up") - } - - // Verify status conditions are cleaned up - dda := &v2alpha1.DatadogAgent{} - err = c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // Both statuses should be cleaned up - // TODO: controller_reconcile_v2.go line 207 adds generated token regardless of whether the component is disabled - assert.NotNil(t, dda.Status.ClusterAgent, "DCA status should be nil when disabled") - assert.Equal(t, dda.Status.ClusterAgent.State, "", "DCA status is empty when disabled") - assert.Equal(t, dda.Status.ClusterAgent.Status, "", "DCA status is empty when disabled") - assert.Nil(t, dda.Status.ClusterChecksRunner, "CCR status should be nil after cleanup") - - // Related conditions should be removed - // TODO: controller_reconcile_v2.go line 189 adds the reconcile_success condition regardless of whether the component is disabled - // for _, cond := range dda.Status.Conditions { - // assert.NotEqual(t, common.ClusterAgentReconcileConditionType, cond.Type, - // "DCA condition should be deleted after cleanup") - // assert.NotEqual(t, common.ClusterChecksRunnerReconcileConditionType, cond.Type, - // "CCR condition should be deleted after cleanup") - // } - return nil - }, - description: "Both DCA and CCR should properly clean up deployments and status when disabled", - }, - - // Test 4: Successful deployment creation for comparison - { - name: "Both DCA and CCR successful deployment", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithClusterChecksUseCLCEnabled(true). - Build() - _ = c.Create(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify both deployments are created - deploymentList := &appsv1.DeploymentList{} - err := c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - dcaFound := false - ccrFound := false - for _, deployment := range deploymentList.Items { - if deployment.Labels[apicommon.AgentDeploymentComponentLabelKey] == "cluster-agent" { - dcaFound = true - } - if deployment.Labels[apicommon.AgentDeploymentComponentLabelKey] == "cluster-checks-runner" { - ccrFound = true - } - } - assert.True(t, dcaFound, "DCA deployment should be created") - assert.True(t, ccrFound, "CCR deployment should be created") - - // Verify status is updated for both components - dda := &v2alpha1.DatadogAgent{} - err = c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - assert.NotNil(t, dda.Status.ClusterAgent, "DCA status should be set") - assert.NotNil(t, dda.Status.ClusterChecksRunner, "CCR status should be set") - return nil - }, - description: "Both DCA and CCR should create deployments and update status when enabled", - }, - - // Test 5: Error Handling Differences - DCA vs CCR behavior when deployment doesn't exist during cleanup - { - name: "DCA cleanup when deployment doesn't exist", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - // Create DDA with DCA enabled first - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - Build() - _ = c.Create(context.TODO(), dda) - - // Set status as if DCA was previously created - dda.Status.ClusterAgent = &v2alpha1.DeploymentStatus{ - State: "Running", - } - _ = c.Status().Update(context.TODO(), dda) - - // Now disable DCA via override (but don't create the deployment) - // This simulates the case where deployment doesn't exist but status does - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Update(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify DCA status is cleaned up even though deployment didn't exist - dda := &v2alpha1.DatadogAgent{} - err := c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // TODO: This currently fails for DCA due to line 123 early return - // DCA returns early on NotFound without cleaning up status - // CCR would clean up status properly - // This test documents the difference that should be unified - if dda.Status.ClusterAgent != nil { - t.Log("DCA status not cleaned up when deployment doesn't exist - this is the bug we're documenting") - // For now, just document the current behavior - assert.NotNil(t, dda.Status.ClusterAgent, "DCA currently doesn't clean up status when deployment not found") - } else { - assert.Nil(t, dda.Status.ClusterAgent, "DCA status should be cleaned up even when deployment doesn't exist") - } - return nil - }, - description: "DCA should clean up status even when deployment doesn't exist during cleanup", - }, - { - name: "CCR cleanup when deployment doesn't exist", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - // Create DDA with CCR enabled first - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithClusterChecksUseCLCEnabled(true). - Build() - _ = c.Create(context.TODO(), dda) - - // Set status as if CCR was previously created - dda.Status.ClusterChecksRunner = &v2alpha1.DeploymentStatus{ - State: "Running", - } - _ = c.Status().Update(context.TODO(), dda) - - // Now disable CCR via override (but don't create the deployment) - // This simulates the case where deployment doesn't exist but status does - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterChecksRunnerComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Update(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify CCR status is cleaned up even though deployment didn't exist - dda := &v2alpha1.DatadogAgent{} - err := c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // CCR should properly clean up status even when deployment doesn't exist - // due to better error handling (line 113-118 vs line 123) - assert.Nil(t, dda.Status.ClusterChecksRunner, "CCR status should be cleaned up even when deployment doesn't exist") - return nil - }, - description: "CCR should clean up status even when deployment doesn't exist during cleanup", - }, - { - name: "DCA cleanup when deployment exists", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - // Create DDA with DCA enabled - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - Build() - _ = c.Create(context.TODO(), dda) - - // Create the actual DCA deployment - dcaDeployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-cluster-agent", resourcesName), - Namespace: resourcesNamespace, - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: "cluster-agent", - }, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "test"}}}, - }, - }, - } - _ = c.Create(context.TODO(), dcaDeployment) - - // Set status as if DCA is running - dda.Status.ClusterAgent = &v2alpha1.DeploymentStatus{ - State: "Running", - } - _ = c.Status().Update(context.TODO(), dda) - - // Now disable DCA via override to trigger cleanup of existing deployment - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Update(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify deployment is deleted - deploymentList := &appsv1.DeploymentList{} - err := c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - for _, deployment := range deploymentList.Items { - assert.NotContains(t, deployment.Name, "cluster-agent", "DCA deployment should be deleted when disabled") - } - - // Verify status is cleaned up - dda := &v2alpha1.DatadogAgent{} - err = c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // Both DCA and CCR should clean up status when deployment exists and gets deleted - // controller_reconcile_v2.go line 207 adds generated token regardless of whether the component is disabled - assert.NotNil(t, dda.Status.ClusterAgent, "DCA status structure exists due to token generation") - assert.Equal(t, "", dda.Status.ClusterAgent.State, "DCA status should be empty when disabled") - return nil - }, - description: "DCA should delete deployment and clean up status when deployment exists during cleanup", - }, - - // Test 6: RBAC Cleanup - DCA should clean up associated RBAC resources during cleanup - { - name: "DCA RBAC cleanup during deployment cleanup", - client: fake.NewClientBuilder().WithObjects(crd).WithStatusSubresource(&appsv1.Deployment{}, &v2alpha1.DatadogAgent{}).Build(), - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - // Create DDA with DCA enabled - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - Build() - _ = c.Create(context.TODO(), dda) - - // Create the actual DCA deployment to trigger cleanup path - dcaDeployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-cluster-agent", resourcesName), - Namespace: resourcesNamespace, - Labels: map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: "cluster-agent", - }, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "test"}}}, - }, - }, - } - _ = c.Create(context.TODO(), dcaDeployment) - - // Now disable DCA via override to trigger cleanup including RBAC cleanup - dda.Spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.ClusterAgentComponentName: { - Disabled: ptr.To(true), - }, - } - _ = c.Update(context.TODO(), dda) - return dda - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) error { - // Verify deployment is deleted (same as before) - deploymentList := &appsv1.DeploymentList{} - err := c.List(context.TODO(), deploymentList, client.InNamespace(resourcesNamespace)) - require.NoError(t, err) - - for _, deployment := range deploymentList.Items { - assert.NotContains(t, deployment.Name, "cluster-agent", "DCA deployment should be deleted when disabled") - } - - // Verify RBAC cleanup was attempted - this test documents that DCA cleanup calls cleanupRelatedResourcesDCA - // which includes RBAC deletion logic that CCR doesn't have - // In a real environment with proper RBAC resources, we'd verify ServiceAccount, Role, and ClusterRole are deleted - // For this test, we're documenting the behavioral difference that DCA has additional cleanup logic - - // Verify status is cleaned up - dda := &v2alpha1.DatadogAgent{} - err = c.Get(context.TODO(), types.NamespacedName{Name: resourcesName, Namespace: resourcesNamespace}, dda) - require.NoError(t, err) - - // DCA should clean up status after RBAC cleanup completes - assert.NotNil(t, dda.Status.ClusterAgent, "DCA status structure exists due to token generation") - assert.Equal(t, "", dda.Status.ClusterAgent.State, "DCA status should be empty when disabled") - return nil - }, - description: "DCA should clean up RBAC resources during deployment cleanup (unlike CCR which has no RBAC cleanup)", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fieldManager, err := newFieldManager(tt.client, s, v1alpha1.GroupVersion.WithKind("DatadogAgentInternal")) - - r, _ := NewReconciler(ReconcilerOptions{ - DatadogAgentInternalEnabled: false, - }, tt.client, tt.platformInfo, s, logger, recorder, forwarders) - r.fieldManager = fieldManager - - // Setup DatadogAgent - dda := tt.loadFunc(tt.client) - - // Run full reconcile - result, err := r.Reconcile(context.TODO(), dda) - - // Verify results - if tt.wantErr { - assert.Error(t, err, tt.description) - } else { - assert.NoError(t, err, tt.description) - } - - if !tt.wantErr { - assert.Equal(t, tt.want, result, tt.description) - } - - // Run verification function - if tt.wantFunc != nil { - err := tt.wantFunc(t, tt.client) - assert.NoError(t, err, "Verification failed for test: %s", tt.description) - } - }) - } -} diff --git a/internal/controller/datadogagent/controller_reconcile_v2.go b/internal/controller/datadogagent/controller_reconcile_v2.go index 7bacd49fe7..d6147c0179 100644 --- a/internal/controller/datadogagent/controller_reconcile_v2.go +++ b/internal/controller/datadogagent/controller_reconcile_v2.go @@ -21,13 +21,11 @@ import ( "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component" "github.com/DataDog/datadog-operator/internal/controller/datadogagent/defaults" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" "github.com/DataDog/datadog-operator/internal/controller/finalizer" "github.com/DataDog/datadog-operator/pkg/agentprofile" "github.com/DataDog/datadog-operator/pkg/condition" "github.com/DataDog/datadog-operator/pkg/controller/utils" pkgutils "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" - "github.com/DataDog/datadog-operator/pkg/kubernetes" ) func (r *Reconciler) internalReconcileV2(ctx context.Context, instance *datadoghqv2alpha1.DatadogAgent) (reconcile.Result, error) { @@ -51,10 +49,7 @@ func (r *Reconciler) internalReconcileV2(ctx context.Context, instance *datadogh defaults.DefaultDatadogAgentSpec(&instanceCopy.Spec) // 4. Delegate to the main reconcile function. - if r.options.DatadogAgentInternalEnabled { - return r.reconcileInstanceV3(ctx, reqLogger, instanceCopy) - } - return r.reconcileInstanceV2(ctx, reqLogger, instanceCopy) + return r.reconcileInstanceV3(ctx, reqLogger, instanceCopy) } func (r *Reconciler) reconcileInstanceV3(ctx context.Context, logger logr.Logger, instance *datadoghqv2alpha1.DatadogAgent) (reconcile.Result, error) { @@ -146,109 +141,6 @@ func (r *Reconciler) reconcileInstanceV3(ctx context.Context, logger logr.Logger return r.updateStatusIfNeededV2(logger, instance, newDDAStatus, result, err, now) } -func (r *Reconciler) reconcileInstanceV2(ctx context.Context, logger logr.Logger, instance *datadoghqv2alpha1.DatadogAgent) (reconcile.Result, error) { - var result reconcile.Result - newStatus := instance.Status.DeepCopy() - now := metav1.Now() - - configuredFeatures, enabledFeatures, requiredComponents := feature.BuildFeatures(instance, &instance.Spec, instance.Status.RemoteConfigConfiguration, reconcilerOptionsToFeatureOptions(&r.options, r.log)) - // update list of enabled features for metrics forwarder - r.updateMetricsForwardersFeatures(instance, enabledFeatures) - - // 1. Manage dependencies. - depsStore, resourceManagers := r.setupDependencies(instance, logger) - - providerList := map[string]struct{}{kubernetes.LegacyProvider: {}} - k8sProvider := kubernetes.LegacyProvider - if r.options.IntrospectionEnabled { - nodeList, err := r.getNodeList(ctx) - if err != nil { - return reconcile.Result{}, err - } - providerList = kubernetes.GetProviderListFromNodeList(nodeList, logger) - - k8sProvider = kubernetes.DefaultProvider - if len(providerList) == 1 { - for provider := range providerList { - k8sProvider = provider - break - } - } else if len(providerList) == 2 { - if _, ok := providerList[kubernetes.DefaultProvider]; ok { - for provider := range providerList { - if provider != kubernetes.DefaultProvider { - k8sProvider = provider - logger.Info("Multiple providers detected, using selected provider for cluster agent and dependencies", "provider", k8sProvider) - break - } - } - } else { - logger.Error(nil, "Multiple specialized providers detected, falling back to default provider for cluster agent and dependencies", "selected_provider", k8sProvider) - } - } else { - logger.Error(nil, "Multiple specialized providers detected, falling back to default provider for cluster agent and dependencies", "selected_provider", k8sProvider) - } - } - - var err error - if err = r.manageGlobalDependencies(logger, instance, resourceManagers, requiredComponents); err != nil { - return r.updateStatusIfNeededV2(logger, instance, newStatus, reconcile.Result{}, err, now) - } - if err = r.manageFeatureDependencies(logger, enabledFeatures, resourceManagers, k8sProvider); err != nil { - return r.updateStatusIfNeededV2(logger, instance, newStatus, reconcile.Result{}, err, now) - } - if err = r.overrideDependencies(logger, resourceManagers, instance); err != nil { - return r.updateStatusIfNeededV2(logger, instance, newStatus, reconcile.Result{}, err, now) - } - - // 1. Apply and cleanup dependencies before reconciling components to ensure deps exist at reconciliation time. - if err = r.applyAndCleanupDependencies(ctx, logger, depsStore); err != nil { - return r.updateStatusIfNeededV2(logger, instance, newStatus, reconcile.Result{}, err, now) - } - - // 2. Reconcile each component using the component registry - params := &ReconcileComponentParams{ - Logger: logger, - DDA: instance, - RequiredComponents: requiredComponents, - Features: append(configuredFeatures, enabledFeatures...), - ResourceManagers: resourceManagers, - Status: newStatus, - Provider: k8sProvider, - ProviderList: providerList, - } - - result, err = r.componentRegistry.ReconcileComponents(ctx, params) - if utils.ShouldReturn(result, err) { - return r.updateStatusIfNeededV2(logger, instance, newStatus, result, err, now) - } - - // 2.b. Node Agent and profiles - // TODO: ignore profiles and introspection for DDAI - - if result, err = r.reconcileAgentProfiles(ctx, logger, instance, requiredComponents, append(configuredFeatures, enabledFeatures...), resourceManagers, newStatus, now); utils.ShouldReturn(result, err) { - return r.updateStatusIfNeededV2(logger, instance, newStatus, result, err, now) - } - - // TODO: this feels like it should be moved somewhere else - userSpecifiedClusterAgentToken := instance.Spec.Global.ClusterAgentToken != nil || instance.Spec.Global.ClusterAgentTokenSecret != nil - if !userSpecifiedClusterAgentToken { - ensureAutoGeneratedTokenInStatus(instance, newStatus, resourceManagers, logger) - } - - // 3. Cleanup extraneous resources. - if err = r.cleanupExtraneousResources(ctx, logger, instance, newStatus, resourceManagers); err != nil { - logger.Error(err, "Error cleaning up extraneous resources") - return r.updateStatusIfNeededV2(logger, instance, newStatus, result, err, now) - } - - // Always requeue - if result.IsZero() { - result.RequeueAfter = defaultRequeuePeriod - } - return r.updateStatusIfNeededV2(logger, instance, newStatus, result, err, now) -} - func (r *Reconciler) updateStatusIfNeededV2(logger logr.Logger, agentdeployment *datadoghqv2alpha1.DatadogAgent, newStatus *datadoghqv2alpha1.DatadogAgentStatus, result reconcile.Result, currentError error, now metav1.Time) (reconcile.Result, error) { if currentError == nil { condition.UpdateDatadogAgentStatusConditions(newStatus, now, common.DatadogAgentReconcileErrorConditionType, metav1.ConditionFalse, "DatadogAgent_reconcile_ok", "DatadogAgent reconcile ok", false) @@ -274,29 +166,6 @@ func (r *Reconciler) updateStatusIfNeededV2(logger logr.Logger, agentdeployment return result, currentError } -func (r *Reconciler) updateDAPStatus(ctx context.Context, logger logr.Logger, profile *datadoghqv1alpha1.DatadogAgentProfile, oldStatus *datadoghqv1alpha1.DatadogAgentProfileStatus) (reconcile.Result, error) { - // update dap status for non-default profiles only - if !agentprofile.IsDefaultProfile(profile.Namespace, profile.Name) { - if !agentprofile.IsEqualStatus(oldStatus, &profile.Status) { - // Update a deep copy to avoid mutating the in-memory object used later - toUpdate := profile.DeepCopy() - if err := r.client.Status().Update(ctx, toUpdate); err != nil { - if apierrors.IsConflict(err) { - logger.V(1).Info("unable to update DatadogAgentProfile status due to update conflict") - return reconcile.Result{RequeueAfter: time.Second}, nil - } - if apierrors.IsNotFound(err) { - // Profile deleted between list and update; no action needed - return reconcile.Result{}, nil - } - logger.Error(err, "unable to update DatadogAgentProfile status") - return reconcile.Result{}, err - } - } - } - return reconcile.Result{}, nil -} - // setMetricsForwarderStatus sets the metrics forwarder status condition if enabled func (r *Reconciler) setMetricsForwarderStatusV2(logger logr.Logger, agentdeployment *datadoghqv2alpha1.DatadogAgent, newStatus *datadoghqv2alpha1.DatadogAgentStatus) { if r.options.OperatorMetricsEnabled { @@ -316,59 +185,6 @@ func (r *Reconciler) setMetricsForwarderStatusV2(logger logr.Logger, agentdeploy } } -func (r *Reconciler) updateMetricsForwardersFeatures(dda *datadoghqv2alpha1.DatadogAgent, features []feature.Feature) { - if r.forwarders != nil { - featureIDs := make([]string, len(features)) - for i, f := range features { - featureIDs[i] = string(f.ID()) - } - - r.forwarders.SetEnabledFeatures(dda, featureIDs) - } -} - -// profilesToApply gets a list of profiles and returns the ones that should be -// applied in the cluster. -// - If there are no profiles, it returns the default profile. -// - If there are no conflicting profiles, it returns all the profiles plus the default one. -// - If there are conflicting profiles, it returns a subset that does not -// conflict plus the default one. When there are conflicting profiles, the -// oldest one is the one that takes precedence. When two profiles share an -// identical creation timestamp, the profile whose name is alphabetically first -// is considered to have priority. -// This function also returns a map that maps each node name to the profile that -// should be applied to it. -func (r *Reconciler) profilesToApply(ctx context.Context, logger logr.Logger, nodeList []corev1.Node, now metav1.Time, ddaSpec *datadoghqv2alpha1.DatadogAgentSpec) ([]datadoghqv1alpha1.DatadogAgentProfile, map[string]types.NamespacedName, error) { - profilesList := datadoghqv1alpha1.DatadogAgentProfileList{} - err := r.client.List(ctx, &profilesList) - if err != nil { - logger.Info("unable to list DatadogAgentProfiles", "error", err) - } - - profileAppliedByNode := make(map[string]types.NamespacedName, len(nodeList)) - sortedProfiles := agentprofile.SortProfiles(profilesList.Items) - profileListToApply := make([]datadoghqv1alpha1.DatadogAgentProfile, 0, len(sortedProfiles)) - for _, profile := range sortedProfiles { - maxUnavailable := agentprofile.GetMaxUnavailable(logger, ddaSpec, &profile, len(nodeList), &r.options.ExtendedDaemonsetOptions) - oldStatus := profile.Status - profileAppliedByNode, err = agentprofile.ApplyProfile(logger, &profile, nodeList, profileAppliedByNode, now, maxUnavailable, r.options.DatadogAgentInternalEnabled) - if result, e := r.updateDAPStatus(ctx, logger, &profile, &oldStatus); utils.ShouldReturn(result, e) { - logger.Info("unable to update DatadogAgentProfile status", "error", e, "requeueAfter", result.RequeueAfter, "requeueIntent", !result.IsZero()) - } - if err != nil { - // profile is invalid or conflicts - logger.Error(err, "profile cannot be applied", "datadogagentprofile", profile.Name, "datadogagentprofile_namespace", profile.Namespace) - continue - } - profileListToApply = append(profileListToApply, profile) - } - - // add default profile - profileListToApply = agentprofile.ApplyDefaultProfile(profileListToApply, profileAppliedByNode, nodeList) - - return profileListToApply, profileAppliedByNode, nil -} - func (r *Reconciler) getNodeList(ctx context.Context) ([]corev1.Node, error) { nodeList := corev1.NodeList{} err := r.client.List(ctx, &nodeList) diff --git a/internal/controller/datadogagent/controller_reconcile_v2_common.go b/internal/controller/datadogagent/controller_reconcile_v2_common.go index 89deeee536..6f97c57e02 100644 --- a/internal/controller/datadogagent/controller_reconcile_v2_common.go +++ b/internal/controller/datadogagent/controller_reconcile_v2_common.go @@ -7,15 +7,12 @@ package datadogagent import ( "context" - "errors" "fmt" "maps" "slices" - "strconv" "strings" "time" - edsv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -29,7 +26,6 @@ import ( apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/pkg/agentprofile" "github.com/DataDog/datadog-operator/pkg/condition" "github.com/DataDog/datadog-operator/pkg/constants" "github.com/DataDog/datadog-operator/pkg/controller/utils/comparison" @@ -42,13 +38,9 @@ const ( updateSucceeded = "UpdateSucceeded" createSucceeded = "CreateSucceeded" patchSucceeded = "PatchSucceeded" - - profileWaitForCanaryKey = "agent.datadoghq.com/profile-wait-for-canary" ) type updateDepStatusComponentFunc func(deployment *appsv1.Deployment, newStatus *v2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) -type updateDSStatusComponentFunc func(daemonsetName string, daemonset *appsv1.DaemonSet, newStatus *v2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) -type updateEDSStatusComponentFunc func(eds *edsv1alpha1.ExtendedDaemonSet, newStatus *v2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *v2alpha1.DatadogAgent, deployment *appsv1.Deployment, newStatus *v2alpha1.DatadogAgentStatus, updateStatusFunc updateDepStatusComponentFunc) (reconcile.Result, error) { logger := parentLogger.WithValues("deployment.Namespace", deployment.Namespace, "deployment.Name", deployment.Name) @@ -165,427 +157,6 @@ func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *v2a return result, err } -func (r *Reconciler) createOrUpdateDaemonset(parentLogger logr.Logger, dda *v2alpha1.DatadogAgent, daemonset *appsv1.DaemonSet, newStatus *v2alpha1.DatadogAgentStatus, updateStatusFunc updateDSStatusComponentFunc, profile *v1alpha1.DatadogAgentProfile) (reconcile.Result, error) { - logger := parentLogger.WithValues("daemonset.Namespace", daemonset.Namespace, "daemonset.Name", daemonset.Name) - - var result reconcile.Result - var err error - - // Set DatadogAgent instance as the owner and controller - if err = controllerutil.SetControllerReference(dda, daemonset, r.scheme); err != nil { - return reconcile.Result{}, err - } - - // Get the current daemonset and compare - currentDaemonset, err := r.getCurrentDaemonset(dda, daemonset) - if err != nil { - logger.Error(err, "unexpected error during daemonset get") - return reconcile.Result{}, err - } - - alreadyExists := true - if currentDaemonset == nil { - logger.Info("daemonset is not found") - alreadyExists = false - } - - if alreadyExists { - // check owner reference - if shouldUpdateOwnerReference(currentDaemonset.OwnerReferences) { - logger.Info("Updating Daemonset owner reference") - now := metav1.NewTime(time.Now()) - patch, e := createOwnerReferencePatch(currentDaemonset.OwnerReferences, dda, dda.GetObjectKind().GroupVersionKind()) - if e != nil { - logger.Error(e, "Unable to patch Daemonset owner reference") - updateStatusFunc(currentDaemonset.Name, currentDaemonset, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to patch Daemonset owner reference") - return reconcile.Result{}, e - } - // use merge patch to replace the entire existing owner reference list - err = r.client.Patch(context.TODO(), currentDaemonset, client.RawPatch(types.MergePatchType, patch)) - if err != nil { - logger.Error(err, "Unable to patch Daemonset owner reference") - updateStatusFunc(currentDaemonset.Name, currentDaemonset, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to patch Daemonset owner reference") - return reconcile.Result{}, err - } - logger.Info("Daemonset owner reference patched") - } - - if restartDaemonset(daemonset, currentDaemonset) { - if err = deleteObjectAndOrphanDependents(context.TODO(), logger, r.client, daemonset, constants.DefaultAgentResourceSuffix); err != nil { - return result, err - } - return result, nil - } - - now := metav1.Now() - if agentprofile.CreateStrategyEnabled() { - if profile.Status.CreateStrategy != nil { - profile.Status.CreateStrategy.PodsReady = currentDaemonset.Status.NumberReady - } - if shouldCheckCreateStrategyStatus(profile) { - newStatus := v1alpha1.WaitingStatus - - if int(profile.Status.CreateStrategy.NodesLabeled-currentDaemonset.Status.NumberReady) < int(profile.Status.CreateStrategy.MaxUnavailable) { - newStatus = v1alpha1.InProgressStatus - } - - if profile.Status.CreateStrategy.Status != newStatus { - profile.Status.CreateStrategy.LastTransition = &now - } - profile.Status.CreateStrategy.Status = newStatus - } - oldStatus := profile.Status - r.updateDAPStatus(context.TODO(), logger, profile, &oldStatus) - } - - // When overriding node labels in <1.7.0, the hash could be updated - // without updating the pod template spec in <1.7.0 since pod template - // labels were copied over directly from the existing daemonset. - // With operator <1.7.0, it would look like: - // 1. Set override node label `abc: def` - // a. Daemonset annotation: `agentspechash: 12345` - // 2. Change label to `abc: xyz` - // a. Daemonset annotation: `agentspechash: 67890` - // b. Pod template spec still has `abc: def` (set in step 1) - // To ensure the pod template label updates, we compare the existing - // daemonset's pod template labels with the new daemonset's pod - // template labels. - var currentDaemonsetPodTemplateLabelHash string - currentDaemonsetPodTemplateLabelHash, err = comparison.GenerateMD5ForSpec(currentDaemonset.Spec.Template.Labels) - if err != nil { - return result, err - } - - // From here the PodTemplateSpec should be ready, we can generate the hash that will be used to compare this daemonset with the current one (if it exists). - var hash, daemonsetPodTemplateLabelHash string - hash, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&daemonset.ObjectMeta, daemonset.Spec) - if err != nil { - return result, err - } - // create a separate hash to compare pod template labels - daemonsetPodTemplateLabelHash, err = comparison.GenerateMD5ForSpec(daemonset.Spec.Template.Labels) - if err != nil { - return result, err - } - - // check if same hash - needUpdate := !comparison.IsSameSpecMD5Hash(hash, currentDaemonset.GetAnnotations()) || currentDaemonsetPodTemplateLabelHash != daemonsetPodTemplateLabelHash - if !needUpdate { - // Even if the DaemonSet is still the same, its status might have - // changed (for example, the number of pods ready). This call is - // needed to keep the agent status updated. - newStatus.AgentList = condition.UpdateDaemonSetStatus(currentDaemonset.Name, currentDaemonset, newStatus.AgentList, &now) - newStatus.Agent = condition.UpdateCombinedDaemonSetStatus(newStatus.AgentList) - - // Stop reconcile loop since DaemonSet hasn't changed - return reconcile.Result{}, nil - } - - // TODO: these parameters can be added to the override.PodTemplateSpec. (It exists in v1alpha1) - keepAnnotationsFilter := "" - keepLabelsFilter := "" - - // Copy possibly changed fields - updateDaemonset := daemonset.DeepCopy() - updateDaemonset.Spec = *daemonset.Spec.DeepCopy() - updateDaemonset.Annotations = mergeAnnotationsLabels(logger, currentDaemonset.GetAnnotations(), daemonset.GetAnnotations(), keepAnnotationsFilter) - updateDaemonset.Labels = mergeAnnotationsLabels(logger, currentDaemonset.GetLabels(), daemonset.GetLabels(), keepLabelsFilter) - // manually remove the old profile label because mergeAnnotationsLabels - // won't filter labels with "datadoghq.com" in the key - delete(updateDaemonset.Labels, agentprofile.OldProfileLabelKey) - - updateProfileDS := true - if shouldProfileWaitForCanary(logger, dda.Annotations) { - ddaLastSpecUpdate := getDDALastUpdatedTime(dda.ManagedFields, dda.CreationTimestamp) - updateProfileDS, err = r.shouldUpdateProfileDaemonSet(profile, ddaLastSpecUpdate, now) - } - if err != nil { - return result, err - } - - if updateProfileDS { - logger.Info("Updating Daemonset") - - err = kubernetes.UpdateFromObject(context.TODO(), r.client, updateDaemonset, currentDaemonset.ObjectMeta) - if err != nil { - updateStatusFunc(updateDaemonset.Name, updateDaemonset, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to update Daemonset") - return reconcile.Result{}, err - } - event := buildEventInfo(updateDaemonset.Name, updateDaemonset.Namespace, kubernetes.DaemonSetKind, datadog.UpdateEvent) - r.recordEvent(dda, event) - updateStatusFunc(updateDaemonset.Name, updateDaemonset, newStatus, now, metav1.ConditionTrue, updateSucceeded, "Daemonset updated") - } - } else { - // From here the PodTemplateSpec should be ready, we can generate the hash that will be added to this daemonset. - _, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&daemonset.ObjectMeta, daemonset.Spec) - if err != nil { - return result, err - } - - now := metav1.Now() - logger.Info("Creating Daemonset") - - err = r.client.Create(context.TODO(), daemonset) - if err != nil { - updateStatusFunc(daemonset.Name, nil, newStatus, now, metav1.ConditionFalse, createSucceeded, "Unable to create Daemonset") - return reconcile.Result{}, err - } - event := buildEventInfo(daemonset.Name, daemonset.Namespace, kubernetes.DaemonSetKind, datadog.CreationEvent) - r.recordEvent(dda, event) - updateStatusFunc(daemonset.Name, daemonset, newStatus, now, metav1.ConditionTrue, createSucceeded, "Daemonset created") - } - - return result, err -} - -func (r *Reconciler) createOrUpdateExtendedDaemonset(parentLogger logr.Logger, dda *v2alpha1.DatadogAgent, eds *edsv1alpha1.ExtendedDaemonSet, newStatus *v2alpha1.DatadogAgentStatus, updateStatusFunc updateEDSStatusComponentFunc) (reconcile.Result, error) { - logger := parentLogger.WithValues("ExtendedDaemonSet.Namespace", eds.Namespace, "ExtendedDaemonSet.Name", eds.Name) - - var result reconcile.Result - var err error - - // Set DatadogAgent instance as the owner and controller - if err = controllerutil.SetControllerReference(dda, eds, r.scheme); err != nil { - return reconcile.Result{}, err - } - - // From here the PodTemplateSpec should be ready, we can generate the hash that will be used to compare this extendeddaemonset with the current one (if it exists). - var hash string - hash, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&eds.ObjectMeta, eds.Spec) - if err != nil { - return result, err - } - - // Get the current extendeddaemonset and compare - nsName := types.NamespacedName{ - Name: eds.GetName(), - Namespace: eds.GetNamespace(), - } - - currentEDS := &edsv1alpha1.ExtendedDaemonSet{} - alreadyExists := true - err = r.client.Get(context.TODO(), nsName, currentEDS) - if err != nil { - if apierrors.IsNotFound(err) { - logger.Info("ExtendedDaemonSet is not found") - alreadyExists = false - } else { - logger.Error(err, "unexpected error during ExtendedDaemonSet get") - return reconcile.Result{}, err - } - } - - if alreadyExists { - // check owner reference - if shouldUpdateOwnerReference(currentEDS.OwnerReferences) { - logger.Info("Updating ExtendedDaemonSet owner reference") - now := metav1.NewTime(time.Now()) - patch, e := createOwnerReferencePatch(currentEDS.OwnerReferences, dda, dda.GetObjectKind().GroupVersionKind()) - if e != nil { - logger.Error(e, "Unable to patch ExtendedDaemonSet owner reference") - updateStatusFunc(currentEDS, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to patch ExtendedDaemonSet owner reference") - return reconcile.Result{}, e - } - // use merge patch to replace the entire existing owner reference list - err = r.client.Patch(context.TODO(), currentEDS, client.RawPatch(types.MergePatchType, patch)) - if err != nil { - logger.Error(err, "Unable to patch ExtendedDaemonSet owner reference") - updateStatusFunc(currentEDS, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to patch ExtendedDaemonSet owner reference") - return reconcile.Result{}, err - } - logger.Info("ExtendedDaemonSet owner reference patched") - } - // check if same hash - needUpdate := !comparison.IsSameSpecMD5Hash(hash, currentEDS.GetAnnotations()) - if !needUpdate { - // Even if the EDS is still the same, its status might have - // changed (for example, the number of pods ready). This call is - // needed to keep the agent status updated. - now := metav1.NewTime(time.Now()) - newStatus.AgentList = condition.UpdateExtendedDaemonSetStatus(currentEDS, newStatus.AgentList, &now) - newStatus.Agent = condition.UpdateCombinedDaemonSetStatus(newStatus.AgentList) - - // Stop reconcile loop since EDS hasn't changed - return reconcile.Result{}, nil - } - - logger.Info("Updating ExtendedDaemonSet") - - // TODO: these parameters can be added to the override.PodTemplateSpec. (It exists in v1alpha1) - keepAnnotationsFilter := "" - keepLabelsFilter := "" - - // Copy possibly changed fields - updateEDS := eds.DeepCopy() - updateEDS.Spec = *eds.Spec.DeepCopy() - updateEDS.Annotations = mergeAnnotationsLabels(logger, currentEDS.GetAnnotations(), eds.GetAnnotations(), keepAnnotationsFilter) - updateEDS.Labels = mergeAnnotationsLabels(logger, currentEDS.GetLabels(), eds.GetLabels(), keepLabelsFilter) - - now := metav1.NewTime(time.Now()) - err = kubernetes.UpdateFromObject(context.TODO(), r.client, updateEDS, currentEDS.ObjectMeta) - if err != nil { - updateStatusFunc(updateEDS, newStatus, now, metav1.ConditionFalse, updateSucceeded, "Unable to update ExtendedDaemonSet") - return reconcile.Result{}, err - } - event := buildEventInfo(updateEDS.Name, updateEDS.Namespace, kubernetes.ExtendedDaemonSetKind, datadog.UpdateEvent) - r.recordEvent(dda, event) - updateStatusFunc(updateEDS, newStatus, now, metav1.ConditionTrue, updateSucceeded, "ExtendedDaemonSet updated") - } else { - now := metav1.NewTime(time.Now()) - - err = r.client.Create(context.TODO(), eds) - if err != nil { - updateStatusFunc(nil, newStatus, now, metav1.ConditionFalse, createSucceeded, "Unable to create ExtendedDaemonSet") - return reconcile.Result{}, err - } - event := buildEventInfo(eds.Name, eds.Namespace, kubernetes.ExtendedDaemonSetKind, datadog.CreationEvent) - r.recordEvent(dda, event) - updateStatusFunc(eds, newStatus, now, metav1.ConditionTrue, createSucceeded, "ExtendedDaemonSet created") - } - - logger.Info("Creating ExtendedDaemonSet") - - return result, err -} - -func shouldCheckCreateStrategyStatus(profile *v1alpha1.DatadogAgentProfile) bool { - if profile == nil { - return false - } - - if profile.Name == "" || profile.Name == "default" { - return false - } - - if profile.Status.CreateStrategy == nil { - return false - } - - return profile.Status.CreateStrategy.Status != v1alpha1.CompletedStatus -} - -// shouldUpdateProfileDaemonSet determines if we should update a daemonset -// created from a profile based on the canary status, if one exists -// * true causes the daemonset to be updated immediately -// * false causes the reconcile to skip updating the daemonset -func (r *Reconciler) shouldUpdateProfileDaemonSet(profile *v1alpha1.DatadogAgentProfile, ddaLastUpdateTime metav1.Time, now metav1.Time) (bool, error) { - // eds needs to be enabled - if !r.options.ExtendedDaemonsetOptions.Enabled { - return true, nil - } - - // profiles need to be enabled - if !r.options.DatadogAgentProfileEnabled { - return true, nil - } - - // profile should not be nil or the default profile - if profile == nil { - return true, nil - } - if agentprofile.IsDefaultProfile(profile.Namespace, profile.Name) { - return true, nil - } - - // TODO: check that EDS is for a specific DDA - edsList := edsv1alpha1.ExtendedDaemonSetList{} - if err := r.client.List(context.TODO(), &edsList, client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - }); err != nil { - return false, err - } - - for _, eds := range edsList.Items { - // eds canary was paused - if eds.Annotations[edsv1alpha1.ExtendedDaemonSetCanaryPausedAnnotationKey] == "true" { - r.log.Info("Waiting to update profile DaemonSet because the canary was paused") - return false, nil - } - - // wait if eds has an active canary - if eds.Status.Canary != nil { - r.log.Info("Waiting to update profile DaemonSet because of an active canary") - return false, nil - } - // get ers associated with eds - ersList := edsv1alpha1.ExtendedDaemonSetReplicaSetList{} - if err := r.client.List(context.TODO(), &ersList, client.MatchingLabels{ - edsv1alpha1.ExtendedDaemonSetNameLabelKey: eds.Name, - }); err != nil { - return false, err - } - // there should be at least 1 ers - if len(ersList.Items) == 0 { - return false, errors.New("there must exist at least 1 ExtendedDaemonSetReplicaSet") - } - // wait for canary ers to be cleaned up if there are multiple ers - if len(ersList.Items) > 1 { - r.log.Info("Waiting to update profile DaemonSet until unused ExtendedDaemonSetReplicaSets are cleaned up") - return false, nil - } - ers := ersList.Items[0] - // the eds's active ers should match the ers name - if eds.Status.ActiveReplicaSet != ers.Name { - return false, errors.New("ExtendedDaemonSetReplicaSet name does not match ExtendedDaemonSet's active replicaset") - } - - // add reconcile requeue time buffer to allow eds time to update before making a decision - if ddaLastUpdateTime.Add(defaultRequeuePeriod).After(now.Time) { - r.log.Info("Waiting to update profile DaemonSet after DatadogAgent update", "last update", ddaLastUpdateTime, "wait period", defaultRequeuePeriod) - return false, nil - } - - hashesMatch := eds.Annotations[constants.MD5AgentDeploymentAnnotationKey] == ers.Annotations[constants.MD5AgentDeploymentAnnotationKey] - // eds canary was validated manually - if eds.Annotations[edsv1alpha1.ExtendedDaemonSetCanaryValidAnnotationKey] == ers.Name && hashesMatch { - r.log.Info("Updating profile DaemonSet because the canary was validated and EDS and ERS hashes match") - return true, nil - } - - // wait for canary duration to elapse - if ddaLastUpdateTime.Add(r.options.ExtendedDaemonsetOptions.CanaryDuration).After(now.Time) { - r.log.Info("Waiting to update profile DaemonSet because the canary duration has not yet elapsed") - return false, nil - } - - // if eds and ers agentspechash match, canary was successful - if hashesMatch { - r.log.Info("Updating profile DaemonSet because the EDS and ERS hashes match") - return true, nil - } - } - return false, nil -} - -// getDDALastUpdatedTime returns the latest timestamp from managedFields -// ignoring the `status` subresource. If there are no managed fields, it uses -// the creation timestamp -func getDDALastUpdatedTime(managedFields []metav1.ManagedFieldsEntry, creationTimestamp metav1.Time) metav1.Time { - lastUpdateTime := creationTimestamp - for _, mf := range managedFields { - if mf.Subresource != "status" { - if mf.Time != nil && mf.Time.After(lastUpdateTime.Time) { - lastUpdateTime = *mf.Time - } - } - } - return lastUpdateTime -} - -// shouldProfileWaitForCanary returns the value of the profile-wait-for-canary annotation -func shouldProfileWaitForCanary(logger logr.Logger, annotations map[string]string) bool { - if val, exists := annotations[profileWaitForCanaryKey]; exists { - waitForCanary, err := strconv.ParseBool(val) - if err != nil { - logger.Error(err, "Failed to parse annotation value", "key", profileWaitForCanaryKey, "value", val) - return false - } - return waitForCanary - } - return false -} - func (r *Reconciler) createOrUpdateDDAI(ddai *v1alpha1.DatadogAgentInternal) error { currentDDAI := &v1alpha1.DatadogAgentInternal{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ddai.Name, Namespace: ddai.Namespace}, currentDDAI); err != nil { @@ -694,91 +265,6 @@ func restartDeployment(deployment, currentDeployment *appsv1.Deployment) bool { return false } -// getCurrentDaemonset returns the current daemonset for a given DDA -// The Daemonset may use the old naming format so we retrieve it via labels -func (r *Reconciler) getCurrentDaemonset(dda, daemonset metav1.Object) (*appsv1.DaemonSet, error) { - // Profile daemonset - if profileName, ok := daemonset.GetLabels()[constants.ProfileLabelKey]; ok { - dsList := appsv1.DaemonSetList{} - if err := r.client.List(context.TODO(), &dsList, client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - constants.ProfileLabelKey: profileName, - }); err != nil { - return nil, err - } - switch len(dsList.Items) { - case 0: - r.log.Info("daemonset is not found") - return nil, nil - case 1: - return &dsList.Items[0], nil - default: - return nil, fmt.Errorf("expected 1 daemonset for profile: %s, got %d", profileName, len(dsList.Items)) - } - } - - // Helm-migrated daemonset - if helm.IsHelmMigration(dda) { - dsList := appsv1.DaemonSetList{} - if err := r.client.List(context.TODO(), &dsList, client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "Helm", - }); err != nil { - return nil, err - } - switch len(dsList.Items) { - case 0: // No Helm-managed daemonsets found; check for operator-managed daemonset - nsName := types.NamespacedName{ - Name: daemonset.GetName(), - Namespace: daemonset.GetNamespace(), - } - existingDaemonset := &appsv1.DaemonSet{} - if err := r.client.Get(context.TODO(), nsName, existingDaemonset); err != nil { - r.log.V(1).Info("Helm migration in progress: operator-managed daemonset not found") - } else { - if _, exists := existingDaemonset.Labels[constants.MD5AgentDeploymentMigratedLabelKey]; !exists { - r.log.V(1).Info("Helm migration in progress: operator-managed daemonset exists and migration label not found") - } - } - case 1: - return &dsList.Items[0], nil - default: - return nil, fmt.Errorf("expected 1 daemonset for datadog helm release: %s, got %d", dda.GetName(), len(dsList.Items)) - } - } - - // Default daemonset - nsName := types.NamespacedName{ - Name: daemonset.GetName(), - Namespace: daemonset.GetNamespace(), - } - currentDaemonset := &appsv1.DaemonSet{} - if err := r.client.Get(context.TODO(), nsName, currentDaemonset); err != nil { - if apierrors.IsNotFound(err) { - r.log.Info("daemonset is not found") - return nil, nil - } - return nil, err - } - - return currentDaemonset, nil -} - -func restartDaemonset(daemonset, currentDaemonset *appsv1.DaemonSet) bool { - // name change - if daemonset.Name != currentDaemonset.Name { - return true - } - - // selectors are immutable - if !maps.Equal(daemonset.Spec.Selector.MatchLabels, currentDaemonset.Spec.Selector.MatchLabels) { - return true - } - - return false -} - func IsEqualStatus(current *v2alpha1.DatadogAgentStatus, newStatus *v2alpha1.DatadogAgentStatus) bool { if current == nil && newStatus == nil { return true diff --git a/internal/controller/datadogagent/controller_reconcile_v2_common_test.go b/internal/controller/datadogagent/controller_reconcile_v2_common_test.go index 329e7e5383..7ebabbf579 100644 --- a/internal/controller/datadogagent/controller_reconcile_v2_common_test.go +++ b/internal/controller/datadogagent/controller_reconcile_v2_common_test.go @@ -1,19 +1,12 @@ package datadogagent import ( - "errors" "testing" - "time" "k8s.io/utils/ptr" - apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/agent" - "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/kubernetes" - edsdatadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" assert "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,769 +17,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" ) -func Test_shouldCheckCreateStrategyStatus(t *testing.T) { - tests := []struct { - name string - profile *v1alpha1.DatadogAgentProfile - CreateStrategy string - expected bool - }{ - { - name: "nil profile", - profile: nil, - CreateStrategy: "true", - expected: false, - }, - { - name: "create strategy false", - profile: nil, - CreateStrategy: "false", - expected: false, - }, - { - name: "empty profile", - profile: &v1alpha1.DatadogAgentProfile{}, - CreateStrategy: "true", - expected: false, - }, - { - name: "default profile", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - }, - }, - CreateStrategy: "true", - expected: false, - }, - { - name: "empty profile status", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: v1alpha1.DatadogAgentProfileStatus{}, - }, - CreateStrategy: "true", - expected: false, - }, - { - name: "completed create strategy status", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: v1alpha1.DatadogAgentProfileStatus{ - CreateStrategy: &v1alpha1.CreateStrategy{ - Status: v1alpha1.CompletedStatus, - }, - }, - }, - CreateStrategy: "true", - expected: false, - }, - { - name: "in progress create strategy status", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: v1alpha1.DatadogAgentProfileStatus{ - CreateStrategy: &v1alpha1.CreateStrategy{ - Status: v1alpha1.InProgressStatus, - }, - }, - }, - CreateStrategy: "true", - expected: true, - }, - { - name: "waiting create strategy status", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: v1alpha1.DatadogAgentProfileStatus{ - CreateStrategy: &v1alpha1.CreateStrategy{ - Status: v1alpha1.WaitingStatus, - }, - }, - }, - CreateStrategy: "true", - expected: true, - }, - { - name: "empty status in create strategy status", - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: v1alpha1.DatadogAgentProfileStatus{ - CreateStrategy: &v1alpha1.CreateStrategy{ - Status: "", - }, - }, - }, - CreateStrategy: "true", - expected: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Setenv(apicommon.CreateStrategyEnabled, tt.CreateStrategy) - actual := shouldCheckCreateStrategyStatus(tt.profile) - assert.Equal(t, tt.expected, actual) - }) - } -} - -func Test_shouldUpdateProfileDaemonSet(t *testing.T) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - _ = edsdatadoghqv1alpha1.AddToScheme(sch) - - testNS := "test" - now := metav1.Now() - now5MinBefore := metav1.NewTime(now.Add(-5 * time.Minute)) - now15MinBefore := metav1.NewTime(now.Add(-15 * time.Minute)) - testProfile := v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-profile", - Namespace: testNS, - }, - } - testEDSLabels := map[string]string{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - } - testERSLabels := map[string]string{ - edsdatadoghqv1alpha1.ExtendedDaemonSetNameLabelKey: "test-eds", - } - - tests := []struct { - name string - reconcilerOptions ReconcilerOptions - profile *v1alpha1.DatadogAgentProfile - ddaLastUpdateTime metav1.Time - now metav1.Time - existingObjects []client.Object - expectedShouldUpdate bool - errorMessage error - }{ - { - name: "EDS not enabled", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: false, - }, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: nil, - expectedShouldUpdate: true, - errorMessage: nil, - }, - { - name: "Profiles not enabled", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: false, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: nil, - expectedShouldUpdate: true, - errorMessage: nil, - }, - { - name: "Profiles nil", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: nil, - ddaLastUpdateTime: now, - now: now, - existingObjects: nil, - expectedShouldUpdate: true, - errorMessage: nil, - }, - { - name: "Default profile", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "", - }, - }, - ddaLastUpdateTime: now, - now: now, - existingObjects: nil, - expectedShouldUpdate: true, - errorMessage: nil, - }, - { - name: "No EDSs exist", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{}, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Canary paused", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Annotations: map[string]string{ - edsdatadoghqv1alpha1.ExtendedDaemonSetCanaryPausedAnnotationKey: "true", - }, - Labels: testEDSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Active canary", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - Canary: &edsdatadoghqv1alpha1.ExtendedDaemonSetStatusCanary{ - ReplicaSet: "test-ers-2", - Nodes: []string{"node-foo"}, - }, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "No ERS", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: errors.New("there must exist at least 1 ExtendedDaemonSetReplicaSet"), - }, - { - name: "More than 1 ERS", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-2", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "ERS name and active replicaSet name don't match", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: errors.New("ExtendedDaemonSetReplicaSet name does not match ExtendedDaemonSet's active replicaset"), - }, - { - name: "DDA was just updated", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Canary validated annotation present but agent spec hashes doesn't match", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now5MinBefore, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Annotations: map[string]string{ - edsdatadoghqv1alpha1.ExtendedDaemonSetCanaryValidAnnotationKey: "test-ers-2", - constants.MD5AgentDeploymentAnnotationKey: "12345", - }, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - Annotations: map[string]string{ - constants.MD5AgentDeploymentAnnotationKey: "67890", - }, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Canary validated", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now5MinBefore, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Annotations: map[string]string{ - edsdatadoghqv1alpha1.ExtendedDaemonSetCanaryValidAnnotationKey: "test-ers-1", - }, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - }, - expectedShouldUpdate: true, - errorMessage: nil, - }, - { - name: "Canary duration hasn't passed", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - CanaryDuration: 10 * time.Minute, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now5MinBefore, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Agent spec hash for EDS and ERS doesn't match", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - CanaryDuration: 10 * time.Minute, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now15MinBefore, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - Annotations: map[string]string{ - constants.MD5AgentDeploymentAnnotationKey: "12345", - }, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - Annotations: map[string]string{ - constants.MD5AgentDeploymentAnnotationKey: "67890", - }, - }, - }, - }, - expectedShouldUpdate: false, - errorMessage: nil, - }, - { - name: "Agent spec hash for EDS and ERS match", - reconcilerOptions: ReconcilerOptions{ - ExtendedDaemonsetOptions: agent.ExtendedDaemonsetOptions{ - Enabled: true, - CanaryDuration: 10 * time.Minute, - }, - DatadogAgentProfileEnabled: true, - }, - profile: &testProfile, - ddaLastUpdateTime: now15MinBefore, - now: now, - existingObjects: []client.Object{ - &edsdatadoghqv1alpha1.ExtendedDaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-eds", - Namespace: testNS, - Labels: testEDSLabels, - Annotations: map[string]string{ - constants.MD5AgentDeploymentAnnotationKey: "12345", - }, - }, - Status: edsdatadoghqv1alpha1.ExtendedDaemonSetStatus{ - ActiveReplicaSet: "test-ers-1", - }, - }, - &edsdatadoghqv1alpha1.ExtendedDaemonSetReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ers-1", - Namespace: testNS, - Labels: testERSLabels, - Annotations: map[string]string{ - constants.MD5AgentDeploymentAnnotationKey: "12345", - }, - }, - }, - }, - expectedShouldUpdate: true, - errorMessage: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(tt.existingObjects...).Build() - - r := &Reconciler{ - client: fakeClient, - log: logf.Log.WithName(tt.name), - options: tt.reconcilerOptions, - } - - actual, err := r.shouldUpdateProfileDaemonSet(tt.profile, tt.ddaLastUpdateTime, tt.now) - assert.Equal(t, tt.expectedShouldUpdate, actual) - assert.Equal(t, tt.errorMessage, err) - }) - } -} - -func Test_getDDALastUpdatedTime(t *testing.T) { - now := metav1.Now() - now5MinLater := metav1.NewTime(now.Add(5 * time.Minute)) - now15MinLater := metav1.NewTime(now.Add(15 * time.Minute)) - tests := []struct { - name string - managedFields []metav1.ManagedFieldsEntry - creationTimestamp metav1.Time - expected metav1.Time - }{ - { - name: "no managed field entries", - managedFields: []metav1.ManagedFieldsEntry{}, - creationTimestamp: now, - expected: now, - }, - { - name: "one new entry", - managedFields: []metav1.ManagedFieldsEntry{ - { - Time: &now15MinLater, - }, - }, - creationTimestamp: now, - expected: now15MinLater, - }, - { - name: "multiple entries", - managedFields: []metav1.ManagedFieldsEntry{ - { - Time: &now15MinLater, - }, - { - Time: &now5MinLater, - }, - { - Time: &now, - }, - }, - creationTimestamp: now, - expected: now15MinLater, - }, - { - name: "ignore status entry", - managedFields: []metav1.ManagedFieldsEntry{ - { - Subresource: "status", - Time: &now15MinLater, - }, - { - Time: &now5MinLater, - }, - { - Time: &now, - }, - }, - creationTimestamp: now, - expected: now5MinLater, - }, - { - name: "nil time entry", - managedFields: []metav1.ManagedFieldsEntry{ - { - Time: &now15MinLater, - }, - { - Manager: "test", - }, - { - Time: &now, - }, - }, - creationTimestamp: now, - expected: now15MinLater, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - actual := getDDALastUpdatedTime(tt.managedFields, tt.creationTimestamp) - assert.Equal(t, tt.expected, actual) - }) - } -} - -func Test_shouldProfileWaitForCanary(t *testing.T) { - logger := logf.Log.WithName("Test_shouldProfileWaitForCanary") - - tests := []struct { - name string - annotations map[string]string - expected bool - }{ - { - name: "Nil annotations", - annotations: nil, - expected: false, - }, - { - name: "Empty annotations", - annotations: map[string]string{}, - expected: false, - }, - { - name: "No relevant annotations", - annotations: map[string]string{ - "foo": "bar", - }, - expected: false, - }, - { - name: "Relevant annotation exists", - annotations: map[string]string{ - "foo": "bar", - profileWaitForCanaryKey: "true", - }, - expected: true, - }, - { - name: "Relevant annotation exists and is false", - annotations: map[string]string{ - "foo": "bar", - profileWaitForCanaryKey: "false", - }, - expected: false, - }, - { - name: "Relevant annotation exists, but value is not bool", - annotations: map[string]string{ - "foo": "bar", - profileWaitForCanaryKey: "yes", - }, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - shouldWait := shouldProfileWaitForCanary(logger, tt.annotations) - assert.Equal(t, tt.expected, shouldWait) - }) - } -} - func Test_addDDAIStatusToDDAStatus(t *testing.T) { sch := runtime.NewScheme() _ = scheme.AddToScheme(sch) diff --git a/internal/controller/datadogagent/controller_reconcile_v2_helpers.go b/internal/controller/datadogagent/controller_reconcile_v2_helpers.go index ef0a2b209c..ea4c7787ed 100644 --- a/internal/controller/datadogagent/controller_reconcile_v2_helpers.go +++ b/internal/controller/datadogagent/controller_reconcile_v2_helpers.go @@ -2,163 +2,18 @@ package datadogagent import ( "context" - "time" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - apicommon "github.com/DataDog/datadog-operator/api/datadoghq/common" - datadoghqv1alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" datadoghqv2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/common" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/clusterchecksrunner" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/otelagentgateway" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/global" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/object" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/override" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/store" - "github.com/DataDog/datadog-operator/internal/controller/metrics" - "github.com/DataDog/datadog-operator/pkg/condition" - "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/controller/utils" "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" "github.com/DataDog/datadog-operator/pkg/kubernetes" ) -// STEP 2 of the reconcile loop: reconcile 3 components - -// setupDependencies initializes the store and resource managers. -func (r *Reconciler) setupDependencies(instance *datadoghqv2alpha1.DatadogAgent, logger logr.Logger) (*store.Store, feature.ResourceManagers) { - storeOptions := &store.StoreOptions{ - SupportCilium: r.options.SupportCilium, - PlatformInfo: r.platformInfo, - Logger: logger, - Scheme: r.scheme, - } - depsStore := store.NewStore(instance, storeOptions) - resourceManagers := feature.NewResourceManagers(depsStore) - return depsStore, resourceManagers -} - -// manageGlobalDependencies manages the global dependencies for a component. -func (r *Reconciler) manageGlobalDependencies(logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, resourceManagers feature.ResourceManagers, requiredComponents feature.RequiredComponents) error { - var errs []error - // Non component specific dependencies - if err := global.ApplyGlobalDependencies(logger, dda.GetObjectMeta(), &dda.Spec, resourceManagers, false); len(err) > 0 { - errs = append(errs, err...) - } - - // Component specific dependencies - if err := global.ApplyGlobalComponentDependencies(logger, dda.GetObjectMeta(), &dda.Spec, &dda.Status, resourceManagers, datadoghqv2alpha1.ClusterAgentComponentName, requiredComponents.ClusterAgent, false); len(err) > 0 { - errs = append(errs, err...) - } - if err := global.ApplyGlobalComponentDependencies(logger, dda.GetObjectMeta(), &dda.Spec, &dda.Status, resourceManagers, datadoghqv2alpha1.NodeAgentComponentName, requiredComponents.Agent, false); len(err) > 0 { - errs = append(errs, err...) - } - if err := global.ApplyGlobalComponentDependencies(logger, dda.GetObjectMeta(), &dda.Spec, &dda.Status, resourceManagers, datadoghqv2alpha1.ClusterChecksRunnerComponentName, requiredComponents.ClusterChecksRunner, false); len(err) > 0 { - errs = append(errs, err...) - } - if err := global.ApplyGlobalComponentDependencies(logger, dda.GetObjectMeta(), &dda.Spec, &dda.Status, resourceManagers, datadoghqv2alpha1.OtelAgentGatewayComponentName, requiredComponents.OtelAgentGateway, false); len(err) > 0 { - errs = append(errs, err...) - } - - if len(errs) > 0 { - return errors.NewAggregate(errs) - } - return nil -} - -// manageFeatureDependencies iterates over features to set up dependencies. -func (r *Reconciler) manageFeatureDependencies(logger logr.Logger, features []feature.Feature, resourceManagers feature.ResourceManagers, provider string) error { - var errs []error - - for _, feat := range features { - if err := feat.ManageDependencies(resourceManagers, provider); err != nil { - errs = append(errs, err) - } - } - if len(errs) > 0 { - return errors.NewAggregate(errs) - } - return nil -} - -// overrideDependencies wraps the dependency override logic. -func (r *Reconciler) overrideDependencies(logger logr.Logger, resourceManagers feature.ResourceManagers, instance *datadoghqv2alpha1.DatadogAgent) error { - errs := override.Dependencies(logger, resourceManagers, instance.GetObjectMeta(), &instance.Spec) - if len(errs) > 0 { - return errors.NewAggregate(errs) - } - return nil -} - -// reconcileAgentProfiles handles profiles and agent reconciliation. -func (r *Reconciler) reconcileAgentProfiles(ctx context.Context, logger logr.Logger, instance *datadoghqv2alpha1.DatadogAgent, requiredComponents feature.RequiredComponents, features []feature.Feature, resourceManagers feature.ResourceManagers, newStatus *datadoghqv2alpha1.DatadogAgentStatus, now metav1.Time) (reconcile.Result, error) { - // Start with a default profile and provider. - providerList := map[string]struct{}{kubernetes.LegacyProvider: {}} - profiles := []datadoghqv1alpha1.DatadogAgentProfile{{}} - metrics.IntrospectionEnabled.Set(metrics.FalseValue) - metrics.DAPEnabled.Set(metrics.FalseValue) - // If profiles or introspection is enabled, get the node list and update providers. - if r.options.DatadogAgentProfileEnabled || r.options.IntrospectionEnabled { - nodeList, err := r.getNodeList(ctx) - if err != nil { - return reconcile.Result{}, err - } - if r.options.IntrospectionEnabled { - providerList = kubernetes.GetProviderListFromNodeList(nodeList, logger) - metrics.IntrospectionEnabled.Set(metrics.TrueValue) - } - if r.options.DatadogAgentProfileEnabled { - metrics.DAPEnabled.Set(metrics.TrueValue) - var profilesByNode map[string]types.NamespacedName - profiles, profilesByNode, err = r.profilesToApply(ctx, logger, nodeList, now, &instance.Spec) - // TODO: in main, we error on err instead of e here - // https://github.com/DataDog/datadog-operator/blob/8ba3647fbad340015d835b6fc1cb48639502c33d/internal/controller/datadogagent/controller_reconcile_v2.go#L179-L182 - if err != nil { - return reconcile.Result{}, err - } - if err = r.handleProfiles(ctx, profilesByNode, instance.Namespace); err != nil { - return reconcile.Result{}, err - } - } - } - - // Reconcile the agent for every profile and provider. - var errs []error - var result reconcile.Result - for _, profile := range profiles { - if r.options.IntrospectionEnabled && r.useDefaultDaemonset(providerList) { - // Use legacy provider if EKS or OpenShift providers are present to prevent daemonset overrides - res, err := r.reconcileV2Agent(logger, requiredComponents, features, instance, resourceManagers, newStatus, kubernetes.DefaultProvider, providerList, &profile) - if utils.ShouldReturn(res, err) { - errs = append(errs, err) - } - } else { - // Create one DaemonSet per provider (for GKE, etc.) - for provider := range providerList { - res, err := r.reconcileV2Agent(logger, requiredComponents, features, instance, resourceManagers, newStatus, provider, providerList, &profile) - if utils.ShouldReturn(res, err) { - errs = append(errs, err) - } - } - } - } - if utils.ShouldReturn(result, errors.NewAggregate(errs)) { - return result, errors.NewAggregate(errs) - } - condition.UpdateDatadogAgentStatusConditions(newStatus, now, common.AgentReconcileConditionType, metav1.ConditionTrue, "reconcile_succeed", "reconcile succeed", false) - return reconcile.Result{}, nil -} - // useDefaultDaemonset determines if we should use a legacy provider specific Daemonset for EKS and Openshift providers func (r *Reconciler) useDefaultDaemonset(providerList map[string]struct{}) bool { if len(providerList) == 0 { @@ -167,85 +22,6 @@ func (r *Reconciler) useDefaultDaemonset(providerList map[string]struct{}) bool return kubernetes.ShouldUseDefaultDaemonset(providerList) } -// ************************************* -// STEP 3 of the reconcile loop: cleanup -// ************************************* - -// cleanupExtraneousResources groups the cleanup calls for old components. -func (r *Reconciler) cleanupExtraneousResources(ctx context.Context, logger logr.Logger, instance *datadoghqv2alpha1.DatadogAgent, newStatus *datadoghqv2alpha1.DatadogAgentStatus, resourceManagers feature.ResourceManagers) error { - var errs []error - // Cleanup old DaemonSets, DCA and CCR deployments. - now := metav1.NewTime(time.Now()) - providerList := map[string]struct{}{kubernetes.LegacyProvider: {}} - profiles := []datadoghqv1alpha1.DatadogAgentProfile{{}} - - // Repeat of the code from reconcileAgentProfiles, but this will be removed in DDAI controller since this logic will be from DDA to DDAI. - if r.options.DatadogAgentProfileEnabled || r.options.IntrospectionEnabled { - // Get a node list for profiles and introspection - nodeList, e := r.getNodeList(ctx) - if e != nil { - return e - } - - if r.options.IntrospectionEnabled { - providerList = kubernetes.GetProviderListFromNodeList(nodeList, logger) - } - - if r.options.DatadogAgentProfileEnabled { - var profilesByNode map[string]types.NamespacedName - profiles, profilesByNode, e = r.profilesToApply(ctx, logger, nodeList, now, &instance.Spec) - if e != nil { - return e - } - - if err := r.handleProfiles(ctx, profilesByNode, instance.Namespace); err != nil { - return err - } - } - } - - if err := r.cleanupExtraneousDaemonSets(ctx, logger, instance, newStatus, providerList, profiles); err != nil { - errs = append(errs, err) - logger.Error(err, "Error cleaning up old DaemonSets") - } - if err := r.cleanupOldDCADeployments(ctx, logger, instance); err != nil { - errs = append(errs, err) - logger.Error(err, "Error cleaning up old DCA Deployments") - } - if err := r.cleanupOldCCRDeployments(ctx, logger, instance); err != nil { - errs = append(errs, err) - logger.Error(err, "Error cleaning up old CCR Deployments") - } - if err := r.cleanupOldOtelAgentGatewayDeployments(ctx, logger, instance); err != nil { - errs = append(errs, err) - logger.Error(err, "Error cleaning up old OTel Agent Gateway Deployments") - } - if len(errs) > 0 { - return errors.NewAggregate(errs) - } - return nil -} - -// ************************************* -// STEP 4 of the reconcile loop: cleanup dependencies -// ************************************* - -// applyAndCleanupDependencies applies pending changes and cleans up unused dependencies. -func (r *Reconciler) applyAndCleanupDependencies(ctx context.Context, logger logr.Logger, depsStore *store.Store) error { - logger.V(1).Info("Applying pending dependencies and cleaning up unused dependencies") - var errs []error - errs = append(errs, depsStore.Apply(ctx, r.client)...) - if len(errs) > 0 { - logger.Error(errors.NewAggregate(errs), "Dependencies apply error") - return errors.NewAggregate(errs) - } - if errs = depsStore.Cleanup(ctx, r.client, false); len(errs) > 0 { - logger.Error(errors.NewAggregate(errs), "Dependencies cleanup error") - return errors.NewAggregate(errs) - } - return nil -} - // generateNewStatusFromDDA generates a new status from a DDA status. // If an existing DCA token is present, it is copied to the new status. func generateNewStatusFromDDA(ddaStatus *datadoghqv2alpha1.DatadogAgentStatus) *datadoghqv2alpha1.DatadogAgentStatus { @@ -289,95 +65,3 @@ func (r *Reconciler) deleteDeploymentWithEvent(ctx context.Context, logger logr. return reconcile.Result{}, nil } - -// cleanupOldDCADeployments deletes DCA deployments when a DCA Deployment's name is changed using clusterAgent name override -func (r *Reconciler) cleanupOldDCADeployments(ctx context.Context, logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent) error { - matchLabels := client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterAgentResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: object.NewPartOfLabelValue(dda).String(), - } - deploymentName := component.GetDeploymentNameFromDatadogAgent(dda, &dda.Spec) - deploymentList := appsv1.DeploymentList{} - if err := r.client.List(ctx, &deploymentList, matchLabels); err != nil { - return err - } - for _, deployment := range deploymentList.Items { - if deploymentName != deployment.Name { - if _, err := r.deleteDeploymentWithEvent(ctx, logger, dda, &deployment); err != nil { - return err - } - } - } - return nil -} - -// cleanupOldCCRDeployments deletes CCR deployments when a CCR Deployment's name is changed using clusterChecksRunner name override -func (r *Reconciler) cleanupOldCCRDeployments(ctx context.Context, logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent) error { - matchLabels := client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultClusterChecksRunnerResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: object.NewPartOfLabelValue(dda).String(), - } - deploymentName := getDeploymentNameFromCCR(dda) - deploymentList := appsv1.DeploymentList{} - if err := r.client.List(ctx, &deploymentList, matchLabels); err != nil { - return err - } - for _, deployment := range deploymentList.Items { - if deploymentName != deployment.Name { - if _, err := r.deleteDeploymentWithEvent(ctx, logger, dda, &deployment); err != nil { - return err - } - } - } - - return nil -} - -// getDeploymentNameFromCCR returns the expected CCR deployment name based on -// the DDA name and clusterChecksRunner name override -func getDeploymentNameFromCCR(dda *datadoghqv2alpha1.DatadogAgent) string { - deploymentName := clusterchecksrunner.GetClusterChecksRunnerName(dda) - if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.ClusterChecksRunnerComponentName]; ok { - if componentOverride.Name != nil && *componentOverride.Name != "" { - deploymentName = *componentOverride.Name - } - } - return deploymentName -} - -// cleanupOldOtelAgentGatewayDeployments deletes OTel Agent Gateway deployments when -// the deployment name is changed using otelAgentGateway name override -func (r *Reconciler) cleanupOldOtelAgentGatewayDeployments(ctx context.Context, logger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent) error { - matchLabels := client.MatchingLabels{ - apicommon.AgentDeploymentComponentLabelKey: constants.DefaultOtelAgentGatewayResourceSuffix, - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - kubernetes.AppKubernetesPartOfLabelKey: object.NewPartOfLabelValue(dda).String(), - } - deploymentName := getDeploymentNameFromOtelAgentGateway(dda) - deploymentList := appsv1.DeploymentList{} - if err := r.client.List(ctx, &deploymentList, matchLabels); err != nil { - return err - } - for _, deployment := range deploymentList.Items { - if deploymentName != deployment.Name { - if _, err := r.deleteDeploymentWithEvent(ctx, logger, dda, &deployment); err != nil { - return err - } - } - } - return nil -} - -// getDeploymentNameFromOtelAgentGateway returns the expected OTel Agent Gateway deployment name based on -// the DDA name and otelAgentGateway name override -func getDeploymentNameFromOtelAgentGateway(dda *datadoghqv2alpha1.DatadogAgent) string { - deploymentName := otelagentgateway.GetOtelAgentGatewayName(dda) - if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.OtelAgentGatewayComponentName]; ok { - if componentOverride.Name != nil && *componentOverride.Name != "" { - deploymentName = *componentOverride.Name - } - } - return deploymentName -} diff --git a/internal/controller/datadogagent/controller_reconcile_v2_helpers_test.go b/internal/controller/datadogagent/controller_reconcile_v2_helpers_test.go deleted file mode 100644 index e14e5fa53c..0000000000 --- a/internal/controller/datadogagent/controller_reconcile_v2_helpers_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package datadogagent - -import ( - "fmt" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - v2alpha1 "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature" - "github.com/DataDog/datadog-operator/pkg/kubernetes" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/stretchr/testify/require" -) - -// dummyFeature is a simple implementation of the Feature interface for testing purposes. -type dummyFeature struct { - IDValue string - ConfigureReturn feature.RequiredComponents - ManageDependenciesError error - ManageClusterAgentError error - ManageNodeAgentError error - ManageSingleContainerAgentError error - ManageClusterChecksRunnerError error - ManageOtelAgentGatewayError error -} - -// ID returns the dummy feature's ID. -func (df *dummyFeature) ID() feature.IDType { - return feature.IDType(df.IDValue) -} - -// Configure returns a predefined RequiredComponents value. -func (df *dummyFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgentSpec, _ *v2alpha1.RemoteConfigConfiguration) feature.RequiredComponents { - return df.ConfigureReturn -} - -// ManageDependencies returns a predefined error (or nil for success). -func (df *dummyFeature) ManageDependencies(managers feature.ResourceManagers, provider string) error { - return df.ManageDependenciesError -} - -// ManageClusterAgent returns a predefined error (or nil for success). -func (df *dummyFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error { - return df.ManageClusterAgentError -} - -// ManageNodeAgent returns a predefined error (or nil for success). -func (df *dummyFeature) ManageNodeAgent(managers feature.PodTemplateManagers, provider string) error { - return df.ManageNodeAgentError -} - -// ManageSingleContainerNodeAgent returns a predefined error (or nil for success). -func (df *dummyFeature) ManageSingleContainerNodeAgent(managers feature.PodTemplateManagers, provider string) error { - return df.ManageSingleContainerAgentError -} - -// ManageClusterChecksRunner returns a predefined error (or nil for success). -func (df *dummyFeature) ManageClusterChecksRunner(managers feature.PodTemplateManagers, provider string) error { - return df.ManageClusterChecksRunnerError -} - -// ManageOtelAgentGateway returns a predefined error (or nil for success). -func (df *dummyFeature) ManageOtelAgentGateway(managers feature.PodTemplateManagers, provider string) error { - return df.ManageOtelAgentGatewayError -} - -// Test_setupDependencies verifies that store and resource managers are initialized. -func Test_setupDependencies(t *testing.T) { - // Create a dummy DatadogAgent instance. - dummyAgent := &v2alpha1.DatadogAgent{} - dummyPlatformInfo := kubernetes.PlatformInfo{} - dummyLogger := logr.Discard() - dummyOpts := &ReconcilerOptions{ - SupportCilium: false, - } - scheme := runtime.NewScheme() - r := &Reconciler{ - options: *dummyOpts, - platformInfo: dummyPlatformInfo, - scheme: scheme, - } - storeObj, resMgrs := r.setupDependencies(dummyAgent, dummyLogger) - require.NotNil(t, storeObj) - require.NotNil(t, resMgrs) -} - -// Test_manageFeatureDependencies checks that feature dependency management aggregates errors correctly. -func Test_manageFeatureDependencies(t *testing.T) { - dummyLogger := logr.Discard() - dummyResMgrs := feature.NewResourceManagers(nil) // passing nil for simplicity - - // One feature succeeds and one fails. - f1 := &dummyFeature{ - IDValue: "f1", - } - f2 := &dummyFeature{ - IDValue: "f2", - ManageDependenciesError: fmt.Errorf("fail dependency"), - } - - r := &Reconciler{} - // Test when all features succeed. - err := r.manageFeatureDependencies(dummyLogger, []feature.Feature{f1}, dummyResMgrs, "") - require.NoError(t, err) - - // Test with one failing feature. - err = r.manageFeatureDependencies(dummyLogger, []feature.Feature{f1, f2}, dummyResMgrs, "") - require.Error(t, err) - require.Contains(t, err.Error(), "fail dependency") -} diff --git a/internal/controller/datadogagent/controller_reconcile_v2_test.go b/internal/controller/datadogagent/controller_reconcile_v2_test.go deleted file mode 100644 index 34de04bada..0000000000 --- a/internal/controller/datadogagent/controller_reconcile_v2_test.go +++ /dev/null @@ -1,639 +0,0 @@ -package datadogagent - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/DataDog/datadog-operator/api/datadoghq/common" - "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" - "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -const testNamespace = "foo" - -func Test_profilesToApply(t *testing.T) { - t1 := time.Now() - t2 := t1.Add(time.Minute) - t3 := t2.Add(time.Minute) - now := metav1.NewTime(t1) - - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - _ = v1alpha1.AddToScheme(sch) - ctx := context.Background() - - testCases := []struct { - name string - nodeList []corev1.Node - profileList []client.Object - wantProfilesToApply func() []v1alpha1.DatadogAgentProfile - wantProfileAppliedByNode map[string]types.NamespacedName - wantError error - }{ - { - name: "no user-created profiles to apply", - nodeList: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - Labels: map[string]string{ - "2": "1", - }, - }, - }, - }, - profileList: []client.Object{}, - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - return []v1alpha1.DatadogAgentProfile{defaultProfile()} - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: "", - Name: "default", - }, - "node2": { - Namespace: "", - Name: "default", - }, - }, - }, - { - name: "no nodes, no profiles", - nodeList: []corev1.Node{}, - profileList: []client.Object{}, - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - return []v1alpha1.DatadogAgentProfile{defaultProfile()} - }, wantProfileAppliedByNode: map[string]types.NamespacedName{}, - }, - { - name: "no nodes", - nodeList: []corev1.Node{}, - profileList: generateObjectList([]string{"1"}, []time.Time{t1}), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"1"}, []time.Time{t1}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "36a4d655a44a0ca07780fff47dd96c6a", - Conditions: nil, - Valid: "Unknown", - Applied: "Unknown", - } - profileList[0].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{}, - }, - { - name: "one profile", - nodeList: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - Labels: map[string]string{ - "2": "1", - }, - }, - }, - }, - profileList: generateObjectList([]string{"1"}, []time.Time{t1}), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"1"}, []time.Time{t1}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "36a4d655a44a0ca07780fff47dd96c6a", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[0].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: testNamespace, - Name: "1", - }, - "node2": { - Namespace: "", - Name: "default", - }, - }, - }, - { - name: "several non-conflicting profiles", - nodeList: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - Labels: map[string]string{ - "2": "1", - }, - }, - }, - }, - profileList: generateObjectList([]string{"1", "2"}, []time.Time{t1, t2}), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"1", "2"}, []time.Time{t1, t2}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "36a4d655a44a0ca07780fff47dd96c6a", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[0].ResourceVersion = "999" - profileList[1].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "e7eda6755e8a98d127140e2169204312", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[1].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: testNamespace, - Name: "1", - }, - "node2": { - Namespace: testNamespace, - Name: "2", - }, - }, - }, - { - // This test defines 3 profiles created in this order: profile-2, - // profile-1, profile-3 (not sorted here to make sure that the code does). - // - profile-1 and profile-2 conflict, but profile-2 is the oldest, - // so it wins. - // - profile-1 and profile-3 conflict, but profile-1 is not applied - // because of the conflict with profile-2, so profile-3 should be. - // So in this case, the returned profiles should be profile-2, - // profile-3 and a default one. - name: "several conflicting profiles with different creation timestamps", - nodeList: []corev1.Node{ - // node1 matches profile-1 and profile-3 - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - "3": "1", - }, - }, - }, - // node2 matches profile-2 - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - Labels: map[string]string{ - "2": "1", - }, - }, - }, - // node3 matches profile-1 and profile-2 - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node3", - Labels: map[string]string{ - "1": "1", - "2": "1", - }, - }, - }, - }, - profileList: generateObjectList([]string{"1", "2", "3"}, []time.Time{t2, t1, t3}), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"2", "3"}, []time.Time{t1, t3}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "e7eda6755e8a98d127140e2169204312", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[0].ResourceVersion = "999" - profileList[1].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "6cc0746a51b8e52da6e4e625d3181686", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[1].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: testNamespace, - Name: "3", - }, - "node2": { - Namespace: testNamespace, - Name: "2", - }, - "node3": { - Namespace: testNamespace, - Name: "2", - }, - }, - }, - { - // This test defines 3 profiles with the same creation timestamp: - // profile-2, profile-1, profile-3 (not sorted alphabetically here - // to make sure that the code does). - // The 3 profiles conflict and only profile-1 should apply because - // it's the first one alphabetically. - name: "conflicting profiles with the same creation timestamp", - nodeList: []corev1.Node{ - // matches all profiles - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - "2": "1", - "3": "1", - }, - }, - }, - }, - profileList: generateObjectList([]string{"2", "1", "3"}, []time.Time{t1, t1, t1}), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"1"}, []time.Time{t1}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "36a4d655a44a0ca07780fff47dd96c6a", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[0].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: testNamespace, - Name: "1", - }, - }, - }, - { - name: "invalid profile", - nodeList: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - }, - }, - }, - }, - profileList: []client.Object{ - &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: "invalid", - }, - Spec: v1alpha1.DatadogAgentProfileSpec{}, - }, - }, - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - return generateProfileList([]string{}, []time.Time{}) - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: "", - Name: "default", - }, - }, - }, - { - // Profile 1 matches node1 and should be applied. - // Profile 2 doesn't conflict with Profile 1 but doesn't apply - // to any nodes since there are no matching nodes. - name: "invalid profiles + valid profiles", - nodeList: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "1": "1", - }, - }, - }, - }, - profileList: append(generateObjectList([]string{"1", "2"}, []time.Time{t1, t2}), []client.Object{ - &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: "invalid-no-affinity", - }, - Spec: v1alpha1.DatadogAgentProfileSpec{ - Config: &v2alpha1.DatadogAgentSpec{ - Override: map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.NodeAgentComponentName: { - Containers: map[common.AgentContainerName]*v2alpha1.DatadogAgentGenericContainer{ - common.CoreAgentContainerName: { - Resources: &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - &v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: "invalid-no-config", - }, - Spec: v1alpha1.DatadogAgentProfileSpec{ - ProfileAffinity: &v1alpha1.ProfileAffinity{ - ProfileNodeAffinity: []corev1.NodeSelectorRequirement{ - { - Key: "os", - Operator: corev1.NodeSelectorOpIn, - Values: []string{"linux"}, - }, - }, - }, - }, - }, - }...), - wantProfilesToApply: func() []v1alpha1.DatadogAgentProfile { - profileList := generateProfileList([]string{"1", "2"}, []time.Time{t1, t2}) - profileList[0].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "36a4d655a44a0ca07780fff47dd96c6a", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - { - Type: "Applied", - Status: "True", - LastTransitionTime: now, - Reason: "Applied", - Message: "Profile applied", - }, - }, - Valid: "True", - Applied: "True", - } - profileList[0].ResourceVersion = "999" - profileList[1].Status = v1alpha1.DatadogAgentProfileStatus{ - LastUpdate: &now, - CurrentHash: "e7eda6755e8a98d127140e2169204312", - Conditions: []metav1.Condition{ - { - Type: "Valid", - Status: "True", - LastTransitionTime: now, - Reason: "Valid", - Message: "Valid manifest", - }, - }, - Valid: "True", - Applied: "Unknown", - } - profileList[1].ResourceVersion = "999" - return profileList - }, - wantProfileAppliedByNode: map[string]types.NamespacedName{ - "node1": { - Namespace: testNamespace, - Name: "1", - }, - }, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - fakeClient := fake.NewClientBuilder().WithScheme(sch).WithStatusSubresource(&v1alpha1.DatadogAgentProfile{}).WithObjects(tt.profileList...).Build() - logger := logf.Log.WithName("Test_profilesToApply") - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "Test_profilesToApply"}) - - r := &Reconciler{ - client: fakeClient, - log: logger, - recorder: recorder, - options: ReconcilerOptions{ - DatadogAgentProfileEnabled: true, - }, - } - - profilesToApply, profileAppliedByNode, err := r.profilesToApply(ctx, logger, tt.nodeList, metav1.NewTime(t1), &v2alpha1.DatadogAgentSpec{}) - require.NoError(t, err) - - wantProfilesToApply := tt.wantProfilesToApply() - assert.Equal(t, wantProfilesToApply, profilesToApply) - assert.Equal(t, tt.wantProfileAppliedByNode, profileAppliedByNode) - }) - } -} - -func generateObjectList(profileIdentifiers []string, creationTimes []time.Time) []client.Object { - objectList := []client.Object{} - for i, j := range profileIdentifiers { - profile := exampleProfile(j, creationTimes[i]) - objectList = append(objectList, &profile) - } - return objectList -} - -func generateProfileList(profileIdentifiers []string, creationTimes []time.Time) []v1alpha1.DatadogAgentProfile { - profileList := []v1alpha1.DatadogAgentProfile{} - for i, j := range profileIdentifiers { - profileList = append(profileList, exampleProfile(j, creationTimes[i])) - } - profileList = append(profileList, defaultProfile()) - return profileList -} - -func exampleProfile(i string, creationTime time.Time) v1alpha1.DatadogAgentProfile { - return v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: i, - CreationTimestamp: metav1.NewTime(creationTime.Truncate(time.Second)), - }, - Spec: v1alpha1.DatadogAgentProfileSpec{ - ProfileAffinity: &v1alpha1.ProfileAffinity{ - ProfileNodeAffinity: []corev1.NodeSelectorRequirement{ - { - Key: i, - Operator: corev1.NodeSelectorOpIn, - Values: []string{"1"}, - }, - }, - }, - Config: &v2alpha1.DatadogAgentSpec{ - Override: map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ - v2alpha1.NodeAgentComponentName: { - Containers: map[common.AgentContainerName]*v2alpha1.DatadogAgentGenericContainer{ - common.CoreAgentContainerName: { - Resources: &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse(fmt.Sprintf("%s00m", i)), - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func defaultProfile() v1alpha1.DatadogAgentProfile { - return v1alpha1.DatadogAgentProfile{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "default", - }, - } -} diff --git a/internal/controller/datadogagent/controller_revision_integration_test.go b/internal/controller/datadogagent/controller_revision_integration_test.go index d04a7b87e6..b6e67bf1fa 100644 --- a/internal/controller/datadogagent/controller_revision_integration_test.go +++ b/internal/controller/datadogagent/controller_revision_integration_test.go @@ -81,8 +81,7 @@ func newRevisionIntegrationReconciler(t *testing.T) (*Reconciler, client.Client) log: logf.Log.WithName(t.Name()), forwarders: dummyManager{}, options: ReconcilerOptions{ - CreateControllerRevisions: true, - DatadogAgentInternalEnabled: true, + CreateControllerRevisions: true, }, } r.initializeComponentRegistry() diff --git a/internal/controller/datadogagent/controller_v2_test.go b/internal/controller/datadogagent/controller_v2_test.go index a119123afe..4941b0359d 100644 --- a/internal/controller/datadogagent/controller_v2_test.go +++ b/internal/controller/datadogagent/controller_v2_test.go @@ -58,7 +58,6 @@ type testCase struct { wantFunc func(t *testing.T, c client.Client) profile *v1alpha1.DatadogAgentProfile // For DDAI tests profilesEnabled bool // For DDAI tests - ddaiEnabled bool // For DDAI tests introspectionEnabled bool // For introspection tests } @@ -68,9 +67,8 @@ func runTestCases(t *testing.T, tests []testCase, testFunc func(t *testing.T, tt t.Run(tt.name, func(t *testing.T) { // Create a copy of opts for this test testOpts := ReconcilerOptions{ - DatadogAgentInternalEnabled: tt.ddaiEnabled, - DatadogAgentProfileEnabled: tt.profilesEnabled, - IntrospectionEnabled: tt.introspectionEnabled, + DatadogAgentProfileEnabled: tt.profilesEnabled, + IntrospectionEnabled: tt.introspectionEnabled, } testFunc(t, tt, testOpts) @@ -78,7 +76,10 @@ func runTestCases(t *testing.T, tests []testCase, testFunc func(t *testing.T, tt } } -// runDDAReconcilerTest runs test case using only the DDA reconciler +// runDDAReconcilerTest runs test case using both DDA and DDAI reconcilers. +// Since DDAI is always enabled, the DDA controller delegates resource creation +// to the DDAI controller via reconcileInstanceV3, so the DDAI reconciler must +// also run for resources (DaemonSets, Deployments, etc.) to be created. func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { t.Helper() @@ -90,7 +91,7 @@ func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { recorder := eventBroadcaster.NewRecorder(s, corev1.EventSource{Component: "test"}) forwarders := dummyManager{} - c := buildClient(t, tt, s, false) + c := buildClient(t, tt, s) // Create reconciler r := &Reconciler{ @@ -104,6 +105,14 @@ func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { } r.initializeComponentRegistry() + ri := datadogagentinternal.NewReconciler( + datadogagentinternal.ReconcilerOptions{}, + c, + kubernetes.PlatformInfo{}, + s, + recorder, + forwarders) + // Load or create DatadogAgent var dda *v2alpha1.DatadogAgent if tt.loadFunc != nil { @@ -114,19 +123,29 @@ func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { _ = r.client.Create(context.TODO(), dda) } - // Run reconciliation - got, err := r.Reconcile(context.TODO(), dda) + // Run DDA reconciliation (creates DDAI) + got, ddaErr := r.Reconcile(context.TODO(), dda) // Assert on error expectation if tt.wantErr { - assert.Error(t, err, "ReconcileDatadogAgent.Reconcile() expected an error") + assert.Error(t, ddaErr, "ReconcileDatadogAgent.Reconcile() expected an error") } else { - assert.NoError(t, err, "ReconcileDatadogAgent.Reconcile() unexpected error: %v", err) + assert.NoError(t, ddaErr, "ReconcileDatadogAgent.Reconcile() unexpected error: %v", ddaErr) } // Assert on reconciliation result assert.Equal(t, tt.want, got, "ReconcileDatadogAgent.Reconcile() unexpected result") + // Run DDAI reconciliation (creates DaemonSets, Deployments, etc.) + ddais := &v1alpha1.DatadogAgentInternalList{} + err := c.List(context.TODO(), ddais) + assert.NoError(t, err, "Failed to list datadogagentinternal") + for i := range ddais.Items { + ddai := &ddais.Items[i] + _, ddaiErr := ri.Reconcile(context.TODO(), ddai) + assert.NoError(t, ddaiErr, "Failed to reconcile datadogagentinternal") + } + // Run custom validation if provided if tt.wantFunc != nil { tt.wantFunc(t, r.client) @@ -145,8 +164,7 @@ func runFullReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { recorder := eventBroadcaster.NewRecorder(s, corev1.EventSource{Component: "test"}) forwarders := dummyManager{} - opts.DatadogAgentInternalEnabled = true - c := buildClient(t, tt, s, true) + c := buildClient(t, tt, s) // Create reconciler r := &Reconciler{ @@ -205,7 +223,7 @@ func runFullReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) { } } -func buildClient(t *testing.T, tt testCase, s *runtime.Scheme, ddaiEnabled bool) client.Client { +func buildClient(t *testing.T, tt testCase, s *runtime.Scheme) client.Client { var builder *fake.ClientBuilder if tt.clientBuilder != nil { // Deep copy primarily to avoid adding CRD twice when running both DDA and full reconciler tests @@ -222,12 +240,10 @@ func buildClient(t *testing.T, tt testCase, s *runtime.Scheme, ddaiEnabled bool) builder = builder.WithObjects(tt.nodes...) } - // Add DDAI CRD from file if DDAI is enabled - if tt.ddaiEnabled || ddaiEnabled { - crd, err := getDDAICRDFromConfig(s) - assert.NoError(t, err) - builder = builder.WithObjects(crd).WithStatusSubresource(&v1alpha1.DatadogAgentInternal{}) - } + // Always add DDAI CRD since DDAI is always enabled + crd, err := getDDAICRDFromConfig(s) + assert.NoError(t, err) + builder = builder.WithObjects(crd).WithStatusSubresource(&v1alpha1.DatadogAgentInternal{}) return builder.Build() } @@ -978,73 +994,6 @@ func TestReconcileDatadogAgentV2_Reconcile(t *testing.T) { runTestCases(t, tests, runFullReconcilerTest) } -func Test_Introspection(t *testing.T) { - const resourcesName = "foo" - const resourcesNamespace = "bar" - - defaultRequeueDuration := 15 * time.Second - - tests := []testCase{ - { - name: "[introspection] Daemonset names with affinity override", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName). - WithComponentOverride(v2alpha1.NodeAgentComponentName, v2alpha1.DatadogAgentComponentOverride{ - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - TopologyKey: "baz", - }, - }, - }, - }, - }). - Build() - _ = c.Create(context.TODO(), dda) - return dda - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-node", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gke-cos-node", - Labels: map[string]string{ - kubernetes.GKEProviderLabel: kubernetes.GKECosType, - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - expectedDaemonsets := []string{ - string("foo-agent-default"), - string("foo-agent-gke-cos"), - } - - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - } - - // introspection is supported only with the DDA reconciler - runTestCases(t, tests, runDDAReconcilerTest) -} - func Test_otelImageTags(t *testing.T) { const resourcesName = "foo" const resourcesNamespace = "bar" @@ -1425,299 +1374,6 @@ func Test_AutopilotOverrides(t *testing.T) { runTestCases(t, tests, runFullReconcilerTest) } -// Helper function for creating DatadogAgent with cluster checks enabled -func createDatadogAgentWithClusterChecks(c client.Client, namespace, name string) *v2alpha1.DatadogAgent { - dda := testutils.NewInitializedDatadogAgentBuilder(namespace, name). - WithClusterChecksEnabled(true). - WithClusterChecksUseCLCEnabled(true). - Build() - _ = c.Create(context.TODO(), dda) - return dda -} - -func Test_Control_Plane_Monitoring(t *testing.T) { - const resourcesName = "foo" - const resourcesNamespace = "bar" - const dcaName = "foo-cluster-agent" - const dsName = "foo-agent-default" - - defaultRequeueDuration := 15 * time.Second - - tests := []testCase{ - { - name: "[introspection] Control Plane Monitoring for Openshift", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-node-1", - Labels: map[string]string{ - kubernetes.OpenShiftProviderLabel: "rhel", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "openshift") - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - verifyEtcdMountsOpenshift(t, c, resourcesNamespace, dsName, "openshift") - }, - }, - { - name: "[introspection] Control Plane Monitoring with EKS", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-node-1", - Labels: map[string]string{ - kubernetes.EKSProviderLabel: "amazon-eks-node-1.29-v20240627", - }, - }, - }, - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-node-2", - Labels: map[string]string{ - kubernetes.DefaultProvider: "", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "eks") - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - { - name: "[introspection] Control Plane Monitoring with EKS multi-node (Fargate + Standard + Bottlerocket)", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-fargate-node", - Labels: map[string]string{ - "eks.amazonaws.com/compute-type": "fargate", - "eks.amazonaws.com/fargate-profile": "my-profile", - }, - }, - }, - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-standard-node", - Labels: map[string]string{ - kubernetes.EKSProviderLabel: "ami-0e7f88829f3d06e29", - "eks.amazonaws.com/nodegroup": "standard-nodes", - }, - }, - }, - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-bottlerocket-node", - Labels: map[string]string{ - kubernetes.EKSProviderLabel: "ami-0fa9d45aa38272f15", - "eks.amazonaws.com/nodegroup": "bottlerocket-nodes", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - // All EKS nodes should be detected as single "eks" provider - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "eks") - // Should create single DaemonSet for all EKS node types - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - { - name: "[introspection] Control Plane Monitoring with EKS eksctl-provisioned cluster", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-eksctl-node", - Labels: map[string]string{ - "alpha.eksctl.io/cluster-name": "my-cluster", - "alpha.eksctl.io/nodegroup-name": "my-nodegroup", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "eks") - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - { - name: "[introspection] Control Plane Monitoring with multiple providers", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eks-node-1", - Labels: map[string]string{ - kubernetes.EKSProviderLabel: "amazon-eks-node-1.29-v20240627", - }, - }, - }, - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-node-2", - Labels: map[string]string{ - kubernetes.OpenShiftProviderLabel: "rhcos", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "default") - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - { - // This test verifies that when a node has a GKE provider label with an unsupported OS value, - // the system falls back to the "default" provider for control plane monitoring - name: "[introspection] Control Plane Monitoring with unsupported provider", - introspectionEnabled: true, - loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { - return createDatadogAgentWithClusterChecks(c, resourcesNamespace, resourcesName) - }, - nodes: []client.Object{ - &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gke-node-1", - Labels: map[string]string{ - // Use unsupported OS value to trigger fallback to "default" provider - kubernetes.GKEProviderLabel: "unsupported-os", - }, - }, - }, - }, - want: reconcile.Result{RequeueAfter: defaultRequeueDuration}, - wantErr: false, - wantFunc: func(t *testing.T, c client.Client) { - verifyDCADeployment(t, c, resourcesName, resourcesNamespace, dcaName, "default") - expectedDaemonsets := []string{ - dsName, - } - verifyDaemonsetNames(t, c, resourcesNamespace, expectedDaemonsets) - }, - }, - } - - // introspection is supported only with the DDA reconciler - runTestCases(t, tests, runDDAReconcilerTest) -} - -func verifyDCADeployment(t *testing.T, c client.Client, ddaName, resourcesNamespace, expectedName string, provider string) { - dcaDeployment := appsv1.Deployment{} - err := c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: expectedName}, &dcaDeployment) - assert.NoError(t, err, "Failed to get DCA deployment") - assert.Contains(t, dcaDeployment.ObjectMeta.Labels, constants.MD5AgentDeploymentProviderLabelKey) - - cms := corev1.ConfigMapList{} - err = c.List(context.TODO(), &cms, client.InNamespace(resourcesNamespace)) - assert.NoError(t, err, "Failed to list ConfigMaps") - - if provider == kubernetes.DefaultProvider { - for _, cm := range cms.Items { - assert.NotEqual(t, fmt.Sprintf("datadog-controlplane-monitoring-%s", provider), cm.ObjectMeta.Name, - "Default provider should not create control plane monitoring ConfigMap") - } - for _, volume := range dcaDeployment.Spec.Template.Spec.Volumes { - assert.NotEqual(t, "kube-apiserver-metrics-config", volume.Name, - "Default provider should not have control plane volumes") - } - } else if provider == kubernetes.OpenshiftProvider || provider == kubernetes.EKSCloudProvider { - cpCm := corev1.ConfigMap{} - err := c.Get(context.TODO(), types.NamespacedName{ - Name: fmt.Sprintf("datadog-controlplane-monitoring-%s", provider), - Namespace: resourcesNamespace, - }, &cpCm) - assert.NoError(t, err, "Control plane monitoring ConfigMap should exist for provider %s", provider) - - verifyCheckMounts(t, dcaDeployment, provider, "kube-apiserver-metrics") - verifyCheckMounts(t, dcaDeployment, provider, "kube-controller-manager") - verifyCheckMounts(t, dcaDeployment, provider, "kube-scheduler") - } - if provider == kubernetes.OpenshiftProvider { - verifyCheckMounts(t, dcaDeployment, provider, "etcd") - } -} - -func verifyCheckMounts(t *testing.T, dcaDeployment appsv1.Deployment, provider string, checkName string) { - volumeToKeyMap := map[string]string{ - "kube-apiserver-metrics": "kube_apiserver_metrics", - "kube-controller-manager": "kube_controller_manager", - "kube-scheduler": "kube_scheduler", - "etcd": "etcd", - } - configMapKey := volumeToKeyMap[checkName] - - assert.Contains(t, dcaDeployment.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: fmt.Sprintf("%s-config", checkName), - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: fmt.Sprintf("datadog-controlplane-monitoring-%s", provider), - }, - Items: []corev1.KeyToPath{ - { - Key: fmt.Sprintf("%s.yaml", configMapKey), - Path: fmt.Sprintf("%s.yaml", configMapKey), - }, - }, - }, - }, - }) - - dcaContainer := dcaDeployment.Spec.Template.Spec.Containers[0] - assert.Contains(t, dcaContainer.VolumeMounts, corev1.VolumeMount{ - Name: fmt.Sprintf("%s-config", checkName), - MountPath: fmt.Sprintf("/etc/datadog-agent/conf.d/%s.d", configMapKey), - ReadOnly: true, - }) -} - func verifyDaemonsetContainers(t *testing.T, c client.Client, resourcesNamespace, dsName string, expectedContainers []string) { ds := &appsv1.DaemonSet{} err := c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: dsName}, ds) @@ -1733,101 +1389,6 @@ func verifyDaemonsetContainers(t *testing.T, c client.Client, resourcesNamespace assert.Equal(t, expectedContainers, dsContainers, "Container names don't match") } -func verifyDaemonsetNames(t *testing.T, c client.Client, resourcesNamespace string, expectedDSNames []string) { - daemonSetList := appsv1.DaemonSetList{} - err := c.List(context.TODO(), &daemonSetList, client.HasLabels{constants.MD5AgentDeploymentProviderLabelKey}) - assert.NoError(t, err, "Failed to list DaemonSets") - - actualDSNames := []string{} - for _, ds := range daemonSetList.Items { - actualDSNames = append(actualDSNames, ds.Name) - } - sort.Strings(actualDSNames) - sort.Strings(expectedDSNames) - assert.Equal(t, expectedDSNames, actualDSNames, "DaemonSet names don't match") -} - -func verifyEtcdMountsOpenshift(t *testing.T, c client.Client, resourcesNamespace, dsName string, provider string) { - expectedMounts := []corev1.VolumeMount{ - { - Name: "etcd-client-certs", - MountPath: "/etc/etcd-certs", - ReadOnly: true, - }, - { - Name: "disable-etcd-autoconf", - MountPath: "/etc/datadog-agent/conf.d/etcd.d", - ReadOnly: false, - }, - } - - // Node Agent - ds := &appsv1.DaemonSet{} - err := c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: dsName}, ds) - assert.NoError(t, err, "Failed to get DaemonSet %s/%s", resourcesNamespace, dsName) - - var coreAgentContainer *corev1.Container - for _, container := range ds.Spec.Template.Spec.Containers { - if container.Name == string(apicommon.CoreAgentContainerName) { - coreAgentContainer = &container - break - } - } - - assert.NotNil(t, coreAgentContainer, "core agent container not found in DaemonSet %s", dsName) - - for _, expectedMount := range expectedMounts { - found := false - for _, mount := range coreAgentContainer.VolumeMounts { - if mount.Name == expectedMount.Name { - found = true - assert.Equal(t, expectedMount.MountPath, mount.MountPath, "Mount path mismatch for %s in core agent", expectedMount.Name) - assert.Equal(t, expectedMount.ReadOnly, mount.ReadOnly, "ReadOnly mismatch for %s in core agent", expectedMount.Name) - break - } - } - assert.True(t, found, "Expected volume mount %s not found in core agent container", expectedMount.Name) - } - - // Cluster Checks Runner - deploymentList := appsv1.DeploymentList{} - err = c.List(context.TODO(), &deploymentList, client.InNamespace(resourcesNamespace)) - assert.NoError(t, err, "Failed to list deployments") - - var ccrDeployment *appsv1.Deployment - for _, deployment := range deploymentList.Items { - if deployment.Name == "foo-cluster-checks-runner" { - ccrDeployment = &deployment - break - } - } - - assert.NotNil(t, ccrDeployment, "cluster-checks-runner deployment not found") - - var ccrContainer *corev1.Container - for _, container := range ccrDeployment.Spec.Template.Spec.Containers { - if container.Name == string(apicommon.ClusterChecksRunnersContainerName) { - ccrContainer = &container - break - } - } - - assert.NotNil(t, ccrContainer, "cluster-checks-runner container not found in deployment") - - for _, expectedMount := range expectedMounts { - found := false - for _, mount := range ccrContainer.VolumeMounts { - if mount.Name == expectedMount.Name { - found = true - assert.Equal(t, expectedMount.MountPath, mount.MountPath, "Mount path mismatch for %s in CCR", expectedMount.Name) - assert.Equal(t, expectedMount.ReadOnly, mount.ReadOnly, "ReadOnly mismatch for %s in CCR", expectedMount.Name) - break - } - } - assert.True(t, found, "Expected volume mount %s not found in cluster-checks-runner container", expectedMount.Name) - } -} - func verifyPDB(t *testing.T, c client.Client) { pdbList := policyv1.PodDisruptionBudgetList{} err := c.List(context.TODO(), &pdbList) @@ -1844,7 +1405,6 @@ func verifyPDB(t *testing.T, c client.Client) { assert.Equal(t, intstr.FromInt(1), *ccrPDB.Spec.MaxUnavailable) assert.Nil(t, ccrPDB.Spec.MinAvailable) } - func Test_DDAI_ReconcileV3(t *testing.T) { const resourcesName = "foo" const resourcesNamespace = "bar" @@ -1883,8 +1443,7 @@ func Test_DDAI_ReconcileV3(t *testing.T) { tests := []testCase{ { - name: "[ddai] Create DDAI from minimal DDA", - ddaiEnabled: true, + name: "[ddai] Create DDAI from minimal DDA", clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}), loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { @@ -1901,8 +1460,7 @@ func Test_DDAI_ReconcileV3(t *testing.T) { }, }, { - name: "[ddai] Create DDAI from customized DDA", - ddaiEnabled: true, + name: "[ddai] Create DDAI from customized DDA", clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}), loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { @@ -1966,8 +1524,7 @@ func Test_DDAI_ReconcileV3(t *testing.T) { }, }, { - name: "[ddai] Explicitly disabled service discovery remains disabled in DDAI", - ddaiEnabled: true, + name: "[ddai] Explicitly disabled service discovery remains disabled in DDAI", clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}), loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { @@ -1992,8 +1549,7 @@ func Test_DDAI_ReconcileV3(t *testing.T) { }, }, { - name: "[ddai] Create DDAI from minimal DDA and default profile", - ddaiEnabled: true, + name: "[ddai] Create DDAI from minimal DDA and default profile", clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}), loadFunc: func(c client.Client) *v2alpha1.DatadogAgent { @@ -2011,8 +1567,7 @@ func Test_DDAI_ReconcileV3(t *testing.T) { }, }, { - name: "[ddai] Create DDAI from minimal DDA and user created profile", - ddaiEnabled: true, + name: "[ddai] Create DDAI from minimal DDA and user created profile", clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}). WithObjects(fooProfile), @@ -2101,8 +1656,8 @@ func Test_DDAI_ReconcileV3(t *testing.T) { // Test_StaleAgentPodCleanup tests that agent pods whose profile assignment has changed are deleted. // It exercises both code paths: -// - Non-DDAI path via runDDAReconcilerTest (DatadogAgentInternalEnabled=false): reconcileInstanceV2 → handleProfiles → cleanupPodsForProfilesThatNoLongerApply -// - DDAI path via runFullReconcilerTest (forces DatadogAgentInternalEnabled=true): reconcileInstanceV3 → reconcileProfiles → cleanupPodsForProfilesThatNoLongerApply +// - DDA-only path via runDDAReconcilerTest: reconcileInstanceV3 +// - Full path via runFullReconcilerTest (DDA + DDAI reconcilers): reconcileInstanceV3 → reconcileProfiles → cleanupPodsForProfilesThatNoLongerApply func Test_StaleAgentPodCleanup(t *testing.T) { const resourcesName = "foo" const resourcesNamespace = "bar" @@ -2112,7 +1667,7 @@ func Test_StaleAgentPodCleanup(t *testing.T) { dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName).BuildWithDefaults() // newProfile selects nodes with label role=new-profile. - // Config.Override must be non-nil for the non-DDAI code path (DatadogAgentInternalEnabled=false). + // Config.Override is set for compatibility with profile validation. newProfile := &v1alpha1.DatadogAgentProfile{ ObjectMeta: metav1.ObjectMeta{ Name: "new-profile", @@ -2163,7 +1718,6 @@ func Test_StaleAgentPodCleanup(t *testing.T) { { name: "stale agent pods from old profile are deleted when node profile assignment changes", profilesEnabled: true, - ddaiEnabled: false, // runFullReconcilerTest will force DatadogAgentInternalEnabled=true nodes: []client.Object{profileChangeNode}, clientBuilder: fake.NewClientBuilder(). WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}). diff --git a/internal/controller/datadogagent/dependencies.go b/internal/controller/datadogagent/dependencies.go index 76df86e22b..2d077fe0b9 100644 --- a/internal/controller/datadogagent/dependencies.go +++ b/internal/controller/datadogagent/dependencies.go @@ -22,9 +22,9 @@ import ( "github.com/DataDog/datadog-operator/pkg/secrets" ) -// setupDDADependenciesStore initializes a store specifically for DDA controller dependencies -// when DatadogAgentInternalEnabled is true. The store is marked with IsDDAControllerStore -// so that resources created by it are labeled and won't be cleaned up by the DDAI controller. +// setupDDADependenciesStore initializes a store specifically for DDA controller dependencies. +// The store is marked with IsDDAControllerStore so that resources created by it are labeled +// and won't be cleaned up by the DDAI controller. func (r *Reconciler) setupDDADependenciesStore(instance *v2alpha1.DatadogAgent, logger logr.Logger) (*store.Store, feature.ResourceManagers) { storeOptions := &store.StoreOptions{ SupportCilium: r.options.SupportCilium, diff --git a/internal/controller/datadogagent/finalizer.go b/internal/controller/datadogagent/finalizer.go index dad9e3aa14..ef084c7d5d 100644 --- a/internal/controller/datadogagent/finalizer.go +++ b/internal/controller/datadogagent/finalizer.go @@ -13,12 +13,9 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/object" - "github.com/DataDog/datadog-operator/internal/controller/datadogagent/store" "github.com/DataDog/datadog-operator/internal/controller/finalizer" "github.com/DataDog/datadog-operator/pkg/agentprofile" "github.com/DataDog/datadog-operator/pkg/constants" - "github.com/DataDog/datadog-operator/pkg/kubernetes" ) const ( @@ -36,12 +33,6 @@ func (r *Reconciler) finalizeDadV2(reqLogger logr.Logger, obj client.Object) err r.forwarders.Unregister(obj) } - if !r.options.DatadogAgentInternalEnabled { - // Namespaced resources from the store should be deleted automatically due to owner reference - // Delete cluster level resources - r.cleanUpClusterLevelResources(reqLogger, obj) - } - if err := r.profilesCleanup(); err != nil { return err } @@ -86,25 +77,3 @@ func (r *Reconciler) profilesCleanup() error { return nil } - -func (r *Reconciler) cleanUpClusterLevelResources(reqLogger logr.Logger, dda client.Object) error { - // Cluster level resources must be deleted manually since they cannot have an owner reference - r.log.Info("Cleaning up cluster level resources") - deleteObjectsForResource(r.client, dda, kubernetes.ObjectFromKind(kubernetes.ClusterRolesKind, r.platformInfo)) - deleteObjectsForResource(r.client, dda, kubernetes.ObjectFromKind(kubernetes.ClusterRoleBindingKind, r.platformInfo)) - deleteObjectsForResource(r.client, dda, kubernetes.ObjectFromKind(kubernetes.APIServiceKind, r.platformInfo)) - - return nil -} - -func deleteObjectsForResource(c client.Client, dda client.Object, kind client.Object) error { - matchingLabels := client.MatchingLabels{ - store.OperatorStoreLabelKey: "true", - kubernetes.AppKubernetesPartOfLabelKey: object.NewPartOfLabelValue(dda).String(), - kubernetes.AppKubernetesManageByLabelKey: "datadog-operator", - } - if err := c.DeleteAllOf(context.TODO(), kind, matchingLabels); err != nil { - return err - } - return nil -} diff --git a/internal/controller/datadogagent/finalizer_test.go b/internal/controller/datadogagent/finalizer_test.go index 1654145c82..cba6688301 100644 --- a/internal/controller/datadogagent/finalizer_test.go +++ b/internal/controller/datadogagent/finalizer_test.go @@ -18,7 +18,6 @@ import ( "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" @@ -142,22 +141,16 @@ func Test_handleFinalizer(t *testing.T) { err := reconciler.finalizeDadV2(logf.Log.WithName("Handle Finalizer V2 test"), dda) assert.NoError(t, err) - // Check that the cluster roles associated with the Datadog Agent have been deleted + // With DDAI always enabled, the DDA finalizer no longer deletes cluster-level resources + // (the DDAI controller handles cleanup). Verify they still exist. for _, clusterRole := range existingClusterRoles { err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: clusterRole.Name}, &rbacv1.ClusterRole{}) - assert.Error(t, err, fmt.Sprintf("ClusterRole %s not deleted", clusterRole.Name)) - if err != nil { - assert.True(t, apierrors.IsNotFound(err), fmt.Sprintf("Unexpected error %s", err)) - } + assert.NoError(t, err, fmt.Sprintf("ClusterRole %s should still exist (DDAI handles cleanup)", clusterRole.Name)) } - // Check that the cluster role bindings associated with the Datadog Agent have been deleted for _, clusterRoleBinding := range existingClusterRoleBindings { err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: clusterRoleBinding.Name}, &rbacv1.ClusterRoleBinding{}) - assert.Error(t, err, fmt.Sprintf("ClusterRoleBinding %s not deleted", clusterRoleBinding.Name)) - if err != nil { - assert.True(t, apierrors.IsNotFound(err), fmt.Sprintf("Unexpected error %s", err)) - } + assert.NoError(t, err, fmt.Sprintf("ClusterRoleBinding %s should still exist (DDAI handles cleanup)", clusterRoleBinding.Name)) } // Check that the nodes don't have the profile label anymore diff --git a/internal/controller/datadogagent/profile.go b/internal/controller/datadogagent/profile.go index be9d2eb177..decda4abac 100644 --- a/internal/controller/datadogagent/profile.go +++ b/internal/controller/datadogagent/profile.go @@ -126,7 +126,7 @@ func (r *Reconciler) reconcileProfiles(ctx context.Context, dsNSName types.Names func (r *Reconciler) reconcileProfile(ctx context.Context, profile *v1alpha1.DatadogAgentProfile, nodeList []corev1.Node, profilesByNode map[string]types.NamespacedName, csInfo map[types.NamespacedName]*agentprofile.CreateStrategyInfo, now metav1.Time) error { r.log.Info("reconciling profile", "datadogagentprofile", profile.Name, "datadogagentprofile_namespace", profile.Namespace) // validate profile name, spec, and selectors - requirements, err := agentprofile.ValidateProfileAndReturnRequirements(profile, r.options.DatadogAgentInternalEnabled) + requirements, err := agentprofile.ValidateProfileAndReturnRequirements(profile) if err != nil { metrics.DAPValid.With(prometheus.Labels{"datadogagentprofile": profile.Name}).Set(metrics.FalseValue) profile.Status.Conditions = agentprofile.SetDatadogAgentProfileCondition(profile.Status.Conditions, agentprofile.NewDatadogAgentProfileCondition(agentprofile.ValidConditionType, metav1.ConditionFalse, now, agentprofile.InvalidConditionReason, err.Error())) diff --git a/internal/controller/datadogagent/profile_test.go b/internal/controller/datadogagent/profile_test.go index 09bafc379e..8bc0719d46 100644 --- a/internal/controller/datadogagent/profile_test.go +++ b/internal/controller/datadogagent/profile_test.go @@ -1012,9 +1012,7 @@ func Test_reconcileProfile(t *testing.T) { log: logger, scheme: sch, recorder: recorder, - options: ReconcilerOptions{ - DatadogAgentInternalEnabled: true, - }, + options: ReconcilerOptions{}, } // Pre-populate with default profile for nodes without a profile (matching production code) @@ -1210,9 +1208,7 @@ func Test_reconcileProfiles(t *testing.T) { log: logger, scheme: sch, recorder: recorder, - options: ReconcilerOptions{ - DatadogAgentInternalEnabled: true, - }, + options: ReconcilerOptions{}, } dsNSName := types.NamespacedName{ diff --git a/internal/controller/datadogagent/store/store.go b/internal/controller/datadogagent/store/store.go index bc0e97779b..54b9ed6463 100644 --- a/internal/controller/datadogagent/store/store.go +++ b/internal/controller/datadogagent/store/store.go @@ -31,9 +31,9 @@ import ( const ( // OperatorStoreLabelKey used to identified which resource is managed by the store. OperatorStoreLabelKey = "operator.datadoghq.com/managed-by-store" - // ManagedByDDAControllerLabelKey used to identify resources managed by the DDA controller - // when DatadogAgentInternalEnabled is true. These resources should not be cleaned up - // by the DDAI controller to avoid competition between the two controllers. + // ManagedByDDAControllerLabelKey used to identify resources managed by the DDA controller. + // These resources should not be cleaned up by the DDAI controller to avoid competition + // between the two controllers. ManagedByDDAControllerLabelKey = "operator.datadoghq.com/managed-by-dda-controller" ) @@ -88,8 +88,7 @@ type StoreOptions struct { Scheme *runtime.Scheme Logger logr.Logger - // IsDDAControllerStore indicates that this store is used by the DDA controller - // to manage dependencies when DatadogAgentInternalEnabled is true. + // IsDDAControllerStore indicates that this store is used by the DDA controller. // Resources created by this store will be labeled with ManagedByDDAControllerLabelKey // so they won't be cleaned up by the DDAI controller. IsDDAControllerStore bool @@ -112,9 +111,8 @@ func (ds *Store) AddOrUpdate(kind kubernetes.ObjectKind, obj client.Object) erro } obj.GetLabels()[OperatorStoreLabelKey] = "true" - // Add the DDA controller label when this store is used by the DDA controller - // with DatadogAgentInternalEnabled. This prevents DDAI controller from cleaning - // up these resources. + // Add the DDA controller label when this store is used by the DDA controller. + // This prevents the DDAI controller from cleaning up these resources. if ds.isDDAControllerStore { obj.GetLabels()[ManagedByDDAControllerLabelKey] = "true" } diff --git a/internal/controller/datadogagent/utils.go b/internal/controller/datadogagent/utils.go index 6e411743ea..dd41a21704 100644 --- a/internal/controller/datadogagent/utils.go +++ b/internal/controller/datadogagent/utils.go @@ -246,12 +246,3 @@ func deleteObjectAndOrphanDependents(ctx context.Context, logger logr.Logger, c } return nil } - -// default to true as of 1.21 -// TODO: remove once the UpdateMetadataAnnotationKey annotation is removed -func useV3Metadata(dda metav1.Object) bool { - if val, ok := dda.GetAnnotations()[apicommon.UpdateMetadataAnnotationKey]; ok && val == "false" { - return false - } - return true -} diff --git a/internal/controller/datadogagent_controller.go b/internal/controller/datadogagent_controller.go index 372c351ca7..56d8af639c 100644 --- a/internal/controller/datadogagent_controller.go +++ b/internal/controller/datadogagent_controller.go @@ -248,11 +248,8 @@ func (r *DatadogAgentReconciler) SetupWithManager(mgr ctrl.Manager, metricForwar Owns(&corev1.ServiceAccount{}). // We let PlatformInfo supply PDB object based on the current API version Owns(r.PlatformInfo.CreatePDBObject()). - Owns(&networkingv1.NetworkPolicy{}) - - if r.Options.DatadogAgentInternalEnabled { - builder.Owns(&v1alpha1.DatadogAgentInternal{}) - } + Owns(&networkingv1.NetworkPolicy{}). + Owns(&v1alpha1.DatadogAgentInternal{}) if r.Options.DatadogCSIDriverEnabled { builder.Owns(&v1alpha1.DatadogCSIDriver{}) diff --git a/internal/controller/datadogagent_controller_test.go b/internal/controller/datadogagent_controller_test.go index 82c5a5f174..243bdb8719 100644 --- a/internal/controller/datadogagent_controller_test.go +++ b/internal/controller/datadogagent_controller_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1" "github.com/DataDog/datadog-operator/api/datadoghq/v2alpha1" "github.com/DataDog/datadog-operator/internal/controller/testutils" ) @@ -203,9 +204,9 @@ func checkAgentStatus(namespace string, ddaName string) { Name: ddaName, } - agent := &v2alpha1.DatadogAgent{} - getObjectAndCheck(agent, key, func() bool { - return agent.Status.Agent != nil && agent.Status.ClusterAgent != nil + ddai := &v1alpha1.DatadogAgentInternal{} + getObjectAndCheck(ddai, key, func() bool { + return ddai.Status.Agent != nil && ddai.Status.ClusterAgent != nil }) } diff --git a/internal/controller/datadogagentinternal/controller_reconcile_v2_helpers.go b/internal/controller/datadogagentinternal/controller_reconcile_v2_helpers.go index 587e4c4172..52fa5e23e4 100644 --- a/internal/controller/datadogagentinternal/controller_reconcile_v2_helpers.go +++ b/internal/controller/datadogagentinternal/controller_reconcile_v2_helpers.go @@ -139,7 +139,7 @@ func (r *Reconciler) cleanupExtraneousResources(ctx context.Context, instance *d // applyAndCleanupDependencies applies pending changes and cleans up unused dependencies. // It excludes DDA-managed resources from cleanup to avoid competition between the DDA -// and DDAI controllers when DatadogAgentInternalEnabled is true. +// and DDAI controllers. func (r *Reconciler) applyAndCleanupDependencies(ctx context.Context, depsStore *store.Store) error { logger := ctrl.LoggerFrom(ctx) logger.V(1).Info("Applying pending dependencies and cleaning up unused dependencies") @@ -151,10 +151,9 @@ func (r *Reconciler) applyAndCleanupDependencies(ctx context.Context, depsStore } // Cleanup unused dependencies, excluding resources managed by the DDA controller. - // When DatadogAgentInternalEnabled is true, the DDA controller manages certain - // dependencies (like credentials, DCA token, DCA service) and labels them with - // ManagedByDDAControllerLabelKey. We exclude these from cleanup to prevent - // the DDAI controller from deleting them. + // The DDA controller manages certain dependencies (like credentials, DCA token, + // DCA service) and labels them with ManagedByDDAControllerLabelKey. We exclude + // these from cleanup to prevent the DDAI controller from deleting them. if errs = depsStore.Cleanup(ctx, r.client, true); len(errs) > 0 { logger.V(2).Info("Dependencies cleanup error", "errs", errs) return errors.NewAggregate(errs) diff --git a/internal/controller/setup.go b/internal/controller/setup.go index d92babbd50..adbf7ee113 100644 --- a/internal/controller/setup.go +++ b/internal/controller/setup.go @@ -6,17 +6,9 @@ package controller import ( - "context" - "fmt" "time" "github.com/go-logr/logr" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -26,7 +18,6 @@ import ( "github.com/DataDog/datadog-operator/pkg/config" "github.com/DataDog/datadog-operator/pkg/controller/utils/datadog" "github.com/DataDog/datadog-operator/pkg/kubernetes" - "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" ) const ( @@ -46,7 +37,6 @@ type SetupOptions struct { SupportCilium bool CredsManager *config.CredentialManager DatadogAgentEnabled bool - DatadogAgentInternalEnabled bool DatadogMonitorEnabled bool DatadogSLOEnabled bool OperatorMetricsEnabled bool @@ -94,7 +84,7 @@ func SetupControllers(logger logr.Logger, mgr manager.Manager, platformInfo kube // Metrics Forwarder created -- creds var metricForwardersMgr datadog.MetricsForwardersManager if options.OperatorMetricsEnabled { - metricForwardersMgr = datadog.NewForwardersManager(mgr.GetClient(), &platformInfo, options.DatadogAgentInternalEnabled, options.CredsManager) + metricForwardersMgr = datadog.NewForwardersManager(mgr.GetClient(), &platformInfo, options.CredsManager) } for controller, starter := range controllerStarters { @@ -133,19 +123,20 @@ func startDatadogAgent(logger logr.Logger, mgr manager.Manager, pInfo kubernetes CanaryAutoFailEnabled: options.SupportExtendedDaemonset.CanaryAutoFailEnabled, CanaryAutoFailMaxRestarts: int32(options.SupportExtendedDaemonset.CanaryAutoFailMaxRestarts), }, - SupportCilium: options.SupportCilium, - OperatorMetricsEnabled: options.OperatorMetricsEnabled, - IntrospectionEnabled: options.IntrospectionEnabled, - DatadogAgentProfileEnabled: options.DatadogAgentProfileEnabled, - DatadogAgentInternalEnabled: options.DatadogAgentInternalEnabled, - DatadogCSIDriverEnabled: options.DatadogCSIDriverEnabled, - CreateControllerRevisions: options.CreateControllerRevisions, + SupportCilium: options.SupportCilium, + OperatorMetricsEnabled: options.OperatorMetricsEnabled, + IntrospectionEnabled: options.IntrospectionEnabled, + DatadogAgentProfileEnabled: options.DatadogAgentProfileEnabled, + DatadogCSIDriverEnabled: options.DatadogCSIDriverEnabled, + CreateControllerRevisions: options.CreateControllerRevisions, }, }).SetupWithManager(mgr, metricForwardersMgr) } func startDatadogAgentInternal(logger logr.Logger, mgr manager.Manager, pInfo kubernetes.PlatformInfo, options SetupOptions, metricForwardersMgr datadog.MetricsForwardersManager) error { - if !options.DatadogAgentInternalEnabled { + // Since v1.27, DatadogAgentInternal is always enabled when DatadogAgent is enabled. + // There is no separate flag — DDAI is an internal implementation detail of DDA reconciliation. + if !options.DatadogAgentEnabled { logger.Info("Feature disabled, not starting the controller", "controller", agentInternalControllerName) return nil } @@ -271,88 +262,3 @@ func startDatadogCSIDriver(logger logr.Logger, mgr manager.Manager, pInfo kubern Recorder: mgr.GetEventRecorderFor(csiDriverControllerName), }).SetupWithManager(mgr) } - -// CleanupDatadogAgentInternalResources removes leftover DatadogAgentInternal resources when DDAI controller is disabled -func CleanupDatadogAgentInternalResources(logger logr.Logger, restConfig *rest.Config) error { - logger.Info("Cleaning up leftover DatadogAgentInternal resources") - - // Create a dynamic client for direct API server calls - dynamicClient, err := dynamic.NewForConfig(restConfig) - if err != nil { - return fmt.Errorf("failed to create dynamic client: %w", err) - } - - // Define the GVR for DatadogAgentInternal - ddaiGVR := schema.GroupVersionResource{ - Group: rbac.DatadogAPIGroup, - Version: "v1alpha1", - Resource: rbac.DatadogAgentInternalsResource, - } - - // Try to list DDAI resources directly - this will fail if CRD doesn't exist - ddaiList, err := dynamicClient.Resource(ddaiGVR).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - logger.Info("DatadogAgentInternal CRD not found, skipping cleanup") - return nil - } - return fmt.Errorf("failed to list DatadogAgentInternal resources: %w", err) - } - - logger.Info("Found DatadogAgentInternal resources to cleanup", "count", len(ddaiList.Items)) - - // Process each DDAI resource - for _, ddai := range ddaiList.Items { - namespace := ddai.GetNamespace() - name := ddai.GetName() - - logger.Info("Cleaning up DatadogAgentInternal resource", "namespace", namespace, "name", name) - - // Remove finalizer if it exists - finalizers := ddai.GetFinalizers() - if len(finalizers) > 0 { - // Create a patch to remove the finalizer - patchData := []byte(`{"metadata":{"finalizers":[]}}`) - - _, err = dynamicClient.Resource(ddaiGVR).Namespace(namespace).Patch( - context.TODO(), - name, - types.MergePatchType, - patchData, - metav1.PatchOptions{}, - ) - if err != nil { - if apierrors.IsNotFound(err) { - logger.Info("DatadogAgentInternal resource already deleted", "namespace", namespace, "name", name) - continue - } - logger.Error(err, "Failed to remove finalizer from DatadogAgentInternal resource", "namespace", namespace, "name", name) - // Continue with other resources even if one fails - continue - } - - logger.Info("Removed finalizer from DatadogAgentInternal resource", "namespace", namespace, "name", name) - } - - // Delete the resource - err = dynamicClient.Resource(ddaiGVR).Namespace(namespace).Delete( - context.TODO(), - name, - metav1.DeleteOptions{}, - ) - if err != nil { - if apierrors.IsNotFound(err) { - logger.Info("DatadogAgentInternal resource already deleted", "namespace", namespace, "name", name) - continue - } - logger.Error(err, "Failed to delete DatadogAgentInternal resource", "namespace", namespace, "name", name) - // Continue with other resources even if one fails - continue - } - - logger.Info("Successfully deleted DatadogAgentInternal resource", "namespace", namespace, "name", name) - } - - logger.Info("Completed cleanup of DatadogAgentInternal resources") - return nil -} diff --git a/pkg/agentprofile/agent_profile.go b/pkg/agentprofile/agent_profile.go index 70b4376d8b..622f2db11e 100644 --- a/pkg/agentprofile/agent_profile.go +++ b/pkg/agentprofile/agent_profile.go @@ -88,19 +88,19 @@ func ApplyProfileToNodes(profile metav1.ObjectMeta, profileRequirements []*label } // ValidateProfileAndReturnRequirements validates a profile's name and spec and affinity requirements -func ValidateProfileAndReturnRequirements(profile *v1alpha1.DatadogAgentProfile, ddaiEnabled bool) ([]*labels.Requirement, error) { - if err := validateProfile(profile, ddaiEnabled); err != nil { +func ValidateProfileAndReturnRequirements(profile *v1alpha1.DatadogAgentProfile) ([]*labels.Requirement, error) { + if err := validateProfile(profile); err != nil { return nil, err } return parseProfileRequirements(profile) } // validateProfile validates a profile's name and spec -func validateProfile(profile *v1alpha1.DatadogAgentProfile, ddaiEnabled bool) error { +func validateProfile(profile *v1alpha1.DatadogAgentProfile) error { if err := validateProfileName(profile.Name); err != nil { return fmt.Errorf("profile name is invalid: %w", err) } - if err := v1alpha1.ValidateDatadogAgentProfileSpec(&profile.Spec, ddaiEnabled); err != nil { + if err := v1alpha1.ValidateDatadogAgentProfileSpec(&profile.Spec); err != nil { return fmt.Errorf("profile spec is invalid: %w", err) } return nil @@ -112,7 +112,7 @@ func validateProfile(profile *v1alpha1.DatadogAgentProfile, ddaiEnabled bool) er // - existing nodes with the correct label // - nodes that need a new or corrected label up to maxUnavailable # of nodes func ApplyProfile(logger logr.Logger, profile *v1alpha1.DatadogAgentProfile, nodes []v1.Node, profileAppliedByNode map[string]types.NamespacedName, - now metav1.Time, maxUnavailable int, datadogAgentInternalEnabled bool) (map[string]types.NamespacedName, error) { + now metav1.Time, maxUnavailable int) (map[string]types.NamespacedName, error) { matchingNodes := map[string]bool{} profileStatus := v1alpha1.DatadogAgentProfileStatus{} @@ -130,7 +130,7 @@ func ApplyProfile(logger logr.Logger, profile *v1alpha1.DatadogAgentProfile, nod return profileAppliedByNode, err } - if err := v1alpha1.ValidateDatadogAgentProfileSpec(&profile.Spec, datadogAgentInternalEnabled); err != nil { + if err := v1alpha1.ValidateDatadogAgentProfileSpec(&profile.Spec); err != nil { logger.Error(err, "profile spec is invalid, skipping", "datadogagentprofile", profile.Name, "datadogagentprofile_namespace", profile.Namespace) metrics.DAPValid.With(prometheus.Labels{"datadogagentprofile": profile.Name}).Set(metrics.FalseValue) profileStatus.Conditions = SetDatadogAgentProfileCondition(profileStatus.Conditions, NewDatadogAgentProfileCondition(ValidConditionType, metav1.ConditionFalse, now, InvalidConditionReason, err.Error())) diff --git a/pkg/agentprofile/agent_profile_test.go b/pkg/agentprofile/agent_profile_test.go index 126b597325..e0fd68bec2 100644 --- a/pkg/agentprofile/agent_profile_test.go +++ b/pkg/agentprofile/agent_profile_test.go @@ -38,7 +38,6 @@ func TestApplyProfile(t *testing.T) { name string profile v1alpha1.DatadogAgentProfile nodes []corev1.Node - datadogAgentInternalEnabled bool profileAppliedByNode map[string]types.NamespacedName expectedProfilesAppliedPerNode map[string]types.NamespacedName expectedErr error @@ -56,7 +55,6 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, profileAppliedByNode: map[string]types.NamespacedName{}, expectedProfilesAppliedPerNode: map[string]types.NamespacedName{}, expectedErr: fmt.Errorf("Profile name cannot be empty"), @@ -74,7 +72,6 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, profileAppliedByNode: map[string]types.NamespacedName{ "node1": { Namespace: testNamespace, @@ -99,7 +96,6 @@ func TestApplyProfile(t *testing.T) { Name: "linux", }, }, - datadogAgentInternalEnabled: true, expectedProfilesAppliedPerNode: map[string]types.NamespacedName{ "node1": { Namespace: testNamespace, @@ -121,8 +117,7 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, - profileAppliedByNode: map[string]types.NamespacedName{}, + profileAppliedByNode: map[string]types.NamespacedName{}, expectedProfilesAppliedPerNode: map[string]types.NamespacedName{ "node1": { Namespace: testNamespace, @@ -152,7 +147,6 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, profileAppliedByNode: map[string]types.NamespacedName{ "node2": { Namespace: testNamespace, @@ -181,7 +175,6 @@ func TestApplyProfile(t *testing.T) { Name: "windows", }, }, - datadogAgentInternalEnabled: true, expectedProfilesAppliedPerNode: map[string]types.NamespacedName{ "node2": { Namespace: testNamespace, @@ -211,7 +204,6 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, profileAppliedByNode: map[string]types.NamespacedName{ "node1": { Namespace: testNamespace, @@ -247,7 +239,6 @@ func TestApplyProfile(t *testing.T) { }, }, }, - datadogAgentInternalEnabled: true, profileAppliedByNode: map[string]types.NamespacedName{ "node1": { Namespace: testNamespace, @@ -261,39 +252,13 @@ func TestApplyProfile(t *testing.T) { }, expectedErr: fmt.Errorf("profileAffinity must be defined"), }, - { - name: "feature override when ddai disabled", - profile: exampleFeatureOverrideProfile(), - nodes: []corev1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - Labels: map[string]string{ - "os": "linux", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - Labels: map[string]string{ - "os": "windows", - }, - }, - }, - }, - datadogAgentInternalEnabled: false, - profileAppliedByNode: map[string]types.NamespacedName{}, - expectedProfilesAppliedPerNode: map[string]types.NamespacedName{}, - expectedErr: fmt.Errorf("the 'features' field is only supported when DatadogAgentInternal is enabled"), - }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { testLogger := zap.New(zap.UseDevMode(true)) now := metav1.NewTime(time.Now()) - profileAppliedByNode, err := ApplyProfile(testLogger, &test.profile, test.nodes, test.profileAppliedByNode, now, 1, test.datadogAgentInternalEnabled) + profileAppliedByNode, err := ApplyProfile(testLogger, &test.profile, test.nodes, test.profileAppliedByNode, now, 1) assert.Equal(t, test.expectedErr, err) assert.Equal(t, test.expectedProfilesAppliedPerNode, profileAppliedByNode) }) diff --git a/pkg/config/config.go b/pkg/config/config.go index 7a37b66ed7..f6e192e96a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -66,7 +66,6 @@ var ( type WatchOptions struct { DatadogAgentEnabled bool - DatadogAgentInternalEnabled bool DatadogMonitorEnabled bool DatadogSLOEnabled bool DatadogAgentProfileEnabled bool @@ -186,7 +185,8 @@ func CacheOptions(logger logr.Logger, opts WatchOptions) cache.Options { } } - if opts.DatadogAgentInternalEnabled { + // Since v1.27, DDAI is always tied to DDA — no separate flag. Kept as DDA guard since DDAI cache is only needed when DDA is enabled. + if opts.DatadogAgentEnabled { agentInternalNamespaces := GetWatchNamespacesFromEnv(logger, AgentWatchNamespaceEnvVar) logger.Info("DatadogAgentInternal Enabled", "watching namespaces", slices.Collect(maps.Keys(agentInternalNamespaces))) byObject[agentInternalObj] = cache.ByObject{ diff --git a/pkg/controller/utils/datadog/forwarders_manager.go b/pkg/controller/utils/datadog/forwarders_manager.go index b2e8192322..8c09f0e8c8 100644 --- a/pkg/controller/utils/datadog/forwarders_manager.go +++ b/pkg/controller/utils/datadog/forwarders_manager.go @@ -32,10 +32,9 @@ type MetricsForwardersManager interface { // ForwardersManager is a collection of metricsForwarder per DatadogAgent // ForwardersManager implements the controller-runtime Runnable interface type ForwardersManager struct { - k8sClient client.Client - platformInfo *kubernetes.PlatformInfo - metricsForwarders map[string]*metricsForwarder - datadogAgentInternalEnabled bool + k8sClient client.Client + platformInfo *kubernetes.PlatformInfo + metricsForwarders map[string]*metricsForwarder // TODO expand this to include a metadataForwarder decryptor secrets.Decryptor credsManager *config.CredentialManager @@ -45,15 +44,14 @@ type ForwardersManager struct { // NewForwardersManager builds a new ForwardersManager object // ForwardersManager implements the controller-runtime Runnable interface -func NewForwardersManager(k8sClient client.Client, platformInfo *kubernetes.PlatformInfo, datadogAgentInternalEnabled bool, credsManager *config.CredentialManager) *ForwardersManager { +func NewForwardersManager(k8sClient client.Client, platformInfo *kubernetes.PlatformInfo, credsManager *config.CredentialManager) *ForwardersManager { return &ForwardersManager{ - k8sClient: k8sClient, - platformInfo: platformInfo, - metricsForwarders: make(map[string]*metricsForwarder), - datadogAgentInternalEnabled: datadogAgentInternalEnabled, - decryptor: secrets.NewSecretBackend(), - wg: sync.WaitGroup{}, - credsManager: credsManager, + k8sClient: k8sClient, + platformInfo: platformInfo, + metricsForwarders: make(map[string]*metricsForwarder), + decryptor: secrets.NewSecretBackend(), + wg: sync.WaitGroup{}, + credsManager: credsManager, } } @@ -71,7 +69,7 @@ func (f *ForwardersManager) Register(obj client.Object) { defer f.Unlock() id := getObjID(obj) // nolint: ifshort if _, found := f.metricsForwarders[id]; !found { - f.metricsForwarders[id] = newMetricsForwarder(f.k8sClient, f.decryptor, obj, f.platformInfo, f.datadogAgentInternalEnabled, f.credsManager) + f.metricsForwarders[id] = newMetricsForwarder(f.k8sClient, f.decryptor, obj, f.platformInfo, f.credsManager) f.wg.Add(1) go f.metricsForwarders[id].start(&f.wg) } diff --git a/pkg/controller/utils/datadog/metrics_forwarder.go b/pkg/controller/utils/datadog/metrics_forwarder.go index 91444f760c..1f7009fe45 100644 --- a/pkg/controller/utils/datadog/metrics_forwarder.go +++ b/pkg/controller/utils/datadog/metrics_forwarder.go @@ -122,54 +122,51 @@ type metricsForwarder struct { EnabledFeatures map[string][]string - keysHash uint64 - retryInterval time.Duration - sendMetricsInterval time.Duration - metricsPrefix string - globalTags []string - tags []string - stopChan chan struct{} - errorChan chan error - eventChan chan Event - lastReconcileErr error - namespacedName types.NamespacedName - logger logr.Logger - delegator delegatedAPI - decryptor secrets.Decryptor - creds sync.Map - baseURL string - status *ConditionCommon - credsManager *config.CredentialManager - datadogAgentInternalEnabled bool + keysHash uint64 + retryInterval time.Duration + sendMetricsInterval time.Duration + metricsPrefix string + globalTags []string + tags []string + stopChan chan struct{} + errorChan chan error + eventChan chan Event + lastReconcileErr error + namespacedName types.NamespacedName + logger logr.Logger + delegator delegatedAPI + decryptor secrets.Decryptor + creds sync.Map + baseURL string + status *ConditionCommon + credsManager *config.CredentialManager sync.RWMutex } // newMetricsForwarder returns a new Datadog MetricsForwarder instance -func newMetricsForwarder(k8sClient client.Client, decryptor secrets.Decryptor, obj client.Object, platforminfo *kubernetes.PlatformInfo, datadogAgentInternalEnabled bool, credsManager *config.CredentialManager) *metricsForwarder { - +func newMetricsForwarder(k8sClient client.Client, decryptor secrets.Decryptor, obj client.Object, platforminfo *kubernetes.PlatformInfo, credsManager *config.CredentialManager) *metricsForwarder { objKind := getObjKind(obj) logger := log.WithValues("kind", objKind, "namespace", obj.GetNamespace(), "name", obj.GetName()) return &metricsForwarder{ - id: getObjID(obj), - monitoredObjectKind: objKind, - k8sClient: k8sClient, - platformInfo: platforminfo, - namespacedName: GetNamespacedName(obj), - retryInterval: defaultMetricsRetryInterval, - sendMetricsInterval: defaultSendMetricsInterval, - metricsPrefix: defaultMetricsNamespace, - stopChan: make(chan struct{}), - errorChan: make(chan error, 100), - eventChan: make(chan Event, 10), - lastReconcileErr: errInitValue, - decryptor: decryptor, - creds: sync.Map{}, - baseURL: defaultbaseURL, - logger: logger, - credsManager: credsManager, - EnabledFeatures: make(map[string][]string), - datadogAgentInternalEnabled: datadogAgentInternalEnabled, + id: getObjID(obj), + monitoredObjectKind: objKind, + k8sClient: k8sClient, + platformInfo: platforminfo, + namespacedName: GetNamespacedName(obj), + retryInterval: defaultMetricsRetryInterval, + sendMetricsInterval: defaultSendMetricsInterval, + metricsPrefix: defaultMetricsNamespace, + stopChan: make(chan struct{}), + errorChan: make(chan error, 100), + eventChan: make(chan Event, 10), + lastReconcileErr: errInitValue, + decryptor: decryptor, + creds: sync.Map{}, + baseURL: defaultbaseURL, + logger: logger, + credsManager: credsManager, + EnabledFeatures: make(map[string][]string), } } @@ -414,8 +411,9 @@ func (mf *metricsForwarder) forwardMetrics() error { ctx := mf.generateDatadogContext() // Send status-based metrics - if mf.monitoredObjectKind == datadogAgentKind && mf.datadogAgentInternalEnabled { - mf.logger.V(1).Info("DatadogAgentInternal is enabled, skipping status metrics for DatadogAgent") + // DatadogAgent status metrics are handled by the DDAI controller, so skip them here. + if mf.monitoredObjectKind == datadogAgentKind { + mf.logger.V(1).Info("DatadogAgentInternal handles status metrics for DatadogAgent, skipping") } else { if err = mf.sendStatusMetrics(ctx, mf.dsStatus, mf.dcaStatus, mf.ccrStatus, mf.agentStatus); err != nil { mf.logger.Error(err, "cannot send status metrics to Datadog")