Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions internal/controller/datadogagent/controller_reconcile_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ func (r *Reconciler) reconcileInstanceV3(ctx context.Context, logger logr.Logger
}
}

// Manage dependencies
if err := r.manageDDADependenciesWithDDAI(ctx, logger, instance, newDDAStatus); err != nil {
return r.updateStatusIfNeededV2(logger, instance, ddaStatusCopy, result, err, now)
}

// Generate default DDAI object from DDA
ddai, err := r.generateDDAIFromDDA(instance)
if err != nil {
Expand Down Expand Up @@ -119,6 +114,12 @@ func (r *Reconciler) reconcileInstanceV3(ctx context.Context, logger logr.Logger
ddais = profileDDAIs
}

// Manage dependencies after DDAIs are computed to include profile changes
err = r.manageDDADependenciesWithDDAI(ctx, logger, instance, newDDAStatus, ddais)
if err != nil {
return r.updateStatusIfNeededV2(logger, instance, ddaStatusCopy, result, err, now)
}

// Create or update the DDAI object in k8s
for _, ddai := range ddais {
if e := r.createOrUpdateDDAI(ddai); e != nil {
Expand Down
172 changes: 168 additions & 4 deletions internal/controller/datadogagent/controller_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/tools/record"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -37,8 +38,10 @@ import (
"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/experimental"
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/store"
agenttestutils "github.com/DataDog/datadog-operator/internal/controller/datadogagent/testutils"
"github.com/DataDog/datadog-operator/internal/controller/datadogagentinternal"
"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"
Expand All @@ -61,6 +64,7 @@ type testCase struct {
profile *v1alpha1.DatadogAgentProfile // For DDAI tests
profilesEnabled bool // For DDAI tests
introspectionEnabled bool // For introspection tests
platformInfo *kubernetes.PlatformInfo
}

// ddaiReconcilerOptionsFromDDA mirrors setup.go wiring so DDAI tests behave like production
Expand All @@ -74,6 +78,13 @@ func ddaiReconcilerOptionsFromDDA(opts ReconcilerOptions) datadogagentinternal.R
}
}

func platformInfoForTest(tt testCase) kubernetes.PlatformInfo {
if tt.platformInfo != nil {
return *tt.platformInfo
}
return kubernetes.PlatformInfo{}
}

// runTestCases runs test cases
func runTestCases(t *testing.T, tests []testCase, testFunc func(t *testing.T, tt testCase, opts ReconcilerOptions)) {
for _, tt := range tests {
Expand Down Expand Up @@ -105,12 +116,13 @@ func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) {
forwarders := dummyManager{}

c := buildClient(t, tt, s)
platformInfo := platformInfoForTest(tt)

// Create reconciler
r := &Reconciler{
client: c,
scheme: s,
platformInfo: kubernetes.PlatformInfo{},
platformInfo: platformInfo,
recorder: recorder,
log: logf.Log.WithName(tt.name),
forwarders: forwarders,
Expand All @@ -121,7 +133,7 @@ func runDDAReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) {
ri := datadogagentinternal.NewReconciler(
ddaiReconcilerOptionsFromDDA(opts),
c,
kubernetes.PlatformInfo{},
platformInfo,
s,
recorder,
forwarders)
Expand Down Expand Up @@ -178,12 +190,13 @@ func runFullReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) {
forwarders := dummyManager{}

c := buildClient(t, tt, s)
platformInfo := platformInfoForTest(tt)

// Create reconciler
r := &Reconciler{
client: c,
scheme: s,
platformInfo: kubernetes.PlatformInfo{},
platformInfo: platformInfo,
recorder: recorder,
log: logf.Log.WithName(tt.name),
forwarders: forwarders,
Expand All @@ -194,7 +207,7 @@ func runFullReconcilerTest(t *testing.T, tt testCase, opts ReconcilerOptions) {
ri := datadogagentinternal.NewReconciler(
ddaiReconcilerOptionsFromDDA(opts),
c,
kubernetes.PlatformInfo{},
platformInfo,
s,
recorder,
forwarders)
Expand Down Expand Up @@ -1602,6 +1615,157 @@ func Test_COSProviderOverrides(t *testing.T) {
runTestCases(t, tests, runFullReconcilerTest)
}

func Test_ProfileAPMOverrideAddsDDAOwnedLocalAgentServicePort(t *testing.T) {
const resourcesName = "foo"
const resourcesNamespace = "bar"
const profileName = "apm-profile"
defaultRequeueDuration := 15 * time.Second

dda := testutils.NewInitializedDatadogAgentBuilder(resourcesNamespace, resourcesName).
WithAPMEnabled(false).
BuildWithDefaults()
localAgentServiceName := constants.GetLocalAgentServiceName(resourcesName, &dda.Spec)
platformInfo := kubernetes.NewPlatformInfoFromVersionMaps(&version.Info{GitVersion: "1.32.0"}, nil, nil)

newAPMProfile := func(name string, apm *v2alpha1.APMFeatureConfig) *v1alpha1.DatadogAgentProfile {
return &v1alpha1.DatadogAgentProfile{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: resourcesNamespace,
},
Spec: v1alpha1.DatadogAgentProfileSpec{
ProfileAffinity: &v1alpha1.ProfileAffinity{
ProfileNodeAffinity: []corev1.NodeSelectorRequirement{
{
Key: "profile",
Operator: corev1.NodeSelectorOpIn,
Values: []string{name},
},
},
},
Config: &v2alpha1.DatadogAgentSpec{
Features: &v2alpha1.DatadogFeatures{
APM: apm,
},
},
},
}
}
profile := newAPMProfile(profileName, &v2alpha1.APMFeatureConfig{
Enabled: ptr.To(true),
})
conflictingProfileA := newAPMProfile("apm-profile-a", &v2alpha1.APMFeatureConfig{
Enabled: ptr.To(true),
HostPortConfig: &v2alpha1.HostPortConfig{
Enabled: ptr.To(true),
Port: ptr.To[int32](8126),
},
})
conflictingProfileB := newAPMProfile("apm-profile-b", &v2alpha1.APMFeatureConfig{
Enabled: ptr.To(true),
HostPortConfig: &v2alpha1.HostPortConfig{
Enabled: ptr.To(true),
Port: ptr.To[int32](9126),
},
})

tests := []testCase{
{
name: "profile APM override adds trace port to DDA-owned local Agent Service",
clientBuilder: fake.NewClientBuilder().
WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}).
WithObjects(profile),
loadFunc: func(c client.Client) *v2alpha1.DatadogAgent {
ddaCopy := dda.DeepCopy()
_ = c.Create(context.TODO(), ddaCopy)
return ddaCopy
},
profilesEnabled: true,
platformInfo: &platformInfo,
want: reconcile.Result{RequeueAfter: defaultRequeueDuration},
wantErr: false,
wantFunc: func(t *testing.T, c client.Client) {
service := &corev1.Service{}
err := c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: localAgentServiceName}, service)
assert.NoError(t, err)
assert.Equal(t, "true", service.Labels[store.ManagedByDDAControllerLabelKey])

apmPort := findServicePortByName(service.Spec.Ports, constants.DefaultApmPortName)
assert.NotNil(t, apmPort)
assert.Equal(t, corev1.ProtocolTCP, apmPort.Protocol)
assert.Equal(t, int32(constants.DefaultApmPort), apmPort.Port)
assert.Equal(t, intstr.FromInt(int(constants.DefaultApmPort)), apmPort.TargetPort)

defaultDDAI := &v1alpha1.DatadogAgentInternal{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: resourcesName}, defaultDDAI)
assert.NoError(t, err)
assert.False(t, ptr.Deref(defaultDDAI.Spec.Features.APM.Enabled, true))

profileDDAI := &v1alpha1.DatadogAgentInternal{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: profileName}, profileDDAI)
assert.NoError(t, err)
assert.True(t, ptr.Deref(profileDDAI.Spec.Features.APM.Enabled, false))
},
},
{
name: "conflicting profile APM override rejects conflicting profile and reconciles accepted profile",
clientBuilder: fake.NewClientBuilder().
WithStatusSubresource(&v2alpha1.DatadogAgent{}, &v1alpha1.DatadogAgentProfile{}, &v1alpha1.DatadogAgentInternal{}).
WithObjects(conflictingProfileA, conflictingProfileB),
loadFunc: func(c client.Client) *v2alpha1.DatadogAgent {
ddaCopy := dda.DeepCopy()
_ = c.Create(context.TODO(), ddaCopy)
return ddaCopy
},
profilesEnabled: true,
platformInfo: &platformInfo,
want: reconcile.Result{RequeueAfter: defaultRequeueDuration},
wantErr: false,
wantFunc: func(t *testing.T, c client.Client) {
service := &corev1.Service{}
err := c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: localAgentServiceName}, service)
assert.NoError(t, err)
assert.Equal(t, "true", service.Labels[store.ManagedByDDAControllerLabelKey])

apmPort := findServicePortByName(service.Spec.Ports, constants.DefaultApmPortName)
assert.NotNil(t, apmPort)
assert.Equal(t, int32(8126), apmPort.Port)
assert.Equal(t, intstr.FromInt(int(constants.DefaultApmPort)), apmPort.TargetPort)

appliedProfile := &v1alpha1.DatadogAgentProfile{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: "apm-profile-a"}, appliedProfile)
assert.NoError(t, err)
assert.Equal(t, metav1.ConditionTrue, appliedProfile.Status.Applied)

