1 package clusterctl
2
3 import (
4 "encoding/base64"
5 "encoding/json"
6 "fmt"
7 "strings"
8 "testing"
9 "time"
10
11 containerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/container/v1beta1"
12 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
13 kustomizeApi "github.com/fluxcd/kustomize-controller/api/v1beta2"
14 sourceApi "github.com/fluxcd/source-controller/api/v1beta2"
15 "github.com/google/uuid"
16 "github.com/stretchr/testify/assert"
17 corev1 "k8s.io/api/core/v1"
18 "k8s.io/apimachinery/pkg/api/errors"
19 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20 "k8s.io/apimachinery/pkg/types"
21 "sigs.k8s.io/controller-runtime/pkg/client"
22
23 "edge-infra.dev/pkg/edge/api/graph/mapper"
24 sqlquery "edge-infra.dev/pkg/edge/api/sql"
25 clusterApi "edge-infra.dev/pkg/edge/apis/cluster/v1alpha1"
26 gkeClusterApi "edge-infra.dev/pkg/edge/apis/gkecluster/v1alpha1"
27 syncedobjectApi "edge-infra.dev/pkg/edge/apis/syncedobject/apis/v1alpha1"
28 "edge-infra.dev/pkg/edge/compatibility"
29 "edge-infra.dev/pkg/edge/constants"
30 clusterConstant "edge-infra.dev/pkg/edge/constants/api/cluster"
31 "edge-infra.dev/pkg/edge/constants/api/fleet"
32 "edge-infra.dev/pkg/edge/controllers/util/edgedb"
33 "edge-infra.dev/pkg/edge/k8objectsutils"
34 whv1 "edge-infra.dev/pkg/f8n/warehouse/k8s/apis/v1alpha2"
35 "edge-infra.dev/pkg/k8s/runtime/conditions"
36 euuid "edge-infra.dev/pkg/lib/uuid"
37 "edge-infra.dev/pkg/sds/clustersecrets/breakglass"
38 "edge-infra.dev/pkg/sds/clustersecrets/grub"
39 raconstants "edge-infra.dev/pkg/sds/remoteaccess/constants"
40 "edge-infra.dev/test/framework/integration"
41 )
42
43 func (s *Suite) TestClusterControllerDelete() {
44
45 var infraShip whv1.Shipment
46 var sos syncedobjectApi.SyncedObjectList
47
48 cluster := clusterApi.NewCluster(uuid.New().String(),
49 s.ProjectID,
50 s.Organization,
51 fleet.Store,
52 clusterConstant.GKE,
53 s.Location, s.NodeVersion, s.MachineType, uuid.NewString(), s.NumNodes, s.Banner)
54
55 s.createCluster(cluster)
56 defer func() {
57 s.deleteCluster(cluster)
58
59 s.Eventually(func() bool {
60 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, &infraShip)
61 return errors.IsNotFound(err)
62 }, s.timeout, s.tick, "shipment not cleaned up")
63
64 s.Eventually(func() bool {
65 s.NoError(s.Client.List(s.ctx, &sos))
66 return len(sos.Items) == 0
67 }, s.timeout, s.tick, "syncedobjects not cleaned up")
68 }()
69
70
71 s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
72
73
74 s.NoError(s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, &infraShip))
75 s.NoError(s.Client.List(s.ctx, &sos))
76 s.NotZero(len(sos.Items))
77 }
78
79 func (s *Suite) TestClusterControllerSetsInfraStatusError() {
80 cluster := clusterApi.NewCluster(uuid.New().String(), s.ProjectID, s.Organization,
81 "",
82 clusterConstant.GKE, s.Location, s.NodeVersion, s.MachineType, uuid.NewString(), s.NumNodes, s.Banner)
83
84 s.createCluster(cluster)
85 defer s.deleteCluster(cluster)
86
87
88 s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusError)
89 }
90
91 func (s *Suite) TestClusterControllerGKE() {
92 cluster := clusterApi.NewCluster(uuid.New().String(),
93 s.ProjectID,
94 s.Organization,
95 fleet.Store,
96 clusterConstant.GKE,
97 s.Location, s.NodeVersion, s.MachineType, uuid.NewString(), s.NumNodes, s.Banner)
98
99 s.createCluster(cluster)
100 defer s.deleteCluster(cluster)
101
102
103 ns := &corev1.Namespace{}
104 s.Eventually(func() bool {
105 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, ns)
106 return !errors.IsNotFound(err)
107 }, s.timeout, s.tick, "expected namespace was never created")
108
109
110 s.checkShipment(cluster)
111
112
113 s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
114
115
116 var sos syncedobjectApi.SyncedObjectList
117 s.Eventually(func() bool {
118 if err := s.Client.List(s.ctx, &sos); err != nil {
119 return false
120 }
121 s.NotZero(sos.Items)
122 var filtered []syncedobjectApi.SyncedObject
123 for _, so := range sos.Items {
124
125 if *so.Spec.Directory != constants.KustomizationsDir {
126 continue
127 }
128 obj, err := base64.StdEncoding.DecodeString(so.Spec.Object)
129 if err != nil {
130 s.NoError(err, "failed to decode syncedobject data")
131 }
132
133
134 if strings.Contains(so.Name, "banner-sync") {
135 kusto := &kustomizeApi.Kustomization{}
136 s.NoError(json.Unmarshal(obj, kusto), "failed to unmarshal kustomization from syncedobject")
137 s.NotEmpty(kusto.Spec)
138 s.Equal(
139 kusto.Spec.SourceRef.Name,
140 constants.EdgeBannerBucketName,
141 fmt.Sprintf("SO '%v' has incorrect or missing sourceRef name", so.Name),
142 )
143 filtered = append(filtered, so)
144 continue
145 }
146 if strings.Contains(so.Name, "edge-banner-bucket") {
147 bucko := &sourceApi.Bucket{}
148 s.NoError(json.Unmarshal(obj, bucko), "failed to unmarshal bucket from syncedobject")
149 s.NotEmpty(bucko.Spec)
150 s.Contains(
151 *bucko.Spec.Ignore,
152 "chariot",
153 fmt.Sprintf("SO '%v' has incorrect or missing ignore", so.Name),
154 )
155 filtered = append(filtered, so)
156 continue
157 }
158 kusto := &kustomizeApi.Kustomization{}
159 s.NoError(json.Unmarshal(obj, kusto), "failed to unmarshal kustomization from syncedobject")
160 s.NotEmpty(kusto.Spec)
161 s.Equal(
162 kusto.Spec.SourceRef.Name,
163 constants.EdgeBucketName,
164 fmt.Sprintf("SO '%v' has incorrect or missing sourceRef name", so.Name),
165 )
166 s.True(
167 strings.HasPrefix(kusto.Spec.Path, fmt.Sprintf("./%s/", cluster.Name)),
168 "expected component manifest kustomizations to point to a path prefixed by the cluser edge id",
169 )
170 filtered = append(filtered, so)
171 }
172 return len(filtered) == 4
173 }, s.timeout, s.tick, "expected syncedobjects were never created")
174
175 s.Eventually(func() bool {
176 s.NoError(s.Client.Get(s.ctx, client.ObjectKeyFromObject(cluster), cluster))
177 if cluster.Status.Inventory == nil {
178 return false
179 }
180 return len(cluster.Status.Inventory.Entries) == 9
181 }, s.timeout, s.tick, "expected inventory not created")
182
183
184 gkeCluster := &gkeClusterApi.GKECluster{}
185 s.Never(func() bool {
186 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Name}, gkeCluster)
187 return !errors.IsNotFound(err)
188 }, time.Second, 100*time.Millisecond, "GKECluster should not exist for store clusters")
189 }
190
191 func (s *Suite) TestClusterControllerGKENonStore() {
192 cluster := clusterApi.NewCluster(uuid.New().String(),
193 s.ProjectID,
194 s.Organization,
195 fleet.Cluster,
196 clusterConstant.GKE,
197 s.Location, s.NodeVersion, s.MachineType, uuid.NewString(), s.NumNodes, s.Banner)
198
199 s.createCluster(cluster)
200 defer s.deleteCluster(cluster)
201
202
203 s.checkShipment(cluster)
204
205
206 s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
207
208
209 s.checkGKECluster(cluster, fleet.Cluster)
210
211
212 ns := &corev1.Namespace{}
213 s.Eventually(func() bool {
214 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, ns)
215 return !errors.IsNotFound(err)
216 }, s.timeout, s.tick, "expected namespace was never created")
217
218 s.Eventually(func() bool {
219 return checkNSOwnerReference(cluster, ns.GetOwnerReferences())
220 }, s.timeout, s.tick, "expected namespace owner reference was not found")
221
222
223 s.checkContainerCluster(cluster)
224
225 s.Eventually(func() bool {
226 s.NoError(s.Client.Get(s.ctx, client.ObjectKeyFromObject(cluster), cluster))
227 if cluster.Status.Inventory == nil {
228 return false
229 }
230 return len(cluster.Status.Inventory.Entries) == 7
231 }, s.timeout, s.tick, "expected inventory not created")
232 }
233
234 func (s *Suite) TestClusterControllerSDSDistributed() {
235 integration.SkipIf(s.Framework)
236 cluster := clusterApi.NewCluster(uuid.New().String(),
237 s.ProjectID,
238 s.Organization,
239 fleet.Store,
240 clusterConstant.DSDS,
241
242 s.Location, "", "", uuid.NewString(), 0, s.Banner)
243
244 s.createCluster(cluster)
245 defer s.deleteCluster(cluster)
246
247
248 s.checkShipment(cluster)
249
250
251 s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
252
253
254 ns := &corev1.Namespace{}
255 s.Eventually(func() bool {
256 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, ns)
257 return !errors.IsNotFound(err)
258 }, s.timeout, s.tick, "expected namespace was never created")
259
260 s.Eventually(func() bool {
261 return checkNSOwnerReference(cluster, ns.GetOwnerReferences())
262 }, s.timeout, s.tick, "expected namespace owner reference was not found")
263
264
265 s.checkHashedGrubSecret(cluster)
266
267
268 s.checkHashedBreakGlassSecret(cluster)
269
270
271 gkeCluster := &gkeClusterApi.GKECluster{}
272 s.Never(func() bool {
273 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Name}, gkeCluster)
274 return !errors.IsNotFound(err)
275 }, time.Second, 100*time.Millisecond, "GKECluster should not exits for non-gke clusters")
276 }
277
278 func (s *Suite) TestExplicitClusterArtifacts_WithDigest() {
279 cluster := s.genStoreCluster()
280 s.createCluster(cluster)
281 defer s.deleteCluster(cluster)
282 withDigest := "sha256:d00a39e1a5dce242e7928cb8377df2540197f9d8710f2e6ccd620cb2e324a64a"
283 s.updateClusterArtifactVersion(cluster, "store", withDigest)
284
285
286 shipment := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipment)
287 s.Require().True(shipment.Spec.Runtime, "expected Shipment in cluster's SyncedObject to be runtime only")
288 s.Require().False(shipment.Spec.Infra, "expected Shipment in cluster's SyncedObject to be runtime only")
289 s.Require().Len(shipment.Spec.Pallets, 1, "expected 1 pallet for store")
290 storePallet := shipment.Spec.Pallets[0]
291 s.Require().Equal("store", storePallet.Name, "expected store pallet to match cluster fleet name")
292 s.Require().Equal("", storePallet.Tag, "expected store pallet tag to be empty if digest is set")
293 s.Require().Equal(withDigest, storePallet.Digest, "expected store pallet version to be set to a non-default value")
294 }
295
296 func (s *Suite) TestExplicitClusterArtifacts_WithTag() {
297 cluster := s.genStoreCluster()
298 s.createCluster(cluster)
299 defer s.deleteCluster(cluster)
300 withTag := "0.14.0-test"
301 s.updateClusterArtifactVersion(cluster, "store", withTag)
302
303
304 shipment := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipment)
305 s.Require().True(shipment.Spec.Runtime, "expected Shipment in cluster's SyncedObject to be runtime only")
306 s.Require().False(shipment.Spec.Infra, "expected Shipment in cluster's SyncedObject to be runtime only")
307 s.Require().Len(shipment.Spec.Pallets, 1, "expected 1 pallet for store")
308 storePallet := shipment.Spec.Pallets[0]
309 s.Require().Equal("store", storePallet.Name, "expected store pallet to match cluster fleet name")
310 s.Require().Equal("", storePallet.Digest, "expected store pallet digest to be empty if tag is set")
311 s.Require().Equal(withTag, storePallet.Tag, "expected store pallet version to be set to a non-default value")
312 }
313
314 func (s *Suite) TestRequeueInterval() {
315 cluster := s.genStoreCluster()
316
317 cluster.Spec.Interval = &v1.Duration{Duration: s.tick}
318 startingVersion := "0.16-test"
319 s.createCluster(cluster)
320 s.updateClusterArtifactVersion(cluster, "store", startingVersion)
321 defer s.deleteCluster(cluster)
322
323
324 shipment := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipment)
325 s.Require().Equal(startingVersion, shipment.Spec.Pallets[0].Tag, "expected store pallet version to be fetched from database")
326
327
328 expectUpdateTo := "0.17-updated"
329 go s.updateClusterArtifactVersion(cluster, "store", expectUpdateTo)
330
331 s.Eventually(func() bool {
332
333 updatedShipment := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipment)
334 return updatedShipment.Spec.Pallets[0].Tag == expectUpdateTo
335 }, s.timeout, s.tick, "expected store pallet version to be updated asynchronously and corrected on requeue")
336 }
337
338
339 func (s *Suite) TestRemoteAccessIPRecorded() {
340 ns := &corev1.Namespace{ObjectMeta: v1.ObjectMeta{Name: raconstants.VPNNamespace}}
341 s.NoError(s.Client.Create(s.ctx, ns))
342 loadBalancerIP := "34.123.45.67"
343 wireguardRelayService := &corev1.Service{
344 ObjectMeta: v1.ObjectMeta{Namespace: raconstants.VPNNamespace, Name: raconstants.RelayName},
345 Spec: corev1.ServiceSpec{
346 Ports: []corev1.ServicePort{
347 {
348 Port: 8080,
349 },
350 },
351 },
352 }
353 s.NoError(s.Client.Create(s.ctx, wireguardRelayService))
354 wireguardRelayService.Status.LoadBalancer.Ingress = []corev1.LoadBalancerIngress{
355 {IP: loadBalancerIP},
356 }
357 s.NoError(s.Client.Status().Update(s.ctx, wireguardRelayService))
358 s.Eventually(func() bool {
359 service := &corev1.Service{}
360 if err := s.Client.Get(s.ctx, client.ObjectKeyFromObject(wireguardRelayService), service); err != nil {
361 return false
362 }
363 if len(service.Status.LoadBalancer.Ingress) != 1 {
364 return false
365 }
366 return service.Status.LoadBalancer.Ingress[0].IP == loadBalancerIP
367 }, s.timeout, s.tick, "no ingress IP")
368 clusterObj := clusterApi.NewCluster(uuid.NewString(),
369 s.ProjectID,
370 s.Organization,
371 fleet.Store,
372 clusterConstant.DSDS,
373 s.Location,
374 "",
375 "",
376 uuid.NewString(),
377 0,
378 s.Banner)
379 s.createCluster(clusterObj)
380 defer s.deleteCluster(clusterObj)
381 s.Eventually(func() bool {
382 cluster := &clusterApi.Cluster{}
383 if err := s.Client.Get(s.ctx, client.ObjectKeyFromObject(clusterObj), cluster); err != nil {
384 return false
385 }
386 return conditions.IsReady(cluster)
387 }, s.timeout, s.tick, "cluster did not become ready")
388 s.checkRemoteAccessIPDatabaseValue(clusterObj, loadBalancerIP)
389 }
390
391 func (s *Suite) TestClusterShipmentLocationRegression() {
392 testCases := []struct {
393 title string
394 tag string
395 cluster *clusterApi.Cluster
396 legacy bool
397 }{
398 {
399 title: "Latest Tag",
400 tag: "latest",
401 cluster: s.genStoreCluster(),
402 legacy: false,
403 },
404 {
405 title: "Greater than 0.18",
406 tag: "0.19",
407 cluster: s.genStoreCluster(),
408 legacy: false,
409 },
410 {
411 title: "Less than 0.18",
412 tag: "0.17.2",
413 cluster: s.genStoreCluster(),
414 legacy: true,
415 },
416 {
417 title: "Equal to 0.18",
418 tag: "0.18",
419 cluster: s.genStoreCluster(),
420 legacy: false,
421 },
422 }
423
424 for _, testCase := range testCases {
425 s.Run(testCase.title, func() {
426 s.createCluster(testCase.cluster)
427 defer s.deleteCluster(testCase.cluster)
428 s.updateClusterArtifactVersion(testCase.cluster, "store", testCase.tag)
429
430 shipment := s.getRuntimeShipmentForCluster(testCase.cluster, constants.ClusterShipment)
431 s.Require().True(shipment.Spec.Runtime, "expected Shipment in cluster's SyncedObject to be runtime only")
432 s.Require().False(shipment.Spec.Infra, "expected Shipment in cluster's SyncedObject to be runtime only")
433 s.Require().Len(shipment.Spec.Pallets, 1, "expected 1 pallet for store")
434 storePallet := shipment.Spec.Pallets[0]
435 s.Require().Equal("store", storePallet.Name, "expected store pallet to match cluster fleet name")
436 s.Require().Equal("", storePallet.Digest, "expected store pallet digest to be empty if tag is set")
437 s.Require().Equal(testCase.tag, storePallet.Tag, "expected store pallet version to be set to a non-default value")
438 if testCase.legacy {
439 s.Require().NotEmpty(shipment.Spec.Rendering[0].Variables["cluster_location"])
440 } else {
441 s.Require().Equal(shipment.Spec.Rendering[0].Variables["gcp_zone"], "c")
442 s.Require().Equal(shipment.Spec.Rendering[0].Variables["gcp_region"], "us-east1")
443 }
444 })
445 }
446 }
447
448 func (s *Suite) checkRemoteAccessIPDatabaseValue(cluster *clusterApi.Cluster, remoteAccessIP string) {
449 s.Require().Eventually(func() bool {
450 var v string
451 err := s.DB.QueryRowContext(s.ctx, sqlquery.GetRemoteAccessIPByBannerEdgeID, cluster.Spec.BannerEdgeID).Scan(&v)
452 if err != nil {
453 return false
454 }
455 return v == remoteAccessIP
456 }, s.timeout, s.tick, "expected remote_access_ip value %s not set in database", remoteAccessIP)
457 }
458
459 func (s *Suite) genStoreCluster() *clusterApi.Cluster {
460 clusterEdgeID := uuid.NewString()
461 return clusterApi.NewCluster(clusterEdgeID,
462 s.ProjectID,
463 s.Organization,
464 fleet.Store,
465 clusterConstant.Generic,
466 s.Location,
467 "",
468 "",
469 clusterEdgeID,
470 0,
471 s.Banner,
472 )
473 }
474
475 func (s *Suite) checkGKECluster(cluster *clusterApi.Cluster, fleetType string) {
476
477 gkeCluster := &gkeClusterApi.GKECluster{}
478 s.Eventually(func() bool {
479 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Name}, gkeCluster)
480 return !errors.IsNotFound(err)
481 }, s.timeout, s.tick, "expected GKECluster was never created")
482
483 s.Equal(s.ProjectID, gkeCluster.Spec.ProjectID)
484 s.Equal(s.Banner.Name, gkeCluster.Spec.Banner)
485 s.Equal(s.Organization, gkeCluster.Spec.Organization)
486 s.Equal(cluster.Spec.Name, gkeCluster.Spec.Name)
487 s.Equal(fleetType, string(gkeCluster.Spec.Fleet))
488 s.Equal(s.Location, gkeCluster.Spec.Location)
489 s.Equal(s.NodeVersion, gkeCluster.Spec.NodeVersion)
490 s.Equal(s.NumNodes, gkeCluster.Spec.NumNode)
491 }
492
493 func (s *Suite) getRuntimeShipmentForCluster(cluster *clusterApi.Cluster, soPrefix string) *whv1.Shipment {
494 so := &syncedobjectApi.SyncedObject{}
495 soName := k8objectsutils.NameWithPrefix(soPrefix, cluster.Name)
496 s.Eventually(func() bool {
497 err := s.Client.Get(s.ctx, types.NamespacedName{Name: soName, Namespace: cluster.Name}, so)
498
499 return !errors.IsNotFound(err)
500 }, s.timeout, s.tick, "expected synced object was never created for: %s", soName)
501 obj, err := base64.StdEncoding.DecodeString(so.Spec.Object)
502 s.Require().NoError(err)
503 clusterShip := &whv1.Shipment{}
504 s.Require().NoError(json.Unmarshal(obj, clusterShip))
505 return clusterShip
506 }
507
508 func (s *Suite) checkShipment(cluster *clusterApi.Cluster) {
509 clusterShip := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipment)
510 s.Equal(cluster.Name, clusterShip.Name)
511
512 oldClusterShip := s.getRuntimeShipmentForCluster(cluster, constants.ClusterShipmentOld)
513 s.Equal(cluster.Name, oldClusterShip.Name)
514
515 infraShip := &whv1.Shipment{}
516 s.Eventually(func() bool {
517 err := s.Client.Get(s.ctx, types.NamespacedName{Name: cluster.Name}, infraShip)
518 return !errors.IsNotFound(err)
519 }, s.timeout, s.tick, "expected infraShipment was never created for: %s", cluster.Name)
520 s.Equal(cluster.Name, infraShip.Name)
521 }
522
523 func (s *Suite) checkHashedBreakGlassSecret(cluster *clusterApi.Cluster) {
524 breakGlassSecret := &syncedobjectApi.SyncedObject{}
525 secretName := k8objectsutils.NameWithPrefix(breakglass.HashedSecretName, cluster.Name)
526 s.Eventually(func() bool {
527 err := s.Client.Get(s.ctx, types.NamespacedName{Name: secretName, Namespace: cluster.Name}, breakGlassSecret)
528 return !errors.IsNotFound(err)
529 }, s.timeout, s.tick, "expected synced object was never created for: %s", secretName)
530
531 s.Equal(breakGlassSecret.Spec.Banner, cluster.Spec.ProjectID)
532 s.Equal(breakGlassSecret.Spec.Cluster, cluster.Name)
533 }
534
535 func (s *Suite) checkContainerCluster(cluster *clusterApi.Cluster) {
536 key := client.ObjectKey{Namespace: cluster.Name, Name: euuid.FromUUID(cluster.Name).Hash()}
537 cc := &containerAPI.ContainerCluster{}
538 s.Eventually(func() bool {
539 err := s.Client.Get(s.ctx, key, cc)
540 return !errors.IsNotFound(err)
541 }, s.timeout, s.tick, "expected container cluster was never created")
542
543 if !integration.IsIntegrationTest() {
544
545 status := containerAPI.ContainerClusterStatus{Conditions: []v1alpha1.Condition{{Status: "True", Type: "Ready"}}}
546 cc.Status = status
547 s.NoError(s.Client.Update(s.ctx, cc))
548 }
549
550 nodePool := &containerAPI.ContainerNodePool{}
551 s.Eventually(func() bool {
552 err := s.Client.Get(s.ctx, key, nodePool)
553 return !errors.IsNotFound(err)
554 }, s.timeout, s.tick, "expected container node pool was never created")
555 }
556
557
558 func checkNSOwnerReference(cluster *clusterApi.Cluster, ownerRef []v1.OwnerReference) bool {
559 if len(ownerRef) < 1 {
560 return false
561 }
562 if ownerRef[0].Name != mapper.ConvertK8sName(cluster.Name) {
563 return false
564 }
565 if ownerRef[0].UID != cluster.GetUID() {
566 return false
567 }
568 return true
569 }
570
571 func (s *Suite) checkHashedGrubSecret(cluster *clusterApi.Cluster) {
572 grubSecret := &syncedobjectApi.SyncedObject{}
573 secretName := k8objectsutils.NameWithPrefix(grub.HashedSecretName, cluster.Name)
574 s.Eventually(func() bool {
575 err := s.Client.Get(s.ctx, types.NamespacedName{Name: secretName, Namespace: cluster.Name}, grubSecret)
576 return !errors.IsNotFound(err)
577 }, s.timeout, s.tick, "expected synced object was never created for: %s", secretName)
578
579 s.Equal(grubSecret.Spec.Banner, cluster.Spec.ProjectID)
580 s.Equal(grubSecret.Spec.Cluster, cluster.Name)
581 }
582
583 func (s *Suite) checkInfraStatusDatabaseValue(cluster *clusterApi.Cluster, infraStatus edgedb.InfraStatus) {
584 const stmt = "SELECT infra_status FROM clusters WHERE cluster_edge_id=$1"
585 s.Require().Eventually(func() bool {
586 var v string
587 err := s.DB.QueryRow(stmt, cluster.ObjectMeta.Name).Scan(&v)
588 if err != nil {
589 return false
590 }
591 return v == string(infraStatus)
592 }, s.timeout, s.tick, "expected infra_status value %q not set in database", infraStatus)
593 }
594
595 func (s *Suite) createCluster(cluster *clusterApi.Cluster) {
596
597 const stmt = "INSERT INTO clusters (cluster_edge_id, cluster_name, project_id, registered, active, banner_edge_id) VALUES ($1, $2, $3, $4, $5, $6)"
598 _, err := s.DB.Exec(stmt, cluster.ObjectMeta.Name, cluster.Spec.Name, cluster.Spec.ProjectID, true, true, cluster.Spec.BannerEdgeID)
599 s.Require().NoError(err)
600
601
602 if fleet.IsStoreCluster(cluster.Spec.Fleet) {
603 const stmt2 = `INSERT INTO cluster_artifact_versions (cluster_edge_id, artifact_name, artifact_version) VALUES ($1, $2, $3)`
604 _, err := s.DB.Exec(stmt2, cluster.ObjectMeta.Name, "store", "0.0.0-test")
605 s.Require().NoError(err)
606 }
607 s.NoError(s.Client.Create(s.ctx, cluster))
608 }
609
610 func (s *Suite) deleteCluster(cluster *clusterApi.Cluster) {
611 s.NoError(s.Client.Delete(s.ctx, cluster))
612 s.Eventually(func() bool {
613 return errors.IsNotFound(s.Client.Get(s.ctx, client.ObjectKeyFromObject(cluster), cluster))
614 }, s.timeout, s.tick, "cluster resource was never deleted %v", cluster)
615 }
616
617 func (s *Suite) updateClusterArtifactVersion(cluster *clusterApi.Cluster, artifactName, artifactVersion string) {
618 const stmt = `UPDATE cluster_artifact_versions SET artifact_version = $3 WHERE cluster_edge_id = $1 AND artifact_name = $2;`
619 _, err := s.DB.Exec(stmt, cluster.ObjectMeta.Name, artifactName, artifactVersion)
620 s.Require().NoError(err)
621 }
622
623 func TestSupportsOptionalDatasync(t *testing.T) {
624 testCases := []struct {
625 title string
626 version string
627 expected bool
628 }{
629 {
630 title: "Version 0.19",
631 version: "0.19",
632 expected: false,
633 },
634 {
635 title: "Version 0.20",
636 version: "0.20",
637 expected: true,
638 },
639 {
640 title: "Version 0.21",
641 version: "0.21",
642 expected: true,
643 },
644 {
645 title: "Latest Version",
646 version: "latest",
647 expected: true,
648 },
649 {
650 title: "Version 0.18",
651 version: "0.18",
652 expected: false,
653 },
654 }
655 for _, testCase := range testCases {
656 t.Run(testCase.title, func(t *testing.T) {
657 supported, err := compatibility.Compare(compatibility.LessThanOrEqual, "0.20", testCase.version)
658 assert.NoError(t, err)
659 assert.Equal(t, testCase.expected, supported)
660 })
661 }
662 }
663
View as plain text