github.com/cilium/cilium@v1.16.2/pkg/alibabacloud/eni/node_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package eni 5 6 import ( 7 "context" 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/require" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 15 "github.com/cilium/cilium/pkg/alibabacloud/api/mock" 16 "github.com/cilium/cilium/pkg/alibabacloud/eni/limits" 17 eniTypes "github.com/cilium/cilium/pkg/alibabacloud/eni/types" 18 "github.com/cilium/cilium/pkg/alibabacloud/utils" 19 "github.com/cilium/cilium/pkg/ipam" 20 metricsmock "github.com/cilium/cilium/pkg/ipam/metrics/mock" 21 ipamTypes "github.com/cilium/cilium/pkg/ipam/types" 22 v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 23 "github.com/cilium/cilium/pkg/testutils" 24 ) 25 26 var ( 27 k8sapi = &k8sMock{} 28 alibabaAPI *mock.API 29 instances *InstancesManager 30 metricsapi = metricsmock.NewMockMetrics() 31 ) 32 33 func setup(tb testing.TB) { 34 tb.Helper() 35 limits.Update(map[string]ipamTypes.Limits{ 36 "ecs.g7ne.large": {Adapters: 3, IPv4: 10, IPv6: 0}, 37 "ecs.g7ne.24xlarge": {Adapters: 15, IPv4: 50, IPv6: 0}, 38 "ecs.g8m.small": {Adapters: 2, IPv4: 3, IPv6: 0}, 39 }) 40 41 metricsapi = metricsmock.NewMockMetrics() 42 alibabaAPI = mock.NewAPI(subnets, vpcs, securityGroups) 43 require.NotNil(tb, alibabaAPI) 44 alibabaAPI.UpdateENIs(primaryENIs) 45 instances = NewInstancesManager(alibabaAPI) 46 require.NotNil(tb, instances) 47 48 tb.Cleanup(func() { 49 metricsapi = nil 50 alibabaAPI = nil 51 instances = nil 52 }) 53 } 54 55 func TestGetMaximumAllocatableIPv4(t *testing.T) { 56 setup(t) 57 58 n := &Node{} 59 n.k8sObj = newCiliumNode("node", "i-1", "ecs.g7ne.24xlarge", "cn-hangzhou-i", "vpc-1") 60 require.Equal(t, n.GetMaximumAllocatableIPv4(), 700) 61 } 62 63 func TestCreateInterface(t *testing.T) { 64 setup(t) 65 66 alibabaAPI.UpdateENIs(primaryENIs) 67 instances.Resync(context.TODO()) 68 69 mngr, err := ipam.NewNodeManager(instances, k8sapi, metricsapi, 10, false, false) 70 require.NoError(t, err) 71 require.NotNil(t, mngr) 72 73 mngr.Upsert(newCiliumNode("node1", "i-1", "ecs.g7ne.large", "cn-hangzhou-i", "vpc-1")) 74 mngr.Upsert(newCiliumNode("node2", "i-2", "ecs.g7ne.large", "cn-hangzhou-h", "vpc-1")) 75 names := mngr.GetNames() 76 require.Len(t, names, 2) 77 78 err = testutils.WaitUntil(func() bool { 79 return mngr.InstancesAPIIsReady() 80 }, 10*time.Second) 81 require.NoError(t, err) 82 83 instances.ForeachInstance("i-1", func(instanceID, interfaceID string, rev ipamTypes.InterfaceRevision) error { 84 e, ok := rev.Resource.(*eniTypes.ENI) 85 if !ok { 86 return fmt.Errorf("resource is not ENI type") 87 } 88 switch e.Type { 89 case eniTypes.ENITypeSecondary: 90 require.Equal(t, utils.GetENIIndexFromTags(e.Tags), 1) 91 case eniTypes.ENITypePrimary: 92 require.Equal(t, utils.GetENIIndexFromTags(e.Tags), 0) 93 } 94 return nil 95 }) 96 97 toAlloc, _, err := mngr.Get("node1").Ops().CreateInterface(context.Background(), &ipam.AllocationAction{ 98 IPv4: ipam.IPAllocationAction{ 99 MaxIPsToAllocate: 10, 100 }, 101 EmptyInterfaceSlots: 2, 102 }, log) 103 require.NoError(t, err) 104 require.Equal(t, toAlloc, 10) 105 106 toAlloc, _, err = mngr.Get("node1").Ops().CreateInterface(context.Background(), &ipam.AllocationAction{ 107 IPv4: ipam.IPAllocationAction{ 108 MaxIPsToAllocate: 11, 109 }, 110 EmptyInterfaceSlots: 1, 111 }, log) 112 require.NoError(t, err) 113 require.Equal(t, toAlloc, 10) 114 } 115 116 func TestCandidateAndEmptyInterfaces(t *testing.T) { 117 setup(t) 118 119 alibabaAPI.UpdateENIs(primaryENIs) 120 instances.Resync(context.TODO()) 121 122 mngr, err := ipam.NewNodeManager(instances, k8sapi, metricsapi, 10, false, false) 123 require.NoError(t, err) 124 require.NotNil(t, mngr) 125 // Set PreAllocate as 1 126 cn := newCiliumNodeWithIpamParams("node3", "i-3", "ecs.g8m.small", "cn-hangzhou-h", "vpc-1", 1, 0, 0) 127 cn.Spec.AlibabaCloud.VSwitches = []string{"vsw-2"} 128 mngr.Upsert(cn) 129 130 n := &Node{} 131 n.k8sObj = cn 132 // Primary ENI excluded, max allocatable = 3 ( 1 (ENI) * 3 (IPv4/ENI) ) 133 require.Equal(t, n.GetMaximumAllocatableIPv4(), 3) 134 135 // Wait for IPs to become available 136 require.Eventually(t, func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 5*time.Second, 1*time.Second) 137 138 node3 := mngr.Get("node3") 139 a, err := node3.Ops().PrepareIPAllocation(log) 140 require.NoError(t, err) 141 // 1 ENI attached, 1/3 IPs allocated, 0 empty slots left 142 require.Equal(t, a.IPv4.InterfaceCandidates, 1) 143 require.Equal(t, a.EmptyInterfaceSlots, 0) 144 require.Equal(t, 1, node3.Stats().IPv4.AvailableIPs) 145 } 146 147 func TestPrepareIPAllocation(t *testing.T) { 148 setup(t) 149 150 alibabaAPI.UpdateENIs(primaryENIs) 151 instances.Resync(context.TODO()) 152 153 mngr, err := ipam.NewNodeManager(instances, k8sapi, metricsapi, 10, false, false) 154 require.NoError(t, err) 155 require.NotNil(t, mngr) 156 mngr.SetInstancesAPIReadiness(false) // to avoid the manager background jobs starting and racing us. 157 158 mngr.Upsert(newCiliumNode("node1", "i-1", "ecs.g7ne.large", "cn-hangzhou-i", "vpc-1")) 159 a, err := mngr.Get("node1").Ops().PrepareIPAllocation(log) 160 require.NoError(t, err) 161 require.Equal(t, 2, a.EmptyInterfaceSlots+a.IPv4.InterfaceCandidates, fmt.Sprintf("empty: %v, candidates: %v", a.EmptyInterfaceSlots, a.IPv4.InterfaceCandidates)) 162 163 // create one eni 164 toAlloc, _, err := mngr.Get("node1").Ops().CreateInterface(context.Background(), &ipam.AllocationAction{ 165 IPv4: ipam.IPAllocationAction{ 166 MaxIPsToAllocate: 10, 167 }, 168 EmptyInterfaceSlots: 2, 169 }, log) 170 require.NoError(t, err) 171 require.Equal(t, toAlloc, 10) 172 173 // one eni left 174 a, err = mngr.Get("node1").Ops().PrepareIPAllocation(log) 175 require.NoError(t, err) 176 require.Equal(t, 1, a.EmptyInterfaceSlots, fmt.Sprintf("empty: %v, candidates: %v", a.EmptyInterfaceSlots, a.IPv4.InterfaceCandidates)) 177 } 178 179 func TestNode_allocENIIndex(t *testing.T) { 180 n := Node{enis: map[string]eniTypes.ENI{ 181 "eni-1": { 182 InstanceID: "eni-1", 183 Type: eniTypes.ENITypePrimary, 184 Tags: nil, 185 }, 186 }} 187 index, err := n.allocENIIndex() 188 require.NoError(t, err) 189 require.Equal(t, index, 1) 190 191 n.enis["eni-2"] = eniTypes.ENI{ 192 InstanceID: "eni-2", 193 Type: eniTypes.ENITypeSecondary, 194 Tags: map[string]string{"cilium-eni-index": "1"}, 195 } 196 index, err = n.allocENIIndex() 197 require.NoError(t, err) 198 require.Equal(t, index, 2) 199 } 200 201 type k8sMock struct{} 202 203 func (k *k8sMock) Create(node *v2.CiliumNode) (*v2.CiliumNode, error) { 204 return nil, nil 205 } 206 207 func (k *k8sMock) Update(origNode, node *v2.CiliumNode) (*v2.CiliumNode, error) { 208 return nil, nil 209 } 210 211 func (k *k8sMock) UpdateStatus(origNode, node *v2.CiliumNode) (*v2.CiliumNode, error) { 212 return nil, nil 213 } 214 215 func (k *k8sMock) Get(node string) (*v2.CiliumNode, error) { 216 return &v2.CiliumNode{}, nil 217 } 218 219 func newCiliumNode(node, instanceID, instanceType, az, vpcID string) *v2.CiliumNode { 220 cn := &v2.CiliumNode{ 221 ObjectMeta: metav1.ObjectMeta{Name: node, Namespace: "default"}, 222 Spec: v2.NodeSpec{ 223 InstanceID: instanceID, 224 AlibabaCloud: eniTypes.Spec{ 225 InstanceType: instanceType, 226 VPCID: vpcID, 227 AvailabilityZone: az, 228 }, 229 IPAM: ipamTypes.IPAMSpec{ 230 Pool: ipamTypes.AllocationMap{}, 231 }, 232 }, 233 Status: v2.NodeStatus{ 234 IPAM: ipamTypes.IPAMStatus{ 235 Used: ipamTypes.AllocationMap{}, 236 }, 237 }, 238 } 239 240 return cn 241 } 242 243 func newCiliumNodeWithIpamParams(node, instanceID, instanceType, az, vpcID string, preAllocate, minAllocate, maxAllocate int) *v2.CiliumNode { 244 cn := &v2.CiliumNode{ 245 ObjectMeta: metav1.ObjectMeta{Name: node, Namespace: "default"}, 246 Spec: v2.NodeSpec{ 247 InstanceID: instanceID, 248 AlibabaCloud: eniTypes.Spec{ 249 InstanceType: instanceType, 250 VPCID: vpcID, 251 AvailabilityZone: az, 252 }, 253 IPAM: ipamTypes.IPAMSpec{ 254 Pool: ipamTypes.AllocationMap{}, 255 PreAllocate: preAllocate, 256 MinAllocate: minAllocate, 257 MaxAllocate: maxAllocate, 258 }, 259 }, 260 Status: v2.NodeStatus{ 261 IPAM: ipamTypes.IPAMStatus{ 262 Used: ipamTypes.AllocationMap{}, 263 }, 264 }, 265 } 266 267 return cn 268 } 269 270 func reachedAddressesNeeded(mngr *ipam.NodeManager, nodeName string, needed int) (success bool) { 271 if node := mngr.Get(nodeName); node != nil { 272 success = node.GetNeededAddresses() == needed 273 } 274 return 275 }