conflictingProfile := &v1alpha1.DatadogAgentProfile{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: "apm-profile-b"}, conflictingProfile)
assert.NoError(t, err)
assert.Equal(t, metav1.ConditionFalse, conflictingProfile.Status.Applied)
assert.Contains(t, profileConditionMessage(conflictingProfile.Status.Conditions, agentprofile.AppliedConditionType), "port \"traceport\" conflicts")

appliedDDAI := &v1alpha1.DatadogAgentInternal{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: "apm-profile-a"}, appliedDDAI)
assert.NoError(t, err)

conflictingDDAI := &v1alpha1.DatadogAgentInternal{}
err = c.Get(context.TODO(), types.NamespacedName{Namespace: resourcesNamespace, Name: "apm-profile-b"}, conflictingDDAI)
assert.True(t, apierrors.IsNotFound(err), "conflicting profile DDAI should not be rendered")
},
},
}

runTestCases(t, tests, runFullReconcilerTest)
}

func profileConditionMessage(conditions []metav1.Condition, conditionType string) string {
for _, condition := range conditions {
if condition.Type == conditionType {
return condition.Message
}
}
return ""
}

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)
Expand Down
31 changes: 27 additions & 4 deletions internal/controller/datadogagent/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ package datadogagent

import (
"context"
"fmt"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/errors"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

v1alpha1 "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"
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/component/clusteragent"
Expand All @@ -38,7 +40,7 @@ func (r *Reconciler) setupDDADependenciesStore(instance *v2alpha1.DatadogAgent,
return depsStore, resourceManagers
}

func (r *Reconciler) manageDDADependenciesWithDDAI(ctx context.Context, logger logr.Logger, instance *v2alpha1.DatadogAgent, newDDAStatus *v2alpha1.DatadogAgentStatus) error {
func (r *Reconciler) manageDDADependenciesWithDDAI(ctx context.Context, logger logr.Logger, instance *v2alpha1.DatadogAgent, newDDAStatus *v2alpha1.DatadogAgentStatus, ddais []*v1alpha1.DatadogAgentInternal) error {
// Use a store marked as DDA controller store so resources are labeled
// with ManagedByDDAControllerLabelKey and won't be cleaned up by DDAI controller.
depsStore, resourceManagers := r.setupDDADependenciesStore(instance, logger)
Expand Down Expand Up @@ -71,16 +73,22 @@ func (r *Reconciler) manageDDADependenciesWithDDAI(ctx context.Context, logger l
return err
}

// Dependencies that can get configs from DDA and DDAI
// Example: agent local service for APM, DSD, and OTLP
if err := r.addDDASharedDependencies(instance, ddais, resourceManagers); err != nil {
return err
}

// Apply dependencies
if err := depsStore.Apply(ctx, r.client); err != nil {
return errors.NewAggregate(err)
return utilerrors.NewAggregate(err)
}

// Cleanup unused DDA controller dependencies.
// Pass false since we want to clean up DDA-managed resources (this is the DDA controller).
// Note that we don't really need to clean these dependencies as they're all ownerRef'ed by the DDA, so they will be cleaned if the DDA is deleted.
if err := depsStore.Cleanup(ctx, r.client, false); err != nil {
return errors.NewAggregate(err)
return utilerrors.NewAggregate(err)
}

// DatadogCSIDriver: create or delete based on spec.global.csi configuration.
Expand All @@ -93,6 +101,21 @@ func (r *Reconciler) manageDDADependenciesWithDDAI(ctx context.Context, logger l
return nil
}

func (r *Reconciler) addDDASharedDependencies(dda *v2alpha1.DatadogAgent, ddais []*v1alpha1.DatadogAgentInternal, managers feature.ResourceManagers) error {
var errs []error

for _, ddai := range ddais {
if err := feature.ApplyDDASharedDependencies(dda, &dda.Spec, ddai, &ddai.Spec, managers); err != nil {
errs = append(errs, fmt.Errorf("%s/%s DDA shared dependencies failed: %w", ddai.Namespace, ddai.Name, err))
}
}

if len(errs) > 0 {
return utilerrors.NewAggregate(errs)
}
return nil
}

func ensureAutoGeneratedTokenInStatus(instance *v2alpha1.DatadogAgent, newStatus *v2alpha1.DatadogAgentStatus, resourceManagers feature.ResourceManagers, logger logr.Logger) {
if instance.Status.ClusterAgent != nil && instance.Status.ClusterAgent.GeneratedToken != "" {
// Already there; nothing to do.
Expand Down
Loading
Loading