44 "bytes"
55 "os"
66 "path/filepath"
7+ "sort"
78 "testing"
89
910 corev1 "k8s.io/api/core/v1"
@@ -12,6 +13,9 @@ import (
1213
1314 api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1"
1415 "github.com/percona/percona-server-mongodb-operator/pkg/naming"
16+ "github.com/percona/percona-server-mongodb-operator/pkg/psmdb"
17+ "github.com/stretchr/testify/assert"
18+ "github.com/stretchr/testify/require"
1519)
1620
1721func TestReconcileReplsetServices (t * testing.T ) {
@@ -34,9 +38,8 @@ func TestReconcileReplsetServices(t *testing.T) {
3438 t .Helper ()
3539
3640 svcList := new (corev1.ServiceList )
37- if err := cl .List (ctx , svcList , client .InNamespace (cr .Namespace )); err != nil {
38- t .Fatal (err )
39- }
41+ err := cl .List (ctx , svcList , client .InNamespace (cr .Namespace ))
42+ require .NoError (t , err )
4043
4144 for i := range svcList .Items {
4245 svcList .Items [i ].APIVersion = "v1"
@@ -66,76 +69,209 @@ func TestReconcileReplsetServices(t *testing.T) {
6669
6770 t .Run ("expose toggle: not sharded cluster" , func (t * testing.T ) {
6871 cr , err := readDefaultCR (crName , ns )
69- if err != nil {
70- t .Fatal (err )
71- }
72+ require .NoError (t , err )
73+
7274 cr .Spec .Replsets [0 ].Expose .Enabled = false
7375 cr .Spec .Sharding .Enabled = false
74- if err := cr .CheckNSetDefaults (ctx , "" ); err != nil {
75- t .Fatal (err )
76- }
76+ require .NoError (t , cr .CheckNSetDefaults (ctx , "" ))
77+
7778 r := buildFakeClient (prepareObjects (t , cr )... )
7879
79- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
80- t .Fatal (err )
81- }
80+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
8281 compareSvcList (t , r .client , cr , "svc_list_expose_off.yaml" )
8382
8483 cr .Spec .Replsets [0 ].Expose .Enabled = true
85- if err := r .client .Update (ctx , cr ); err != nil {
86- t .Fatal (err )
87- }
88- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
89- t .Fatal (err )
90- }
84+ require .NoError (t , r .client .Update (ctx , cr ))
85+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
9186 compareSvcList (t , r .client , cr , "svc_list_expose_on.yaml" )
9287
9388 cr .Spec .Replsets [0 ].Expose .Enabled = false
94- if err := r .client .Update (ctx , cr ); err != nil {
95- t .Fatal (err )
96- }
97- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
98- t .Fatal (err )
99- }
89+ require .NoError (t , r .client .Update (ctx , cr ))
90+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
10091 compareSvcList (t , r .client , cr , "svc_list_expose_off.yaml" )
10192 })
10293
10394 t .Run ("expose toggle: sharded cluster" , func (t * testing.T ) {
10495 cr , err := readDefaultCR (crName , ns )
105- if err != nil {
106- t .Fatal (err )
107- }
96+ require .NoError (t , err )
97+
10898 cr .Spec .Replsets [0 ].Expose .Enabled = false
10999 cr .Spec .Sharding .Enabled = true
110- if err := cr .CheckNSetDefaults (ctx , "" ); err != nil {
111- t .Fatal (err )
112- }
100+ require .NoError (t , cr .CheckNSetDefaults (ctx , "" ))
113101
114102 r := buildFakeClient (prepareObjects (t , cr )... )
115103
116- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
117- t .Fatal (err )
118- }
104+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
119105 compareSvcList (t , r .client , cr , "svc_list_sharded_expose_off.yaml" )
120106
121107 cr .Spec .Replsets [0 ].Expose .Enabled = true
122- if err := r .client .Update (ctx , cr ); err != nil {
123- t .Fatal (err )
124- }
125- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
126- t .Fatal (err )
127- }
108+ require .NoError (t , r .client .Update (ctx , cr ))
109+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
128110 compareSvcList (t , r .client , cr , "svc_list_sharded_expose_on.yaml" )
129111
130112 cr .Spec .Replsets [0 ].Expose .Enabled = false
131- if err := r .client .Update (ctx , cr ); err != nil {
132- t .Fatal (err )
113+ require .NoError (t , r .client .Update (ctx , cr ))
114+ require .NoError (t , r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )))
115+ compareSvcList (t , r .client , cr , "svc_list_sharded_expose_off.yaml" )
116+ })
117+ }
118+
119+ func TestRemoveOutdatedServices (t * testing.T ) {
120+ ctx := t .Context ()
121+ const ns = "rs-svc-outdated"
122+ const crName = ns
123+ const rsName = "rs0"
124+
125+ // names of all per-pod external services that may exist for the replset,
126+ // covering every component type. removeOutdatedServices is expected to keep
127+ // the ones backing currently-enabled members and delete the rest.
128+ prefix := crName + "-" + rsName
129+ baseSvcs := []string {prefix + "-0" , prefix + "-1" , prefix + "-2" }
130+ nonVotingSvcs := []string {
131+ prefix + "-" + naming .ComponentNonVotingShort + "-0" ,
132+ prefix + "-" + naming .ComponentNonVotingShort + "-1" ,
133+ prefix + "-" + naming .ComponentNonVotingShort + "-2" ,
134+ }
135+ hiddenSvcs := []string {
136+ prefix + "-" + naming .ComponentHidden + "-0" ,
137+ prefix + "-" + naming .ComponentHidden + "-1" ,
138+ }
139+ arbiterSvcs := []string {prefix + "-" + naming .ComponentArbiter + "-0" }
140+
141+ concat := func (lists ... []string ) []string {
142+ out := []string {}
143+ for _ , l := range lists {
144+ out = append (out , l ... )
133145 }
134- if err := r .reconcileReplsetServices (ctx , cr , getReplsets (t , cr )); err != nil {
146+ sort .Strings (out )
147+ return out
148+ }
149+
150+ allSvcs := concat (baseSvcs , nonVotingSvcs , hiddenSvcs , arbiterSvcs )
151+
152+ // seedServices builds external services (with the labels removeOutdatedServices
153+ // selects on) for every name in allSvcs.
154+ seedServices := func (cr * api.PerconaServerMongoDB , rs * api.ReplsetSpec ) []client.Object {
155+ objs := make ([]client.Object , 0 , len (allSvcs ))
156+ for _ , name := range allSvcs {
157+ objs = append (objs , psmdb .ExternalService (cr , rs , name ))
158+ }
159+ return objs
160+ }
161+
162+ remainingServices := func (t * testing.T , cl client.Client ) []string {
163+ t .Helper ()
164+
165+ svcList := new (corev1.ServiceList )
166+ if err := cl .List (ctx , svcList , client .InNamespace (ns )); err != nil {
135167 t .Fatal (err )
136168 }
137- compareSvcList (t , r .client , cr , "svc_list_sharded_expose_off.yaml" )
138- })
169+ names := make ([]string , 0 , len (svcList .Items ))
170+ for _ , svc := range svcList .Items {
171+ names = append (names , svc .Name )
172+ }
173+ sort .Strings (names )
174+ return names
175+ }
176+
177+ tests := []struct {
178+ name string
179+ configure func (rs * api.ReplsetSpec )
180+ pause bool
181+ want []string
182+ }{
183+ {
184+ name : "exposed replset without special members keeps only base services" ,
185+ configure : func (rs * api.ReplsetSpec ) {
186+ rs .Expose .Enabled = true
187+ },
188+ want : baseSvcs ,
189+ },
190+ {
191+ name : "not exposed replset removes all services" ,
192+ configure : func (rs * api.ReplsetSpec ) {
193+ rs .Expose .Enabled = false
194+ rs .NonVoting .Enabled = true
195+ rs .Hidden .Enabled = true
196+ rs .Arbiter .Enabled = true
197+ },
198+ want : nil ,
199+ },
200+ {
201+ name : "exposed replset with hidden members keeps base and hidden services" ,
202+ configure : func (rs * api.ReplsetSpec ) {
203+ rs .Expose .Enabled = true
204+ rs .Hidden .Enabled = true
205+ rs .Hidden .Size = 2
206+ },
207+ want : concat (baseSvcs , hiddenSvcs ),
208+ },
209+ {
210+ name : "exposed replset with non-voting members keeps base and non-voting services" ,
211+ configure : func (rs * api.ReplsetSpec ) {
212+ rs .Expose .Enabled = true
213+ rs .NonVoting .Enabled = true
214+ rs .NonVoting .Size = 3
215+ },
216+ want : concat (baseSvcs , nonVotingSvcs ),
217+ },
218+ {
219+ name : "exposed replset with arbiter keeps base and arbiter services" ,
220+ configure : func (rs * api.ReplsetSpec ) {
221+ rs .Expose .Enabled = true
222+ rs .Arbiter .Enabled = true
223+ rs .Arbiter .Size = 1
224+ },
225+ want : concat (baseSvcs , arbiterSvcs ),
226+ },
227+ {
228+ name : "all member types enabled keeps every service" ,
229+ configure : func (rs * api.ReplsetSpec ) {
230+ rs .Expose .Enabled = true
231+ rs .Hidden .Enabled = true
232+ rs .Hidden .Size = 2
233+ rs .NonVoting .Enabled = true
234+ rs .NonVoting .Size = 3
235+ rs .Arbiter .Enabled = true
236+ rs .Arbiter .Size = 1
237+ },
238+ want : allSvcs ,
239+ },
240+ {
241+ name : "paused cluster keeps all services untouched" ,
242+ configure : func (rs * api.ReplsetSpec ) {
243+ rs .Expose .Enabled = true
244+ },
245+ pause : true ,
246+ want : allSvcs ,
247+ },
248+ }
249+
250+ for _ , tt := range tests {
251+ t .Run (tt .name , func (t * testing.T ) {
252+ cr , err := readDefaultCR (crName , ns )
253+ require .NoError (t , err )
254+
255+ cr .Spec .Sharding .Enabled = false
256+ require .NoError (t , cr .CheckNSetDefaults (ctx , "" ))
257+
258+ rs := cr .Spec .Replsets [0 ]
259+ rs .Size = 3
260+ tt .configure (rs )
261+ cr .Spec .Pause = tt .pause
262+
263+ objs := append ([]client.Object {cr }, seedServices (cr , rs )... )
264+ r := buildFakeClient (objs ... )
265+
266+ require .NoError (t , r .removeOutdatedServices (ctx , cr , rs ))
267+
268+ got := remainingServices (t , r .client )
269+ want := tt .want
270+ sort .Strings (want )
271+ assert .Equal (t , len (want ), len (got ), "unexpected remaining service count" )
272+ assert .ElementsMatch (t , want , got , "unexpected remaining services" )
273+ })
274+ }
139275}
140276
141277func yamlCompare (t * testing.T , ns string , filename string , compare any ) {
0 commit comments