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  }