Skip to content

Commit efca921

Browse files
committed
Add test for leader election sync logic
1 parent b750c33 commit efca921

File tree

1 file changed

+116
-3
lines changed

1 file changed

+116
-3
lines changed

pkg/server/server_test.go

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package server
22

33
import (
4+
"errors"
45
"testing"
56

7+
"github.com/rancher/webhook/pkg/health"
68
"github.com/rancher/wrangler/v3/pkg/generic/fake"
79
"github.com/stretchr/testify/assert"
810
"github.com/stretchr/testify/require"
911
"go.uber.org/mock/gomock"
1012
v1 "k8s.io/api/admissionregistration/v1"
11-
"k8s.io/apimachinery/pkg/api/errors"
13+
corev1 "k8s.io/api/core/v1"
14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1215
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1316
"k8s.io/apimachinery/pkg/runtime/schema"
1417
)
@@ -23,14 +26,14 @@ func TestSecretHandlerEnsureWebhookConfigurationCreate(t *testing.T) {
2326

2427
ctrl := gomock.NewController(t)
2528
validatingController := fake.NewMockNonNamespacedClientInterface[*v1.ValidatingWebhookConfiguration, *v1.ValidatingWebhookConfigurationList](ctrl)
26-
validatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, errors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "validatingwebhookconfiguration"}, configName)).Times(1)
29+
validatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "validatingwebhookconfiguration"}, configName)).Times(1)
2730
validatingController.EXPECT().Create(gomock.Any()).DoAndReturn(func(obj *v1.ValidatingWebhookConfiguration) (*v1.ValidatingWebhookConfiguration, error) {
2831
storedValidatingConfig = obj.DeepCopy()
2932
return obj, nil
3033
}).Times(1)
3134

