github.com/cilium/cilium@v1.16.2/operator/cmd/allocator_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/runtime/schema"
    16  
    17  	"github.com/cilium/cilium/pkg/ipam/allocator/clusterpool/cidralloc"
    18  	"github.com/cilium/cilium/pkg/ipam/allocator/podcidr"
    19  	"github.com/cilium/cilium/pkg/ipam/cidrset"
    20  	"github.com/cilium/cilium/pkg/ipam/types"
    21  	cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    22  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    23  	cilium_fake "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/fake"
    24  	"github.com/cilium/cilium/pkg/testutils"
    25  )
    26  
    27  // TestPodCIDRAllocatorOverlap tests that, on startup all nodes with assigned podCIDRs are processed so that nodes
    28  // without pod CIDRs will not get the same CIDR ranges assigned as existing nodes.
    29  func TestPodCIDRAllocatorOverlap(t *testing.T) {
    30  	// We need to run the test multiple times since we are testing a race condition which is dependant on the order
    31  	// of a hash map.
    32  	for i := 0; i < 5; i++ {
    33  		fmt.Printf("Run %d/5\n", i+1)
    34  
    35  		podCIDRAllocatorOverlapTestRun(t)
    36  	}
    37  }
    38  
    39  func podCIDRAllocatorOverlapTestRun(t *testing.T) {
    40  	var wg sync.WaitGroup
    41  	defer wg.Wait()
    42  
    43  	ctx, cancel := context.WithCancel(context.Background())
    44  	defer cancel()
    45  
    46  	// Create a new CIDR allocator
    47  
    48  	_, cidr, err := net.ParseCIDR("10.129.0.0/16")
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  
    53  	set, err := cidrset.NewCIDRSet(cidr, 24)
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  
    58  	// Create a mock APIServer client where we have 2 existing nodes, one with a PodCIDR and one without.
    59  	// When List'ed from the client, first node-a is returned then node-b
    60  
    61  	fakeClient := cilium_fake.NewSimpleClientset(&cilium_api_v2.CiliumNode{
    62  		ObjectMeta: v1.ObjectMeta{
    63  			Name: "node-a",
    64  		},
    65  		Spec: cilium_api_v2.NodeSpec{
    66  			IPAM: types.IPAMSpec{
    67  				PodCIDRs: []string{},
    68  			},
    69  		},
    70  	}, &cilium_api_v2.CiliumNode{
    71  		ObjectMeta: v1.ObjectMeta{
    72  			Name: "node-b",
    73  		},
    74  		Spec: cilium_api_v2.NodeSpec{
    75  			IPAM: types.IPAMSpec{
    76  				PodCIDRs: []string{
    77  					"10.129.0.0/24",
    78  				},
    79  			},
    80  		},
    81  	})
    82  
    83  	// Make a set out of the fake cilium client.
    84  	fakeSet := &k8sClient.FakeClientset{
    85  		CiliumFakeClientset: fakeClient,
    86  	}
    87  
    88  	// Create a new pod manager with only our IPv4 allocator and fake client set.
    89  	podCidrManager := podcidr.NewNodesPodCIDRManager([]cidralloc.CIDRAllocator{
    90  		set,
    91  	}, nil, &ciliumNodeUpdateImplementation{clientset: fakeSet}, nil)
    92  
    93  	// start synchronization.
    94  	cns := newCiliumNodeSynchronizer(fakeSet, podCidrManager, false)
    95  	if err := cns.Start(ctx, &wg); err != nil {
    96  		t.Fatal(err)
    97  	}
    98  
    99  	// Wait for the "node manager synced" signal, just like we would normally.
   100  	<-cns.ciliumNodeManagerQueueSynced
   101  
   102  	// Trigger the Resync after the cache sync signal
   103  	podCidrManager.Resync(ctx, time.Time{})
   104  
   105  	err = testutils.WaitUntil(func() bool {
   106  		// Get node A from the mock APIServer
   107  		nodeAInt, err := fakeClient.Tracker().Get(ciliumnodesResource, "", "node-a")
   108  		if err != nil {
   109  			return false
   110  		}
   111  		nodeA := nodeAInt.(*cilium_api_v2.CiliumNode)
   112  
   113  		// Get node B from the mock APIServer
   114  		nodeBInt, err := fakeClient.Tracker().Get(ciliumnodesResource, "", "node-b")
   115  		if err != nil {
   116  			return false
   117  		}
   118  		nodeB := nodeBInt.(*cilium_api_v2.CiliumNode)
   119  
   120  		if len(nodeA.Spec.IPAM.PodCIDRs) != 1 {
   121  			return false
   122  		}
   123  
   124  		if len(nodeB.Spec.IPAM.PodCIDRs) != 1 {
   125  			return false
   126  		}
   127  
   128  		// The PodCIDRs should be distinct.
   129  		if nodeA.Spec.IPAM.PodCIDRs[0] == nodeB.Spec.IPAM.PodCIDRs[0] {
   130  			t.Fatal("Node A and Node B are assigned overlapping PodCIDRs")
   131  		}
   132  
   133  		return true
   134  	}, 2*time.Minute)
   135  	if err != nil {
   136  		t.Fatalf("nodes have no pod CIDR: %s", err)
   137  	}
   138  }
   139  
   140  var ciliumnodesResource = schema.GroupVersionResource{Group: "cilium.io", Version: "v2", Resource: "ciliumnodes"}
   141  
   142  type MockObserver struct{}
   143  
   144  // PostRun is called after a trigger run with the call duration, the
   145  // latency between 1st queue request and the call run and the number of
   146  // queued events folded into the last run
   147  func (o *MockObserver) PostRun(callDuration, latency time.Duration, folds int) {}
   148  
   149  // QueueEvent is called when Trigger() is called to schedule a trigger
   150  // run
   151  func (o *MockObserver) QueueEvent(reason string) {}