@@ -3,14 +3,21 @@ package main
33import (
44 "bytes"
55 "context"
6+ "fmt"
67 "io"
78 "log/slog"
89 "regexp"
910 "testing"
1011
12+ "github.com/nginx/kubernetes-ingress/internal/configs/commonhelpers"
1113 nl "github.com/nginx/kubernetes-ingress/internal/logger"
1214 nic_glog "github.com/nginx/kubernetes-ingress/internal/logger/glog"
1315 "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"
1421 pkgversion "k8s.io/apimachinery/pkg/version"
1522 fakediscovery "k8s.io/client-go/discovery/fake"
1623 "k8s.io/client-go/kubernetes/fake"
@@ -131,3 +138,176 @@ func TestK8sVersionValidationBad(t *testing.T) {
131138 })
132139 }
133140}
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