github.com/cilium/cilium@v1.16.2/pkg/clustermesh/remote_cluster.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package clustermesh 5 6 import ( 7 "context" 8 "path" 9 10 "github.com/sirupsen/logrus" 11 12 "github.com/cilium/cilium/api/v1/models" 13 "github.com/cilium/cilium/pkg/allocator" 14 "github.com/cilium/cilium/pkg/clustermesh/common" 15 cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" 16 "github.com/cilium/cilium/pkg/clustermesh/wait" 17 identityCache "github.com/cilium/cilium/pkg/identity/cache" 18 "github.com/cilium/cilium/pkg/ipcache" 19 "github.com/cilium/cilium/pkg/kvstore" 20 "github.com/cilium/cilium/pkg/kvstore/store" 21 "github.com/cilium/cilium/pkg/lock" 22 "github.com/cilium/cilium/pkg/logging/logfields" 23 nodeStore "github.com/cilium/cilium/pkg/node/store" 24 serviceStore "github.com/cilium/cilium/pkg/service/store" 25 ) 26 27 // remoteCluster implements the clustermesh business logic on top of 28 // common.RemoteCluster. 29 type remoteCluster struct { 30 // name is the name of the cluster 31 name string 32 33 // clusterID is the clusterID advertized by the remote cluster 34 clusterID uint32 35 36 // clusterConfigValidator validates the cluster configuration advertised 37 // by remote clusters. 38 clusterConfigValidator func(cmtypes.CiliumClusterConfig) error 39 40 usedIDs ClusterIDsManager 41 42 // mutex protects the following variables: 43 // - remoteIdentityCache 44 mutex lock.RWMutex 45 46 // store is the shared store representing all nodes in the remote cluster 47 remoteNodes store.WatchStore 48 49 // remoteServices is the shared store representing services in remote 50 // clusters 51 remoteServices store.WatchStore 52 53 // ipCacheWatcher is the watcher that notifies about IP<->identity 54 // changes in the remote cluster 55 ipCacheWatcher *ipcache.IPIdentityWatcher 56 57 // ipCacheWatcherExtraOpts returns extra options for watching ipcache entries. 58 ipCacheWatcherExtraOpts IPCacheWatcherOptsFn 59 60 // remoteIdentityWatcher allows watching remote identities. 61 remoteIdentityWatcher RemoteIdentityWatcher 62 63 // remoteIdentityCache is a locally cached copy of the identity 64 // allocations in the remote cluster 65 remoteIdentityCache *allocator.RemoteCache 66 67 // status is the function which fills the common part of the status. 68 status common.StatusFunc 69 70 storeFactory store.Factory 71 72 // synced tracks the initial synchronization with the remote cluster. 73 synced synced 74 75 log logrus.FieldLogger 76 } 77 78 func (rc *remoteCluster) Run(ctx context.Context, backend kvstore.BackendOperations, config cmtypes.CiliumClusterConfig, ready chan<- error) { 79 if err := rc.clusterConfigValidator(config); err != nil { 80 ready <- err 81 close(ready) 82 return 83 } 84 85 if err := rc.onUpdateConfig(config); err != nil { 86 ready <- err 87 close(ready) 88 return 89 } 90 91 remoteIdentityCache, err := rc.remoteIdentityWatcher.WatchRemoteIdentities(rc.name, rc.clusterID, backend, config.Capabilities.Cached) 92 if err != nil { 93 ready <- err 94 close(ready) 95 return 96 } 97 98 rc.mutex.Lock() 99 rc.remoteIdentityCache = remoteIdentityCache 100 rc.mutex.Unlock() 101 102 var mgr store.WatchStoreManager 103 if config.Capabilities.SyncedCanaries { 104 mgr = rc.storeFactory.NewWatchStoreManager(backend, rc.name) 105 } else { 106 mgr = store.NewWatchStoreManagerImmediate(rc.name) 107 } 108 109 adapter := func(prefix string) string { return prefix } 110 if config.Capabilities.Cached { 111 adapter = kvstore.StateToCachePrefix 112 } 113 114 mgr.Register(adapter(nodeStore.NodeStorePrefix), func(ctx context.Context) { 115 rc.remoteNodes.Watch(ctx, backend, path.Join(adapter(nodeStore.NodeStorePrefix), rc.name)) 116 }) 117 118 mgr.Register(adapter(serviceStore.ServiceStorePrefix), func(ctx context.Context) { 119 rc.remoteServices.Watch(ctx, backend, path.Join(adapter(serviceStore.ServiceStorePrefix), rc.name)) 120 }) 121 122 mgr.Register(adapter(ipcache.IPIdentitiesPath), func(ctx context.Context) { 123 rc.ipCacheWatcher.Watch(ctx, backend, rc.ipCacheWatcherOpts(&config)...) 124 }) 125 126 mgr.Register(adapter(identityCache.IdentitiesPath), func(ctx context.Context) { 127 rc.remoteIdentityCache.Watch(ctx, func(context.Context) { rc.synced.identities.Done() }) 128 }) 129 130 close(ready) 131 mgr.Run(ctx) 132 } 133 134 func (rc *remoteCluster) Stop() { 135 rc.synced.Stop() 136 } 137 138 func (rc *remoteCluster) Remove(context.Context) { 139 // Draining shall occur only when the configuration for the remote cluster 140 // is removed, and not in case the agent is shutting down, otherwise we 141 // would break existing connections on restart. 142 rc.remoteNodes.Drain() 143 rc.remoteServices.Drain() 144 rc.ipCacheWatcher.Drain() 145 146 rc.remoteIdentityWatcher.RemoveRemoteIdentities(rc.name) 147 148 rc.usedIDs.ReleaseClusterID(rc.clusterID) 149 } 150 151 func (rc *remoteCluster) Status() *models.RemoteCluster { 152 status := rc.status() 153 154 rc.mutex.RLock() 155 defer rc.mutex.RUnlock() 156 157 status.NumNodes = int64(rc.remoteNodes.NumEntries()) 158 status.NumSharedServices = int64(rc.remoteServices.NumEntries()) 159 status.NumIdentities = int64(rc.remoteIdentityCache.NumEntries()) 160 status.NumEndpoints = int64(rc.ipCacheWatcher.NumEntries()) 161 162 status.Synced = &models.RemoteClusterSynced{ 163 Nodes: rc.remoteNodes.Synced(), 164 Services: rc.remoteServices.Synced(), 165 Identities: rc.remoteIdentityCache.Synced(), 166 Endpoints: rc.ipCacheWatcher.Synced(), 167 } 168 169 status.Ready = status.Ready && 170 status.Synced.Nodes && status.Synced.Services && 171 status.Synced.Identities && status.Synced.Endpoints 172 173 return status 174 } 175 176 func (rc *remoteCluster) onUpdateConfig(newConfig cmtypes.CiliumClusterConfig) error { 177 if newConfig.ID == rc.clusterID { 178 return nil 179 } 180 181 // Let's fully drain all previously known entries if the remote cluster changed 182 // the cluster ID. Although synthetic deletion events would be generated in any 183 // case upon initial listing (as the entries with the incorrect ID would not pass 184 // validation), that would leave a window of time in which there would still be 185 // stale entries for a Cluster ID that has already been released, potentially 186 // leading to inconsistencies if the same ID is acquired again in the meanwhile. 187 if rc.clusterID != cmtypes.ClusterIDUnset { 188 rc.log.WithField(logfields.ClusterID, newConfig.ID). 189 Info("Remote Cluster ID changed: draining all known entries before reconnecting. ", 190 "Expect connectivity disruption towards this cluster") 191 rc.remoteNodes.Drain() 192 rc.remoteServices.Drain() 193 rc.ipCacheWatcher.Drain() 194 rc.remoteIdentityWatcher.RemoveRemoteIdentities(rc.name) 195 } 196 197 if err := rc.usedIDs.ReserveClusterID(newConfig.ID); err != nil { 198 return err 199 } 200 201 rc.usedIDs.ReleaseClusterID(rc.clusterID) 202 rc.clusterID = newConfig.ID 203 204 return nil 205 } 206 207 func (rc *remoteCluster) ipCacheWatcherOpts(config *cmtypes.CiliumClusterConfig) []ipcache.IWOpt { 208 var opts []ipcache.IWOpt 209 210 if config != nil { 211 opts = append(opts, ipcache.WithCachedPrefix(config.Capabilities.Cached)) 212 } 213 214 if rc.ipCacheWatcherExtraOpts != nil { 215 opts = append(opts, rc.ipCacheWatcherExtraOpts(config)...) 216 } 217 218 return opts 219 } 220 221 type synced struct { 222 wait.SyncedCommon 223 services *lock.StoppableWaitGroup 224 nodes chan struct{} 225 ipcache chan struct{} 226 identities *lock.StoppableWaitGroup 227 } 228 229 func newSynced() synced { 230 // Use a StoppableWaitGroup for identities, instead of a plain channel to 231 // avoid having to deal with the possibility of a closed channel if already 232 // synced (as the callback is executed every time the etcd connection 233 // is restarted, differently from the other resource types). 234 idswg := lock.NewStoppableWaitGroup() 235 idswg.Add() 236 idswg.Stop() 237 238 return synced{ 239 SyncedCommon: wait.NewSyncedCommon(), 240 services: lock.NewStoppableWaitGroup(), 241 nodes: make(chan struct{}), 242 ipcache: make(chan struct{}), 243 identities: idswg, 244 } 245 } 246 247 // Nodes returns after that the initial list of nodes has been received 248 // from the remote cluster, and synchronized with the different subscribers, 249 // the remote cluster is disconnected, or the given context is canceled. 250 func (s *synced) Nodes(ctx context.Context) error { 251 return s.Wait(ctx, s.nodes) 252 } 253 254 // Services returns after that the initial list of shared services has been 255 // received from the remote cluster, and synchronized with the BPF datapath, 256 // the remote cluster is disconnected, or the given context is canceled. 257 func (s *synced) Services(ctx context.Context) error { 258 return s.Wait(ctx, s.services.WaitChannel()) 259 } 260 261 // IPIdentities returns after that the initial list of ipcache entries and 262 // identities has been received from the remote cluster, and synchronized 263 // with the BPF datapath, the remote cluster is disconnected, or the given 264 // context is canceled. We additionally need to explicitly wait for nodes 265 // synchronization because they also trigger the insertion of ipcache entries 266 // (i.e., node addresses, health, ingress, ...). 267 func (s *synced) IPIdentities(ctx context.Context) error { 268 return s.Wait(ctx, s.ipcache, s.identities.WaitChannel(), s.nodes) 269 }