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 }