Skip to content

Commit 4c037ce

Browse files
committed
increase unit test coverage
1 parent e82e571 commit 4c037ce

3 files changed

Lines changed: 218 additions & 7 deletions

File tree

internal/controller/setup.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010
"time"
1111

1212
"github.com/go-logr/logr"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/client-go/tools/record"
1315
ctrl "sigs.k8s.io/controller-runtime"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1417
"sigs.k8s.io/controller-runtime/pkg/manager"
1518

1619
"github.com/DataDog/datadog-operator/internal/controller/datadogagent"
@@ -273,17 +276,28 @@ func startDatadogAgentProfiles(logger logr.Logger, mgr manager.Manager, pInfo ku
273276
}).SetupWithManager(mgr)
274277
}
275278

276-
func startDatadogCSIDriver(logger logr.Logger, mgr manager.Manager, pInfo kubernetes.PlatformInfo, options SetupOptions, metricForwardersMgr datadog.MetricsForwardersManager) error {
277-
if !options.DatadogCSIDriverEnabled {
278-
logger.Info("Feature disabled, not starting the controller", "controller", csiDriverControllerName)
279-
return nil
280-
}
279+
// csiDriverManagerDeps is the subset of manager.Manager required to construct DatadogCSIDriverReconciler.
280+
type csiDriverManagerDeps interface {
281+
GetClient() client.Client
282+
GetScheme() *runtime.Scheme
283+
GetEventRecorderFor(name string) record.EventRecorder
284+
}
281285

282-
return (&DatadogCSIDriverReconciler{
286+
func newDatadogCSIDriverReconciler(mgr csiDriverManagerDeps, options SetupOptions) *DatadogCSIDriverReconciler {
287+
return &DatadogCSIDriverReconciler{
283288
Client: mgr.GetClient(),
284289
Scheme: mgr.GetScheme(),
285290
Recorder: mgr.GetEventRecorderFor(csiDriverControllerName),
286291
// Inject startup toleration on CSI node DaemonSet only when untaint coordinates with CSI.
287292
UntaintInjectCSIStartupToleration: options.UntaintControllerEnabled && options.UntaintControllerWaitForCSIDriver,
288-
}).SetupWithManager(mgr)
293+
}
294+
}
295+
296+
func startDatadogCSIDriver(logger logr.Logger, mgr manager.Manager, pInfo kubernetes.PlatformInfo, options SetupOptions, metricForwardersMgr datadog.MetricsForwardersManager) error {
297+
if !options.DatadogCSIDriverEnabled {
298+
logger.Info("Feature disabled, not starting the controller", "controller", csiDriverControllerName)
299+
return nil
300+
}
301+
302+
return newDatadogCSIDriverReconciler(mgr, options).SetupWithManager(mgr)
289303
}

internal/controller/setup_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@ package controller
77

88
import (
99
"errors"
10+
"os"
1011
"testing"
1112

1213
"github.com/go-logr/logr"
1314
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
18+
"k8s.io/client-go/rest"
19+
"k8s.io/client-go/tools/record"
20+
ctrl "sigs.k8s.io/controller-runtime"
21+
"sigs.k8s.io/controller-runtime/pkg/client"
22+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1423
"sigs.k8s.io/controller-runtime/pkg/log"
1524
"sigs.k8s.io/controller-runtime/pkg/manager"
1625

@@ -42,3 +51,57 @@ func TestSetupControllers_StarterErrorsAreBestEffort(t *testing.T) {
4251
SetupOptions{UntaintControllerEnabled: true},
4352
))
4453
}
54+
55+
type csiMgrStub struct {
56+
cli client.Client
57+
sch *runtime.Scheme
58+
rec record.EventRecorder
59+
}
60+
61+
func (s *csiMgrStub) GetClient() client.Client { return s.cli }
62+
63+
func (s *csiMgrStub) GetScheme() *runtime.Scheme { return s.sch }
64+
65+
func (s *csiMgrStub) GetEventRecorderFor(string) record.EventRecorder { return s.rec }
66+
67+
func TestNewDatadogCSIDriverReconciler_UntaintInjectCSIStartupToleration(t *testing.T) {
68+
s := runtime.NewScheme()
69+
cli := fake.NewClientBuilder().WithScheme(s).Build()
70+
rec := record.NewFakeRecorder(1)
71+
stub := &csiMgrStub{cli: cli, sch: s, rec: rec}
72+
73+
for _, tc := range []struct {
74+
name string
75+
untaint bool
76+
waitCSI bool
77+
want bool
78+
}{
79+
{"both true", true, true, true},
80+
{"untaint off", false, true, false},
81+
{"wait CSI off", true, false, false},
82+
{"both off", false, false, false},
83+
} {
84+
t.Run(tc.name, func(t *testing.T) {
85+
r := newDatadogCSIDriverReconciler(stub, SetupOptions{
86+
UntaintControllerEnabled: tc.untaint,
87+
UntaintControllerWaitForCSIDriver: tc.waitCSI,
88+
})
89+
assert.Equal(t, tc.want, r.UntaintInjectCSIStartupToleration)
90+
})
91+
}
92+
}
93+
94+
func TestStartUntaint_NewUntaintReconcilerErrorIsWrapped(t *testing.T) {
95+
ctrl.SetLogger(logr.Discard())
96+
t.Setenv(EnvTimeoutPolicy, "unknown")
97+
t.Cleanup(func() { _ = os.Unsetenv(EnvTimeoutPolicy) })
98+
99+
s := runtime.NewScheme()
100+
require.NoError(t, clientgoscheme.AddToScheme(s))
101+
mgr, err := ctrl.NewManager(&rest.Config{}, manager.Options{Scheme: s, LeaderElection: false})
102+
require.NoError(t, err)
103+
104+
err = startUntaint(logr.Discard(), mgr, kubernetes.PlatformInfo{}, SetupOptions{UntaintControllerEnabled: true}, nil)
105+
require.Error(t, err)
106+
assert.Contains(t, err.Error(), "untaint controller setup")
107+
}

