1 package integration
2
3 import (
4 _ "embed"
5 "fmt"
6 "testing"
7
8 "github.com/davecgh/go-spew/spew"
9 "gotest.tools/v3/assert"
10 "gotest.tools/v3/assert/cmp"
11 "gotest.tools/v3/poll"
12 appsv1 "k8s.io/api/apps/v1"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/types"
15 "sigs.k8s.io/controller-runtime/pkg/client"
16 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
17
18 whv1 "edge-infra.dev/pkg/f8n/warehouse/k8s/apis/v1alpha2"
19 "edge-infra.dev/pkg/f8n/warehouse/oci/layer"
20 "edge-infra.dev/pkg/k8s/meta/status"
21 "edge-infra.dev/pkg/k8s/runtime/conditions"
22 "edge-infra.dev/pkg/k8s/testing/kmp"
23 "edge-infra.dev/pkg/k8s/unstructured"
24 "edge-infra.dev/test/f2"
25 "edge-infra.dev/test/f2/x/ktest"
26 )
27
28 func TestUnpackedPalletController_Inventory(t *testing.T) {
29 var (
30 id string
31 v1 testPallet
32 v2 testPallet
33 u = &whv1.UnpackedPallet{}
34 )
35
36 inv := f2.NewFeature("Inventories reconciled objects").
37 Setup("Create and push test packages", func(ctx f2.Context, t *testing.T) f2.Context {
38 id = fmt.Sprintf("inv-%s", ctx.RunID)
39 v1 = createAndPushPallet(ctx, t, id, "v1", createLayer(t, layer.Runtime, minimalv1))
40 v2 = createAndPushPallet(ctx, t, id, "v2", createLayer(t, layer.Runtime, minimalv2))
41 return ctx
42 }).
43 Test("Creates inventory", func(ctx f2.Context, t *testing.T) f2.Context {
44 var (
45 u = unpackedPallet(ctx, t, v1.name, v1.pallet, whv1.WithPrune())
46 k = ktest.FromContextT(ctx, t)
47 )
48
49 assert.NilError(t, k.Client.Create(ctx, u))
50 k.WaitOn(t, k.Check(u, kmp.HasInventory(v1.inv)))
51 k.WaitOn(t, k.InventoryExists(v1.inv))
52
53 return ctx
54 }).
55 Test("Handles inventory changes", func(ctx f2.Context, t *testing.T) f2.Context {
56 k := ktest.FromContextT(ctx, t)
57
58 assert.NilError(t, k.Client.Get(
59 ctx, types.NamespacedName{Name: v1.name, Namespace: k.Namespace}, u,
60 ))
61
62 patch := client.MergeFrom(u.DeepCopy())
63 u.Spec.Digest = v2.digest
64 assert.NilError(t, k.Client.Patch(ctx, u, patch))
65
66 k.WaitOn(t, k.Check(u, kmp.HasInventory(v2.inv)))
67 k.WaitOn(t, k.InventoryExists(v2.inv))
68 k.WaitOn(t, k.InventoryPruned(v2.inv, v1.inv))
69 assert.Equal(t, u.Status.ObservedGeneration, int64(2),
70 "inventory was updated without updating status.observedGeneration")
71
72 return ctx
73 }).
74 Test("With pruning disabled", func(ctx f2.Context, t *testing.T) f2.Context {
75 k := ktest.FromContextT(ctx, t)
76
77
78
79 assert.NilError(t, k.Client.Get(
80 ctx, types.NamespacedName{Name: v1.name, Namespace: k.Namespace}, u,
81 ))
82
83 patch := client.MergeFrom(u.DeepCopy())
84 u.Spec.Prune = false
85 u.Spec.Digest = v1.digest
86 assert.NilError(t, k.Client.Patch(ctx, u, patch))
87
88
89
90 k.WaitOn(t, k.Check(u, kmp.HasInventory(v1.inv)))
91 k.WaitOn(t, k.InventoryExists(v1.inv))
92
93
94 k.WaitOn(t, k.Check(u, unpackedPalletReady))
95
96 diff, err := v2.inv.Diff(v1.inv)
97 assert.NilError(t, err)
98 k.WaitOn(t, k.ObjsExist(unstructured.ToClientObjArray(diff...)))
99
100 return ctx
101 }).
102 Feature()
103
104 f.Test(t, inv)
105 }
106
107 func TestUnpackedPalletController_Status(t *testing.T) {
108 var (
109 id string
110 v1 testPallet
111 u *whv1.UnpackedPallet
112 )
113
114 status := f2.NewFeature("Reconciles its Status").
115 Setup("Create and push test packages", func(ctx f2.Context, t *testing.T) f2.Context {
116 id = fmt.Sprintf("status-%s", ctx.RunID)
117 v1 = createAndPushPallet(ctx, t, id, "v1",
118 createLayer(t, layer.Runtime, minimalv1),
119 fakeInfraLayer(t),
120 fakeL5dLayer(t),
121 )
122 u = unpackedPallet(ctx, t, v1.name, v1.pallet,
123 whv1.WithInfra(),
124 whv1.WithCapabilities(string(l5dCap)),
125 whv1.WithPrune(),
126 )
127
128 k := ktest.FromContextT(ctx, t)
129
130 assert.NilError(t, k.Client.Create(ctx, u))
131 k.WaitOn(t, k.Check(u, unpackedPalletReady))
132
133 return ctx
134 }).
135 Test("Package digest is persisted on status", func(ctx f2.Context, t *testing.T) f2.Context {
136 assert.Equal(t, v1.digest, u.Status.LastAttempted)
137 assert.Equal(t, int64(1), u.Status.ObservedGeneration)
138 return ctx
139 }).
140 Test("Updates Conditions Based on Layers", func(ctx f2.Context, t *testing.T) f2.Context {
141 var (
142 k = ktest.FromContextT(ctx, t)
143
144 healthy = conditions.TrueCondition(whv1.HealthyCondition, status.SucceededReason, "Objects reconciled")
145 fetched = conditions.TrueCondition(whv1.FetchedArtifactCondition, whv1.FetchSucceededReason, "Successfully retrieved artifact")
146 runtimeReady = conditions.TrueCondition(whv1.RuntimeReadyCondition, status.SucceededReason, "Objects reconciled")
147 runCapsReady = conditions.TrueCondition(whv1.RuntimeCapabilitiesReadyCondition, status.SucceededReason, "Objects reconciled")
148 infraReady = conditions.TrueCondition(whv1.InfraReadyCondition, status.SucceededReason, "Objects reconciled")
149 ready = conditions.TrueCondition(status.ReadyCondition, status.SucceededReason, "Objects reconciled")
150 reconciling = conditions.TrueCondition(status.ReconcilingCondition, status.ProgressingWithRetryReason, "")
151 notReady = conditions.FalseCondition(status.ReadyCondition, whv1.FetchFailedReason, "404")
152 fetchFail = conditions.FalseCondition(whv1.FetchedArtifactCondition, whv1.FetchFailedReason, "404")
153 )
154
155
156
157 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
158 *ready,
159 *fetched,
160 *healthy,
161 *infraReady,
162 *runCapsReady,
163 *runtimeReady,
164 })))
165
166
167 patch := client.MergeFrom(u.DeepCopy())
168 u.Spec.UnpackOptions.Infra = false
169 assert.NilError(t, k.Client.Patch(ctx, u, patch))
170 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
171 *ready,
172 *fetched,
173 *healthy,
174 *runCapsReady,
175 *runtimeReady,
176 })))
177
178
179 patch = client.MergeFrom(u.DeepCopy())
180 u.Spec.UnpackOptions.Capabilities = []string{}
181 assert.NilError(t, k.Client.Patch(ctx, u, patch))
182 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
183 *ready,
184 *fetched,
185 *healthy,
186 *runtimeReady,
187 })))
188
189
190 patch = client.MergeFrom(u.DeepCopy())
191 u.Spec.UnpackOptions.Runtime = false
192 u.Spec.UnpackOptions.Infra = true
193 assert.NilError(t, k.Client.Patch(ctx, u, patch))
194 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
195 *ready,
196 *fetched,
197 *healthy,
198 *infraReady,
199 })))
200
201
202 oldTag := u.Spec.Tag
203 oldDigest := u.Spec.Digest
204 patch = client.MergeFrom(u.DeepCopy())
205 u.Spec.Digest = ""
206 u.Spec.Tag = "not-found"
207 assert.NilError(t, k.Client.Patch(ctx, u, patch))
208 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
209 *reconciling,
210 *notReady,
211 *fetchFail,
212 *healthy,
213 *infraReady,
214 })))
215
216
217 patch = client.MergeFrom(u.DeepCopy())
218 u.Spec.Digest = oldDigest
219 u.Spec.Tag = oldTag
220 assert.NilError(t, k.Client.Patch(ctx, u, patch))
221 k.WaitOn(t, k.Check(u, kmp.HasEqualConditions([]metav1.Condition{
222 *ready,
223 *fetched,
224 *healthy,
225 *infraReady,
226 })))
227
228 return ctx
229 }).
230 Feature()
231
232 f.Test(t, status)
233 }
234
235 func TestUnpackedPalletController_Finalizer(t *testing.T) {
236 var (
237 p testPallet
238 u = &whv1.UnpackedPallet{}
239 )
240
241 finalizer := f2.NewFeature("Finalizers").
242 Setup("Setup UnpackedPallet", func(ctx f2.Context, t *testing.T) f2.Context {
243 k := ktest.FromContextT(ctx, t)
244
245 p = createAndPushPallet(ctx, t, fmt.Sprintf("fin-%s", ctx.RunID), "v1",
246 createLayer(t, layer.Runtime, minimalv1))
247 u = unpackedPallet(ctx, t, p.name, p.pallet, whv1.WithPrune())
248
249 assert.NilError(t, k.Client.Create(ctx, u))
250
251 k.WaitOn(t, k.Check(u, kmp.HasInventory(p.inv)))
252 k.WaitOn(t, k.InventoryExists(p.inv))
253
254 return ctx
255 }).
256 Test("Finalizer is added", func(ctx f2.Context, t *testing.T) f2.Context {
257 if !controllerutil.ContainsFinalizer(u, whv1.WarehouseFinalizer) {
258 t.Error("finalizer not added to unpackedpallet", spew.Sprintln(u))
259 }
260
261 return ctx
262 }).
263 Test("Garbage is collected on delete", func(ctx f2.Context, t *testing.T) f2.Context {
264 k := ktest.FromContextT(ctx, t)
265
266 assert.NilError(t, k.Client.Delete(ctx, u))
267
268 k.WaitOn(t, k.ObjDeleted(u))
269
270 objs, err := p.inv.ListObjects()
271 assert.NilError(t, err)
272 k.WaitOn(t, k.ObjsDeleted(unstructured.ToClientObjArray(objs...)))
273
274 return ctx
275 }).
276 Test("Honors spec.prune", func(ctx f2.Context, t *testing.T) f2.Context {
277 var (
278 k = ktest.FromContextT(ctx, t)
279 id = fmt.Sprintf("fin-prune-%s", ctx.RunID)
280 v1 = createAndPushPallet(ctx, t, id, "v1", createLayer(t, layer.Runtime, minimalv1))
281 u = unpackedPallet(ctx, t, v1.name, v1.pallet)
282 )
283
284 assert.NilError(t, k.Client.Create(ctx, u))
285 k.WaitOn(t, k.Check(u, kmp.HasInventory(v1.inv)))
286 k.WaitOn(t, k.InventoryExists(v1.inv))
287 assert.NilError(t, k.Client.Delete(ctx, u))
288
289 objs, err := p.inv.ListObjects()
290 assert.NilError(t, err)
291 k.WaitOn(t, k.ObjsExist(unstructured.ToClientObjArray(objs...)))
292
293 return ctx
294 }).Feature()
295
296 f.Test(t, finalizer)
297 }
298
299 func TestUnpackedPalletController_Suspend(t *testing.T) {
300 var (
301 id string
302 v1 testPallet
303 v2 testPallet
304 u = &whv1.UnpackedPallet{}
305 )
306
307 sus := f2.NewFeature("UnpackedPallet.spec.suspend").
308 Setup("Setup UnpackedPallet", func(ctx f2.Context, t *testing.T) f2.Context {
309 k := ktest.FromContextT(ctx, t)
310
311 id = fmt.Sprintf("suspend-%s", ctx.RunID)
312 v1 = createAndPushPallet(ctx, t, id, "v1", createLayer(t, layer.Runtime, minimalv1))
313 v2 = createAndPushPallet(ctx, t, id, "v2", createLayer(t, layer.Runtime, minimalv2))
314 u = unpackedPallet(ctx, t, v1.name, v1.pallet)
315
316 assert.NilError(t, k.Client.Create(ctx, u))
317 k.WaitOn(t, k.Check(u, kmp.HasInventory(v1.inv)))
318 k.WaitOn(t, k.InventoryExists(v1.inv))
319
320 return ctx
321 }).
322 Test("Becomes ready after setting spec.suspend", func(ctx f2.Context, t *testing.T) f2.Context {
323 k := ktest.FromContextT(ctx, t)
324
325 assert.NilError(t, k.Client.Get(
326 ctx, types.NamespacedName{Name: v1.name}, u,
327 ))
328
329 patch := client.MergeFrom(u.DeepCopy())
330 u.Spec.Suspend = true
331 assert.NilError(t, k.Client.Patch(ctx, u, patch))
332
333 k.WaitOn(t, k.Check(u, unpackedPalletReady))
334
335 return ctx
336 }).
337 Test("New digests are not reconciled", func(ctx f2.Context, t *testing.T) f2.Context {
338 k := ktest.FromContextT(ctx, t)
339
340 assert.NilError(t, k.Client.Get(
341 ctx, types.NamespacedName{Name: v1.name}, u,
342 ))
343
344 patch := client.MergeFrom(u.DeepCopy())
345 u.Spec.Digest = v2.digest
346 assert.NilError(t, k.Client.Patch(ctx, u, patch))
347
348 k.WaitOn(t, k.Check(u, func(o client.Object) cmp.Result {
349 u := o.(*whv1.UnpackedPallet)
350 if u.Status.LastApplied == v2.digest && u.Status.LastAttempted == v2.digest {
351 return cmp.ResultFailure("new digest was reconciled for suspended pallet")
352 }
353 return cmp.ResultSuccess
354 }), poll.WithTimeout(k.Timeout/2))
355
356 assert.Assert(t, kmp.HasInventory(v1.inv)(u).Success(),
357 "inventory should not have changed for suspended pallet")
358
359 objs, err := v1.inv.ListObjects()
360 assert.NilError(t, err)
361 for _, o := range objs {
362 assert.NilError(t, k.Client.Delete(ctx, o))
363 }
364
365 k.WaitOn(t, k.ObjsDeleted(unstructured.ToClientObjArray(objs...)))
366
367 return ctx
368 }).Feature()
369
370 f.Test(t, sus)
371 }
372
373 func TestUnpackedPalletController_Dependencies(t *testing.T) {
374 var (
375 infraOnly = "infra-only"
376 rtOnly = "rt-only"
377 rtInfra = "rt-infra"
378 rtCap = "rt-cap"
379 rtInfraCap = "rt-infra-cap"
380 rootObj *whv1.UnpackedPallet
381 objs = make(map[string]*whv1.UnpackedPallet)
382 pkgs = make(map[string]testPallet)
383 )
384
385 deps := f2.NewFeature("UnpackedPallet.spec.dependsOn").
386 Setup("Test Fixtures", func(ctx f2.Context, t *testing.T) f2.Context {
387 infraOnly = id(ctx, infraOnly)
388 rtOnly = id(ctx, rtOnly)
389 rtInfra = id(ctx, rtInfra)
390 rtCap = id(ctx, rtCap)
391 rtInfraCap = id(ctx, rtInfraCap)
392 root := id(ctx, "root")
393
394 pkgs[infraOnly] = createAndPushPallet(ctx, t, infraOnly, "v1", fakeInfraLayer(t))
395 objs[infraOnly] = unpackedPallet(ctx, t, infraOnly, pkgs[infraOnly].pallet,
396 whv1.WithUnpackOpts(whv1.UnpackOptions{Infra: true}))
397
398 pkgs[rtOnly] = createAndPushPallet(ctx, t, rtOnly, "v1",
399 createLayer(t, layer.Runtime, minimalv1))
400 objs[rtOnly] = unpackedPallet(ctx, t, rtOnly, pkgs[rtOnly].pallet)
401
402 pkgs[rtInfra] = createAndPushPallet(ctx, t, rtInfra, "v1",
403 createLayer(t, layer.Runtime, minimalv1),
404 fakeInfraLayer(t),
405 )
406 objs[rtInfra] = unpackedPallet(ctx, t, rtInfra, pkgs[rtInfra].pallet,
407 whv1.WithInfra(),
408 whv1.WithDeps(rtOnly),
409 )
410
411 pkgs[rtCap] = createAndPushPallet(ctx, t, rtCap, "v1",
412 createLayer(t, layer.Runtime, minimalv1),
413 fakeL5dLayer(t),
414 )
415 objs[rtCap] = unpackedPallet(ctx, t, rtCap, pkgs[rtCap].pallet,
416 whv1.WithCapabilities(string(l5dCap)),
417 whv1.WithDeps(rtOnly, rtInfra),
418 )
419
420 pkgs[rtInfraCap] = createAndPushPallet(ctx, t, rtInfraCap, "v1",
421 createLayer(t, layer.Runtime, minimalv1),
422 fakeInfraLayer(t),
423 fakeL5dLayer(t),
424 )
425 objs[rtInfraCap] = unpackedPallet(ctx, t, rtInfraCap, pkgs[rtInfraCap].pallet,
426 whv1.WithInfra(),
427 whv1.WithCapabilities(string(l5dCap)),
428 whv1.WithDeps(infraOnly, rtInfra),
429 )
430
431 rootPkg := createAndPushPallet(ctx, t, root, "v1",
432 createLayer(t, layer.Runtime, minimalv1))
433 rootObj = unpackedPallet(ctx, t, root, rootPkg.pallet,
434 whv1.WithDeps(infraOnly, rtCap, rtOnly, rtInfra, rtInfraCap))
435
436 return ctx
437 }).Test("Dependency Analysis", func(ctx f2.Context, t *testing.T) f2.Context {
438 k := ktest.FromContextT(ctx, t)
439
440
441
442 assert.NilError(t, k.Client.Create(ctx, rootObj))
443
444 for _, obj := range objs {
445 assert.NilError(t, k.Client.Create(ctx, obj))
446 }
447
448
449
450 k.WaitOn(t, k.Check(rootObj, unpackedPalletReady), poll.WithTimeout(k.Timeout*2))
451
452 for _, obj := range objs {
453 k.WaitOn(t, k.Check(obj, unpackedPalletReady))
454 }
455
456 return ctx
457 }).Feature()
458
459 f.Test(t, deps)
460 }
461
462 func TestUnpackedPalletController_ObjectPatching(t *testing.T) {
463 var u = &whv1.UnpackedPallet{}
464
465 finalizer := f2.NewFeature("Apply removes deleted fields").
466 Setup("Set up UnpackedPallet", func(ctx f2.Context, t *testing.T) f2.Context {
467 k := ktest.FromContextT(ctx, t)
468 v1 := createAndPushPallet(ctx, t, fmt.Sprintf("patch-%s", ctx.RunID), "0.1.0",
469 createLayer(t, layer.Runtime, sapInitial))
470 u = unpackedPallet(ctx, t, v1.name, v1.pallet)
471 assert.NilError(t, k.Client.Create(ctx, u))
472
473
474 ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: "nodeagent", Namespace: k.Namespace}}
475 k.WaitOn(t, k.ObjExists(ds))
476 envFrom := ds.Spec.Template.Spec.Containers[0].EnvFrom
477 if envFrom[0].SecretRef.Name != "ldkey" {
478 t.FailNow()
479 }
480
481 return ctx
482 }).
483 Test("Empty fields are removed when object is patched", func(ctx f2.Context, t *testing.T) f2.Context {
484 k := ktest.FromContextT(ctx, t)
485
486
487 v2 := createAndPushPallet(ctx, t, fmt.Sprintf("patch-%s", ctx.RunID), "0.2.0",
488 createLayer(t, layer.Runtime, sapFinal))
489 assert.NilError(t, k.Client.Get(
490 ctx, types.NamespacedName{Name: u.Name, Namespace: u.Namespace}, u,
491 ))
492 patch := client.MergeFrom(u.DeepCopy())
493 u.Spec.Digest = v2.digest
494 assert.NilError(t, k.Client.Patch(ctx, u, patch))
495 k.WaitOn(t, k.Check(u, func(o client.Object) cmp.Result {
496 u := o.(*whv1.UnpackedPallet)
497 a := u.Spec.Artifact
498 if a.Digest != v2.digest {
499 return cmp.ResultFailure(fmt.Sprintf("digest should be %s. actual: %s", v2.digest, a.Digest))
500 }
501 return cmp.ResultSuccess
502 }))
503
504 ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: "nodeagent", Namespace: k.Namespace}}
505 k.WaitOn(t, k.Check(ds, func(o client.Object) cmp.Result {
506 ds := o.(*appsv1.DaemonSet)
507 envFrom := ds.Spec.Template.Spec.Containers[0].EnvFrom
508 if envFrom != nil {
509 return cmp.ResultFailure(fmt.Sprintf("envfrom should be removed. actual: %+v", envFrom))
510 }
511 return cmp.ResultSuccess
512 }))
513
514 return ctx
515 }).Feature()
516
517 f.Test(t, finalizer)
518 }
519
View as plain text