...

Source file src/edge-infra.dev/pkg/sds/interlock/topic/instances/instances_test.go

Documentation: edge-infra.dev/pkg/sds/interlock/topic/instances

     1  package instances
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/spf13/afero"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  	corev1 "k8s.io/api/core/v1"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	kruntime "k8s.io/apimachinery/pkg/runtime"
    13  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    14  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    15  	"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    18  	"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
    19  
    20  	"edge-infra.dev/pkg/sds/interlock/internal/config"
    21  	"edge-infra.dev/pkg/sds/interlock/topic"
    22  	"edge-infra.dev/pkg/sds/interlock/websocket"
    23  	"edge-infra.dev/pkg/sds/lib/k8s/retryclient"
    24  )
    25  
    26  // CreateScheme creates a new scheme, adds all types of the automatically generated
    27  // clientset, and returns it.
    28  func createScheme() *kruntime.Scheme {
    29  	scheme := kruntime.NewScheme()
    30  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
    31  	return scheme
    32  }
    33  
    34  // GetFakeKubeClient returns a fake client initialised with a slice of
    35  // Kubernets objects. To be used for testing purposes.
    36  func GetFakeKubeClient(initObjs ...client.Object) client.Client {
    37  	return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build()
    38  }
    39  
    40  // getTestPod returns a test representation of a pod.
    41  func getTestPod(name, namespace, nodeName, ip string) *corev1.Pod {
    42  	return &corev1.Pod{
    43  		ObjectMeta: metav1.ObjectMeta{
    44  			Name:      name,
    45  			Namespace: namespace,
    46  		},
    47  		Spec: corev1.PodSpec{
    48  			NodeName: nodeName,
    49  		},
    50  		Status: corev1.PodStatus{
    51  			PodIP: ip,
    52  		},
    53  	}
    54  }
    55  
    56  func TestIsInterlockPod(t *testing.T) {
    57  	tests := map[string]struct {
    58  		pod      interface{}
    59  		expected bool
    60  	}{
    61  		"Invalid": {
    62  			pod:      struct{}{},
    63  			expected: false,
    64  		},
    65  		"Incorrect_Prefix": {
    66  			pod:      getTestPod("notinterlock-hsj45", "interlock", "", ""),
    67  			expected: false,
    68  		},
    69  		"Incorrect_Suffix": {
    70  			pod:      getTestPod("interlock-hsj4", "interlock", "", ""),
    71  			expected: false,
    72  		},
    73  		"Incorrect_Namespace": {
    74  			pod:      getTestPod("interlock-hsj45", "notinterlock", "", ""),
    75  			expected: false,
    76  		},
    77  		"Correct": {
    78  			pod:      getTestPod("interlock-hsj45", "interlock", "", ""),
    79  			expected: true,
    80  		},
    81  	}
    82  
    83  	for name, tc := range tests {
    84  		t.Run(name, func(t *testing.T) {
    85  			got := IsInterlockPod(tc.pod)
    86  			assert.Equal(t, tc.expected, got)
    87  		})
    88  	}
    89  }
    90  
    91  func TestNewState(t *testing.T) {
    92  	pod1 := getTestPod("interlock-11111", "interlock", "node1", "1.1.1.1")
    93  	pod2 := getTestPod("interlock-22222", "interlock", "node2", "2.2.2.2")
    94  	cli := GetFakeKubeClient(pod1, pod2)
    95  
    96  	cfg := &config.Config{
    97  		Fs:              afero.NewMemMapFs(),
    98  		KubeRetryClient: retryclient.New(cli, cli, retryclient.Config{}),
    99  		Cache:           &informertest.FakeInformers{},
   100  	}
   101  
   102  	state, err := newState(context.Background(), cfg)
   103  	require.NoError(t, err)
   104  
   105  	assert.Equal(t, "http://1.1.1.1:80", state.Instances["node1"].URL)
   106  	assert.Equal(t, "http://2.2.2.2:80", state.Instances["node2"].URL)
   107  }
   108  
   109  type mockCache struct {
   110  	*informertest.FakeInformers
   111  }
   112  
   113  func newTestInstancesWithFakeInformer(t *testing.T, initState *State) (*Instances, *controllertest.FakeInformer) {
   114  	testCfg := config.Config{
   115  		Cache: mockCache{&informertest.FakeInformers{}},
   116  	}
   117  	testInstances := Instances{
   118  		topic: topic.NewTopic(
   119  			TopicName,
   120  			initState,
   121  			nil,
   122  			websocket.NewManager(),
   123  		),
   124  	}
   125  
   126  	err := testInstances.SetupAPIInformers(context.Background(), &testCfg)
   127  	require.NoError(t, err)
   128  
   129  	informer, err := testCfg.Cache.GetInformer(context.Background(), &corev1.Pod{})
   130  	require.NoError(t, err)
   131  
   132  	fakeInformer, ok := informer.(*controllertest.FakeInformer)
   133  	require.True(t, ok)
   134  
   135  	return &testInstances, fakeInformer
   136  }
   137  
   138  func TestOnAdd(t *testing.T) {
   139  	testInstances, fakeInformer := newTestInstancesWithFakeInformer(t, &State{Instances: map[string]Instance{}})
   140  
   141  	newNode := getTestPod("interlock-12345", "interlock", "node1", "1.2.3.4")
   142  	fakeInformer.Add(newNode)
   143  
   144  	instancesState := testInstances.topic.State()
   145  	state, ok := instancesState.(*State)
   146  	require.True(t, ok)
   147  
   148  	assert.Equal(t, "http://1.2.3.4:80", state.Instances["node1"].URL)
   149  }
   150  
   151  func TestOnUpdate(t *testing.T) {
   152  	initState := &State{
   153  		Instances: map[string]Instance{
   154  			"node1": {URL: "http://1.2.3.4:80"},
   155  		},
   156  	}
   157  	testInstances, fakeInformer := newTestInstancesWithFakeInformer(t, initState)
   158  
   159  	oldPod := getTestPod("interlock-12345", "interlock", "node1", "1.2.3.4")
   160  	newPod := getTestPod("interlock-12345", "interlock", "node1", "4.3.2.1")
   161  	fakeInformer.Update(oldPod, newPod)
   162  
   163  	instancesState := testInstances.topic.State()
   164  	state, ok := instancesState.(*State)
   165  	require.True(t, ok)
   166  
   167  	assert.Equal(t, "http://4.3.2.1:80", state.Instances["node1"].URL)
   168  }
   169  
   170  func TestOnDelete(t *testing.T) {
   171  	testInstances, fakeInformer := newTestInstancesWithFakeInformer(t, &State{
   172  		Instances: map[string]Instance{
   173  			"node1": {URL: "http://1.2.3.4:80"},
   174  		},
   175  	})
   176  
   177  	pod := getTestPod("interlock-12345", "interlock", "node1", "1.2.3.4")
   178  	fakeInformer.Delete(pod)
   179  
   180  	instancesState := testInstances.topic.State()
   181  	state, ok := instancesState.(*State)
   182  	require.True(t, ok)
   183  
   184  	_, ok = state.Instances["node1"]
   185  	require.False(t, ok)
   186  }
   187  
   188  type testStruct struct {
   189  	state *State
   190  }
   191  
   192  func (t *testStruct) updateState(fn topic.UpdateFunc) error {
   193  	return fn(t.state)
   194  }
   195  
   196  func TestUpdateInstance(t *testing.T) {
   197  	tests := map[string]struct {
   198  		previousIP string
   199  		delete     bool
   200  		want       string
   201  	}{
   202  		"NotPreviouslySet": {
   203  			previousIP: "",
   204  			delete:     false,
   205  			want:       "1.2.3.4",
   206  		},
   207  		"PreviouslySet": {
   208  			previousIP: "1.2.3.4",
   209  			delete:     false,
   210  			want:       "1.2.3.4",
   211  		},
   212  		"Delete": {
   213  			previousIP: "1.2.3.4",
   214  			delete:     true,
   215  			want:       "",
   216  		},
   217  	}
   218  
   219  	for name, tc := range tests {
   220  		t.Run(name, func(t *testing.T) {
   221  			nodeName := "node1"
   222  			ts := testStruct{
   223  				&State{
   224  					Instances: map[string]Instance{
   225  						nodeName: {URL: "http://" + tc.previousIP + ":80"},
   226  					},
   227  				},
   228  			}
   229  
   230  			pod := getTestPod("interlock-11111", "interlock", nodeName, tc.want)
   231  
   232  			updateInstance(ts.updateState, pod, tc.delete)
   233  
   234  			if tc.delete {
   235  				_, ok := ts.state.Instances[nodeName]
   236  				require.False(t, ok)
   237  
   238  				return
   239  			}
   240  
   241  			want := "http://" + tc.want + ":80"
   242  
   243  			assert.Equal(t, want, ts.state.Instances[nodeName].URL)
   244  		})
   245  	}
   246  }
   247  

View as plain text