github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/ipcache/kvstore.go (about) 1 // Copyright 2018 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ipcache 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "net" 22 "path" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/cilium/cilium/pkg/identity" 28 "github.com/cilium/cilium/pkg/kvstore" 29 "github.com/cilium/cilium/pkg/lock" 30 "github.com/cilium/cilium/pkg/logging/logfields" 31 "github.com/cilium/cilium/pkg/option" 32 "github.com/cilium/cilium/pkg/source" 33 34 "github.com/sirupsen/logrus" 35 ) 36 37 const ( 38 // DefaultAddressSpace is the address space used if none is provided. 39 // TODO - once pkg/node adds this to clusterConfiguration, remove. 40 DefaultAddressSpace = "default" 41 ) 42 43 var ( 44 // IPIdentitiesPath is the path to where endpoint IPs are stored in the key-value 45 //store. 46 IPIdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "ip", "v1") 47 48 // AddressSpace is the address space (cluster, etc.) in which policy is 49 // computed. It is determined by the orchestration system / runtime. 50 AddressSpace = DefaultAddressSpace 51 52 // globalMap wraps the kvstore and provides a cache of all entries 53 // which are owned by a local user 54 globalMap = newKVReferenceCounter(kvstoreImplementation{}) 55 56 setupIPIdentityWatcher sync.Once 57 ) 58 59 // store is a key-value store for an underlying implementation, provided to 60 // mock out the kvstore for unit testing. 61 type store interface { 62 // update will insert the {key, value} tuple into the underlying 63 // kvstore. 64 upsert(ctx context.Context, key string, value []byte, lease bool) error 65 66 // delete will remove the key from the underlying kvstore. 67 release(ctx context.Context, key string) error 68 } 69 70 // kvstoreImplementation is a store implementation backed by the kvstore. 71 type kvstoreImplementation struct{} 72 73 // upsert places the mapping of {key, value} into the kvstore, optionally with 74 // a lease. 75 func (k kvstoreImplementation) upsert(ctx context.Context, key string, value []byte, lease bool) error { 76 _, err := kvstore.Client().UpdateIfDifferent(ctx, key, value, lease) 77 return err 78 } 79 80 // release removes the specified key from the kvstore. 81 func (k kvstoreImplementation) release(ctx context.Context, key string) error { 82 return kvstore.Client().Delete(key) 83 } 84 85 // kvReferenceCounter provides a thin wrapper around the kvstore which adds 86 // reference tracking for all entries which are used by a local user. 87 type kvReferenceCounter struct { 88 lock.Mutex 89 store 90 91 // marshaledIPIDPair is map indexed by the key that contains the 92 // marshaled IPIdentityPair 93 marshaledIPIDPairs map[string][]byte 94 } 95 96 // newKVReferenceCounter creates a new reference counter using the specified 97 // store as the underlying location for key/value pairs to be stored. 98 func newKVReferenceCounter(s store) *kvReferenceCounter { 99 return &kvReferenceCounter{ 100 store: s, 101 marshaledIPIDPairs: map[string][]byte{}, 102 } 103 } 104 105 // UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the 106 // kvstore, which will subsequently trigger an event in NewIPIdentityWatcher(). 107 func UpsertIPToKVStore(ctx context.Context, IP, hostIP net.IP, ID identity.NumericIdentity, key uint8, metadata string) error { 108 ipKey := path.Join(IPIdentitiesPath, AddressSpace, IP.String()) 109 ipIDPair := identity.IPIdentityPair{ 110 IP: IP, 111 ID: ID, 112 Metadata: metadata, 113 HostIP: hostIP, 114 Key: key, 115 } 116 117 marshaledIPIDPair, err := json.Marshal(ipIDPair) 118 if err != nil { 119 return err 120 } 121 122 log.WithFields(logrus.Fields{ 123 logfields.IPAddr: ipIDPair.IP, 124 logfields.Identity: ipIDPair.ID, 125 logfields.Key: ipIDPair.Key, 126 logfields.Modification: Upsert, 127 }).Debug("Upserting IP->ID mapping to kvstore") 128 129 err = globalMap.store.upsert(ctx, ipKey, marshaledIPIDPair, true) 130 if err == nil { 131 globalMap.Lock() 132 globalMap.marshaledIPIDPairs[ipKey] = marshaledIPIDPair 133 globalMap.Unlock() 134 } 135 return err 136 } 137 138 // keyToIPNet returns the IPNet describing the key, whether it is a host, and 139 // an error (if one occurs) 140 func keyToIPNet(key string) (parsedPrefix *net.IPNet, host bool, err error) { 141 requiredPrefix := fmt.Sprintf("%s/", path.Join(IPIdentitiesPath, AddressSpace)) 142 if !strings.HasPrefix(key, requiredPrefix) { 143 err = fmt.Errorf("found invalid key %s outside of prefix %s", key, IPIdentitiesPath) 144 return 145 } 146 147 suffix := strings.TrimPrefix(key, requiredPrefix) 148 149 // Key is formatted as "prefix/192.0.2.0/24" for CIDRs 150 _, parsedPrefix, err = net.ParseCIDR(suffix) 151 if err != nil { 152 // Key is likely a host in the format "prefix/192.0.2.3" 153 parsedIP := net.ParseIP(suffix) 154 if parsedIP == nil { 155 err = fmt.Errorf("unable to parse IP from suffix %s", suffix) 156 return 157 } 158 err = nil 159 host = true 160 ipv4 := parsedIP.To4() 161 bits := net.IPv6len * 8 162 if ipv4 != nil { 163 parsedIP = ipv4 164 bits = net.IPv4len * 8 165 } 166 parsedPrefix = &net.IPNet{IP: parsedIP, Mask: net.CIDRMask(bits, bits)} 167 } 168 169 return 170 } 171 172 // DeleteIPFromKVStore removes the IP->Identity mapping for the specified ip 173 // from the kvstore, which will subsequently trigger an event in 174 // NewIPIdentityWatcher(). 175 func DeleteIPFromKVStore(ctx context.Context, ip string) error { 176 ipKey := path.Join(IPIdentitiesPath, AddressSpace, ip) 177 globalMap.Lock() 178 delete(globalMap.marshaledIPIDPairs, ipKey) 179 globalMap.Unlock() 180 return globalMap.store.release(ctx, ipKey) 181 } 182 183 // IPIdentityWatcher is a watcher that will notify when IP<->identity mappings 184 // change in the kvstore 185 type IPIdentityWatcher struct { 186 backend kvstore.BackendOperations 187 stop chan struct{} 188 synced chan struct{} 189 stopOnce sync.Once 190 } 191 192 // NewIPIdentityWatcher creates a new IPIdentityWatcher using the specified 193 // kvstore backend 194 func NewIPIdentityWatcher(backend kvstore.BackendOperations) *IPIdentityWatcher { 195 watcher := &IPIdentityWatcher{ 196 backend: backend, 197 stop: make(chan struct{}), 198 synced: make(chan struct{}), 199 } 200 201 return watcher 202 } 203 204 // Watch starts the watcher and blocks waiting for events. When events are 205 // received from the kvstore, All IPIdentityMappingListener are notified. The 206 // function returns when IPIdentityWatcher.Close() is called. The watcher will 207 // automatically restart as required. 208 func (iw *IPIdentityWatcher) Watch() { 209 210 var scopedLog *logrus.Entry 211 restart: 212 watcher := iw.backend.ListAndWatch("endpointIPWatcher", IPIdentitiesPath, 512) 213 214 for { 215 select { 216 // Get events from channel as they come in. 217 case event, ok := <-watcher.Events: 218 if !ok { 219 log.Debugf("%s closed, restarting watch", watcher.String()) 220 time.Sleep(500 * time.Millisecond) 221 goto restart 222 } 223 224 if option.Config.Debug { 225 scopedLog = log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key}) 226 scopedLog.Debug("Received event") 227 } 228 229 // Synchronize local caching of endpoint IP to ipIDPair mapping with 230 // operation key-value store has informed us about. 231 // 232 // To resolve conflicts between hosts and full CIDR prefixes: 233 // - Insert hosts into the cache as ".../w.x.y.z" 234 // - Insert CIDRS into the cache as ".../w.x.y.z/N" 235 // - If a host entry created, notify the listeners. 236 // - If a CIDR is created and there's no overlapping host 237 // entry, ie it is a less than fully masked CIDR, OR 238 // it is a fully masked CIDR and there is no corresponding 239 // host entry, then: 240 // - Notify the listeners. 241 // - Otherwise, do not notify listeners. 242 // - If a host is removed, check for an overlapping CIDR 243 // and if it exists, notify the listeners with an upsert 244 // for the CIDR's identity 245 // - If any other deletion case, notify listeners of 246 // the deletion event. 247 switch event.Typ { 248 case kvstore.EventTypeListDone: 249 IPIdentityCache.Lock() 250 for _, listener := range IPIdentityCache.listeners { 251 listener.OnIPIdentityCacheGC() 252 } 253 IPIdentityCache.Unlock() 254 close(iw.synced) 255 256 case kvstore.EventTypeCreate, kvstore.EventTypeModify: 257 var ipIDPair identity.IPIdentityPair 258 err := json.Unmarshal(event.Value, &ipIDPair) 259 if err != nil { 260 log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key}). 261 WithError(err).Error("Not adding entry to ip cache; error unmarshaling data from key-value store") 262 continue 263 } 264 ip := ipIDPair.PrefixString() 265 if ip == "<nil>" { 266 scopedLog.Debug("Ignoring entry with nil IP") 267 continue 268 } 269 270 IPIdentityCache.Upsert(ip, ipIDPair.HostIP, ipIDPair.Key, Identity{ 271 ID: ipIDPair.ID, 272 Source: source.KVStore, 273 }) 274 275 case kvstore.EventTypeDelete: 276 // Value is not present in deletion event; 277 // need to convert kvstore key to IP. 278 ipnet, isHost, err := keyToIPNet(event.Key) 279 if err != nil { 280 log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key}). 281 WithError(err).Error("Error parsing IP from key") 282 continue 283 } 284 var ip string 285 if isHost { 286 ip = ipnet.IP.String() 287 } else { 288 ip = ipnet.String() 289 } 290 globalMap.Lock() 291 292 if m, ok := globalMap.marshaledIPIDPairs[event.Key]; ok { 293 log.WithField("ip", ip).Warning("Received kvstore delete notification for alive ipcache entry") 294 err := globalMap.store.upsert(context.TODO(), event.Key, m, true) 295 if err != nil { 296 log.WithError(err).WithField("ip", ip).Warning("Unable to re-create alive ipcache entry") 297 } 298 globalMap.Unlock() 299 } else { 300 globalMap.Unlock() 301 302 // The key no longer exists in the 303 // local cache, it is safe to remove 304 // from the datapath ipcache. 305 IPIdentityCache.Delete(ip, source.KVStore) 306 } 307 } 308 309 case <-iw.stop: 310 // identity watcher was stopped 311 watcher.Stop() 312 return 313 } 314 } 315 } 316 317 // Close stops the IPIdentityWatcher and causes Watch() to return 318 func (iw *IPIdentityWatcher) Close() { 319 iw.stopOnce.Do(func() { 320 close(iw.stop) 321 }) 322 } 323 324 func (iw *IPIdentityWatcher) waitForInitialSync() { 325 <-iw.synced 326 } 327 328 var ( 329 watcher *IPIdentityWatcher 330 initialized = make(chan struct{}) 331 ) 332 333 // InitIPIdentityWatcher initializes the watcher for ip-identity mapping events 334 // in the key-value store. 335 func InitIPIdentityWatcher() { 336 setupIPIdentityWatcher.Do(func() { 337 go func() { 338 log.Info("Starting IP identity watcher") 339 watcher = NewIPIdentityWatcher(kvstore.Client()) 340 close(initialized) 341 watcher.Watch() 342 }() 343 }) 344 } 345 346 // WaitForKVStoreSync waits until the ipcache has been synchronized from the kvstore 347 func WaitForKVStoreSync() { 348 <-initialized 349 watcher.waitForInitialSync() 350 }