github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_endpoint_slice_subscriber_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  package watchers
     4  
     5  import (
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  
    12  	"github.com/cilium/cilium/pkg/endpoint"
    13  	v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    14  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    15  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    16  	"github.com/cilium/cilium/pkg/k8s/types"
    17  )
    18  
    19  type endpointUpdate struct {
    20  	oldEp, newEp *types.CiliumEndpoint
    21  }
    22  
    23  func epToString(e *types.CiliumEndpoint) string {
    24  	if e == nil {
    25  		return "nil"
    26  	}
    27  	return fmt.Sprintf("ep(id: %d)", e.Identity.ID)
    28  }
    29  
    30  func (u endpointUpdate) toString() string {
    31  	return fmt.Sprintf("(%s, %s)", epToString(u.oldEp), epToString(u.newEp))
    32  }
    33  
    34  func epEqual(e1, e2 *types.CiliumEndpoint) bool {
    35  	return ((e1 == nil) == (e2 == nil)) && (e1 == nil || e1.Identity.ID == e2.Identity.ID)
    36  }
    37  
    38  func updateEqual(u1, u2 endpointUpdate) bool {
    39  	return epEqual(u1.oldEp, u2.oldEp) && epEqual(u1.newEp, u2.newEp)
    40  }
    41  
    42  type fakeEPWatcher struct {
    43  	lastUpdate endpointUpdate
    44  	lastDelete *types.CiliumEndpoint
    45  }
    46  
    47  func createFakeEPWatcher() *fakeEPWatcher {
    48  	return &fakeEPWatcher{}
    49  }
    50  
    51  func (fw *fakeEPWatcher) endpointUpdated(oldC, newC *types.CiliumEndpoint) {
    52  	fw.lastUpdate = endpointUpdate{oldC, newC}
    53  }
    54  
    55  func (fw *fakeEPWatcher) endpointDeleted(c *types.CiliumEndpoint) {
    56  	fw.lastDelete = c
    57  }
    58  
    59  func (fw *fakeEPWatcher) assertUpdate(u endpointUpdate) (string, bool) {
    60  	if !updateEqual(fw.lastUpdate, u) {
    61  		return fmt.Sprintf("Expected %s, got %s", u.toString(), fw.lastUpdate.toString()), false
    62  	}
    63  	return "", true
    64  }
    65  
    66  func (fw *fakeEPWatcher) assertNoDelete() (string, bool) {
    67  	if fw.lastDelete != nil {
    68  		return fmt.Sprintf("Expected no delete, got %s", epToString(fw.lastDelete)), false
    69  	}
    70  	return "", true
    71  }
    72  
    73  func (fw *fakeEPWatcher) assertDelete(e *types.CiliumEndpoint) (string, bool) {
    74  	if !epEqual(fw.lastDelete, e) {
    75  		return fmt.Sprintf("Expected no delete, got %s", epToString(fw.lastDelete)), false
    76  	}
    77  	return "", true
    78  }
    79  
    80  type fakeEndpointCache struct{}
    81  
    82  func (fe *fakeEndpointCache) LookupCEPName(namespacedName string) *endpoint.Endpoint {
    83  	return nil
    84  }
    85  
    86  func createCES(name, namespace string, endpoints []v2alpha1.CoreCiliumEndpoint) *v2alpha1.CiliumEndpointSlice {
    87  	return &v2alpha1.CiliumEndpointSlice{
    88  		Namespace: namespace,
    89  		ObjectMeta: v1.ObjectMeta{
    90  			Name: name,
    91  		},
    92  		Endpoints: endpoints,
    93  	}
    94  }
    95  
    96  func createEndpoint(name, namespace string, id int64) *types.CiliumEndpoint {
    97  	return &types.CiliumEndpoint{
    98  		ObjectMeta: slim_metav1.ObjectMeta{
    99  			Namespace: namespace,
   100  			Name:      name,
   101  		},
   102  		Identity:   &v2.EndpointIdentity{ID: id},
   103  		Encryption: &v2.EncryptionSpec{},
   104  	}
   105  }
   106  
   107  // TestCESSubscriber_CEPTransfer tests a CEP being transferred between two
   108  // CESes. The order of events:
   109  // 1. CES add event for the new CES with latest CEP state
   110  // 2. CES add event for the old CES with old CEP state
   111  // 3. CES delete event for the old CES.
   112  func TestCESSubscriber_CEPTransferOnStartup(t *testing.T) {
   113  	cepNamespace := "ns1"
   114  	cepName := "cep1"
   115  	newCEPID := int64(3)
   116  	oldCEPID := int64(2)
   117  	fakeEPWatcher := createFakeEPWatcher()
   118  	fakeEndpointCache := &fakeEndpointCache{}
   119  	cesSub := &cesSubscriber{
   120  		epWatcher: fakeEPWatcher,
   121  		epCache:   fakeEndpointCache,
   122  		cepMap:    newCEPToCESMap(),
   123  	}
   124  	// Add for new CES
   125  	cesSub.OnAdd(
   126  		createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   127  			{
   128  				Name:       cepName,
   129  				IdentityID: newCEPID,
   130  			},
   131  		}))
   132  	diff, ok := fakeEPWatcher.assertUpdate(endpointUpdate{
   133  		newEp: createEndpoint("cep1", "ns1", newCEPID),
   134  	})
   135  	if !ok {
   136  		t.Fatal(diff)
   137  	}
   138  	// Add for old CES
   139  	cesSub.OnAdd(
   140  		createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   141  			{
   142  				Name:       cepName,
   143  				IdentityID: oldCEPID,
   144  			},
   145  		}))
   146  	diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{
   147  		oldEp: createEndpoint("cep1", "ns1", newCEPID),
   148  		newEp: createEndpoint("cep1", "ns1", oldCEPID),
   149  	})
   150  	if !ok {
   151  		t.Fatal(diff)
   152  	}
   153  	// Delete the old CES
   154  	cesSub.OnDelete(
   155  		createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   156  			{
   157  				Name:       cepName,
   158  				IdentityID: oldCEPID,
   159  			},
   160  		}))
   161  	diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{
   162  		oldEp: createEndpoint("cep1", "ns1", oldCEPID),
   163  		newEp: createEndpoint("cep1", "ns1", newCEPID),
   164  	})
   165  	if !ok {
   166  		t.Fatal(diff)
   167  	}
   168  	diff, ok = fakeEPWatcher.assertNoDelete()
   169  	if !ok {
   170  		t.Fatal(diff)
   171  	}
   172  	wantCEPMap := map[string]cesToCEPRef{
   173  		"ns1/cep1": {
   174  			"new-ces": createEndpoint("cep1", "ns1", 3),
   175  		},
   176  	}
   177  	if diff := cmp.Diff(wantCEPMap, cesSub.cepMap.cepMap); diff != "" {
   178  		t.Fatalf("Unexpected CEP map (-want +got):\n%s", diff)
   179  	}
   180  }
   181  
   182  // TestCESSubscriber_CEPTransferViaUpdate tests a CEP being transferred between
   183  // two CESes. The order of events:
   184  // 1. CES add event for the old CES with old CEP state
   185  // 2. CES update event for the old CES with deleting old CEP state
   186  // 3. CES update event for the new CES with latest CEP state
   187  func TestCESSubscriber_CEPTransferViaUpdate(t *testing.T) {
   188  	cepNamespace := "ns1"
   189  	cepName := "cep1"
   190  	newCEPID := int64(3)
   191  	oldCEPID := int64(2)
   192  	fakeEPWatcher := createFakeEPWatcher()
   193  	fakeEndpointCache := &fakeEndpointCache{}
   194  	cesSub := &cesSubscriber{
   195  		epWatcher: fakeEPWatcher,
   196  		epCache:   fakeEndpointCache,
   197  		cepMap:    newCEPToCESMap(),
   198  	}
   199  	// Add for old CES
   200  	cesSub.OnAdd(
   201  		createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   202  			{
   203  				Name:       cepName,
   204  				IdentityID: oldCEPID,
   205  			},
   206  		}))
   207  	diff, ok := fakeEPWatcher.assertUpdate(endpointUpdate{
   208  		newEp: createEndpoint("cep1", "ns1", oldCEPID),
   209  	})
   210  	if !ok {
   211  		t.Fatal(diff)
   212  	}
   213  	// Update for old CES removing CEP
   214  	cesSub.OnUpdate(
   215  		createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   216  			{
   217  				Name:       cepName,
   218  				IdentityID: oldCEPID,
   219  			},
   220  		}),
   221  		createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{}))
   222  
   223  	diff, ok = fakeEPWatcher.assertDelete(createEndpoint("cep1", "ns1", oldCEPID))
   224  	if !ok {
   225  		t.Fatal(diff)
   226  	}
   227  	// Update for new CES
   228  	cesSub.OnUpdate(
   229  		createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{}),
   230  		createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{
   231  			{
   232  				Name:       cepName,
   233  				IdentityID: newCEPID,
   234  			},
   235  		}))
   236  	diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{
   237  		newEp: createEndpoint("cep1", "ns1", newCEPID),
   238  	})
   239  	if !ok {
   240  		t.Fatal(diff)
   241  	}
   242  	wantCEPMap := map[string]cesToCEPRef{
   243  		"ns1/cep1": {
   244  			"new-ces": createEndpoint("cep1", "ns1", 3),
   245  		},
   246  	}
   247  	if diff := cmp.Diff(wantCEPMap, cesSub.cepMap.cepMap); diff != "" {
   248  		t.Fatalf("Unexpected CEP map (-want +got):\n%s", diff)
   249  	}
   250  }
   251  
   252  func TestCESSubscriber_deleteCEPfromCES(t *testing.T) {
   253  	for _, tc := range []struct {
   254  		desc           string
   255  		initCEPMap     map[string]cesToCEPRef
   256  		initCurrentCES map[string]string
   257  		deletedCesName string
   258  		deletedCep     *types.CiliumEndpoint
   259  		expectedUpdate endpointUpdate
   260  		expectedDelete *types.CiliumEndpoint
   261  	}{
   262  		{
   263  			desc: "delete CEP triggers deletion",
   264  			initCEPMap: map[string]cesToCEPRef{
   265  				"ns1/cep1": {
   266  					"ces1": createEndpoint("cep1", "ns1", 3),
   267  				},
   268  			},
   269  			initCurrentCES: map[string]string{"ns1/cep1": "ces1"},
   270  			deletedCesName: "ces1",
   271  			deletedCep:     createEndpoint("cep1", "ns1", 3),
   272  			expectedDelete: createEndpoint("cep1", "ns1", 3),
   273  		},
   274  		{
   275  			desc: "delete CEP triggers update",
   276  			initCEPMap: map[string]cesToCEPRef{
   277  				"ns1/cep1": {
   278  					"ces1": createEndpoint("cep1", "ns1", 2),
   279  					"ces2": createEndpoint("cep1", "ns1", 3),
   280  				},
   281  			},
   282  			initCurrentCES: map[string]string{"ns1/cep1": "ces1"},
   283  			deletedCesName: "ces1",
   284  			deletedCep:     createEndpoint("cep1", "ns1", 2),
   285  			expectedUpdate: endpointUpdate{
   286  				oldEp: createEndpoint("cep1", "ns1", 2),
   287  				newEp: createEndpoint("cep1", "ns1", 3),
   288  			},
   289  		},
   290  		{
   291  			desc: "delete CEP triggers no update or deletion",
   292  			initCEPMap: map[string]cesToCEPRef{
   293  				"ns1/cep1": {
   294  					"ces1": createEndpoint("cep1", "ns1", 1),
   295  					"ces2": createEndpoint("cep1", "ns1", 2),
   296  				},
   297  			},
   298  			initCurrentCES: map[string]string{"ns1/cep1": "ces1"},
   299  			deletedCesName: "ces2",
   300  			deletedCep:     createEndpoint("cep1", "ns1", 2),
   301  		},
   302  	} {
   303  		t.Run(tc.desc, func(t *testing.T) {
   304  			fakeEPWatcher := createFakeEPWatcher()
   305  			cesSub := &cesSubscriber{
   306  				epWatcher: fakeEPWatcher,
   307  				cepMap:    newCEPToCESMap(),
   308  			}
   309  			if tc.initCEPMap != nil {
   310  				cesSub.cepMap.cepMap = tc.initCEPMap
   311  			}
   312  			if tc.initCurrentCES != nil {
   313  				cesSub.cepMap.currentCES = tc.initCurrentCES
   314  			}
   315  			cepName := tc.deletedCep.Namespace + "/" + tc.deletedCep.Name
   316  			cesSub.deleteCEPfromCES(cepName, tc.deletedCesName, tc.deletedCep)
   317  			diff, ok := fakeEPWatcher.assertUpdate(tc.expectedUpdate)
   318  			if !ok {
   319  				t.Error(diff)
   320  			}
   321  			diff, ok = fakeEPWatcher.assertDelete(tc.expectedDelete)
   322  			if !ok {
   323  				t.Error(diff)
   324  			}
   325  		})
   326  	}
   327  }
   328  
   329  func TestCEPToCESmap_insertCEP(t *testing.T) {
   330  	for _, tc := range []struct {
   331  		desc           string
   332  		initCEPMap     map[string]cesToCEPRef
   333  		initCurrentCES map[string]string
   334  		cesName        string
   335  		cep            *types.CiliumEndpoint
   336  		wantCEPMap     map[string]cesToCEPRef
   337  		wantCurrentCES map[string]string
   338  	}{
   339  		{
   340  			desc:    "add new cep",
   341  			cep:     createEndpoint("cep1", "ns1", 3),
   342  			cesName: "cesx",
   343  			wantCEPMap: map[string]cesToCEPRef{
   344  				"ns1/cep1": {
   345  					"cesx": createEndpoint("cep1", "ns1", 3),
   346  				},
   347  			},
   348  			wantCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   349  		},
   350  		{
   351  			desc: "update cep object",
   352  			initCEPMap: map[string]cesToCEPRef{
   353  				"ns1/cep1": {
   354  					"cesx": createEndpoint("cep1", "ns1", 3),
   355  				},
   356  			},
   357  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   358  			cep:            createEndpoint("cep1", "ns1", 1),
   359  			cesName:        "cesx",
   360  			wantCEPMap: map[string]cesToCEPRef{
   361  				"ns1/cep1": {
   362  					"cesx": createEndpoint("cep1", "ns1", 1),
   363  				},
   364  			},
   365  			wantCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   366  		},
   367  		{
   368  			desc: "add new ces for existing cep",
   369  			initCEPMap: map[string]cesToCEPRef{
   370  				"ns1/cep1": {
   371  					"cesx": createEndpoint("cep1", "ns1", 3),
   372  				},
   373  			},
   374  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   375  			cep:            createEndpoint("cep1", "ns1", 1),
   376  			cesName:        "cesy",
   377  			wantCEPMap: map[string]cesToCEPRef{
   378  				"ns1/cep1": {
   379  					"cesx": createEndpoint("cep1", "ns1", 3),
   380  					"cesy": createEndpoint("cep1", "ns1", 1),
   381  				},
   382  			},
   383  			wantCurrentCES: map[string]string{"ns1/cep1": "cesy"},
   384  		},
   385  	} {
   386  		t.Run(tc.desc, func(t *testing.T) {
   387  			cepToCESmap := newCEPToCESMap()
   388  			if tc.initCEPMap != nil {
   389  				cepToCESmap.cepMap = tc.initCEPMap
   390  			}
   391  			if tc.initCurrentCES != nil {
   392  				cepToCESmap.currentCES = tc.initCurrentCES
   393  			}
   394  			cepName := tc.cep.Namespace + "/" + tc.cep.Name
   395  			cepToCESmap.insertCEPLocked(cepName, tc.cesName, tc.cep)
   396  			if diff := cmp.Diff(tc.wantCEPMap, cepToCESmap.cepMap); diff != "" {
   397  				t.Fatalf("Unexpected CEP map entries (-want +got):\n%s", diff)
   398  			}
   399  			if diff := cmp.Diff(tc.wantCurrentCES, cepToCESmap.currentCES); diff != "" {
   400  				t.Fatalf("Unexpected currentCES map entries (-want +got):\n%s", diff)
   401  			}
   402  		})
   403  	}
   404  }
   405  
   406  func TestCEPToCESmap_deleteCEP(t *testing.T) {
   407  	for _, tc := range []struct {
   408  		desc           string
   409  		initCEPMap     map[string]cesToCEPRef
   410  		initCurrentCES map[string]string
   411  		cesName        string
   412  		cepName        string
   413  		wantCEPMap     map[string]cesToCEPRef
   414  		wantCurrentCES map[string]string
   415  	}{
   416  		{
   417  			desc: "missing ces does not delete any entries",
   418  			initCEPMap: map[string]cesToCEPRef{
   419  				"ns1/cep1": {
   420  					"cesx": createEndpoint("cep1", "ns1", 3),
   421  				},
   422  			},
   423  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   424  			cepName:        "ns1/cep1",
   425  			cesName:        "cesy",
   426  			wantCEPMap: map[string]cesToCEPRef{
   427  				"ns1/cep1": {
   428  					"cesx": createEndpoint("cep1", "ns1", 3),
   429  				},
   430  			},
   431  			wantCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   432  		},
   433  		{
   434  			desc: "missing cep does not delete any entries",
   435  			initCEPMap: map[string]cesToCEPRef{
   436  				"ns1/cep1": {
   437  					"cesx": createEndpoint("cep1", "ns1", 3),
   438  				},
   439  			},
   440  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   441  			cepName:        "ns1/cep2",
   442  			cesName:        "cesx",
   443  			wantCEPMap: map[string]cesToCEPRef{
   444  				"ns1/cep1": {
   445  					"cesx": createEndpoint("cep1", "ns1", 3),
   446  				},
   447  			},
   448  			wantCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   449  		},
   450  		{
   451  			desc: "last ces entry",
   452  			initCEPMap: map[string]cesToCEPRef{
   453  				"ns1/cep1": {
   454  					"cesx": createEndpoint("cep1", "ns1", 3),
   455  				},
   456  			},
   457  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   458  			cepName:        "ns1/cep1",
   459  			cesName:        "cesx",
   460  			wantCEPMap:     map[string]cesToCEPRef{},
   461  			wantCurrentCES: map[string]string{},
   462  		},
   463  		{
   464  			desc: "multiple ces entries",
   465  			initCEPMap: map[string]cesToCEPRef{
   466  				"ns1/cep1": {
   467  					"cesx": createEndpoint("cep1", "ns1", 3),
   468  					"cesy": createEndpoint("cep1", "ns1", 2),
   469  				},
   470  			},
   471  			initCurrentCES: map[string]string{"ns1/cep1": "cesx"},
   472  			cepName:        "ns1/cep1",
   473  			cesName:        "cesx",
   474  			wantCEPMap: map[string]cesToCEPRef{
   475  				"ns1/cep1": {
   476  					"cesy": createEndpoint("cep1", "ns1", 2),
   477  				},
   478  			},
   479  			wantCurrentCES: map[string]string{"ns1/cep1": "cesy"},
   480  		},
   481  	} {
   482  		t.Run(tc.desc, func(t *testing.T) {
   483  			cepToCESmap := newCEPToCESMap()
   484  			if tc.initCEPMap != nil {
   485  				cepToCESmap.cepMap = tc.initCEPMap
   486  			}
   487  			if tc.initCurrentCES != nil {
   488  				cepToCESmap.currentCES = tc.initCurrentCES
   489  			}
   490  			cepToCESmap.deleteCEPLocked(tc.cepName, tc.cesName)
   491  			if diff := cmp.Diff(tc.wantCEPMap, cepToCESmap.cepMap); diff != "" {
   492  				t.Fatalf("Unexpected CEP map entries (-want +got):\n%s", diff)
   493  			}
   494  			if diff := cmp.Diff(tc.wantCurrentCES, cepToCESmap.currentCES); diff != "" {
   495  				t.Fatalf("Unexpected currentCES map entries (-want +got):\n%s", diff)
   496  			}
   497  		})
   498  	}
   499  }