github.phpd.cn/cilium/cilium@v1.6.12/pkg/aws/eni/node_manager_test.go (about)

     1  // Copyright 2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // +build !privileged_tests
    16  
    17  package eni
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"time"
    23  
    24  	ec2mock "github.com/cilium/cilium/pkg/aws/ec2/mock"
    25  	metricsmock "github.com/cilium/cilium/pkg/aws/eni/metrics/mock"
    26  	"github.com/cilium/cilium/pkg/aws/types"
    27  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    28  	"github.com/cilium/cilium/pkg/option"
    29  	"github.com/cilium/cilium/pkg/testutils"
    30  
    31  	"gopkg.in/check.v1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  )
    34  
    35  var (
    36  	testSubnet = &types.Subnet{
    37  		ID:                 "s-1",
    38  		AvailabilityZone:   "us-west-1",
    39  		VpcID:              "vpc-1",
    40  		AvailableAddresses: 200,
    41  		Tags:               types.Tags{"k": "v"},
    42  	}
    43  	testVpc = &types.Vpc{
    44  		ID:          "v-1",
    45  		PrimaryCIDR: "10.10.0.0/16",
    46  	}
    47  	k8sapi     = &k8sMock{}
    48  	metricsapi = metricsmock.NewMockMetrics()
    49  )
    50  
    51  func (e *ENISuite) TestGetNodeNames(c *check.C) {
    52  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
    53  	instances := NewInstancesManager(ec2api, metricsapi)
    54  	c.Assert(instances, check.Not(check.IsNil))
    55  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
    56  	c.Assert(err, check.IsNil)
    57  	c.Assert(mngr, check.Not(check.IsNil))
    58  
    59  	mngr.Update(newCiliumNode("node1", "i-1", "m4.large", "us-west-1", "vpc-1", 0, 0, 0, 0))
    60  
    61  	names := mngr.GetNames()
    62  	c.Assert(len(names), check.Equals, 1)
    63  	c.Assert(names[0], check.Equals, "node1")
    64  
    65  	mngr.Update(newCiliumNode("node2", "i-2", "m4.large", "us-west-1", "vpc-1", 0, 0, 0, 0))
    66  
    67  	names = mngr.GetNames()
    68  	c.Assert(len(names), check.Equals, 2)
    69  
    70  	mngr.Delete("node1")
    71  
    72  	names = mngr.GetNames()
    73  	c.Assert(len(names), check.Equals, 1)
    74  	c.Assert(names[0], check.Equals, "node2")
    75  }
    76  
    77  func (e *ENISuite) TestNodeManagerGet(c *check.C) {
    78  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
    79  	instances := NewInstancesManager(ec2api, metricsapi)
    80  	c.Assert(instances, check.Not(check.IsNil))
    81  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
    82  	c.Assert(err, check.IsNil)
    83  	c.Assert(mngr, check.Not(check.IsNil))
    84  
    85  	mngr.Update(newCiliumNode("node1", "i-1", "m4.large", "us-west-1", "vpc-1", 0, 0, 0, 0))
    86  
    87  	c.Assert(mngr.Get("node1"), check.Not(check.IsNil))
    88  	c.Assert(mngr.Get("node2"), check.IsNil)
    89  
    90  	mngr.Delete("node1")
    91  	c.Assert(mngr.Get("node1"), check.IsNil)
    92  	c.Assert(mngr.Get("node2"), check.IsNil)
    93  }
    94  
    95  type k8sMock struct{}
    96  
    97  func (k *k8sMock) Update(node, origNode *v2.CiliumNode) (*v2.CiliumNode, error) {
    98  	return nil, nil
    99  }
   100  
   101  func (k *k8sMock) UpdateStatus(node, origNode *v2.CiliumNode) (*v2.CiliumNode, error) {
   102  	return nil, nil
   103  }
   104  
   105  func (k *k8sMock) Get(node string) (*v2.CiliumNode, error) {
   106  	return &v2.CiliumNode{}, nil
   107  }
   108  
   109  func newCiliumNode(node, instanceID, instanceType, az, vpcID string, preAllocate, minAllocate, available, used int) *v2.CiliumNode {
   110  	cn := &v2.CiliumNode{
   111  		ObjectMeta: metav1.ObjectMeta{Name: node, Namespace: "default"},
   112  		Spec: v2.NodeSpec{
   113  			ENI: v2.ENISpec{
   114  				InstanceID:       instanceID,
   115  				InstanceType:     instanceType,
   116  				PreAllocate:      preAllocate,
   117  				MinAllocate:      minAllocate,
   118  				AvailabilityZone: az,
   119  				VpcID:            vpcID,
   120  			},
   121  			IPAM: v2.IPAMSpec{
   122  				Pool: map[string]v2.AllocationIP{},
   123  			},
   124  		},
   125  		Status: v2.NodeStatus{
   126  			IPAM: v2.IPAMStatus{
   127  				Used: map[string]v2.AllocationIP{},
   128  			},
   129  		},
   130  	}
   131  
   132  	updateCiliumNode(cn, available, used)
   133  
   134  	return cn
   135  }
   136  
   137  func updateCiliumNode(cn *v2.CiliumNode, available, used int) *v2.CiliumNode {
   138  	cn.Spec.IPAM.Pool = map[string]v2.AllocationIP{}
   139  	for i := 0; i < used; i++ {
   140  		cn.Spec.IPAM.Pool[fmt.Sprintf("1.1.1.%d", i)] = v2.AllocationIP{Resource: "foo"}
   141  	}
   142  
   143  	cn.Status.IPAM.Used = map[string]v2.AllocationIP{}
   144  	for ip, ipAllocation := range cn.Spec.IPAM.Pool {
   145  		if used > 0 {
   146  			delete(cn.Spec.IPAM.Pool, ip)
   147  			cn.Status.IPAM.Used[ip] = ipAllocation
   148  			used--
   149  		}
   150  	}
   151  
   152  	return cn
   153  }
   154  
   155  func reachedAddressesNeeded(mngr *NodeManager, nodeName string, needed int) (success bool) {
   156  	if node := mngr.Get(nodeName); node != nil {
   157  		success = node.getNeededAddresses() == needed
   158  	}
   159  	return
   160  }
   161  
   162  // TestNodeManagerDefaultAllocation tests allocation with default parameters
   163  //
   164  // - m4.large (2x ENIs, 2x10 IPs)
   165  // - MinAllocate 0
   166  // - PreAllocate 0 (default: 8)
   167  func (e *ENISuite) TestNodeManagerDefaultAllocation(c *check.C) {
   168  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   169  	instances := NewInstancesManager(ec2api, metricsapi)
   170  	c.Assert(instances, check.Not(check.IsNil))
   171  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
   172  	c.Assert(err, check.IsNil)
   173  	c.Assert(mngr, check.Not(check.IsNil))
   174  
   175  	// Announce node wait for IPs to become available
   176  	cn := newCiliumNode("node1", "i-0", "m4.large", "us-west-1", "vpc-1", 0, 0, 0, 0)
   177  	mngr.Update(cn)
   178  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node1", 0) }, 5*time.Second), check.IsNil)
   179  
   180  	node := mngr.Get("node1")
   181  	c.Assert(node, check.Not(check.IsNil))
   182  	c.Assert(node.stats.availableIPs, check.Equals, 8)
   183  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   184  
   185  	// Use 7 out of 8 IPs
   186  	mngr.Update(updateCiliumNode(cn, 8, 7))
   187  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node1", 0) }, 5*time.Second), check.IsNil)
   188  
   189  	node = mngr.Get("node1")
   190  	c.Assert(node, check.Not(check.IsNil))
   191  	c.Assert(node.stats.availableIPs, check.Equals, 15)
   192  	c.Assert(node.stats.usedIPs, check.Equals, 7)
   193  }
   194  
   195  // TestNodeManagerMinAllocate20 tests MinAllocate without PreAllocate
   196  //
   197  // - m4.large (2x ENIs, 2x10 IPs)
   198  // - MinAllocate 10
   199  // - PreAllocate -1
   200  func (e *ENISuite) TestNodeManagerMinAllocate20(c *check.C) {
   201  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   202  	instances := NewInstancesManager(ec2api, metricsapi)
   203  	c.Assert(instances, check.Not(check.IsNil))
   204  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
   205  	c.Assert(err, check.IsNil)
   206  	c.Assert(mngr, check.Not(check.IsNil))
   207  
   208  	// Announce node wait for IPs to become available
   209  	cn := newCiliumNode("node2", "i-1", "m5.4xlarge", "us-west-1", "vpc-1", -1, 10, 0, 0)
   210  	mngr.Update(cn)
   211  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   212  
   213  	node := mngr.Get("node2")
   214  	c.Assert(node, check.Not(check.IsNil))
   215  	c.Assert(node.stats.availableIPs, check.Equals, 10)
   216  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   217  
   218  	mngr.Update(updateCiliumNode(cn, 10, 8))
   219  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   220  
   221  	node = mngr.Get("node2")
   222  	c.Assert(node, check.Not(check.IsNil))
   223  	c.Assert(node.stats.availableIPs, check.Equals, 10)
   224  	c.Assert(node.stats.usedIPs, check.Equals, 8)
   225  
   226  	// Change MinAllocate to 20
   227  	cn = newCiliumNode("node2", "i-1", "m5.4xlarge", "us-west-1", "vpc-1", 0, 20, 10, 8)
   228  	mngr.Update(cn)
   229  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   230  
   231  	node = mngr.Get("node2")
   232  	c.Assert(node, check.Not(check.IsNil))
   233  	c.Assert(node.stats.availableIPs, check.Equals, 20)
   234  	c.Assert(node.stats.usedIPs, check.Equals, 8)
   235  }
   236  
   237  // TestNodeManagerMinAllocateAndPreallocate tests MinAllocate in combination with PreAllocate
   238  //
   239  // - m4.large (2x ENIs, 2x10 IPs)
   240  // - MinAllocate 10
   241  // - PreAllocate 1
   242  func (e *ENISuite) TestNodeManagerMinAllocateAndPreallocate(c *check.C) {
   243  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   244  	instances := NewInstancesManager(ec2api, metricsapi)
   245  	c.Assert(instances, check.Not(check.IsNil))
   246  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
   247  	c.Assert(err, check.IsNil)
   248  	c.Assert(mngr, check.Not(check.IsNil))
   249  
   250  	// Announce node, wait for IPs to become available
   251  	cn := newCiliumNode("node2", "i-1", "m4.large", "us-west-1", "vpc-1", 1, 10, 0, 0)
   252  	mngr.Update(cn)
   253  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   254  
   255  	node := mngr.Get("node2")
   256  	c.Assert(node, check.Not(check.IsNil))
   257  	c.Assert(node.stats.availableIPs, check.Equals, 10)
   258  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   259  
   260  	// Use 9 out of 10 IPs, no additional IPs should be allocated
   261  	mngr.Update(updateCiliumNode(cn, 10, 9))
   262  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   263  	node = mngr.Get("node2")
   264  	c.Assert(node, check.Not(check.IsNil))
   265  	c.Assert(node.stats.availableIPs, check.Equals, 10)
   266  	c.Assert(node.stats.usedIPs, check.Equals, 9)
   267  
   268  	// Use 10 out of 10 IPs, PreAllocate 1 must kick in and allocate an additional IP
   269  	mngr.Update(updateCiliumNode(cn, 10, 10))
   270  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   271  	node = mngr.Get("node2")
   272  	c.Assert(node, check.Not(check.IsNil))
   273  	c.Assert(node.stats.availableIPs, check.Equals, 11)
   274  	c.Assert(node.stats.usedIPs, check.Equals, 10)
   275  
   276  	// Release some IPs, no additional IPs should be allocated
   277  	mngr.Update(updateCiliumNode(cn, 10, 8))
   278  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   279  	node = mngr.Get("node2")
   280  	c.Assert(node, check.Not(check.IsNil))
   281  	c.Assert(node.stats.availableIPs, check.Equals, 11)
   282  	c.Assert(node.stats.usedIPs, check.Equals, 8)
   283  }
   284  
   285  // TestNodeManagerReleaseAddress tests PreAllocate, MinAllocate and MaxAboveWatermark
   286  // when release excess IP is enabled
   287  //
   288  // - m4.large (4x ENIs, 4x15 IPs)
   289  // - MinAllocate 10
   290  // - PreAllocate 4
   291  // - MaxAboveWatermark 4
   292  // - FirstInterfaceIndex 1
   293  func (e *ENISuite) TestNodeManagerReleaseAddress(c *check.C) {
   294  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   295  	instances := NewInstancesManager(ec2api, metricsapi)
   296  	c.Assert(instances, check.Not(check.IsNil))
   297  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
   298  	c.Assert(err, check.IsNil)
   299  	c.Assert(mngr, check.Not(check.IsNil))
   300  
   301  	option.Config.AwsReleaseExcessIps = true
   302  
   303  	// Announce node, wait for IPs to become available
   304  	cn := newCiliumNode("node3", "i-3", "m4.xlarge", "us-west-1", "vpc-1", 4, 10, 0, 0)
   305  	cn.Spec.ENI.MaxAboveWatermark = 4
   306  	cn.Spec.ENI.FirstInterfaceIndex = 1
   307  	mngr.Update(cn)
   308  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 5*time.Second), check.IsNil)
   309  
   310  	// 10 min-allocate + 4 max-above-watermark => 14 IPs must become
   311  	// available as 14 < 15 (interface limit)
   312  	node := mngr.Get("node3")
   313  	c.Assert(node, check.Not(check.IsNil))
   314  	c.Assert(node.stats.availableIPs, check.Equals, 14)
   315  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   316  
   317  	// Use 11 out of 14 IPs, no additional IPs should be allocated
   318  	mngr.Update(updateCiliumNode(cn, 14, 11))
   319  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 5*time.Second), check.IsNil)
   320  	node = mngr.Get("node3")
   321  	c.Assert(node, check.Not(check.IsNil))
   322  	c.Assert(node.stats.availableIPs, check.Equals, 15)
   323  	c.Assert(node.stats.usedIPs, check.Equals, 11)
   324  
   325  	// Use 14 out of 15 IPs, PreAllocate 4 + MaxAboveWatermark must kick in
   326  	// and allocate 8 additional IPs
   327  	mngr.Update(updateCiliumNode(cn, 15, 14))
   328  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 5*time.Second), check.IsNil)
   329  	node = mngr.Get("node3")
   330  	c.Assert(node, check.Not(check.IsNil))
   331  	c.Assert(node.stats.availableIPs, check.Equals, 22)
   332  	c.Assert(node.stats.usedIPs, check.Equals, 14)
   333  
   334  	// Reduce used IPs to 10, this leads to 15 excess IPs but release
   335  	// occurs at interval based resync, so expect timeout at first
   336  	mngr.Update(updateCiliumNode(cn, 22, 10))
   337  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 2*time.Second), check.Not(check.IsNil))
   338  	node = mngr.Get("node3")
   339  	c.Assert(node, check.Not(check.IsNil))
   340  	c.Assert(node.stats.availableIPs, check.Equals, 22)
   341  	c.Assert(node.stats.usedIPs, check.Equals, 10)
   342  
   343  	// Trigger resync manually, excess IPs should be released
   344  	// 10 used + 4 pre-allocate + 4 max-above-watermark => 18
   345  	node = mngr.Get("node3")
   346  	node.resource.Status.ENI.ENIs = node.enis
   347  	syncTime := instances.Resync()
   348  	mngr.resyncNode(node, &resyncStats{}, syncTime)
   349  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node3", 0) }, 5*time.Second), check.IsNil)
   350  	node = mngr.Get("node3")
   351  	c.Assert(node, check.Not(check.IsNil))
   352  	c.Assert(node.stats.availableIPs, check.Equals, 18)
   353  	c.Assert(node.stats.usedIPs, check.Equals, 10)
   354  
   355  	option.Config.AwsReleaseExcessIps = false
   356  }
   357  
   358  // TestNodeManagerExceedENICapacity tests exceeding ENI capacity
   359  //
   360  // - m4.large (2x ENIs, 2x10 IPs)
   361  // - MinAllocate 20
   362  // - PreAllocate 8
   363  func (e *ENISuite) TestNodeManagerExceedENICapacity(c *check.C) {
   364  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   365  	instances := NewInstancesManager(ec2api, metricsapi)
   366  	c.Assert(instances, check.Not(check.IsNil))
   367  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, 10)
   368  	c.Assert(err, check.IsNil)
   369  	c.Assert(mngr, check.Not(check.IsNil))
   370  
   371  	// Announce node, wait for IPs to become available
   372  	cn := newCiliumNode("node2", "i-1", "m4.large", "us-west-1", "vpc-1", 8, 20, 0, 0)
   373  	mngr.Update(cn)
   374  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.IsNil)
   375  
   376  	node := mngr.Get("node2")
   377  	c.Assert(node, check.Not(check.IsNil))
   378  	c.Assert(node.stats.availableIPs, check.Equals, 20)
   379  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   380  
   381  	// Use 16 out of 20 IPs, we should reach 4 addresses needed but never 0 addresses needed
   382  	mngr.Update(updateCiliumNode(cn, 20, 16))
   383  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 4) }, 5*time.Second), check.IsNil)
   384  	c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, "node2", 0) }, 5*time.Second), check.Not(check.IsNil))
   385  
   386  	node = mngr.Get("node2")
   387  	c.Assert(node, check.Not(check.IsNil))
   388  	c.Assert(node.stats.availableIPs, check.Equals, 20)
   389  	c.Assert(node.stats.usedIPs, check.Equals, 16)
   390  }
   391  
   392  type nodeState struct {
   393  	cn           *v2.CiliumNode
   394  	name         string
   395  	instanceName string
   396  }
   397  
   398  // TestNodeManagerManyNodes tests IP allocation of 100 nodes across 3 subnets
   399  //
   400  // - m4.large (2x ENIs, 2x10 IPs)
   401  // - MinAllocate 10
   402  // - PreAllocate 1
   403  func (e *ENISuite) TestNodeManagerManyNodes(c *check.C) {
   404  	const (
   405  		numNodes    = 100
   406  		minAllocate = 10
   407  	)
   408  
   409  	subnets := []*types.Subnet{
   410  		{ID: "s-1", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 400},
   411  		{ID: "s-2", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 400},
   412  		{ID: "s-3", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 400},
   413  	}
   414  
   415  	ec2api := ec2mock.NewAPI(subnets, []*types.Vpc{testVpc})
   416  	metricsapi := metricsmock.NewMockMetrics()
   417  	instancesManager := NewInstancesManager(ec2api, metricsapi)
   418  	instancesManager.Resync()
   419  	mngr, err := NewNodeManager(instancesManager, ec2api, k8sapi, metricsapi, 10)
   420  	c.Assert(err, check.IsNil)
   421  	c.Assert(mngr, check.Not(check.IsNil))
   422  
   423  	state := make([]*nodeState, numNodes)
   424  
   425  	for i := range state {
   426  		s := &nodeState{name: fmt.Sprintf("node%d", i), instanceName: fmt.Sprintf("i-%d", i)}
   427  		s.cn = newCiliumNode(s.name, s.instanceName, "m4.large", "us-west-1", "vpc-1", 1, minAllocate, 0, 0)
   428  		state[i] = s
   429  		mngr.Update(s.cn)
   430  	}
   431  
   432  	for _, s := range state {
   433  		c.Assert(testutils.WaitUntil(func() bool { return reachedAddressesNeeded(mngr, s.name, 0) }, 5*time.Second), check.IsNil)
   434  
   435  		node := mngr.Get(s.name)
   436  		c.Assert(node, check.Not(check.IsNil))
   437  		if node.stats.availableIPs != minAllocate {
   438  			c.Errorf("Node %s allocation mismatch. expected: %d allocated: %d", s.name, minAllocate, node.stats.availableIPs)
   439  			c.Fail()
   440  		}
   441  		c.Assert(node.stats.usedIPs, check.Equals, 0)
   442  	}
   443  
   444  	// The above check returns as soon as the address requirements are met.
   445  	// The metrics may still be oudated, resync all nodes to update
   446  	// metrics.
   447  	mngr.Resync(time.Now())
   448  
   449  	c.Assert(metricsapi.Nodes("total"), check.Equals, numNodes)
   450  	c.Assert(metricsapi.Nodes("in-deficit"), check.Equals, 0)
   451  	c.Assert(metricsapi.Nodes("at-capacity"), check.Equals, 0)
   452  
   453  	c.Assert(metricsapi.AllocatedIPs("available"), check.Equals, numNodes*minAllocate)
   454  	c.Assert(metricsapi.AllocatedIPs("needed"), check.Equals, 0)
   455  	c.Assert(metricsapi.AllocatedIPs("used"), check.Equals, 0)
   456  
   457  	// All subnets must have been used for allocation
   458  	for _, subnet := range subnets {
   459  		c.Assert(metricsapi.ENIAllocationAttempts("success", subnet.ID), check.Not(check.Equals), 0)
   460  		c.Assert(metricsapi.IPAllocations(subnet.ID), check.Not(check.Equals), 0)
   461  	}
   462  
   463  	c.Assert(metricsapi.ResyncCount(), check.Not(check.Equals), 0)
   464  	c.Assert(metricsapi.AvailableENIs(), check.Not(check.Equals), 0)
   465  }
   466  
   467  // TestNodeManagerInstanceNotRunning verifies that allocation correctly detects
   468  // instances which are no longer running
   469  func (e *ENISuite) TestNodeManagerInstanceNotRunning(c *check.C) {
   470  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet}, []*types.Vpc{testVpc})
   471  	ec2api.SetMockError(ec2mock.AttachNetworkInterface, errors.New("foo is not 'running' foo"))
   472  	metricsMock := metricsmock.NewMockMetrics()
   473  	instances := NewInstancesManager(ec2api, metricsapi)
   474  	c.Assert(instances, check.Not(check.IsNil))
   475  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsMock, 10)
   476  	c.Assert(err, check.IsNil)
   477  	c.Assert(mngr, check.Not(check.IsNil))
   478  
   479  	// Announce node, ENI attachement will fail
   480  	cn := newCiliumNode("node1", "i-0", "m4.large", "us-west-1", "vpc-1", 0, 0, 0, 0)
   481  	mngr.Update(cn)
   482  
   483  	// Wait for node to be declared notRunning
   484  	c.Assert(testutils.WaitUntil(func() bool {
   485  		if n := mngr.Get("node1"); n != nil {
   486  			return n.instanceNotRunning
   487  		}
   488  		return false
   489  	}, 5*time.Second), check.IsNil)
   490  
   491  	// Metric should not indicate failure
   492  	c.Assert(metricsMock.ENIAllocationAttempts("ENI attachment failed", testSubnet.ID), check.Equals, int64(0))
   493  
   494  	node := mngr.Get("node1")
   495  	c.Assert(node, check.Not(check.IsNil))
   496  	c.Assert(node.stats.availableIPs, check.Equals, 0)
   497  	c.Assert(node.stats.usedIPs, check.Equals, 0)
   498  }
   499  
   500  func benchmarkAllocWorker(c *check.C, workers int64, delay time.Duration, rateLimit float64, burst int) {
   501  	testSubnet1 := &types.Subnet{ID: "s-1", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 1000000}
   502  	testSubnet2 := &types.Subnet{ID: "s-2", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 1000000}
   503  	testSubnet3 := &types.Subnet{ID: "s-3", AvailabilityZone: "us-west-1", VpcID: "vpc-1", AvailableAddresses: 1000000}
   504  
   505  	ec2api := ec2mock.NewAPI([]*types.Subnet{testSubnet1, testSubnet2, testSubnet3}, []*types.Vpc{testVpc})
   506  	ec2api.SetDelay(ec2mock.AllOperations, delay)
   507  	ec2api.SetLimiter(rateLimit, burst)
   508  	instances := NewInstancesManager(ec2api, metricsapi)
   509  	c.Assert(instances, check.Not(check.IsNil))
   510  	mngr, err := NewNodeManager(instances, ec2api, k8sapi, metricsapi, workers)
   511  	c.Assert(err, check.IsNil)
   512  	c.Assert(mngr, check.Not(check.IsNil))
   513  
   514  	state := make([]*nodeState, c.N)
   515  
   516  	c.ResetTimer()
   517  	for i := range state {
   518  		s := &nodeState{name: fmt.Sprintf("node%d", i), instanceName: fmt.Sprintf("i-%d", i)}
   519  		s.cn = newCiliumNode(s.name, s.instanceName, "m4.large", "us-west-1", "vpc-1", 1, 10, 0, 0)
   520  		state[i] = s
   521  		mngr.Update(s.cn)
   522  	}
   523  
   524  restart:
   525  	for _, s := range state {
   526  		if !reachedAddressesNeeded(mngr, s.name, 0) {
   527  			time.Sleep(5 * time.Millisecond)
   528  			goto restart
   529  		}
   530  	}
   531  	c.StopTimer()
   532  
   533  }
   534  
   535  func (e *ENISuite) BenchmarkAllocDelay20Worker1(c *check.C) {
   536  	benchmarkAllocWorker(c, 1, 20*time.Millisecond, 100.0, 4)
   537  }
   538  func (e *ENISuite) BenchmarkAllocDelay20Worker10(c *check.C) {
   539  	benchmarkAllocWorker(c, 10, 20*time.Millisecond, 100.0, 4)
   540  }
   541  func (e *ENISuite) BenchmarkAllocDelay20Worker50(c *check.C) {
   542  	benchmarkAllocWorker(c, 50, 20*time.Millisecond, 100.0, 4)
   543  }
   544  func (e *ENISuite) BenchmarkAllocDelay50Worker1(c *check.C) {
   545  	benchmarkAllocWorker(c, 1, 50*time.Millisecond, 100.0, 4)
   546  }
   547  func (e *ENISuite) BenchmarkAllocDelay50Worker10(c *check.C) {
   548  	benchmarkAllocWorker(c, 10, 50*time.Millisecond, 100.0, 4)
   549  }
   550  func (e *ENISuite) BenchmarkAllocDelay50Worker50(c *check.C) {
   551  	benchmarkAllocWorker(c, 50, 50*time.Millisecond, 100.0, 4)
   552  }