github.com/cilium/cilium@v1.16.2/pkg/endpoint/restore_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package endpoint
     5  
     6  import (
     7  	"context"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"net/netip"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"testing"
    15  
    16  	"github.com/stretchr/testify/require"
    17  
    18  	fake "github.com/cilium/cilium/pkg/datapath/fake/types"
    19  	"github.com/cilium/cilium/pkg/identity"
    20  	"github.com/cilium/cilium/pkg/labels"
    21  	"github.com/cilium/cilium/pkg/mac"
    22  	testidentity "github.com/cilium/cilium/pkg/testutils/identity"
    23  	testipcache "github.com/cilium/cilium/pkg/testutils/ipcache"
    24  )
    25  
    26  func (s *EndpointSuite) createEndpoints() ([]*Endpoint, map[uint16]*Endpoint) {
    27  	epsWanted := []*Endpoint{
    28  		s.endpointCreator(256, identity.NumericIdentity(1256)),
    29  		s.endpointCreator(257, identity.NumericIdentity(1257)),
    30  		s.endpointCreator(258, identity.NumericIdentity(1258)),
    31  		s.endpointCreator(259, identity.NumericIdentity(1259)),
    32  	}
    33  	epsMap := map[uint16]*Endpoint{
    34  		epsWanted[0].ID: epsWanted[0],
    35  		epsWanted[1].ID: epsWanted[1],
    36  		epsWanted[2].ID: epsWanted[2],
    37  		epsWanted[3].ID: epsWanted[3],
    38  	}
    39  	return epsWanted, epsMap
    40  }
    41  
    42  func getStrID(id uint16) string {
    43  	return fmt.Sprintf("%05d", id)
    44  }
    45  
    46  func (s *EndpointSuite) endpointCreator(id uint16, secID identity.NumericIdentity) *Endpoint {
    47  	strID := getStrID(id)
    48  	b := make([]byte, 2)
    49  	binary.LittleEndian.PutUint16(b, id)
    50  
    51  	identity := &identity.Identity{
    52  		ID: secID,
    53  		Labels: labels.Labels{
    54  			"foo" + strID: labels.NewLabel("foo"+strID, "", ""),
    55  		},
    56  	}
    57  	identity.Sanitize()
    58  
    59  	repo := s.GetPolicyRepository()
    60  	repo.GetPolicyCache().LocalEndpointIdentityAdded(identity)
    61  
    62  	ep := NewTestEndpointWithState(nil, s, s, testipcache.NewMockIPCache(), &FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), id, StateReady)
    63  	// Random network ID and docker endpoint ID with 59 hex chars + 5 strID = 64 hex chars
    64  	ep.dockerNetworkID = "603e047d2268a57f5a5f93f7f9e1263e9207e348a06654bf64948def001" + strID
    65  	ep.dockerEndpointID = "93529fda8c401a071d21d6bd46fdf5499b9014dcb5a35f2e3efaa8d8002" + strID
    66  	ep.ifName = "lxc" + strID
    67  	ep.mac = mac.MAC([]byte{0x01, 0xff, 0xf2, 0x12, b[0], b[1]})
    68  	ep.IPv4 = netip.AddrFrom4([4]byte{0xc0, 0xa8, b[0], b[1]})
    69  	ep.IPv6 = netip.AddrFrom16([16]byte{0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, b[0], b[1]})
    70  	ep.ifIndex = 1
    71  	ep.nodeMAC = []byte{0x02, 0xff, 0xf2, 0x12, 0x0, 0x0}
    72  	ep.SecurityIdentity = identity
    73  	ep.OpLabels = labels.NewOpLabels()
    74  	ep.NetNsCookie = 1234
    75  	return ep
    76  }
    77  
    78  func TestReadEPsFromDirNames(t *testing.T) {
    79  	s := setupEndpointSuite(t)
    80  	oldDatapath := s.datapath
    81  	defer func() {
    82  		s.datapath = oldDatapath
    83  	}()
    84  
    85  	s.datapath = fake.NewDatapath()
    86  	epsWanted, _ := s.createEndpoints()
    87  	tmpDir, err := os.MkdirTemp("", "cilium-tests")
    88  	defer func() {
    89  		os.RemoveAll(tmpDir)
    90  	}()
    91  
    92  	os.Chdir(tmpDir)
    93  	require.Nil(t, err)
    94  	epsNames := []string{}
    95  	for _, ep := range epsWanted {
    96  		require.NotNil(t, ep)
    97  
    98  		fullDirName := filepath.Join(tmpDir, ep.DirectoryPath())
    99  		err := os.MkdirAll(fullDirName, 0777)
   100  		require.Nil(t, err)
   101  
   102  		err = ep.writeHeaderfile(fullDirName)
   103  		require.Nil(t, err)
   104  
   105  		switch ep.ID {
   106  		case 256, 257:
   107  			failedDir := filepath.Join(tmpDir, ep.FailedDirectoryPath())
   108  			err := os.Rename(fullDirName, failedDir)
   109  			require.Nil(t, err)
   110  			epsNames = append(epsNames, ep.FailedDirectoryPath())
   111  
   112  			// create one failed and the other non failed directory for ep 256.
   113  			if ep.ID == 256 {
   114  				// Change endpoint a little bit so we know which endpoint is in
   115  				// "256_next_fail" and with one is in the "256" directory.
   116  				ep.nodeMAC = []byte{0x02, 0xff, 0xf2, 0x12, 0xc1, 0xc1}
   117  				err = ep.writeHeaderfile(failedDir)
   118  				require.Nil(t, err)
   119  			}
   120  		default:
   121  			epsNames = append(epsNames, ep.DirectoryPath())
   122  		}
   123  	}
   124  	eps := ReadEPsFromDirNames(context.TODO(), s, s, s, tmpDir, epsNames)
   125  	require.Equal(t, len(epsWanted), len(eps))
   126  
   127  	sort.Slice(epsWanted, func(i, j int) bool { return epsWanted[i].ID < epsWanted[j].ID })
   128  	restoredEPs := make([]*Endpoint, 0, len(eps))
   129  	for _, ep := range eps {
   130  		restoredEPs = append(restoredEPs, ep)
   131  	}
   132  	sort.Slice(restoredEPs, func(i, j int) bool { return restoredEPs[i].ID < restoredEPs[j].ID })
   133  
   134  	require.Equal(t, len(epsWanted), len(restoredEPs))
   135  	for i, restoredEP := range restoredEPs {
   136  		// We probably shouldn't modify these, but the status will
   137  		// naturally differ between the wanted endpoint and the version
   138  		// that's restored, because the restored version has log
   139  		// messages relating to the restore.
   140  		restoredEP.status = nil
   141  		wanted := epsWanted[i]
   142  		wanted.status = nil
   143  		require.EqualValues(t, wanted.String(), restoredEP.String())
   144  	}
   145  }
   146  
   147  func TestReadEPsFromDirNamesWithRestoreFailure(t *testing.T) {
   148  	s := setupEndpointSuite(t)
   149  
   150  	oldDatapath := s.datapath
   151  	defer func() {
   152  		s.datapath = oldDatapath
   153  	}()
   154  
   155  	s.datapath = fake.NewDatapath()
   156  
   157  	eps, _ := s.createEndpoints()
   158  	ep := eps[0]
   159  	require.NotNil(t, ep)
   160  	tmpDir, err := os.MkdirTemp("", "cilium-tests")
   161  	defer func() {
   162  		os.RemoveAll(tmpDir)
   163  	}()
   164  
   165  	os.Chdir(tmpDir)
   166  	require.Nil(t, err)
   167  
   168  	fullDirName := filepath.Join(tmpDir, ep.DirectoryPath())
   169  	err = os.MkdirAll(fullDirName, 0777)
   170  	require.Nil(t, err)
   171  
   172  	err = ep.writeHeaderfile(fullDirName)
   173  	require.Nil(t, err)
   174  
   175  	nextDir := filepath.Join(tmpDir, ep.NextDirectoryPath())
   176  	err = os.MkdirAll(nextDir, 0777)
   177  	require.Nil(t, err)
   178  
   179  	// Change endpoint a little bit so we know which endpoint is in
   180  	// "${EPID}_next" and with one is in the "${EPID}" directory.
   181  	tmpNodeMAC := ep.nodeMAC
   182  	ep.nodeMAC = []byte{0x02, 0xff, 0xf2, 0x12, 0xc1, 0xc1}
   183  	err = ep.writeHeaderfile(nextDir)
   184  	require.Nil(t, err)
   185  	ep.nodeMAC = tmpNodeMAC
   186  
   187  	epNames := []string{
   188  		ep.DirectoryPath(), ep.NextDirectoryPath(),
   189  	}
   190  
   191  	epResult := ReadEPsFromDirNames(context.TODO(), s, s, s, tmpDir, epNames)
   192  	require.Equal(t, 1, len(epResult))
   193  
   194  	restoredEP := epResult[ep.ID]
   195  	require.EqualValues(t, ep.String(), restoredEP.String())
   196  
   197  	// Check that the directory for failed restore was removed.
   198  	fileExists := func(fileName string) bool {
   199  		_, err := os.Stat(fileName)
   200  		if err == nil {
   201  			return true
   202  		}
   203  		if !os.IsNotExist(err) {
   204  			require.Error(t, err)
   205  		}
   206  		return false
   207  	}
   208  	require.Equal(t, false, fileExists(nextDir))
   209  	require.Equal(t, true, fileExists(fullDirName))
   210  }
   211  
   212  func BenchmarkReadEPsFromDirNames(b *testing.B) {
   213  	s := setupEndpointSuite(b)
   214  
   215  	b.StopTimer()
   216  
   217  	// For this benchmark, the real linux datapath is necessary to properly
   218  	// serialize config files to disk and benchmark the restore.
   219  	oldDatapath := s.datapath
   220  	defer func() {
   221  		s.datapath = oldDatapath
   222  	}()
   223  
   224  	s.datapath = fake.NewDatapath()
   225  
   226  	epsWanted, _ := s.createEndpoints()
   227  	tmpDir, err := os.MkdirTemp("", "cilium-tests")
   228  	defer func() {
   229  		os.RemoveAll(tmpDir)
   230  	}()
   231  
   232  	os.Chdir(tmpDir)
   233  	require.Nil(b, err)
   234  	epsNames := []string{}
   235  	for _, ep := range epsWanted {
   236  		require.NotNil(b, ep)
   237  
   238  		fullDirName := filepath.Join(tmpDir, ep.DirectoryPath())
   239  		err := os.MkdirAll(fullDirName, 0777)
   240  		require.Nil(b, err)
   241  
   242  		err = ep.writeHeaderfile(fullDirName)
   243  		require.Nil(b, err)
   244  
   245  		epsNames = append(epsNames, ep.DirectoryPath())
   246  	}
   247  	b.StartTimer()
   248  
   249  	for i := 0; i < b.N; i++ {
   250  		eps := ReadEPsFromDirNames(context.TODO(), s, s, s, tmpDir, epsNames)
   251  		require.Equal(b, len(epsWanted), len(eps))
   252  	}
   253  }
   254  
   255  func TestPartitionEPDirNamesByRestoreStatus(t *testing.T) {
   256  	setupEndpointSuite(t)
   257  
   258  	eptsDirNames := []string{
   259  		"4", "12", "12_next", "3_next", "5_next_fail", "5",
   260  	}
   261  	completeWanted := []string{
   262  		"12", "3_next", "4", "5",
   263  	}
   264  	incompleteWanted := []string{
   265  		"12_next", "5_next_fail",
   266  	}
   267  
   268  	complete, incomplete := partitionEPDirNamesByRestoreStatus(eptsDirNames)
   269  
   270  	sort.Strings(complete)
   271  	sort.Strings(completeWanted)
   272  	sort.Strings(incomplete)
   273  	sort.Strings(incompleteWanted)
   274  	require.EqualValues(t, completeWanted, complete)
   275  	require.EqualValues(t, incompleteWanted, incomplete)
   276  }