1 package host
2
3 import (
4 "context"
5 "net/url"
6 "testing"
7
8 "github.com/spf13/afero"
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11 v1 "k8s.io/api/core/v1"
12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 kruntime "k8s.io/apimachinery/pkg/runtime"
14 "k8s.io/apimachinery/pkg/types"
15 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
16 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
17 "sigs.k8s.io/controller-runtime/pkg/cache/informertest"
18 "sigs.k8s.io/controller-runtime/pkg/client"
19 "sigs.k8s.io/controller-runtime/pkg/client/fake"
20 "sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
21
22 "edge-infra.dev/pkg/sds/interlock/internal/config"
23 "edge-infra.dev/pkg/sds/interlock/internal/constants"
24 "edge-infra.dev/pkg/sds/interlock/topic"
25 "edge-infra.dev/pkg/sds/interlock/websocket"
26 "edge-infra.dev/pkg/sds/lib/k8s/retryclient"
27 )
28
29
30
31 func createScheme() *kruntime.Scheme {
32 scheme := kruntime.NewScheme()
33 utilruntime.Must(clientgoscheme.AddToScheme(scheme))
34 return scheme
35 }
36
37
38
39 func GetFakeKubeClient(initObjs ...client.Object) client.Client {
40 return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build()
41 }
42
43
44
45 func getTestHostNode(name, uid string) *v1.Node {
46 return &v1.Node{
47 ObjectMeta: metav1.ObjectMeta{
48 Name: name,
49 UID: types.UID(uid),
50 },
51 }
52 }
53
54 func setLOMFlag(t *testing.T, fs *afero.Fs) {
55 err := (*fs).MkdirAll(constants.ZynstraConfigDir, 0755)
56 require.NoError(t, err)
57
58 path, _ := url.JoinPath(constants.ZynstraConfigDir, constants.LANOutageModeFlagFileName)
59 err = afero.WriteFile(*fs, path, []byte{}, 0644)
60 require.NoError(t, err)
61 }
62
63 func TestIsHostNode(t *testing.T) {
64 tests := map[string]struct {
65 input interface{}
66 nodeNameEnv string
67 want bool
68 }{
69 "Invalid": {
70 input: struct{}{},
71 nodeNameEnv: "",
72 want: false,
73 },
74 "Incorrect_HostName": {
75 input: getTestHostNode("node1", ""),
76 nodeNameEnv: "node2",
77 want: false,
78 },
79 "Correct_HostName": {
80 input: getTestHostNode("node1", ""),
81 nodeNameEnv: "node1",
82 want: true,
83 },
84 }
85
86 for name, tc := range tests {
87 t.Run(name, func(t *testing.T) {
88 t.Setenv("NODE_NAME", tc.nodeNameEnv)
89 got := IsHostNode(tc.input)
90 assert.Equal(t, tc.want, got)
91 })
92 }
93 }
94
95 func TestDiscoverLANOutageMode(t *testing.T) {
96 trueMemFs := afero.NewMemMapFs()
97 setLOMFlag(t, &trueMemFs)
98
99 falseMemFs := afero.NewMemMapFs()
100 err := falseMemFs.MkdirAll(constants.ZynstraConfigDir, 0755)
101 require.NoError(t, err)
102
103 emptyMemFs := afero.NewMemMapFs()
104
105 testCases := map[string]struct {
106 fs afero.Fs
107 want bool
108 }{
109 "FileExists": {
110 trueMemFs,
111 true,
112 },
113 "FolderExistsFileDoesNotExist": {
114 falseMemFs,
115 false,
116 },
117 "FolderDoesNotExist": {
118 emptyMemFs,
119 false,
120 },
121 }
122
123 for name, tc := range testCases {
124 t.Run(name, func(t *testing.T) {
125 got, err := discoverLANOutageMode(tc.fs)
126 require.NoError(t, err)
127 assert.Equal(t, tc.want, got)
128 })
129 }
130 }
131
132 func TestNewState(t *testing.T) {
133 t.Setenv("NODE_NAME", "this-node")
134
135 node := getTestHostNode("this-node", "uid")
136 cli := GetFakeKubeClient(node)
137 fs := afero.NewMemMapFs()
138 setLOMFlag(t, &fs)
139
140 cfg := &config.Config{
141 Fs: fs,
142 KubeRetryClient: retryclient.New(cli, cli, retryclient.Config{}),
143 Cache: &informertest.FakeInformers{},
144 }
145
146 state, err := newState(context.Background(), cfg)
147 require.NoError(t, err)
148
149 assert.Equal(t, "this-node", state.Hostname)
150 assert.Equal(t, "uid", state.NodeUID)
151 assert.True(t, state.Network.LANOutageMode)
152 }
153
154 type mockCache struct {
155 *informertest.FakeInformers
156 }
157
158 func newTestHostWithFakeInformer(t *testing.T, initState *State) (*Host, *controllertest.FakeInformer) {
159 testCfg := config.Config{
160 Cache: mockCache{&informertest.FakeInformers{}},
161 }
162 testHost := Host{
163 topic: topic.NewTopic(
164 TopicName,
165 initState,
166 nil,
167 websocket.NewManager(),
168 ),
169 }
170
171 err := testHost.SetupAPIInformers(context.Background(), &testCfg)
172 require.NoError(t, err)
173
174 informer, err := testCfg.Cache.GetInformer(context.Background(), &v1.Node{})
175 require.NoError(t, err)
176
177 fakeInformer, ok := informer.(*controllertest.FakeInformer)
178 require.True(t, ok)
179
180 return &testHost, fakeInformer
181 }
182
183 func TestOnAdd(t *testing.T) {
184 t.Setenv("NODE_NAME", "new-node")
185 want := "uid1"
186 testHost, fakeInformer := newTestHostWithFakeInformer(t, &State{})
187
188 newNode := getTestHostNode("new-node", want)
189 fakeInformer.Add(newNode)
190
191 hostState := testHost.topic.State()
192 state, ok := hostState.(*State)
193 require.True(t, ok)
194
195 assert.Equal(t, want, state.NodeUID)
196 }
197
198 func TestOnUpdate(t *testing.T) {
199 initState := &State{
200 NodeUID: "uid1",
201 }
202 testHost, fakeInformer := newTestHostWithFakeInformer(t, initState)
203
204 oldNode := getTestHostNode("new-node", "uid1")
205 newNode := getTestHostNode("new-node", "uid2")
206 fakeInformer.Update(oldNode, newNode)
207
208 hostState := testHost.topic.State()
209 state, ok := hostState.(*State)
210 require.True(t, ok)
211
212 assert.Equal(t, "uid1", state.NodeUID)
213 }
214
215 func TestOnDelete(t *testing.T) {
216 testHost, fakeInformer := newTestHostWithFakeInformer(t, &State{
217 NodeUID: "uid1",
218 })
219
220 node := getTestHostNode("new-node", "uid1")
221 fakeInformer.Delete(node)
222
223 hostState := testHost.topic.State()
224 state, ok := hostState.(*State)
225 require.True(t, ok)
226
227 assert.Equal(t, "uid1", state.NodeUID)
228 }
229
230 type testStruct struct {
231 state *State
232 }
233
234 func (t *testStruct) updateState(fn topic.UpdateFunc) error {
235 return fn(t.state)
236 }
237
238 func TestUpdateNodeUID(t *testing.T) {
239 tests := map[string]struct {
240 previousNodeUID string
241 want string
242 }{
243 "NotPreviouslySet": {
244 previousNodeUID: "",
245 want: "uid1",
246 },
247 "PreviouslySet": {
248 previousNodeUID: "uid",
249 want: "uid1",
250 },
251 }
252
253 for name, tc := range tests {
254 t.Run(name, func(t *testing.T) {
255 ts := testStruct{&State{NodeUID: tc.previousNodeUID}}
256 node := getTestHostNode("test", tc.want)
257
258 updateNodeUID(ts.updateState, node)
259
260 assert.Equal(t, tc.want, ts.state.NodeUID)
261 })
262 }
263 }
264
View as plain text