github.com/cilium/cilium@v1.16.2/pkg/clustermesh/common/services.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package common 5 6 import ( 7 "maps" 8 9 "github.com/sirupsen/logrus" 10 "k8s.io/apimachinery/pkg/types" 11 12 "github.com/cilium/cilium/pkg/kvstore/store" 13 "github.com/cilium/cilium/pkg/lock" 14 "github.com/cilium/cilium/pkg/logging/logfields" 15 "github.com/cilium/cilium/pkg/metrics/metric" 16 serviceStore "github.com/cilium/cilium/pkg/service/store" 17 ) 18 19 type GlobalService struct { 20 ClusterServices map[string]*serviceStore.ClusterService 21 } 22 23 func newGlobalService() *GlobalService { 24 return &GlobalService{ 25 ClusterServices: map[string]*serviceStore.ClusterService{}, 26 } 27 } 28 29 type GlobalServiceCache struct { 30 mutex lock.RWMutex 31 byName map[types.NamespacedName]*GlobalService 32 33 // metricTotalGlobalServices is the gauge metric for total of global services 34 metricTotalGlobalServices metric.Gauge 35 } 36 37 func NewGlobalServiceCache(metricTotalGlobalServices metric.Gauge) *GlobalServiceCache { 38 return &GlobalServiceCache{ 39 byName: map[types.NamespacedName]*GlobalService{}, 40 metricTotalGlobalServices: metricTotalGlobalServices, 41 } 42 } 43 44 // Has returns whether a given service is present in the cache. 45 func (c *GlobalServiceCache) Has(svc *serviceStore.ClusterService) bool { 46 c.mutex.RLock() 47 defer c.mutex.RUnlock() 48 49 if globalSvc, ok := c.byName[svc.NamespaceServiceName()]; ok { 50 _, ok = globalSvc.ClusterServices[svc.Cluster] 51 return ok 52 } 53 54 return false 55 } 56 57 // GetService returns the service for a specific cluster. This function does not 58 // make a copy of the cluster service object and should not be mutated. 59 func (c *GlobalServiceCache) GetService(serviceNN types.NamespacedName, clusterName string) *serviceStore.ClusterService { 60 c.mutex.RLock() 61 defer c.mutex.RUnlock() 62 63 if globalSvc, ok := c.byName[serviceNN]; ok { 64 if svc, ok := globalSvc.ClusterServices[clusterName]; ok { 65 return svc 66 } 67 } 68 69 return nil 70 } 71 72 // GetGlobalService returns a global service object. This function returns 73 // a shallow copy of the GlobalService object, thus the ClusterService objects 74 // should not be mutated. 75 func (c *GlobalServiceCache) GetGlobalService(serviceNN types.NamespacedName) *GlobalService { 76 c.mutex.RLock() 77 defer c.mutex.RUnlock() 78 79 if globalSvc, ok := c.byName[serviceNN]; ok { 80 // We copy the global service to make this thread safe 81 newGlobalSvc := newGlobalService() 82 newGlobalSvc.ClusterServices = maps.Clone(globalSvc.ClusterServices) 83 return newGlobalSvc 84 } 85 86 return nil 87 } 88 89 func (c *GlobalServiceCache) OnUpdate(svc *serviceStore.ClusterService) { 90 scopedLog := log.WithFields(logrus.Fields{ 91 logfields.ServiceName: svc.String(), 92 logfields.ClusterName: svc.Cluster, 93 }) 94 95 c.mutex.Lock() 96 97 // Validate that the global service is known 98 globalSvc, ok := c.byName[svc.NamespaceServiceName()] 99 if !ok { 100 globalSvc = newGlobalService() 101 c.byName[svc.NamespaceServiceName()] = globalSvc 102 scopedLog.Debugf("Created global service %s", svc.NamespaceServiceName()) 103 c.metricTotalGlobalServices.Set(float64(len(c.byName))) 104 } 105 106 scopedLog.Debugf("Updated service definition of remote cluster %#v", svc) 107 108 globalSvc.ClusterServices[svc.Cluster] = svc 109 c.mutex.Unlock() 110 } 111 112 // must be called with c.mutex held 113 func (c *GlobalServiceCache) delete(globalService *GlobalService, clusterName string, serviceNN types.NamespacedName) bool { 114 scopedLog := log.WithFields(logrus.Fields{ 115 logfields.ServiceName: serviceNN.String(), 116 logfields.ClusterName: clusterName, 117 }) 118 119 if _, ok := globalService.ClusterServices[clusterName]; !ok { 120 scopedLog.Debug("Ignoring delete request for unknown cluster") 121 return false 122 } 123 124 scopedLog.Debugf("Deleted service definition of remote cluster") 125 delete(globalService.ClusterServices, clusterName) 126 127 // After the last cluster service is removed, remove the global service 128 if len(globalService.ClusterServices) == 0 { 129 scopedLog.Debugf("Deleted global service %s", serviceNN.String()) 130 delete(c.byName, serviceNN) 131 c.metricTotalGlobalServices.Set(float64(len(c.byName))) 132 } 133 134 return true 135 } 136 137 func (c *GlobalServiceCache) OnDelete(svc *serviceStore.ClusterService) bool { 138 scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()}) 139 scopedLog.Debug("Delete event for service") 140 141 c.mutex.Lock() 142 defer c.mutex.Unlock() 143 144 if globalService, ok := c.byName[svc.NamespaceServiceName()]; ok { 145 return c.delete(globalService, svc.Cluster, svc.NamespaceServiceName()) 146 } else { 147 scopedLog.Debugf("Ignoring delete request for unknown global service") 148 return false 149 } 150 } 151 152 // Size returns the number of global services in the cache 153 func (c *GlobalServiceCache) Size() (num int) { 154 c.mutex.RLock() 155 num = len(c.byName) 156 c.mutex.RUnlock() 157 return 158 } 159 160 type remoteServiceObserver struct { 161 log logrus.FieldLogger 162 163 cache *GlobalServiceCache 164 165 onUpdate func(*serviceStore.ClusterService) 166 onDelete func(*serviceStore.ClusterService) 167 } 168 169 // NewSharedServicesObserver returns an observer implementing the logic to convert 170 // and filter shared services notifications, update the global service cache and 171 // call the upstream handlers when appropriate. 172 func NewSharedServicesObserver( 173 log logrus.FieldLogger, cache *GlobalServiceCache, 174 onUpdate, onDelete func(*serviceStore.ClusterService), 175 ) store.Observer { 176 return &remoteServiceObserver{ 177 log: log, 178 cache: cache, 179 180 onUpdate: onUpdate, 181 onDelete: onDelete, 182 } 183 } 184 185 // OnUpdate is called when a service in a remote cluster is updated 186 func (r *remoteServiceObserver) OnUpdate(key store.Key) { 187 svc := &(key.(*serviceStore.ValidatingClusterService).ClusterService) 188 scopedLog := r.log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()}) 189 scopedLog.Debug("Received remote service update event") 190 191 // Short-circuit the handling of non-shared services 192 if !svc.Shared { 193 if r.cache.Has(svc) { 194 scopedLog.Debug("Previously shared service is no longer shared: triggering deletion event") 195 r.OnDelete(key) 196 } else { 197 scopedLog.Debug("Ignoring remote service update: service is not shared") 198 } 199 return 200 } 201 202 r.cache.OnUpdate(svc) 203 r.onUpdate(svc) 204 } 205 206 // OnDelete is called when a service in a remote cluster is deleted 207 func (r *remoteServiceObserver) OnDelete(key store.NamedKey) { 208 svc := &(key.(*serviceStore.ValidatingClusterService).ClusterService) 209 scopedLog := r.log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()}) 210 scopedLog.Debug("Received remote service delete event") 211 212 // Short-circuit the deletion logic if the service was not present (i.e., not shared) 213 if !r.cache.OnDelete(svc) { 214 scopedLog.Debug("Ignoring remote service delete. Service was not shared") 215 return 216 } 217 218 r.onDelete(svc) 219 }