github.com/cilium/cilium@v1.16.2/pkg/node/local_node_store.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package node
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"sync"
    10  
    11  	"github.com/cilium/hive/cell"
    12  	"github.com/cilium/stream"
    13  	k8stypes "k8s.io/apimachinery/pkg/types"
    14  
    15  	"github.com/cilium/cilium/pkg/cidr"
    16  	"github.com/cilium/cilium/pkg/lock"
    17  	"github.com/cilium/cilium/pkg/node/types"
    18  )
    19  
    20  type LocalNode struct {
    21  	types.Node
    22  	// OptOutNodeEncryption will make the local node opt-out of node-to-node
    23  	// encryption
    24  	OptOutNodeEncryption bool
    25  	// Unique identifier of the Kubernetes node, used to construct the
    26  	// corresponding owner reference.
    27  	UID k8stypes.UID
    28  	// ID of the node assigned by the cloud provider.
    29  	ProviderID string
    30  	// v4 CIDR in which pod IPs are routable
    31  	IPv4NativeRoutingCIDR *cidr.CIDR
    32  	// v6 CIDR in which pod IPs are routable
    33  	IPv6NativeRoutingCIDR *cidr.CIDR
    34  }
    35  
    36  // LocalNodeSynchronizer specifies how to build, and keep synchronized the local
    37  // node object.
    38  type LocalNodeSynchronizer interface {
    39  	InitLocalNode(context.Context, *LocalNode) error
    40  	SyncLocalNode(context.Context, *LocalNodeStore)
    41  }
    42  
    43  // LocalNodeStoreCell provides the LocalNodeStore instance.
    44  // The LocalNodeStore is the canonical owner of `types.Node` for the local node and
    45  // provides a reactive API for observing and updating it.
    46  var LocalNodeStoreCell = cell.Module(
    47  	"local-node-store",
    48  	"Provides LocalNodeStore for observing and updating local node info",
    49  
    50  	cell.Provide(NewLocalNodeStore),
    51  )
    52  
    53  // LocalNodeStoreParams are the inputs needed for constructing LocalNodeStore.
    54  type LocalNodeStoreParams struct {
    55  	cell.In
    56  
    57  	Lifecycle cell.Lifecycle
    58  	Sync      LocalNodeSynchronizer `optional:"true"`
    59  }
    60  
    61  // LocalNodeStore is the canonical owner for the local node object and provides
    62  // a reactive API for observing and updating the state.
    63  type LocalNodeStore struct {
    64  	// Changes to the local node are observable.
    65  	stream.Observable[LocalNode]
    66  
    67  	// mu is the main LocalNodeStore mutex, which protects the access to the
    68  	// different fields during all operations. getMu, instead, is a separate
    69  	// mutex which is used to guard updates of the value field, as well as its
    70  	// access by the Get() method. The reason for using two separate mutexes
    71  	// being that we don't want Get() to be blocked while calling emit, as
    72  	// that synchronously calls into all subscribers, which is a potentially
    73  	// expensive operation, and a possible source of deadlocks (e.g., one of
    74  	// the subscribers needs to acquire another mutex, which is held by a
    75  	// separate goroutine trying to call LocalNodeStore.Get()). In addition,
    76  	// getMu also guards the complete field, as it is used by Get() to
    77  	// determine that the LocalNodeStore was stopped. When both mu and getMu
    78  	// are to be acquired together, mu shall be always acquired first.
    79  	mu    lock.Mutex
    80  	getMu lock.RWMutex
    81  
    82  	value    LocalNode
    83  	hasValue <-chan struct{}
    84  	emit     func(LocalNode)
    85  	complete func(error)
    86  }
    87  
    88  func NewTestLocalNodeStore(mockNode LocalNode) *LocalNodeStore {
    89  	src, emit, complete := stream.Multicast[LocalNode](stream.EmitLatest)
    90  	emit(mockNode)
    91  	return &LocalNodeStore{
    92  		Observable: src,
    93  		emit:       emit,
    94  		complete:   complete,
    95  		value:      mockNode,
    96  		hasValue: func() <-chan struct{} {
    97  			ch := make(chan struct{})
    98  			close(ch)
    99  			return ch
   100  		}(),
   101  	}
   102  }
   103  
   104  func NewLocalNodeStore(params LocalNodeStoreParams) (*LocalNodeStore, error) {
   105  	src, emit, complete := stream.Multicast[LocalNode](stream.EmitLatest)
   106  	hasValue := make(chan struct{})
   107  
   108  	s := &LocalNodeStore{
   109  		Observable: src,
   110  		value: LocalNode{Node: types.Node{
   111  			// Explicitly initialize the labels and annotations maps, so that
   112  			// we don't need to always check for nil values.
   113  			Labels:      make(map[string]string),
   114  			Annotations: make(map[string]string),
   115  		}},
   116  		hasValue: hasValue,
   117  	}
   118  
   119  	bctx, cancel := context.WithCancel(context.Background())
   120  	var wg sync.WaitGroup
   121  
   122  	params.Lifecycle.Append(cell.Hook{
   123  		OnStart: func(ctx cell.HookContext) error {
   124  			s.mu.Lock()
   125  			defer s.mu.Unlock()
   126  			if params.Sync != nil {
   127  				if err := params.Sync.InitLocalNode(ctx, &s.value); err != nil {
   128  					return err
   129  				}
   130  
   131  				// Start the synchronization process in background
   132  				wg.Add(1)
   133  				go func() {
   134  					params.Sync.SyncLocalNode(bctx, s)
   135  					wg.Done()
   136  				}()
   137  			}
   138  
   139  			// Set the global variable still used by getters
   140  			// and setters in address.go. We're setting it in Start
   141  			// to catch uses of it before it's initialized.
   142  			localNode = s
   143  
   144  			s.emit = emit
   145  			s.complete = complete
   146  			emit(s.value)
   147  			close(hasValue)
   148  			return nil
   149  		},
   150  		OnStop: func(cell.HookContext) error {
   151  			// Stop the synchronization process (no-op if it had not been started)
   152  			cancel()
   153  			wg.Wait()
   154  
   155  			s.mu.Lock()
   156  			s.complete(nil)
   157  			s.getMu.Lock()
   158  			s.complete = nil
   159  			s.emit = nil
   160  			s.getMu.Unlock()
   161  			s.mu.Unlock()
   162  
   163  			localNode = nil
   164  			return nil
   165  		},
   166  	})
   167  
   168  	return s, nil
   169  }
   170  
   171  // Get retrieves the current local node. Use Get() only for inspecting the state,
   172  // e.g. in API handlers. Do not assume the value does not change over time.
   173  // Blocks until the store has been initialized.
   174  func (s *LocalNodeStore) Get(ctx context.Context) (LocalNode, error) {
   175  	select {
   176  	case <-s.hasValue:
   177  		s.getMu.RLock()
   178  		defer s.getMu.RUnlock()
   179  
   180  		if s.complete == nil {
   181  			// Return EOF when the LocalNodeStore is stopped, to preserve the
   182  			// same behavior of stream.First[LocalNode].
   183  			return LocalNode{}, io.EOF
   184  		}
   185  
   186  		return s.value, nil
   187  
   188  	case <-ctx.Done():
   189  		return LocalNode{}, ctx.Err()
   190  	}
   191  }
   192  
   193  // Update modifies the local node with a mutator. The updated value
   194  // is passed to observers. Calling LocalNodeStore.Get() from the
   195  // mutation function is forbidden, and would result in a deadlock.
   196  func (s *LocalNodeStore) Update(update func(*LocalNode)) {
   197  	s.mu.Lock()
   198  	defer s.mu.Unlock()
   199  
   200  	s.getMu.Lock()
   201  	update(&s.value)
   202  	s.getMu.Unlock()
   203  
   204  	if s.emit != nil {
   205  		s.emit(s.value)
   206  	}
   207  }