github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_node.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package watchers
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"maps"
    11  	"sync"
    12  	"sync/atomic"
    13  
    14  	"github.com/cilium/hive/cell"
    15  	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
    16  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  
    19  	agentK8s "github.com/cilium/cilium/daemon/k8s"
    20  	"github.com/cilium/cilium/pkg/k8s"
    21  	cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    22  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    23  	"github.com/cilium/cilium/pkg/k8s/resource"
    24  	k8sSynced "github.com/cilium/cilium/pkg/k8s/synced"
    25  	"github.com/cilium/cilium/pkg/kvstore"
    26  	nm "github.com/cilium/cilium/pkg/node/manager"
    27  	"github.com/cilium/cilium/pkg/node/types"
    28  )
    29  
    30  type k8sCiliumNodeWatcherParams struct {
    31  	cell.In
    32  
    33  	Clientset         k8sClient.Clientset
    34  	Resources         agentK8s.Resources
    35  	K8sResourceSynced *k8sSynced.Resources
    36  	K8sAPIGroups      *k8sSynced.APIGroups
    37  
    38  	NodeManager nm.NodeManager
    39  }
    40  
    41  func newK8sCiliumNodeWatcher(params k8sCiliumNodeWatcherParams) *K8sCiliumNodeWatcher {
    42  	return &K8sCiliumNodeWatcher{
    43  		clientset:         params.Clientset,
    44  		k8sResourceSynced: params.K8sResourceSynced,
    45  		k8sAPIGroups:      params.K8sAPIGroups,
    46  		resources:         params.Resources,
    47  		nodeManager:       params.NodeManager,
    48  	}
    49  }
    50  
    51  type K8sCiliumNodeWatcher struct {
    52  	clientset k8sClient.Clientset
    53  
    54  	// k8sResourceSynced maps a resource name to a channel. Once the given
    55  	// resource name is synchronized with k8s, the channel for which that
    56  	// resource name maps to is closed.
    57  	k8sResourceSynced *k8sSynced.Resources
    58  	// k8sAPIGroups is a set of k8s API in use. They are setup in watchers,
    59  	// and may be disabled while the agent runs.
    60  	k8sAPIGroups *k8sSynced.APIGroups
    61  	resources    agentK8s.Resources
    62  
    63  	nodeManager nodeManager
    64  
    65  	ciliumNodeStore atomic.Pointer[resource.Store[*cilium_v2.CiliumNode]]
    66  }
    67  
    68  func (k *K8sCiliumNodeWatcher) ciliumNodeInit(ctx context.Context, asyncControllers *sync.WaitGroup) {
    69  	// CiliumNode objects are used for node discovery until the key-value
    70  	// store is connected
    71  	var once sync.Once
    72  	apiGroup := k8sAPIGroupCiliumNodeV2
    73  
    74  	for {
    75  		var synced atomic.Bool
    76  		stop := make(chan struct{})
    77  
    78  		k.k8sResourceSynced.BlockWaitGroupToSyncResources(
    79  			stop,
    80  			nil,
    81  			func() bool { return synced.Load() },
    82  			apiGroup,
    83  		)
    84  		k.k8sAPIGroups.AddAPI(apiGroup)
    85  
    86  		// Signalize that we have put node controller in the wait group to sync resources.
    87  		once.Do(asyncControllers.Done)
    88  
    89  		// derive another context to signal Events() and Store() in case of kvstore connection
    90  		subCtx, cancel := context.WithCancel(ctx)
    91  
    92  		var wg sync.WaitGroup
    93  
    94  		wg.Add(1)
    95  		go func() {
    96  			defer wg.Done()
    97  			defer close(stop)
    98  
    99  			events := k.resources.CiliumNode.Events(subCtx)
   100  			cache := make(map[resource.Key]*cilium_v2.CiliumNode)
   101  			for event := range events {
   102  				var err error
   103  				switch event.Kind {
   104  				case resource.Sync:
   105  					synced.Store(true)
   106  					k.nodeManager.NodeSync()
   107  				case resource.Upsert:
   108  					var needUpdate bool
   109  					oldObj, ok := cache[event.Key]
   110  					if !ok {
   111  						needUpdate = k.onCiliumNodeInsert(event.Object)
   112  					} else {
   113  						needUpdate = k.onCiliumNodeUpdate(oldObj, event.Object)
   114  					}
   115  					if needUpdate {
   116  						cache[event.Key] = event.Object.DeepCopy()
   117  					}
   118  				case resource.Delete:
   119  					k.onCiliumNodeDelete(event.Object)
   120  					delete(cache, event.Key)
   121  				}
   122  				event.Done(err)
   123  			}
   124  		}()
   125  
   126  		wg.Add(1)
   127  		go func() {
   128  			defer wg.Done()
   129  
   130  			store, err := k.resources.CiliumNode.Store(subCtx)
   131  			if err != nil {
   132  				if !errors.Is(err, context.Canceled) {
   133  					log.WithError(err).Warning("unable to retrieve CiliumNode local store, going to query kube-apiserver directly")
   134  				}
   135  				return
   136  			}
   137  
   138  			k.ciliumNodeStore.Store(&store)
   139  
   140  			<-subCtx.Done()
   141  
   142  			store.Release()
   143  			k.ciliumNodeStore.Store(nil)
   144  		}()
   145  
   146  		select {
   147  		case <-kvstore.Connected():
   148  			log.Info("Connected to key-value store, stopping CiliumNode watcher")
   149  			cancel()
   150  			k.k8sResourceSynced.CancelWaitGroupToSyncResources(apiGroup)
   151  			k.k8sAPIGroups.RemoveAPI(apiGroup)
   152  			wg.Wait()
   153  		case <-ctx.Done():
   154  			cancel()
   155  			wg.Wait()
   156  			return
   157  		}
   158  
   159  		select {
   160  		case <-ctx.Done():
   161  			return
   162  		case <-kvstore.Client().Disconnected():
   163  			log.Info("Disconnected from key-value store, restarting CiliumNode watcher")
   164  		}
   165  	}
   166  }
   167  
   168  func (k *K8sCiliumNodeWatcher) onCiliumNodeInsert(ciliumNode *cilium_v2.CiliumNode) bool {
   169  	if k8s.IsLocalCiliumNode(ciliumNode) {
   170  		return false
   171  	}
   172  	n := types.ParseCiliumNode(ciliumNode)
   173  	k.nodeManager.NodeUpdated(n)
   174  	return true
   175  }
   176  
   177  func (k *K8sCiliumNodeWatcher) onCiliumNodeUpdate(oldNode, newNode *cilium_v2.CiliumNode) bool {
   178  	// Comparing Annotations here since wg-pub-key annotation is used to exchange rotated WireGuard keys.
   179  	if oldNode.DeepEqual(newNode) &&
   180  		maps.Equal(oldNode.ObjectMeta.Labels, newNode.ObjectMeta.Labels) &&
   181  		maps.Equal(oldNode.ObjectMeta.Annotations, newNode.ObjectMeta.Annotations) {
   182  		return false
   183  	}
   184  	return k.onCiliumNodeInsert(newNode)
   185  }
   186  
   187  func (k *K8sCiliumNodeWatcher) onCiliumNodeDelete(ciliumNode *cilium_v2.CiliumNode) {
   188  	if k8s.IsLocalCiliumNode(ciliumNode) {
   189  		return
   190  	}
   191  	n := types.ParseCiliumNode(ciliumNode)
   192  	k.nodeManager.NodeDeleted(n)
   193  }
   194  
   195  // GetCiliumNode returns the CiliumNode "nodeName" from the local Resource[T] store. If the
   196  // local Resource[T] store is not initialized or the key value store is connected, then it will
   197  // retrieve the node from kube-apiserver.
   198  // Note that it may be possible (although rare) that the requested nodeName is not yet in the
   199  // store if the local cache is falling behind due to the high amount of CiliumNode events
   200  // received from the k8s API server. To mitigate this, the caller should retry GetCiliumNode
   201  // for a given interval to be sure that a CiliumNode with that name has not actually been created.
   202  func (k *K8sCiliumNodeWatcher) GetCiliumNode(ctx context.Context, nodeName string) (*cilium_v2.CiliumNode, error) {
   203  	store := k.ciliumNodeStore.Load()
   204  	if store == nil {
   205  		return k.clientset.CiliumV2().CiliumNodes().Get(ctx, nodeName, v1.GetOptions{})
   206  	}
   207  
   208  	ciliumNode, exists, err := (*store).GetByKey(resource.Key{Name: nodeName})
   209  	if err != nil {
   210  		return nil, fmt.Errorf("unable to get CiliumNode %s from local store: %w", nodeName, err)
   211  	}
   212  	if !exists {
   213  		return nil, k8sErrors.NewNotFound(schema.GroupResource{
   214  			Group:    "cilium",
   215  			Resource: "CiliumNode",
   216  		}, nodeName)
   217  	}
   218  	return ciliumNode.DeepCopy(), nil
   219  }