internal/controller/untaint_controller_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"encoding/json"
1111
"errors"
1212
"os"
13+
"sync/atomic"
1314
"testing"
1415
"time"
1516

@@ -721,6 +722,139 @@ func TestReconcile_CSI_agentReadyNoCsiPodKeepsTaint(t *testing.T) {
721722
assert.True(t, hasTaint(fresh))
722723
}
723724

725+
func TestReconcile_CSI_agentReadyNoCsiPod_zeroCreationTimestampRequeuesSchedulingTimeout(t *testing.T) {
726+
now := testNow()
727+
node := &corev1.Node{
728+
ObjectMeta: metav1.ObjectMeta{
729+
Name: testNodeName,
730+
CreationTimestamp: metav1.Time{}, // zero — defensive branch in reconcileAgentReadyWaitForCSI
731+
},
732+
Spec: corev1.NodeSpec{Taints: []corev1.Taint{untaint.AgentNotReadyTaint()}},
733+
}
734+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
735+
c := newFakeClient(t, node, agent)
736+
const scheduling = 7 * time.Minute
737+
r, _ := newReconciler(t, c, now, PolicyRemove, time.Minute, scheduling, true)
738+
739+
result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
740+
require.NoError(t, err)
741+
assert.Equal(t, scheduling, result.RequeueAfter)
742+
743+
fresh := &corev1.Node{}
744+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: testNodeName}, fresh))
745+
require.True(t, hasTaint(fresh))
746+
}
747+
748+
func TestReconcile_CSI_agentReadyNoCsiPod_schedulingTimeoutRemovesTaint(t *testing.T) {
749+
now := testNow()
750+
const scheduling = 5 * time.Minute
751+
node := taintedNode(testNodeName, scheduling+time.Minute, now)
752+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
753+
c := newFakeClient(t, node, agent)
754+
r, _ := newReconciler(t, c, now, PolicyRemove, 10*time.Minute, scheduling, true)
755+
756+
result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
757+
require.NoError(t, err)
758+
assert.Equal(t, ctrl.Result{}, result)
759+
760+
fresh := &corev1.Node{}
761+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: testNodeName}, fresh))
762+
assert.False(t, hasTaint(fresh))
763+
}
764+
765+
func TestReconcile_CSI_agentReadyCsiNotReady_noStartTimeRequeuesReadinessTimeout(t *testing.T) {
766+
now := testNow()
767+
node := taintedNode(testNodeName, 0, now)
768+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
769+
// not Ready, startedAgo 0 → no Status.StartTime → latestPodStartTime !ok branch
770+
csi := csiNodeServerPod("csi-1", testPodNS, testNodeName, false, 0, now)
771+
c := newFakeClient(t, node, agent, csi)
772+
const readiness = 9 * time.Minute
773+
r, _ := newReconciler(t, c, now, PolicyRemove, readiness, time.Minute, true)
774+
775+
result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
776+
require.NoError(t, err)
777+
assert.Equal(t, readiness, result.RequeueAfter)
778+
779+
fresh := &corev1.Node{}
780+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: testNodeName}, fresh))
781+
assert.True(t, hasTaint(fresh))
782+
}
783+
784+
func TestReconcile_CSI_agentReadyCsiNotReady_readinessTimeoutRemovesTaint(t *testing.T) {
785+
now := testNow()
786+
node := taintedNode(testNodeName, 0, now)
787+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
788+
const readiness = time.Minute
789+
csi := csiNodeServerPod("csi-1", testPodNS, testNodeName, false, 2*readiness, now)
790+
c := newFakeClient(t, node, agent, csi)
791+
r, _ := newReconciler(t, c, now, PolicyRemove, readiness, 5*time.Minute, true)
792+
793+
result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
794+
require.NoError(t, err)
795+
assert.Equal(t, ctrl.Result{}, result)
796+
797+
fresh := &corev1.Node{}
798+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: testNodeName}, fresh))
799+
assert.False(t, hasTaint(fresh))
800+
}
801+
802+
func TestReconcile_CSI_listDriverPodsError(t *testing.T) {
803+
now := testNow()
804+
node := taintedNode(testNodeName, 0, now)
805+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
806+
base := newFakeClient(t, node, agent)
807+
var listCalls atomic.Int32
808+
c := interceptor.NewClient(base, interceptor.Funcs{
809+
List: func(ctx context.Context, cl client.WithWatch, list client.ObjectList, opts ...client.ListOption) error {
810+
if listCalls.Add(1) == 2 {
811+
return errors.New("simulated CSI pod list failure")
812+
}
813+
return base.List(ctx, list, opts...)
814+
},
815+
})
816+
r, _ := newReconciler(t, c, now, PolicyRemove, time.Minute, time.Minute, true)
817+
818+
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
819+
require.Error(t, err)
820+
assert.Contains(t, err.Error(), "failed to list CSI driver pods")
821+
}
822+
823+
func TestReconcile_CSI_bothReadyConflictReturnsRequeue(t *testing.T) {
824+
now := testNow()
825+
node := taintedNode(testNodeName, 0, now)
826+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
827+
csi := csiNodeServerPod("csi-1", testPodNS, testNodeName, true, 1*time.Minute, now)
828+
base := newFakeClient(t, node, agent, csi)
829+
c := interceptor.NewClient(base, interceptor.Funcs{
830+
Patch: func(context.Context, client.WithWatch, client.Object, client.Patch, ...client.PatchOption) error {
831+
return apierrors.NewConflict(schema.GroupResource{Resource: "nodes"}, testNodeName, errors.New("race"))
832+
},
833+
})
834+
r, _ := newReconciler(t, c, now, PolicyRemove, time.Minute, time.Minute, true)
835+
836+
result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
837+
require.NoError(t, err)
838+
assert.Equal(t, conflictRequeueDelay, result.RequeueAfter)
839+
}
840+
841+
func TestReconcile_CSI_bothReadyPatchErrorBubbles(t *testing.T) {
842+
now := testNow()
843+
node := taintedNode(testNodeName, 0, now)
844+
agent := agentPod(testPodName, testPodNS, testNodeName, true, 1*time.Minute, now)
845+
csi := csiNodeServerPod("csi-1", testPodNS, testNodeName, true, 1*time.Minute, now)
846+
base := newFakeClient(t, node, agent, csi)
847+
c := interceptor.NewClient(base, interceptor.Funcs{
848+
Patch: func(context.Context, client.WithWatch, client.Object, client.Patch, ...client.PatchOption) error {
849+
return errors.New("boom")
850+
},
851+
})
852+
r, _ := newReconciler(t, c, now, PolicyRemove, time.Minute, time.Minute, true)
853+
854+
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: testNodeName}})
855+
require.Error(t, err)
856+
}
857+
724858
func TestPodWatchPredicate_withCSI(t *testing.T) {
725859
now := testNow()
726860
r, _ := newReconciler(t, newFakeClient(t), now, PolicyRemove, time.Minute, time.Minute, true)

0 commit comments

Comments
 (0)