github.com/cilium/cilium@v1.16.2/operator/pkg/lbipam/lbipam_fixture_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package lbipam
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"reflect"
    11  	"runtime/debug"
    12  	"testing"
    13  	"time"
    14  
    15  	jsonpatch "github.com/evanphx/json-patch"
    16  	"github.com/sirupsen/logrus"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/watch"
    20  
    21  	cilium_api_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    22  	"github.com/cilium/cilium/pkg/k8s/resource"
    23  	slim_core_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    24  	client_typed_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned/typed/core/v1"
    25  )
    26  
    27  // A list of constants which can be re-used during testing.
    28  const (
    29  	poolAUID = types.UID("eb84f074-806e-474e-85c5-001f5780906b")
    30  	poolBUID = types.UID("610a54cf-e0e5-4dc6-ace9-0e6ca9a4aaae")
    31  
    32  	serviceAUID = types.UID("b801e1cf-9e71-455c-9bc8-52c0575c22bd")
    33  	serviceBUID = types.UID("b415933e-524c-4f83-8493-de2157fc736f")
    34  	serviceCUID = types.UID("8d820ef0-d640-497a-bc67-b05190bddee6")
    35  )
    36  
    37  type fakeIPPoolClient struct {
    38  	resources map[resource.Key]*cilium_api_v2alpha1.CiliumLoadBalancerIPPool
    39  }
    40  
    41  func (fic *fakeIPPoolClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *cilium_api_v2alpha1.CiliumLoadBalancerIPPool, err error) {
    42  	existing, found := fic.resources[resource.Key{Name: name}]
    43  	if !found {
    44  		return nil, fmt.Errorf("No IP pool found with name %q", name)
    45  	}
    46  
    47  	old, err := json.Marshal(existing)
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  
    52  	// reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields
    53  	// in obj that are removed by patch are cleared
    54  	value := reflect.ValueOf(existing)
    55  	value.Elem().Set(reflect.New(value.Type().Elem()).Elem())
    56  
    57  	var obj cilium_api_v2alpha1.CiliumLoadBalancerIPPool
    58  
    59  	switch pt {
    60  	case types.JSONPatchType:
    61  		patch, err := jsonpatch.DecodePatch(data)
    62  		if err != nil {
    63  			panic(err)
    64  		}
    65  		modified, err := patch.Apply(old)
    66  		if err != nil {
    67  			panic(err)
    68  		}
    69  
    70  		if err = json.Unmarshal(modified, &obj); err != nil {
    71  			panic(err)
    72  		}
    73  	default:
    74  		panic("Unknown patch type")
    75  	}
    76  
    77  	fic.resources[resource.Key{Name: name}] = &obj
    78  
    79  	return &obj, nil
    80  }
    81  
    82  type fakeSvcClientGetter struct {
    83  	resources map[resource.Key]*slim_core_v1.Service
    84  }
    85  
    86  func (fscg *fakeSvcClientGetter) Services(namespace string) client_typed_v1.ServiceInterface {
    87  	return &fakeSvcClient{
    88  		namespace: namespace,
    89  		getter:    fscg,
    90  	}
    91  }
    92  
    93  type fakeSvcClient struct {
    94  	namespace string
    95  	getter    *fakeSvcClientGetter
    96  }
    97  
    98  func (fsc *fakeSvcClient) Create(ctx context.Context, service *slim_core_v1.Service, opts metav1.CreateOptions) (*slim_core_v1.Service, error) {
    99  	return nil, nil
   100  }
   101  func (fsc *fakeSvcClient) Update(ctx context.Context, service *slim_core_v1.Service, opts metav1.UpdateOptions) (*slim_core_v1.Service, error) {
   102  	return nil, nil
   103  }
   104  func (fsc *fakeSvcClient) UpdateStatus(ctx context.Context, service *slim_core_v1.Service, opts metav1.UpdateOptions) (*slim_core_v1.Service, error) {
   105  	return nil, nil
   106  }
   107  func (fsc *fakeSvcClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
   108  	return nil
   109  }
   110  func (fsc *fakeSvcClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*slim_core_v1.Service, error) {
   111  	return nil, nil
   112  }
   113  func (fsc *fakeSvcClient) List(ctx context.Context, opts metav1.ListOptions) (*slim_core_v1.ServiceList, error) {
   114  	return nil, nil
   115  }
   116  func (fsc *fakeSvcClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   117  	return nil, nil
   118  }
   119  
   120  func (fsc *fakeSvcClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *slim_core_v1.Service, err error) {
   121  	existing, found := fsc.getter.resources[resource.Key{Namespace: fsc.namespace, Name: name}]
   122  	if !found {
   123  		return nil, fmt.Errorf("No service found with name %q", name)
   124  	}
   125  
   126  	old, err := json.Marshal(existing)
   127  	if err != nil {
   128  		panic(err)
   129  	}
   130  
   131  	// reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields
   132  	// in obj that are removed by patch are cleared
   133  	value := reflect.ValueOf(existing)
   134  	value.Elem().Set(reflect.New(value.Type().Elem()).Elem())
   135  
   136  	var obj slim_core_v1.Service
   137  
   138  	switch pt {
   139  	case types.JSONPatchType:
   140  		patch, err := jsonpatch.DecodePatch(data)
   141  		if err != nil {
   142  			panic(err)
   143  		}
   144  		modified, err := patch.Apply(old)
   145  		if err != nil {
   146  			panic(err)
   147  		}
   148  
   149  		if err = json.Unmarshal(modified, &obj); err != nil {
   150  			panic(err)
   151  		}
   152  	default:
   153  		panic("Unknown patch type")
   154  	}
   155  
   156  	fsc.getter.resources[resource.Key{Namespace: fsc.namespace, Name: name}] = &obj
   157  
   158  	return &obj, nil
   159  }
   160  
   161  type newFixture struct {
   162  	lbipam *LBIPAM
   163  
   164  	// mock clients
   165  	poolClient *fakeIPPoolClient
   166  	svcClient  *fakeSvcClientGetter
   167  }
   168  
   169  func (nf *newFixture) GetPool(name string) *cilium_api_v2alpha1.CiliumLoadBalancerIPPool {
   170  	return nf.poolClient.resources[resource.Key{Name: name}]
   171  }
   172  
   173  func (nf *newFixture) UpsertPool(t *testing.T, pool *cilium_api_v2alpha1.CiliumLoadBalancerIPPool) {
   174  	key := resource.Key{Name: pool.Name}
   175  	nf.poolClient.resources[key] = pool
   176  	nf.lbipam.handlePoolEvent(context.Background(), resource.Event[*cilium_api_v2alpha1.CiliumLoadBalancerIPPool]{
   177  		Kind:   resource.Upsert,
   178  		Key:    key,
   179  		Object: pool,
   180  		Done: func(err error) {
   181  			if err != nil {
   182  				t.Fatal(err)
   183  			}
   184  		},
   185  	})
   186  }
   187  
   188  func (nf *newFixture) DeletePool(t *testing.T, pool *cilium_api_v2alpha1.CiliumLoadBalancerIPPool) {
   189  	key := resource.Key{Name: pool.Name}
   190  	delete(nf.poolClient.resources, key)
   191  	nf.lbipam.handlePoolEvent(context.Background(), resource.Event[*cilium_api_v2alpha1.CiliumLoadBalancerIPPool]{
   192  		Kind:   resource.Delete,
   193  		Key:    key,
   194  		Object: pool,
   195  		Done: func(err error) {
   196  			if err != nil {
   197  				t.Fatal(err)
   198  			}
   199  		},
   200  	})
   201  }
   202  
   203  func (nf *newFixture) UpsertSvc(t *testing.T, svc *slim_core_v1.Service) {
   204  	key := resource.Key{Name: svc.Name, Namespace: svc.Namespace}
   205  	nf.svcClient.resources[key] = svc
   206  	nf.lbipam.handleServiceEvent(context.Background(), resource.Event[*slim_core_v1.Service]{
   207  		Kind:   resource.Upsert,
   208  		Key:    key,
   209  		Object: svc,
   210  		Done: func(err error) {
   211  			if err != nil {
   212  				debug.PrintStack()
   213  				t.Fatal(err)
   214  			}
   215  		},
   216  	})
   217  }
   218  
   219  func (nf *newFixture) DeleteSvc(t *testing.T, svc *slim_core_v1.Service) {
   220  	key := resource.Key{Name: svc.Name, Namespace: svc.Namespace}
   221  	delete(nf.svcClient.resources, key)
   222  	nf.lbipam.handleServiceEvent(context.Background(), resource.Event[*slim_core_v1.Service]{
   223  		Kind:   resource.Delete,
   224  		Key:    key,
   225  		Object: svc,
   226  		Done: func(err error) {
   227  			if err != nil {
   228  				t.Fatal(err)
   229  			}
   230  		},
   231  	})
   232  }
   233  
   234  func (nf *newFixture) GetSvc(namespace, name string) *slim_core_v1.Service {
   235  	return nf.svcClient.resources[resource.Key{Namespace: namespace, Name: name}]
   236  }
   237  
   238  func mkTestFixture(ipv4Enabled, ipv6Enabled bool) newFixture {
   239  	log := logrus.New()
   240  	if testing.Verbose() {
   241  		log.SetLevel(logrus.DebugLevel)
   242  	} else {
   243  		log.SetLevel(logrus.ErrorLevel)
   244  	}
   245  
   246  	poolClient := &fakeIPPoolClient{
   247  		resources: make(map[resource.Key]*cilium_api_v2alpha1.CiliumLoadBalancerIPPool),
   248  	}
   249  	svcClient := &fakeSvcClientGetter{
   250  		resources: make(map[resource.Key]*slim_core_v1.Service),
   251  	}
   252  
   253  	return newFixture{
   254  		poolClient: poolClient,
   255  		svcClient:  svcClient,
   256  		lbipam: newLBIPAM(lbIPAMParams{
   257  			logger: log,
   258  			lbClasses: []string{
   259  				cilium_api_v2alpha1.BGPLoadBalancerClass,
   260  				cilium_api_v2alpha1.L2AnnounceLoadBalancerClass,
   261  			},
   262  			ipv4Enabled: ipv4Enabled,
   263  			ipv6Enabled: ipv6Enabled,
   264  
   265  			metrics: newMetrics(),
   266  
   267  			poolClient: poolClient,
   268  			svcClient:  svcClient,
   269  		}),
   270  	}
   271  }
   272  
   273  // mkPool is a constructor function to assist in the creation of new pool objects.
   274  func mkPool(uid types.UID, name string, cidrs []string) *cilium_api_v2alpha1.CiliumLoadBalancerIPPool {
   275  	var blocks []cilium_api_v2alpha1.CiliumLoadBalancerIPPoolIPBlock
   276  	for _, cidr := range cidrs {
   277  		blocks = append(blocks, cilium_api_v2alpha1.CiliumLoadBalancerIPPoolIPBlock{
   278  			Cidr: cilium_api_v2alpha1.IPv4orIPv6CIDR(cidr),
   279  		})
   280  	}
   281  
   282  	return &cilium_api_v2alpha1.CiliumLoadBalancerIPPool{
   283  		ObjectMeta: metav1.ObjectMeta{
   284  			Name:              name,
   285  			UID:               uid,
   286  			CreationTimestamp: metav1.Date(2022, 10, 16, 12, 00, 00, 0, time.UTC),
   287  		},
   288  		Spec: cilium_api_v2alpha1.CiliumLoadBalancerIPPoolSpec{
   289  			Blocks: blocks,
   290  		},
   291  	}
   292  }