@@ -28,11 +28,13 @@ import (
2828 core "k8s.io/api/core/v1"
2929 "k8s.io/apimachinery/pkg/api/equality"
3030 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
31+ "k8s.io/apimachinery/pkg/types"
3132 "k8s.io/apimachinery/pkg/util/intstr"
3233
3334 api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
3435 "github.com/arangodb/kube-arangodb/pkg/apis/shared"
3536 "github.com/arangodb/kube-arangodb/pkg/deployment/features"
37+ "github.com/arangodb/kube-arangodb/pkg/deployment/patch"
3638 "github.com/arangodb/kube-arangodb/pkg/metrics"
3739 "github.com/arangodb/kube-arangodb/pkg/util/errors"
3840 "github.com/arangodb/kube-arangodb/pkg/util/globals"
@@ -231,17 +233,16 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
231233 if single {
232234 role = "single"
233235 }
234- if err := r .ensureExternalAccessServices (ctx , cachedStatus , svcs , eaServiceName , role , "database" ,
235- shared . ArangoPort , false , withLeader , spec .ExternalAccess , apiObject ); err != nil {
236+ if err := r .ensureExternalAccessServices (ctx , cachedStatus , svcs , eaServiceName , role , shared . ArangoPort ,
237+ false , withLeader , spec .ExternalAccess , apiObject ); err != nil {
236238 return errors .WithStack (err )
237239 }
238240
239241 if spec .Sync .IsEnabled () {
240242 // External (and internal) Sync master service
241243 counterMetric .Inc ()
242244 eaServiceName := k8sutil .CreateSyncMasterClientServiceName (deploymentName )
243- role := "syncmaster"
244- if err := r .ensureExternalAccessServices (ctx , cachedStatus , svcs , eaServiceName , role , "sync" ,
245+ if err := r .ensureExternalAccessServices (ctx , cachedStatus , svcs , eaServiceName , api .ServerGroupSyncMastersString ,
245246 shared .ArangoSyncMasterPort , true , false , spec .Sync .ExternalAccess .ExternalAccessSpec , apiObject ); err != nil {
246247 return errors .WithStack (err )
247248 }
@@ -274,13 +275,18 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
274275 return reconcileRequired .Reconcile (ctx )
275276}
276277
277- // EnsureServices creates all services needed to service the deployment
278+ // ensureExternalAccessServices ensures all services needed for a deployment.
278279func (r * Resources ) ensureExternalAccessServices (ctx context.Context , cachedStatus inspectorInterface.Inspector ,
279- svcs servicev1.ModInterface , eaServiceName , svcRole , title string , port int , noneIsClusterIP bool , withLeader bool ,
280+ svcs servicev1.ModInterface , eaServiceName , svcRole string , port int , noneIsClusterIP bool , withLeader bool ,
280281 spec api.ExternalAccessSpec , apiObject k8sutil.APIObject ) error {
281- log := r .log .Str ("section" , "service-ea" )
282282
283- // Database external access service
283+ if spec .GetType ().IsManaged () {
284+ // Managed services should not be created or removed by the operator.
285+ return r .ensureExternalAccessManagedServices (ctx , cachedStatus , svcs , eaServiceName , svcRole , spec , apiObject ,
286+ withLeader )
287+ }
288+
289+ log := r .log .Str ("section" , "service-ea" ).Str ("role" , svcRole ).Str ("service" , eaServiceName )
284290 createExternalAccessService := false
285291 deleteExternalAccessService := false
286292 eaServiceType := spec .GetType ().AsServiceType () // Note: Type auto defaults to ServiceTypeLoadBalancer
@@ -307,7 +313,7 @@ func (r *Resources) ensureExternalAccessServices(ctx context.Context, cachedStat
307313 // See if LoadBalancer has been configured & the service is "old enough"
308314 oldEnoughTimestamp := time .Now ().Add (- 1 * time .Minute ) // How long does the load-balancer provisioner have to act.
309315 if len (existing .Status .LoadBalancer .Ingress ) == 0 && existing .GetObjectMeta ().GetCreationTimestamp ().Time .Before (oldEnoughTimestamp ) {
310- log .Str ( "service" , eaServiceName ). Info ("LoadBalancerIP of %s external access service is not set, switching to NodePort" , title )
316+ log .Info ("LoadBalancerIP of external access service is not set, switching to NodePort" )
311317 createExternalAccessService = true
312318 eaServiceType = core .ServiceTypeNodePort
313319 deleteExternalAccessService = true // Remove the LoadBalancer ex service, then add the NodePort one
@@ -340,7 +346,7 @@ func (r *Resources) ensureExternalAccessServices(ctx context.Context, cachedStat
340346 return err
341347 })
342348 if err != nil {
343- log .Err (err ).Debug ("Failed to update %s external access service" , title )
349+ log .Err (err ).Debug ("Failed to update external access service" )
344350 return errors .WithStack (err )
345351 }
346352 }
@@ -352,12 +358,12 @@ func (r *Resources) ensureExternalAccessServices(ctx context.Context, cachedStat
352358 }
353359
354360 if deleteExternalAccessService {
355- log .Str ( "service" , eaServiceName ). Info ("Removing obsolete %s external access service" , title )
361+ log .Info ("Removing obsolete external access service" )
356362 err := globals .GetGlobalTimeouts ().Kubernetes ().RunWithTimeout (ctx , func (ctxChild context.Context ) error {
357363 return svcs .Delete (ctxChild , eaServiceName , meta.DeleteOptions {})
358364 })
359365 if err != nil {
360- log .Err (err ).Debug ("Failed to remove %s external access service" , title )
366+ log .Err (err ).Debug ("Failed to remove external access service" )
361367 return errors .WithStack (err )
362368 }
363369 }
@@ -371,12 +377,88 @@ func (r *Resources) ensureExternalAccessServices(ctx context.Context, cachedStat
371377 _ , newlyCreated , err := k8sutil .CreateExternalAccessService (ctxChild , svcs , eaServiceName , svcRole , apiObject ,
372378 eaServiceType , port , nodePort , loadBalancerIP , loadBalancerSourceRanges , apiObject .AsOwner (), withLeader )
373379 if err != nil {
374- log .Err (err ).Debug ("Failed to create %s external access service" , title )
380+ log .Err (err ).Debug ("Failed to create external access service" )
375381 return errors .WithStack (err )
376382 }
377383 if newlyCreated {
378- log .Str ( "service" , eaServiceName ). Debug ("Created %s external access service" , title )
384+ log .Debug ("Created %s external access service" )
379385 }
380386 }
381387 return nil
382388}
389+
390+ // ensureExternalAccessServices ensures if there are correct selectors on a managed services.
391+ // If hardcoded external service names are not on the list of managed services then it will be checked additionally.
392+ func (r * Resources ) ensureExternalAccessManagedServices (ctx context.Context , cachedStatus inspectorInterface.Inspector ,
393+ services servicev1.ModInterface , eaServiceName , svcRole string , spec api.ExternalAccessSpec ,
394+ apiObject k8sutil.APIObject , withLeader bool ) error {
395+
396+ log := r .log .Str ("section" , "service-ea" ).Str ("role" , svcRole ).Str ("service" , eaServiceName )
397+ managedServiceNames := spec .GetManagedServiceNames ()
398+ deploymentName := apiObject .GetName ()
399+ var selector map [string ]string
400+ if withLeader {
401+ selector = k8sutil .LabelsForLeaderMember (deploymentName , svcRole , "" )
402+ } else {
403+ selector = k8sutil .LabelsForDeployment (deploymentName , svcRole )
404+ }
405+
406+ // Check if hardcoded service has correct selector.
407+ if svc , ok := cachedStatus .Service ().V1 ().GetSimple (eaServiceName ); ! ok {
408+ // Hardcoded service (e.g. <deplname>-ea or <deplname>-sync) is not mandatory in `managed` type.
409+ if len (managedServiceNames ) == 0 {
410+ log .Warn ("the field \" spec.externalAccess.managedServiceNames\" should be provided for \" managed\" service type" )
411+ return nil
412+ }
413+ } else if changed , err := ensureManagedServiceSelector (ctx , selector , svc , services ); err != nil {
414+ return errors .WithMessage (err , "failed to ensure service selector" )
415+ } else if changed {
416+ log .Info ("selector applied to the managed service \" %s\" " , svc .GetName ())
417+ }
418+
419+ for _ , svcName := range managedServiceNames {
420+ if svcName == eaServiceName {
421+ // Hardcoded service has been applied before this loop.
422+ continue
423+ }
424+
425+ svc , ok := cachedStatus .Service ().V1 ().GetSimple (svcName )
426+ if ! ok {
427+ log .Warn ("managed service \" %s\" should have existed" , svcName )
428+ continue
429+ }
430+
431+ if changed , err := ensureManagedServiceSelector (ctx , selector , svc , services ); err != nil {
432+ return errors .WithMessage (err , "failed to ensure service selector" )
433+ } else if changed {
434+ log .Info ("selector applied to the managed service \" %s\" " , svcName )
435+ }
436+ }
437+
438+ return nil
439+ }
440+
441+ // ensureManagedServiceSelector ensures if there is correct selector on a service.
442+ func ensureManagedServiceSelector (ctx context.Context , selector map [string ]string , svc * core.Service ,
443+ services servicev1.ModInterface ) (bool , error ) {
444+ for key , value := range selector {
445+ if currentValue , ok := svc .Spec .Selector [key ]; ok && value == currentValue {
446+ continue
447+ }
448+
449+ p := patch .NewPatch ()
450+ p .ItemReplace (patch .NewPath ("spec" , "selector" ), selector )
451+ data , err := p .Marshal ()
452+ if err != nil {
453+ return false , errors .WithMessage (err , "failed to marshal service selector" )
454+ }
455+
456+ if _ , err = services .Patch (ctx , svc .GetName (), types .JSONPatchType , data , meta.PatchOptions {}); err != nil {
457+ return false , errors .WithMessage (err , "failed to patch service selector" )
458+ }
459+
460+ return true , nil
461+ }
462+
463+ return false , nil
464+ }
0 commit comments