1 package syncedobject
2
3 import (
4 "context"
5 crypto "crypto/rand"
6 "encoding/base64"
7 "encoding/json"
8 "fmt"
9 "math/rand"
10 "os"
11 "testing"
12 "time"
13
14 "cloud.google.com/go/pubsub"
15 "github.com/google/uuid"
16 "github.com/prometheus/client_golang/prometheus"
17 "github.com/stretchr/testify/require"
18 "google.golang.org/api/option"
19 "gopkg.in/yaml.v2"
20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 "k8s.io/apimachinery/pkg/types"
22 "sigs.k8s.io/controller-runtime/pkg/client"
23 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
24
25 soapi "edge-infra.dev/pkg/edge/apis/syncedobject/apis/v1alpha1"
26 "edge-infra.dev/pkg/edge/chariot"
27 chariotClientApi "edge-infra.dev/pkg/edge/chariot/client"
28 "edge-infra.dev/pkg/edge/controllers/syncedobject"
29 "edge-infra.dev/pkg/k8s/runtime/conditions"
30 "edge-infra.dev/pkg/lib/promassert"
31 "edge-infra.dev/test/f2"
32 "edge-infra.dev/test/f2/x/ktest"
33 "edge-infra.dev/test/f2/x/pstest"
34 )
35
36 const DefaultSecretValue = "mock secret value"
37
38 var testConfig = &syncedobject.Config{
39 PublishTopic: "chariot-rides",
40 GCPProjectID: "test-foreman0",
41 MetricsAddr: "127.0.0.1:8080",
42 }
43
44 func init() {
45 addr := fmt.Sprintf("http://%s/metrics", testConfig.MetricsAddr)
46 promassert.ScrapePrometheusMetrics = promassert.ScrapeHTTP(addr)
47 }
48
49 var f f2.Framework
50
51 const timeout = 10 * time.Second
52 const tick = 50 * time.Millisecond
53
54 func TestMain(m *testing.M) {
55 var ps = pstest.New(
56 pstest.WithProjectID(testConfig.GCPProjectID),
57 )
58 var k = ktest.New(ktest.WithCtrlManager(testConfig.CreateMgr), ktest.WithKonfigKonnector())
59 f = f2.New(context.Background(), f2.WithExtensions(k, ps)).Component("syncedobjectctl")
60 os.Exit(f.Run(m))
61 }
62
63 func TestSyncedObjectReconciliation(t *testing.T) {
64 var feat = f2.NewFeature(t.Name()).
65 Setup("topic", setupTopic).
66 Test("reconciles", func(ctx f2.Context, t *testing.T) f2.Context {
67 var so = randomSyncedObject()
68 so.Spec.Directory = nil
69 createSyncedObject(ctx, t, so)
70 return ctx
71 }).
72 Test("reconciles with dir", func(ctx f2.Context, t *testing.T) f2.Context {
73 var so = randomSyncedObject()
74 var dir = "myDir"
75 so.Spec.Directory = &dir
76 createSyncedObject(ctx, t, so)
77 return ctx
78 }).Feature()
79
80 f.Test(t, feat)
81 }
82
83 func TestSyncedObjectInvalidSpecStalled(t *testing.T) {
84 var feat = f2.NewFeature(t.Name()).
85 Setup("topic", setupTopic).
86 Test("reconciles to stalled condition", func(ctx f2.Context, t *testing.T) f2.Context {
87 var so = randomSyncedObject()
88 so.Spec.Object = ""
89 require.NoError(t, ktest.FromContextT(ctx, t).Client.Create(ctx, &so))
90
91 var kso soapi.SyncedObject
92 require.Eventually(t, func() bool {
93 err := ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
94 Name: so.Name,
95 Namespace: so.Namespace,
96 }, &kso)
97 return err == nil && conditions.IsStalled(&kso)
98 }, timeout, tick)
99 return ctx
100 }).Feature()
101
102 f.Test(t, feat)
103 }
104
105 func TestSyncedObjectExpireAtDeletesExpiredObjects(t *testing.T) {
106 var so = randomSyncedObject()
107 so.Spec.ExpireAt = &metav1.Time{
108 Time: time.Now().Add(3 * time.Second),
109 }
110
111 var reqs = make(chan chariot.Request, 2)
112
113
114 var feat = f2.NewFeature(t.Name()).
115 Setup("topic", setupTopic).
116 Setup("receive", receiveMessages(reqs)).
117 Test("reconcile", func(ctx f2.Context, t *testing.T) f2.Context {
118 createSyncedObjectAndValidateChariotMessage(ctx, t, reqs, so)
119 return ctx
120 }).
121 Test("is deleted when expired", func(ctx f2.Context, t *testing.T) f2.Context {
122 require.Eventually(t, func() bool {
123 var kso soapi.SyncedObject
124 err := ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
125 Name: so.Name,
126 Namespace: so.Namespace,
127 }, &kso)
128 if time.Now().Before(so.Spec.ExpireAt.Time) {
129
130 require.NoError(t, err)
131 }
132 return err != nil && client.IgnoreNotFound(err) == nil
133 }, timeout, tick, "SyncedObject never got deleted")
134 return ctx
135 }).
136 Test("sent delete pubsub message", func(ctx f2.Context, t *testing.T) f2.Context {
137 select {
138 case <-time.After(timeout):
139 t.Fatal("never got delete chariot request for expired synced object")
140 case req := <-reqs:
141 validateChariotMessage(t, chariotClientApi.Delete, so, req)
142 }
143 return ctx
144 }).Feature()
145
146 f.Test(t, feat)
147 }
148
149 func TestSyncedObjectAbandonAnnotation(t *testing.T) {
150 var so = randomSyncedObject()
151 so.ObjectMeta.Annotations = map[string]string{
152 soapi.AnnotationDeletionPolicy: soapi.AnnotationDeletionPolicyAbandon,
153 }
154 so.Spec.ExpireAt = &metav1.Time{
155 Time: time.Now().Add(time.Second),
156 }
157
158 var reqs = make(chan chariot.Request, 2)
159
160
161 var feat = f2.NewFeature(t.Name()).
162 Setup("topic", setupTopic).
163 Setup("receive", receiveMessages(reqs)).
164 Test("reconcile", func(ctx f2.Context, t *testing.T) f2.Context {
165 createSyncedObjectAndValidateChariotMessage(ctx, t, reqs, so)
166 return ctx
167 }).
168 Test("is deleted when expired", func(ctx f2.Context, t *testing.T) f2.Context {
169 require.Eventually(t, func() bool {
170 var kso soapi.SyncedObject
171 err := ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
172 Name: so.Name,
173 Namespace: so.Namespace,
174 }, &kso)
175 if time.Now().Before(so.Spec.ExpireAt.Time) {
176
177 require.NoError(t, err)
178 }
179 return err != nil && client.IgnoreNotFound(err) == nil
180 }, timeout, tick, "SyncedObject never got deleted")
181 return ctx
182 }).
183 Test("delete pubsub message is not sent", func(ctx f2.Context, t *testing.T) f2.Context {
184 select {
185 case <-time.After(2 * time.Second):
186 case req := <-reqs:
187 t.Fatalf("should not get delete chariot request for expired synced object with abandon annotation: %v", req)
188 }
189 return ctx
190 }).Feature()
191
192 f.Test(t, feat)
193 }
194
195 func TestSyncedObjectAbandonAnnotationForOutdated(t *testing.T) {
196 var so = randomSyncedObject()
197 so.ObjectMeta.Annotations = map[string]string{
198 soapi.AnnotationDeletionPolicy: soapi.AnnotationDeletionPolicyAbandon,
199 }
200
201 var reqs = make(chan chariot.Request, 2)
202
203 var feat = f2.NewFeature(t.Name()).
204 Setup("topic", setupTopic).
205 Setup("receive", receiveMessages(reqs)).
206 Test("reconcile", func(ctx f2.Context, t *testing.T) f2.Context {
207 createSyncedObjectAndValidateChariotMessage(ctx, t, reqs, so)
208 return ctx
209 }).
210 Test("update the cluster to change the storage location", func(ctx f2.Context, t *testing.T) f2.Context {
211 require.NoError(t, so.SetStatusDetails())
212 so.Spec.Cluster = uuid.New().String()
213
214 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
215 require.NoError(t, err)
216 require.True(t, storageLocationOutdated)
217
218 updateSyncedObject(ctx, t, so)
219 return ctx
220 }).
221 Test("delete pubsub message is not sent", func(ctx f2.Context, t *testing.T) f2.Context {
222 select {
223 case <-time.After(timeout):
224 t.Fatal("timed out waiting for create message")
225 case req := <-reqs:
226 validateChariotMessage(t, chariotClientApi.Create, so, req)
227 }
228
229 select {
230 case <-time.After(2 * time.Second):
231 case req := <-reqs:
232 t.Fatalf("should not get delete chariot request for updated synced object with abandon annotation: %v", req)
233 }
234 return ctx
235 }).Feature()
236
237 f.Test(t, feat)
238 }
239
240 func TestSyncedObjectMetricsWereSet(t *testing.T) {
241 var so = randomSyncedObject()
242
243
244 const met = "edge_soctl_reconcile_condition_status"
245 var labels = prometheus.Labels{"status": "True"}
246 var initialMetricValue = promassert.Gauge(met).With(labels).TryFold()
247
248 var feat = f2.NewFeature(t.Name()).
249 Setup("topic", setupTopic).
250 Test("metrics test", func(ctx f2.Context, t *testing.T) f2.Context {
251 createSyncedObject(ctx, t, so)
252 return ctx
253 }).
254 Test("metrics test", func(ctx f2.Context, t *testing.T) f2.Context {
255
256 require.Eventually(t, func() bool {
257 finalMetricValue := promassert.Gauge(met).With(labels).TryFold()
258 return 1 == finalMetricValue-initialMetricValue
259 }, timeout, time.Second, "Metric was never set")
260 return ctx
261 }).Feature()
262
263 f.Test(t, feat)
264 }
265
266 func TestSyncedObjectSpecUpdate(t *testing.T) {
267 var (
268 origBanner = "ret-edge-original-banner"
269 origCluster = uuid.New().String()
270 origObj = randomBase64EncodedYamlObject()
271 origChariotID, err = syncedobject.CreateChariotID(origObj)
272 origStorageLocation = chariot.FmtStorageLocation(origBanner, origCluster, "", origChariotID)
273 origNN = types.NamespacedName{
274 Name: "guid-abc-12345",
275 Namespace: "default",
276 }
277 )
278 require.NoError(t, err)
279
280 var origSo = soapi.SyncedObject{
281 ObjectMeta: metav1.ObjectMeta{
282 Name: origNN.Name,
283 Namespace: origNN.Namespace,
284 },
285 Spec: soapi.SyncedObjectSpec{
286 Banner: origBanner,
287 Cluster: origCluster,
288 Object: origObj,
289 Directory: nil,
290 ExpireAt: nil,
291 },
292 Status: soapi.SyncedObjectStatus{
293 StoredObject: origObj,
294 StorageLocation: origStorageLocation,
295 Banner: origBanner,
296 Cluster: origCluster,
297 Directory: nil,
298 },
299 }
300
301 var reqs = make(chan chariot.Request, 2)
302
303 var feat = f2.NewFeature(t.Name()).
304 Setup("topic", setupTopic).
305 Setup("receive", receiveMessages(reqs)).
306 Test("reconcile", func(ctx f2.Context, t *testing.T) f2.Context {
307 var so = origSo
308
309 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
310 require.NoError(t, err)
311 require.False(t, storageLocationOutdated)
312
313 so.Status = soapi.SyncedObjectStatus{}
314 createSyncedObjectAndValidateChariotMessage(ctx, t, reqs, so)
315 return ctx
316 }).
317 Test("validate banner changed", func(ctx f2.Context, t *testing.T) f2.Context {
318 var so = origSo
319 so.Spec.Banner = "ret-edge-updated-banner"
320
321 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
322 require.NoError(t, err)
323 require.True(t, storageLocationOutdated)
324
325 updateAndValidateChariotMessages(ctx, t, reqs, so)
326 updateAndValidateChariotMessages(ctx, t, reqs, origSo)
327 return ctx
328 }).
329 Test("validate cluster changed", func(ctx f2.Context, t *testing.T) f2.Context {
330 var so = origSo
331 so.Spec.Cluster = uuid.New().String()
332
333 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
334 require.NoError(t, err)
335 require.True(t, storageLocationOutdated)
336
337 updateAndValidateChariotMessages(ctx, t, reqs, so)
338 updateAndValidateChariotMessages(ctx, t, reqs, origSo)
339 return ctx
340 }).
341 Test("validate dir changed", func(ctx f2.Context, t *testing.T) f2.Context {
342 var so = origSo
343 var updatedDir = "McRibIsBack"
344 so.Spec.Directory = &updatedDir
345
346 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
347 require.NoError(t, err)
348 require.True(t, storageLocationOutdated)
349
350 updateAndValidateChariotMessages(ctx, t, reqs, so)
351 updateAndValidateChariotMessages(ctx, t, reqs, origSo)
352 return ctx
353 }).
354 Test("validate object changed", func(ctx f2.Context, t *testing.T) f2.Context {
355 var so = origSo
356 so.Spec.Object = randomBase64EncodedYamlObject()
357
358 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
359 require.NoError(t, err)
360 require.True(t, storageLocationOutdated)
361
362 updateAndValidateChariotMessages(ctx, t, reqs, so)
363 updateAndValidateChariotMessages(ctx, t, reqs, origSo)
364 return ctx
365 }).
366 Test("validate object changed without affecting storage location", func(ctx f2.Context, t *testing.T) f2.Context {
367
368
369 var so = origSo
370 var fr, err = DecodeFakeResourceBase64(so.Spec.Object)
371 require.NoError(t, err)
372 fr.Spec = FakeSpec{
373 Foo: "some new foo",
374 Bar: "bar changed too",
375 Baz: "will this cause a deletion snafu",
376 }
377 so.Spec.Object = fr.EncodeBase64()
378
379 storageLocationOutdated, err := syncedobject.IsStorageLocationOutdated(&so)
380 require.NoError(t, err)
381 require.False(t, storageLocationOutdated)
382
383 updateSyncedObject(ctx, t, so)
384
385 select {
386 case <-time.After(timeout):
387 t.Fatal("timed out waiting for create message")
388 case req := <-reqs:
389 validateChariotMessage(t, chariotClientApi.Create, so, req)
390 }
391 return ctx
392 }).Feature()
393
394 f.Test(t, feat)
395 }
396
397 func setupTopic(ctx f2.Context, t *testing.T) f2.Context {
398 var conn = pstest.FromContextT(ctx, t).Conn
399
400 client, err := pubsub.NewClient(context.Background(), testConfig.GCPProjectID, option.WithGRPCConn(conn))
401 require.NoError(t, err)
402
403 testConfig.PubsubTopic = client.Topic(testConfig.PublishTopic)
404
405 exists, err := testConfig.PubsubTopic.Exists(ctx)
406 require.NoError(t, err)
407 if !exists {
408 testConfig.PubsubTopic, err = client.CreateTopic(ctx, testConfig.PublishTopic)
409 require.NoError(t, err)
410 }
411
412 return ctx
413 }
414
415 func receiveMessages(ch chan chariot.Request) func(f2.Context, *testing.T) f2.Context {
416 return func(ctx f2.Context, t *testing.T) f2.Context {
417 pstest.WithNewSubscription("test", testConfig.PublishTopic)(ctx, t)
418 var ps = pstest.FromContextT(ctx, t)
419
420 go func() {
421
422 ps.Subscribe(context.Background(), "test", func(_ context.Context, msg *pubsub.Message) {
423 var req chariot.Request
424 if err := json.Unmarshal(msg.Data, &req); err != nil {
425 t.Fatal(err)
426 }
427
428
429
430 msg.Ack()
431 ch <- req
432 })
433 close(ch)
434 }()
435 return ctx
436 }
437 }
438
439 func createSyncedObject(ctx f2.Context, t *testing.T, so soapi.SyncedObject) {
440 require.NoError(t, ktest.FromContextT(ctx, t).Client.Create(ctx, &so))
441 waitForStatusToUpdate(ctx, t, &so)
442 }
443
444 func createSyncedObjectAndValidateChariotMessage(ctx f2.Context, t *testing.T, reqs chan chariot.Request, so soapi.SyncedObject) {
445 createSyncedObject(ctx, t, so)
446
447 select {
448 case <-time.After(timeout):
449 t.Fatal("timed out waiting for chariot message")
450 case req := <-reqs:
451 validateChariotMessage(t, chariotClientApi.Create, so, req)
452 }
453 }
454
455 func validateChariotMessage(t *testing.T, operation chariotClientApi.Operation, so soapi.SyncedObject, req chariot.Request) {
456 var dir string
457 if so.Spec.Directory != nil {
458 dir = *so.Spec.Directory
459 }
460
461 require.Equal(t, operation.String(), req.Operation)
462 require.Equal(t, so.Spec.Banner, req.Banner)
463 require.Equal(t, so.Spec.Cluster, req.Cluster)
464 require.Equal(t, so.Spec.Object, base64.StdEncoding.EncodeToString(req.Objects[0]))
465 require.Equal(t, dir, req.Dir)
466 require.Equal(t, syncedobject.SyncedObjectChariotOwner, req.Owner)
467 }
468
469 func checkSpec(expected, actual soapi.SyncedObjectSpec) bool {
470 switch {
471 case expected.Directory != nil && actual.Directory == nil:
472 return false
473 case expected.Directory == nil && actual.Directory != nil:
474 return false
475 case expected.Directory != nil && actual.Directory != nil && *expected.Directory != *actual.Directory:
476 return false
477 case expected.ExpireAt != nil && actual.ExpireAt == nil:
478 return false
479 case expected.ExpireAt == nil && actual.ExpireAt != nil:
480 return false
481 case expected.ExpireAt != nil && actual.ExpireAt != nil && !expected.ExpireAt.Equal(actual.ExpireAt):
482 return false
483 case expected.Banner != actual.Banner:
484 return false
485 case expected.Cluster != actual.Cluster:
486 return false
487 case expected.Object != actual.Object:
488 return false
489 }
490 return true
491 }
492
493 func waitForStatusToUpdate(ctx f2.Context, t *testing.T, expected *soapi.SyncedObject) {
494 require.Eventually(t, func() bool {
495 var actual soapi.SyncedObject
496 var err = ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
497 Name: expected.ObjectMeta.Name,
498 Namespace: expected.ObjectMeta.Namespace,
499 }, &actual)
500 if err != nil {
501 return false
502 } else if !conditions.IsReady(&actual) {
503 return false
504 } else if !controllerutil.ContainsFinalizer(&actual, soapi.Finalizer) {
505 return false
506 }
507
508 var expectedDir, actualStatusDir string
509 if expected.Spec.Directory != nil {
510 expectedDir = *expected.Spec.Directory
511 }
512 if actual.Status.Directory != nil {
513 actualStatusDir = *actual.Status.Directory
514 }
515
516 chariotID, err := syncedobject.CreateChariotID(expected.Spec.Object)
517 require.NoError(t, err)
518
519 switch {
520 case err != nil:
521 return false
522 case actual.Status.Banner != expected.Spec.Banner:
523 return false
524 case actual.Status.Cluster != expected.Spec.Cluster:
525 return false
526 case actual.Status.StoredObject != expected.Spec.Object:
527 return false
528 case actual.Status.StorageLocation != chariot.FmtStorageLocation(expected.Spec.Banner, expected.Spec.Cluster, expectedDir, chariotID):
529 return false
530 case actualStatusDir != expectedDir:
531 return false
532 }
533 return checkSpec(expected.Spec, actual.Spec)
534 }, timeout, tick, "SyncedObject never became Ready")
535 }
536
537 func updateSyncedObject(ctx f2.Context, t *testing.T, so soapi.SyncedObject) (before, after soapi.SyncedObject) {
538 var currentSo soapi.SyncedObject
539 require.NoError(t, ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
540 Name: so.ObjectMeta.Name,
541 Namespace: so.ObjectMeta.Namespace,
542 }, ¤tSo))
543
544 var updateSo = currentSo
545 updateSo.Spec = so.Spec
546 require.NoError(t, ktest.FromContextT(ctx, t).Client.Update(ctx, &updateSo))
547
548 var dir string
549 if so.Spec.Directory != nil {
550 dir = *so.Spec.Directory
551 }
552
553 var expectedSo = updateSo
554 expectedChariotID, err := syncedobject.CreateChariotID(so.Spec.Object)
555 expectedSo.Status = soapi.SyncedObjectStatus{
556 StoredObject: so.Spec.Object,
557 StorageLocation: chariot.FmtStorageLocation(so.Spec.Banner, so.Spec.Cluster, dir, expectedChariotID),
558 Banner: so.Spec.Banner,
559 Cluster: so.Spec.Cluster,
560 Directory: so.Spec.Directory,
561 }
562 require.NoError(t, err)
563
564 waitForStatusToUpdate(ctx, t, &expectedSo)
565
566 var afterSo soapi.SyncedObject
567 require.NoError(t, ktest.FromContextT(ctx, t).Client.Get(ctx, types.NamespacedName{
568 Name: so.ObjectMeta.Name,
569 Namespace: so.ObjectMeta.Namespace,
570 }, &afterSo))
571
572 return currentSo, afterSo
573 }
574
575 func updateAndValidateChariotMessages(ctx f2.Context, t *testing.T, reqs chan chariot.Request, so soapi.SyncedObject) {
576 before, after := updateSyncedObject(ctx, t, so)
577
578 select {
579 case <-time.After(timeout):
580 t.Fatal("timed out getting delete message for outdated object on update")
581 case req := <-reqs:
582 validateChariotMessage(t, chariotClientApi.Delete, before, req)
583 }
584
585 select {
586 case <-time.After(timeout):
587 t.Fatal("timed out getting create message for new object on update")
588 case req := <-reqs:
589 validateChariotMessage(t, chariotClientApi.Create, after, req)
590 }
591 }
592
593 func randomSyncedObject() soapi.SyncedObject {
594 var dir *string
595
596
597 if rand.Int63()%2 == 0 {
598 var d = uuid.New().String()
599 dir = &d
600 }
601
602 var bannerBytes [8]byte
603 crypto.Read(bannerBytes[:])
604
605 return soapi.SyncedObject{
606 ObjectMeta: metav1.ObjectMeta{
607 Name: uuid.New().String(),
608 Namespace: "default",
609 },
610 Spec: soapi.SyncedObjectSpec{
611 Banner: fmt.Sprintf("ret-edge-%x", bannerBytes),
612 Cluster: uuid.New().String(),
613 Object: randomBase64EncodedYamlObject(),
614 Directory: dir,
615 },
616 }
617 }
618
619 type FakeSpec struct {
620 Foo string `yaml:"foo"`
621 Bar string `yaml:"bar"`
622 Baz string `yaml:"baz"`
623 }
624
625 type FakeResource struct {
626 APIVersion string `yaml:"apiVersion"`
627 Kind string `yaml:"kind"`
628 Metadata metav1.ObjectMeta `yaml:"metadata"`
629 Spec FakeSpec `yaml:"spec"`
630 }
631
632 func (fr *FakeResource) EncodeBase64() string {
633 b, _ := yaml.Marshal(fr)
634 return base64.StdEncoding.EncodeToString(b)
635 }
636
637 func DecodeFakeResourceBase64(object string) (*FakeResource, error) {
638 var fr FakeResource
639 y, _ := base64.StdEncoding.DecodeString(object)
640 if err := yaml.Unmarshal(y, &fr); err != nil {
641 return nil, err
642 }
643 return &fr, nil
644 }
645
646
647 func randomBase64EncodedYamlObject() string {
648
649 var groupVersion = "clusterregistry.k8s.io/v1alpha1"
650
651
652 var kinds = [3]string{"Cluster", "Namespace", "Tenant"}
653 var kind = kinds[rand.Int()%len(kinds)]
654
655
656 var nameBytes [16]byte
657
658 crypto.Read(nameBytes[:])
659 var name = fmt.Sprintf("%x", nameBytes)
660
661
662 var namespace string
663 if rand.Int()%2 == 0 {
664 var namespaces = [5]string{"bulldozer", "ci", "godoc", "jack-bot", "policy-bot"}
665 namespace = namespaces[rand.Int()%len(namespaces)]
666 }
667
668
669 var labels map[string]string
670 if rand.Int()%2 == 0 {
671 var clusterNameBytes [4]byte
672 var clusterTypeBytes [4]byte
673 var fleetNameBytes [4]byte
674 crypto.Read(clusterNameBytes[:])
675 crypto.Read(clusterTypeBytes[:])
676 crypto.Read(fleetNameBytes[:])
677 labels = map[string]string{
678 "cluster.edge.ncr.com": fmt.Sprintf("%x", clusterNameBytes),
679 "cluster.edge.ncr.com/type": fmt.Sprintf("%x", clusterTypeBytes),
680 "fleet.edge.ncr.com": fmt.Sprintf("%x", fleetNameBytes),
681 }
682 }
683
684
685 var specBytes [64]byte
686 crypto.Read(specBytes[:])
687
688
689 var resource = FakeResource{
690 APIVersion: groupVersion,
691 Kind: kind,
692 Metadata: metav1.ObjectMeta{
693 Name: name,
694 Namespace: namespace,
695 Labels: labels,
696 },
697 Spec: FakeSpec{
698 Foo: fmt.Sprintf("%x", specBytes[:16]),
699 Bar: fmt.Sprintf("%x", specBytes[16:32]),
700 Baz: fmt.Sprintf("%x", specBytes[32:64]),
701 },
702 }
703
704 fmt.Println("synced object base64:", resource.EncodeBase64())
705 return resource.EncodeBase64()
706 }
707
View as plain text