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
27
28 func createScheme() *kruntime.Scheme {
29 scheme := kruntime.NewScheme()
30 utilruntime.Must(clientgoscheme.AddToScheme(scheme))
31 return scheme
32 }
33
34
35
36 func GetFakeKubeClient(initObjs ...client.Object) client.Client {
37 return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build()
38 }
39
40
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