github.com/cilium/cilium@v1.16.2/test/controlplane/node/localnode.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package node
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"testing"
    11  
    12  	"github.com/cilium/hive/cell"
    13  	"github.com/stretchr/testify/assert"
    14  	corev1 "k8s.io/api/core/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  
    17  	"github.com/cilium/cilium/pkg/k8s/client"
    18  	"github.com/cilium/cilium/pkg/node"
    19  	nodeTypes "github.com/cilium/cilium/pkg/node/types"
    20  	"github.com/cilium/cilium/pkg/option"
    21  	"github.com/cilium/cilium/test/controlplane"
    22  	"github.com/cilium/cilium/test/controlplane/suite"
    23  )
    24  
    25  var (
    26  	localNodeObject = &corev1.Node{
    27  		TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: "v1"},
    28  		ObjectMeta: metav1.ObjectMeta{
    29  			Name: "minimal",
    30  			Labels: map[string]string{
    31  				"foo": "bar",
    32  			},
    33  			Annotations: map[string]string{
    34  				"cilium.io/baz": "quux",
    35  			},
    36  		},
    37  		Spec: corev1.NodeSpec{
    38  			PodCIDR:  podCIDR.String(),
    39  			PodCIDRs: []string{podCIDR.String()},
    40  		},
    41  		Status: corev1.NodeStatus{
    42  			Conditions: []corev1.NodeCondition{},
    43  			Addresses: []corev1.NodeAddress{
    44  				{Type: corev1.NodeInternalIP, Address: "10.0.0.1"},
    45  				{Type: corev1.NodeExternalIP, Address: "20.0.0.2"},
    46  				{Type: corev1.NodeHostName, Address: "minimal"},
    47  			},
    48  		},
    49  	}
    50  )
    51  
    52  // errorer implements the interface required by 'assert' to gather
    53  // the assertion errors.
    54  type errorer struct {
    55  	err error
    56  }
    57  
    58  func (e *errorer) Errorf(format string, args ...interface{}) {
    59  	e.err = errors.Join(e.err, fmt.Errorf(format, args...))
    60  }
    61  
    62  func validateLocalNodeInit(lns *node.LocalNodeStore) error {
    63  	// Validate that after LocalNodeStore has started it has been partially populated.
    64  	// This is called before Daemon is started.
    65  
    66  	errs := &errorer{}
    67  	node, err := lns.Get(context.TODO())
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	// These things we expect to be populated right after
    73  	// LocalNodeStore has started:
    74  	assert.Equal(errs, localNodeObject.Name, node.Name)
    75  	assert.Equal(errs, "10.0.0.1", node.GetNodeIP(false).String())
    76  	assert.Equal(errs, "20.0.0.2", node.GetExternalIP(false).String())
    77  	assert.Contains(errs, node.Labels, "foo")
    78  	assert.Contains(errs, node.Annotations, "cilium.io/baz")
    79  
    80  	if errs.err != nil {
    81  		return fmt.Errorf("validateLocalNodeInit: %w", errs.err)
    82  	}
    83  	return nil
    84  }
    85  
    86  func validateLocalNodeAgent(cs client.Clientset, lns *node.LocalNodeStore) error {
    87  	// Validate that the local node information is fully populated after the Daemon
    88  	// has fully started.
    89  
    90  	// The initial assertions should still hold.
    91  	if err := validateLocalNodeInit(lns); err != nil {
    92  		return fmt.Errorf("validateLocalNode: %w", err)
    93  	}
    94  
    95  	errs := &errorer{}
    96  	node, err := lns.Get(context.TODO())
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	// PodCIDR has been populated from the node object.
   102  	assert.Equal(errs, podCIDR.String(), node.IPv4AllocCIDR.String())
   103  
   104  	// HealthIP has been allocated.
   105  	assert.NotEmpty(errs, node.IPv4HealthIP)
   106  	// CiliumNode object has been created and populated correctly
   107  	// and reflects the state of the local node.
   108  	ciliumNodes, err := cs.CiliumV2().CiliumNodes().List(context.TODO(), metav1.ListOptions{})
   109  	assert.NoError(errs, err)
   110  	assert.Len(errs, ciliumNodes.Items, 1)
   111  
   112  	ciliumNode := ciliumNodes.Items[0]
   113  
   114  	if assert.NotEmpty(errs, ciliumNode.OwnerReferences) {
   115  		// CiliumNode should have owner reference to Node
   116  		assert.Equal(errs, localNodeObject.UID, ciliumNode.OwnerReferences[0].UID)
   117  	}
   118  
   119  	parsedCiliumNode := nodeTypes.ParseCiliumNode(&ciliumNode)
   120  	assert.Equal(errs, node.IPv4HealthIP, parsedCiliumNode.IPv4HealthIP, "CiliumNode HealthIP")
   121  	assert.Equal(errs, node.IPAddresses, parsedCiliumNode.IPAddresses, "CiliumNode IPAddresses")
   122  	assert.Equal(errs, node.Labels, parsedCiliumNode.Labels, "CiliumNode Labels")
   123  	if errs.err != nil {
   124  		return fmt.Errorf("validateLocalNode: %w", errs.err)
   125  	}
   126  	return nil
   127  }
   128  
   129  func init() {
   130  	// LocalNodeStore test validates that the local node store is populated correctly
   131  	// at early stages of the agent initialization. This makes sure that components
   132  	// lifted into modules from Daemon can access initialized state before
   133  	// Daemon is started.
   134  	suite.AddTestCase("LocalNodeStore", func(t *testing.T) {
   135  		k8sVersions := controlplane.K8sVersions()
   136  		// We only need to test the last k8s version
   137  		test := suite.NewControlPlaneTest(t, "minimal", k8sVersions[len(k8sVersions)-1])
   138  
   139  		var (
   140  			lns         *node.LocalNodeStore
   141  			cs          client.Clientset
   142  			grabLNSCell = cell.Invoke(
   143  				func(lns_ *node.LocalNodeStore, cs_ client.Clientset) {
   144  					lns = lns_
   145  					cs = cs_
   146  				})
   147  
   148  			validateLNSInit = cell.Invoke(
   149  				func(lc cell.Lifecycle, lns *node.LocalNodeStore) {
   150  					lc.Append(cell.Hook{
   151  						OnStart: func(cell.HookContext) error {
   152  							return validateLocalNodeInit(lns)
   153  						},
   154  					})
   155  				})
   156  		)
   157  
   158  		test.
   159  			UpdateObjects(localNodeObject).
   160  			SetupEnvironment().
   161  			StartAgent(func(*option.DaemonConfig) {}, grabLNSCell, validateLNSInit).
   162  			Eventually(func() error { return validateLocalNodeAgent(cs, lns) }).
   163  			StopAgent().
   164  			ClearEnvironment()
   165  	})
   166  }