3235
mutatingController := fake.NewMockNonNamespacedClientInterface[*v1.MutatingWebhookConfiguration, *v1.MutatingWebhookConfigurationList](ctrl)
33-
mutatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, errors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "mutatingwebhookconfiguration"}, configName)).Times(1)
36+
mutatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "mutatingwebhookconfiguration"}, configName)).Times(1)
3437
mutatingController.EXPECT().Create(gomock.Any()).DoAndReturn(func(obj *v1.MutatingWebhookConfiguration) (*v1.MutatingWebhookConfiguration, error) {
3538
storedMutatingConfig = obj.DeepCopy()
3639
return obj, nil
@@ -73,3 +76,113 @@ func TestSecretHandlerEnsureWebhookConfigurationCreate(t *testing.T) {
7376
require.Len(t, storedMutatingConfig.Webhooks, 1)
7477
assert.Equal(t, mutatingConfig.Webhooks[0].Name, storedMutatingConfig.Webhooks[0].Name)
7578
}
79+
80+
// TestSyncLeaderLogic tests the logic within the sync function related to leader election.
81+
func TestSyncLeaderLogic(t *testing.T) {
82+
t.Parallel()
83+
configName := "rancher.cattle.io"
84+
secret := &corev1.Secret{
85+
ObjectMeta: metav1.ObjectMeta{
86+
Name: caName,
87+
Namespace: namespace,
88+
},
89+
Data: map[string][]byte{
90+
corev1.TLSCertKey: []byte("cert-data"),
91+
},
92+
}
93+
testErr := errors.New("test error")
94+
95+
tests := []struct {
96+
name string
97+
isLeader bool
98+
getMutatingErr error
99+
getValidatingErr error
100+
createMutatingErr error
101+
createValidatingErr error
102+
expectedErr error
103+
expectControllersRun bool
104+
}{
105+
{
106+
name: "follower becomes healthy",
107+
isLeader: false,
108+
expectedErr: nil,
109+
},
110+
{
111+
name: "leader becomes healthy on create",
112+
isLeader: true,
113+
getMutatingErr: apierrors.NewNotFound(schema.GroupResource{}, ""),
114+
getValidatingErr: apierrors.NewNotFound(schema.GroupResource{}, ""),
115+
expectedErr: nil,
116+
expectControllersRun: true,
117+
},
118+
{
119+
name: "leader becomes unhealthy on create error",
120+
isLeader: true,
121+
getValidatingErr: apierrors.NewNotFound(schema.GroupResource{}, ""),
122+
createValidatingErr: testErr,
123+
expectedErr: testErr,
124+
expectControllersRun: true,
125+
},
126+
{
127+
name: "leader becomes healthy on update",
128+
isLeader: true,
129+
expectedErr: nil,
130+
expectControllersRun: true,
131+
},
132+
}
133+
134+
for _, tt := range tests {
135+
tt := tt
136+
t.Run(tt.name, func(t *testing.T) {
137+
t.Parallel()
138+
ctrl := gomock.NewController(t)
139+
validatingController := fake.NewMockNonNamespacedClientInterface[*v1.ValidatingWebhookConfiguration, *v1.ValidatingWebhookConfigurationList](ctrl)
140+
mutatingController := fake.NewMockNonNamespacedClientInterface[*v1.MutatingWebhookConfiguration, *v1.MutatingWebhookConfigurationList](ctrl)
141+
errChecker := health.NewErrorChecker("test")
142+
errChecker.Store(errors.New("initial error"))
143+
144+
handler := &secretHandler{
145+
validatingController: validatingController,
146+
mutatingController: mutatingController,
147+
errChecker: errChecker,
148+
}
149+
150+
leaderFlag.Store(tt.isLeader)
151+
152+
if tt.expectControllersRun {
153+
validatingController.EXPECT().Get(configName, gomock.Any()).Return(&v1.ValidatingWebhookConfiguration{}, tt.getValidatingErr).Times(1)
154+
if apierrors.IsNotFound(tt.getValidatingErr) {
155+
validatingController.EXPECT().Create(gomock.Any()).Return(&v1.ValidatingWebhookConfiguration{}, tt.createValidatingErr).Times(1)
156+
} else if tt.getValidatingErr == nil {
157+
validatingController.EXPECT().Update(gomock.Any()).Return(&v1.ValidatingWebhookConfiguration{}, nil).Times(1)
158+
}
159+
160+
// Only expect calls to the mutating controller if the validating part is expected to succeed.
161+
if (tt.getValidatingErr == nil || apierrors.IsNotFound(tt.getValidatingErr)) && tt.createValidatingErr == nil {
162+
mutatingController.EXPECT().Get(configName, gomock.Any()).Return(&v1.MutatingWebhookConfiguration{}, tt.getMutatingErr).Times(1)
163+
if apierrors.IsNotFound(tt.getMutatingErr) {
164+
mutatingController.EXPECT().Create(gomock.Any()).Return(&v1.MutatingWebhookConfiguration{}, tt.createMutatingErr).Times(1)
165+
} else if tt.getMutatingErr == nil {
166+
mutatingController.EXPECT().Update(gomock.Any()).Return(&v1.MutatingWebhookConfiguration{}, nil).Times(1)
167+
}
168+
}
169+
}
170+
171+
_, err := handler.sync("test-sync", secret)
172+
173+
// The only error we might get is a transient one from ensureWebhookConfiguration.
174+
if tt.expectedErr != nil {
175+
assert.ErrorContains(t, err, tt.expectedErr.Error(), "expected an error when ensuring webhook config")
176+
} else {
177+
require.NoError(t, err)
178+
}
179+
180+
healthErr := errChecker.Check(nil)
181+
if tt.expectedErr == nil {
182+
assert.NoError(t, healthErr, "expected pod to be healthy")
183+
} else {
184+
assert.Error(t, healthErr, "expected pod to be unhealthy")
185+
}
186+
})
187+
}
188+
}

0 commit comments

Comments
 (0)