1 package terminalctl
2
3 import (
4 "context"
5 "os"
6 "sync"
7 "testing"
8 "time"
9
10 "github.com/fluxcd/pkg/ssa"
11 "github.com/google/uuid"
12 "gotest.tools/v3/assert"
13 "gotest.tools/v3/assert/cmp"
14 corev1 "k8s.io/api/core/v1"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 ctrl "sigs.k8s.io/controller-runtime"
17 "sigs.k8s.io/controller-runtime/pkg/client"
18 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
19
20 "edge-infra.dev/pkg/k8s/unstructured"
21 "edge-infra.dev/pkg/lib/fog"
22 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
23 "edge-infra.dev/pkg/sds/k8s/controllers/terminalctl/pkg/plugins"
24 "edge-infra.dev/test/f2"
25 "edge-infra.dev/test/f2/x/ktest"
26 )
27
28 var plugin *fakePlugin
29
30 var f f2.Framework
31
32 func TestMain(m *testing.M) {
33 ctrl.SetLogger(fog.New())
34
35 f = f2.New(context.Background(),
36 f2.WithExtensions(
37 ktest.New(
38 ktest.WithCtrlManager(createMgr),
39 ),
40 ),
41 ).
42 Setup(func(ctx f2.Context) (f2.Context, error) {
43 k, err := ktest.FromContext(ctx)
44 if err != nil {
45 return ctx, err
46 }
47
48
49 if !*k.Env.UseExistingCluster {
50 k.Timeout = 5 * time.Second
51 k.Tick = 10 * time.Millisecond
52 }
53
54 plugin = &fakePlugin{}
55 plugins.Register(plugin)
56
57 if err := registerController(k.Manager, &Config{}); err != nil {
58 return ctx, err
59 }
60
61 return ctx, nil
62 })
63
64 os.Exit(f.Run(m))
65 }
66
67 type fakePlugin struct {
68 plugins.TerminalRegistrationPlugin
69
70 lock sync.Mutex
71 invDisabled bool
72 }
73
74 func (fp *fakePlugin) Finalize(_ context.Context, _ client.Client, _ *v1ien.IENode) error {
75 return nil
76 }
77
78 func (fp *fakePlugin) Reconcile(ctx context.Context, mgr *ssa.ResourceManager, terminal *v1ien.IENode) ([]*unstructured.Unstructured, error) {
79 if terminal.Name == "a-terminal-inventory-creation" {
80 return fp.inventoryCreationReconcile(ctx, mgr, terminal)
81 }
82
83 return nil, nil
84 }
85
86 func (fp *fakePlugin) inventoryCreationReconcile(_ context.Context, _ *ssa.ResourceManager, _ *v1ien.IENode) ([]*unstructured.Unstructured, error) {
87 fp.lock.Lock()
88 defer fp.lock.Unlock()
89 if fp.invDisabled {
90 return nil, nil
91 }
92
93 return unstructured.ToUnstructuredArray(createInventoryCM())
94 }
95
96 func TestTerminalctl_Finalizers(t *testing.T) {
97 var (
98 terminal *v1ien.IENode
99 )
100
101 feature := f2.NewFeature("Finalizers").
102 Setup("Create terminal Resource", func(ctx f2.Context, t *testing.T) f2.Context {
103 k := ktest.FromContextT(ctx, t)
104
105 terminal = createTerminal("a-terminal-finalizers")
106 assert.NilError(t, k.Client.Create(ctx, terminal))
107
108 return ctx
109 }).
110 Test("Finalizer added", func(ctx f2.Context, t *testing.T) f2.Context {
111 k := ktest.FromContextT(ctx, t)
112
113 k.WaitOn(t, k.Check(terminal, terminalFinalizer))
114
115 return ctx
116 }).
117 Test("Terminal deletion", func(ctx f2.Context, t *testing.T) f2.Context {
118 k := ktest.FromContextT(ctx, t)
119
120 assert.NilError(t, k.Client.Delete(ctx, terminal))
121 te := v1ien.IENode{ObjectMeta: metav1.ObjectMeta{Name: terminal.Name}}
122 k.WaitOn(t, k.ObjDeleted(&te))
123
124 return ctx
125 }).
126 Feature()
127 f.Test(t, feature)
128 }
129
130 func TestTerminalctlInventory(t *testing.T) {
131 var (
132 t1 *v1ien.IENode
133 )
134
135 createInvFeature := f2.NewFeature("Terminalctl Inventory Creation").
136 Setup("Create terminal Resource", func(ctx f2.Context, t *testing.T) f2.Context {
137 k := ktest.FromContextT(ctx, t)
138
139 t1 = createTerminal("a-terminal-inventory-creation")
140 assert.NilError(t, k.Client.Create(ctx, t1))
141
142 return ctx
143 }).
144 Test("Plugin Executed", func(ctx f2.Context, t *testing.T) f2.Context {
145 k := ktest.FromContextT(ctx, t)
146
147 k.WaitOn(t, k.ObjExists(createInventoryCM()))
148
149 return ctx
150 }).
151 Test("Inventory added to terminal", func(ctx f2.Context, t *testing.T) f2.Context {
152 k := ktest.FromContextT(ctx, t)
153
154 te := &v1ien.IENode{ObjectMeta: metav1.ObjectMeta{Name: t1.Name}}
155 k.WaitOn(t, k.Check(te, func(o client.Object) cmp.Result {
156 te := o.(*v1ien.IENode)
157 if te.Status.Inventory == nil {
158 return cmp.ResultFailure("nil inventory")
159 }
160
161 entriesLength := len(te.Status.Inventory.Entries) == 1
162
163 entryID := te.Status.Inventory.Entries[0].ID == "default_inventory-cm__ConfigMap"
164
165 if entriesLength && entryID {
166 return cmp.ResultSuccess
167 }
168 return cmp.ResultFailure("inventory didnt match expected")
169 }))
170
171 return ctx
172 }).
173 Test("Old inventory removed", func(ctx f2.Context, t *testing.T) f2.Context {
174 k := ktest.FromContextT(ctx, t)
175
176
177 toggleInvPlugin(true)
178
179
180 updateTerminalDefinition(ctx, t, k.Client, "a-terminal-inventory-creation")
181
182 k.WaitOn(t, k.Check(t1, func(o client.Object) cmp.Result {
183 te := o.(*v1ien.IENode)
184 if len(te.Status.Inventory.Entries) == 0 {
185 return cmp.ResultSuccess
186 }
187 return cmp.ResultFailure("inventory wasn't cleaned up")
188 }))
189
190 k.WaitOn(t, k.ObjDeleted(createInventoryCM()))
191
192 return ctx
193 }).
194 Test("Inventory Recreated", func(ctx f2.Context, t *testing.T) f2.Context {
195 k := ktest.FromContextT(ctx, t)
196
197 toggleInvPlugin(false)
198
199
200 updateTerminalDefinition(ctx, t, k.Client, "a-terminal-inventory-creation")
201
202 k.WaitOn(t, k.Check(t1, func(o client.Object) cmp.Result {
203 te := o.(*v1ien.IENode)
204 if te.Status.Inventory == nil {
205 return cmp.ResultFailure("nil inventory")
206 }
207
208 if len(te.Status.Inventory.Entries) == 1 {
209
210 entryID := te.Status.Inventory.Entries[0].ID == "default_inventory-cm__ConfigMap"
211 if entryID {
212 return cmp.ResultSuccess
213 }
214 }
215 return cmp.ResultFailure("inventory didnt match expected")
216 }))
217
218 k.WaitOn(t, k.ObjExists(createInventoryCM()))
219
220 return ctx
221 }).
222 Test("Inventory removed on deletion", func(ctx f2.Context, t *testing.T) f2.Context {
223 k := ktest.FromContextT(ctx, t)
224
225 assert.NilError(t, k.Client.Delete(ctx, t1))
226 k.WaitOn(t, k.ObjsDeleted([]client.Object{t1, createInventoryCM()}))
227
228 return ctx
229 }).
230 Feature()
231
232 f.Test(t, createInvFeature)
233 }
234
235 func createTerminal(name string) *v1ien.IENode {
236 return &v1ien.IENode{
237 ObjectMeta: metav1.ObjectMeta{
238 Name: name,
239 },
240 TypeMeta: metav1.TypeMeta{
241 Kind: v1ien.IENodeGVK.Kind,
242 APIVersion: v1ien.IENodeGVK.GroupVersion().String(),
243 },
244 Spec: v1ien.IENodeSpec{
245 ClusterEdgeID: "clusterEdgeID-1",
246 Role: "worker",
247 Network: []v1ien.Network{
248 {
249 DHCP4: true,
250 DHCP6: false,
251 MacAddress: "ee:c5:39:b6:47:c3",
252 },
253 },
254 NetworkServices: v1ien.NetworkServices{
255 DNSServers: []string{"8.8.8.8", "8.8.4.4"},
256 KubeVip: "10.10.10.10",
257 },
258 PrimaryInterface: &v1ien.PrimaryInterface{
259 InterfaceID: "b212995a-1af4-4a2a-8479-6b095d788741",
260 MacAddresses: []string{"ee:c5:39:b6:47:c3"},
261 },
262 },
263 }
264 }
265
266 func createInventoryCM() *corev1.ConfigMap {
267 return &corev1.ConfigMap{
268 TypeMeta: metav1.TypeMeta{
269 Kind: "ConfigMap",
270 APIVersion: "v1",
271 },
272 ObjectMeta: metav1.ObjectMeta{
273 Name: "inventory-cm",
274 Namespace: "default",
275 },
276 Data: map[string]string{},
277 }
278 }
279
280 func terminalFinalizer(o client.Object) cmp.Result {
281 if controllerutil.ContainsFinalizer(o, v1ien.Finalizer) {
282 return cmp.ResultSuccess
283 }
284 return cmp.ResultFailure("finalizer not present")
285 }
286
287 func toggleInvPlugin(disabled bool) {
288 plugin.lock.Lock()
289 plugin.invDisabled = disabled
290 plugin.lock.Unlock()
291 }
292
293
294 func updateTerminalDefinition(ctx context.Context, t *testing.T, c client.Client, name string) {
295 t.Helper()
296 te := createTerminal(name)
297 assert.NilError(t, c.Get(ctx, client.ObjectKeyFromObject(te), te))
298 te.Spec.ClusterEdgeID = uuid.NewString()
299 assert.NilError(t, c.Update(ctx, te))
300 }
301
View as plain text