@@ -3,14 +3,21 @@ package main
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "fmt"
6
7
"io"
7
8
"log/slog"
8
9
"regexp"
9
10
"testing"
10
11
12
+ "github.com/nginx/kubernetes-ingress/internal/configs/commonhelpers"
11
13
nl "github.com/nginx/kubernetes-ingress/internal/logger"
12
14
nic_glog "github.com/nginx/kubernetes-ingress/internal/logger/glog"
13
15
"github.com/nginx/kubernetes-ingress/internal/logger/levels"
16
+ "github.com/stretchr/testify/assert"
17
+ api_v1 "k8s.io/api/core/v1"
18
+ meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
+ "k8s.io/apimachinery/pkg/runtime"
20
+ "k8s.io/apimachinery/pkg/types"
14
21
pkgversion "k8s.io/apimachinery/pkg/version"
15
22
fakediscovery "k8s.io/client-go/discovery/fake"
16
23
"k8s.io/client-go/kubernetes/fake"
@@ -131,3 +138,176 @@ func TestK8sVersionValidationBad(t *testing.T) {
131
138
})
132
139
}
133
140
}
141
+
142
+ func TestCreateHeadlessService (t * testing.T ) {
143
+ logger := nl .LoggerFromContext (context .Background ())
144
+ controllerNamespace := "default"
145
+ configMapName := "test-configmap"
146
+ configMapNamespace := "default"
147
+ configMapNamespacedName := fmt .Sprintf ("%s/%s" , configMapNamespace , configMapName )
148
+ podName := "test-pod"
149
+ podLabels := map [string ]string {"app" : "my-app" , "pod-hash" : "12345" }
150
+ svcName := "test-hl-service"
151
+
152
+ pod := & api_v1.Pod {
153
+ ObjectMeta : meta_v1.ObjectMeta {
154
+ Name : podName ,
155
+ Namespace : controllerNamespace ,
156
+ Labels : podLabels ,
157
+ },
158
+ }
159
+
160
+ configMap := & api_v1.ConfigMap {
161
+ ObjectMeta : meta_v1.ObjectMeta {
162
+ Name : configMapName ,
163
+ Namespace : configMapNamespace ,
164
+ UID : types .UID ("uid-cm" ),
165
+ },
166
+ }
167
+
168
+ expectedOwnerReferences := []meta_v1.OwnerReference {
169
+ {
170
+ APIVersion : "v1" ,
171
+ Kind : "ConfigMap" ,
172
+ Name : configMap .Name ,
173
+ UID : configMap .UID ,
174
+ Controller : commonhelpers .BoolToPointerBool (true ),
175
+ BlockOwnerDeletion : commonhelpers .BoolToPointerBool (true ),
176
+ },
177
+ }
178
+
179
+ testCases := []struct {
180
+ name string
181
+ existingService * api_v1.Service
182
+ expectedAction string
183
+ expectedSelector map [string ]string
184
+ expectedOwnerRefs []meta_v1.OwnerReference
185
+ initialClientObjects []runtime.Object
186
+ }{
187
+ {
188
+ name : "Create service if none found" ,
189
+ expectedAction : "create" ,
190
+ expectedSelector : podLabels ,
191
+ expectedOwnerRefs : expectedOwnerReferences ,
192
+ initialClientObjects : []runtime.Object {pod , configMap },
193
+ },
194
+ {
195
+ name : "Skip update if labels and ownerReferences are the same" ,
196
+ existingService : & api_v1.Service {
197
+ ObjectMeta : meta_v1.ObjectMeta {
198
+ Name : svcName ,
199
+ Namespace : controllerNamespace ,
200
+ OwnerReferences : expectedOwnerReferences ,
201
+ },
202
+ Spec : api_v1.ServiceSpec {
203
+ Selector : podLabels ,
204
+ },
205
+ },
206
+ expectedAction : "none" ,
207
+ expectedSelector : podLabels ,
208
+ expectedOwnerRefs : expectedOwnerReferences ,
209
+ initialClientObjects : []runtime.Object {pod , configMap },
210
+ },
211
+ {
212
+ name : "Update service if labels differ" ,
213
+ existingService : & api_v1.Service {
214
+ ObjectMeta : meta_v1.ObjectMeta {
215
+ Name : svcName ,
216
+ Namespace : controllerNamespace ,
217
+ OwnerReferences : expectedOwnerReferences ,
218
+ },
219
+ Spec : api_v1.ServiceSpec {
220
+ Selector : map [string ]string {"pod-hash" : "67890" },
221
+ },
222
+ },
223
+ expectedAction : "update" ,
224
+ expectedSelector : podLabels ,
225
+ expectedOwnerRefs : expectedOwnerReferences ,
226
+ initialClientObjects : []runtime.Object {pod , configMap },
227
+ },
228
+ {
229
+ name : "Update service if ownerReferences differ" ,
230
+ existingService : & api_v1.Service {
231
+ ObjectMeta : meta_v1.ObjectMeta {
232
+ Name : svcName ,
233
+ Namespace : controllerNamespace ,
234
+ OwnerReferences : []meta_v1.OwnerReference {
235
+ {Name : "old-owner" },
236
+ },
237
+ },
238
+ Spec : api_v1.ServiceSpec {
239
+ Selector : podLabels ,
240
+ },
241
+ },
242
+ expectedAction : "update" ,
243
+ expectedSelector : podLabels ,
244
+ expectedOwnerRefs : expectedOwnerReferences ,
245
+ initialClientObjects : []runtime.Object {pod , configMap },
246
+ },
247
+ {
248
+ name : "Update service if both labels and ownerReferences differ" ,
249
+ existingService : & api_v1.Service {
250
+ ObjectMeta : meta_v1.ObjectMeta {
251
+ Name : svcName ,
252
+ Namespace : controllerNamespace ,
253
+ OwnerReferences : []meta_v1.OwnerReference {
254
+ {Name : "old-owner" },
255
+ },
256
+ },
257
+ Spec : api_v1.ServiceSpec {
258
+ Selector : map [string ]string {"old-label" : "true" },
259
+ },
260
+ },
261
+ expectedAction : "update" ,
262
+ expectedSelector : podLabels ,
263
+ expectedOwnerRefs : expectedOwnerReferences ,
264
+ initialClientObjects : []runtime.Object {pod , configMap },
265
+ },
266
+ }
267
+
268
+ for _ , tc := range testCases {
269
+ t .Run (tc .name , func (t * testing.T ) {
270
+ clientObjects := tc .initialClientObjects
271
+ if tc .existingService != nil {
272
+ clientObjects = append (clientObjects , tc .existingService )
273
+ }
274
+ clientset := fake .NewSimpleClientset (clientObjects ... )
275
+
276
+ err := createHeadlessService (logger , clientset , controllerNamespace , svcName , configMapNamespacedName , pod )
277
+ assert .NoError (t , err )
278
+
279
+ service , err := clientset .CoreV1 ().Services (controllerNamespace ).Get (context .Background (), svcName , meta_v1.GetOptions {})
280
+ assert .NoError (t , err , "Failed to get service after create/update" )
281
+
282
+ if err == nil {
283
+ assert .Equal (t , tc .expectedSelector , service .Spec .Selector , "Service selector mismatch" )
284
+ assert .Equal (t , tc .expectedOwnerRefs , service .OwnerReferences , "Service OwnerReferences mismatch" )
285
+ }
286
+
287
+ actions := clientset .Actions ()
288
+ var serviceCreated , serviceUpdated bool
289
+ for _ , action := range actions {
290
+ if action .Matches ("create" , "services" ) {
291
+ serviceCreated = true
292
+ }
293
+ if action .Matches ("update" , "services" ) {
294
+ serviceUpdated = true
295
+ }
296
+ }
297
+
298
+ switch tc .expectedAction {
299
+ case "create" :
300
+ assert .True (t , serviceCreated , "service to be created" )
301
+ assert .False (t , serviceUpdated , "no service update when creation is expected" )
302
+ case "update" :
303
+ assert .True (t , serviceUpdated , "service to be updated" )
304
+ assert .False (t , serviceCreated , "no service creation when update is expected" )
305
+ case "none" :
306
+ assert .False (t , serviceCreated , "no service creation when no action is expected" )
307
+ assert .False (t , serviceUpdated , "no service update when no action is expected" )
308
+ default :
309
+ t .Fatalf ("Invalid expectedAction: %s" , tc .expectedAction )
310
+ }
311
+ })
312
+ }
313
+ }
0 commit comments