...

Source file src/edge-infra.dev/pkg/edge/controllers/clusterctl/cluster_controller_test.go

Documentation: edge-infra.dev/pkg/edge/controllers/clusterctl

     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  	// sos & infraShip must be deleted when the cluster is deleted
    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  	// check infra_status is READY
    71  	s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
    72  
    73  	// ensure infraShip was created & sos was populated, before checking if they were deleted.
    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  		"", // Set the fleet to "" so the reconcile loop produces an error
    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  	// Ensure the cluster reconciler properly set the infra_status to ERROR
    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  	// check for Namespace creation
   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  	// check shipment(s) created
   110  	s.checkShipment(cluster)
   111  
   112  	// check infra_status is READY
   113  	s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
   114  
   115  	// syncedobjects containing cluster's kustomizations
   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  			// skip non-kustomize syncedobjects
   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  			// special checks for banner kustomization
   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, // ensure the kustomization sourceRef Name is cluster_edge_id
   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", // ensure the kustomization sourceRef Name is cluster_edge_id
   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, // ensure the kustomization sourceRef Name is cluster_edge_id
   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)), // ensure the kustomization path is cluster_edge_id
   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  	// check that GKECluster is not created for store cluster
   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  	// check shipment(s) created
   203  	s.checkShipment(cluster)
   204  
   205  	// check infra_status is READY
   206  	s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
   207  
   208  	// check for GKECluster creation
   209  	s.checkGKECluster(cluster, fleet.Cluster)
   210  
   211  	// check for ns creation
   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  	// check gke cluster resources at the end, they take longer to create
   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) // this is for DSDS clusters not GKE
   236  	cluster := clusterApi.NewCluster(uuid.New().String(),
   237  		s.ProjectID,
   238  		s.Organization,
   239  		fleet.Store,
   240  		clusterConstant.DSDS,
   241  		// for non gke clusters below fields are ignored
   242  		s.Location, "", "", uuid.NewString(), 0, s.Banner)
   243  
   244  	s.createCluster(cluster)
   245  	defer s.deleteCluster(cluster)
   246  
   247  	// check shipment(s) created
   248  	s.checkShipment(cluster)
   249  
   250  	// check infra_status is READY
   251  	s.checkInfraStatusDatabaseValue(cluster, edgedb.InfraStatusReady)
   252  
   253  	// check for cluster ns creation
   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  	// check store-credentials plugin: grub2 resources
   265  	s.checkHashedGrubSecret(cluster)
   266  
   267  	// check store-credentials plugin: IEN host OS user resources
   268  	s.checkHashedBreakGlassSecret(cluster)
   269  
   270  	// check that GKECluster was never created
   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" //nolint
   283  	s.updateClusterArtifactVersion(cluster, "store", withDigest)
   284  
   285  	// check that specified cluster artifacts are added to shipment
   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  	// check that specified cluster artifacts are added to shipment
   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  	// configure this Cluster so that it re-reconciles within 1-2 ticks
   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  	// get shipments, make sure pallet is store:latest
   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  	// update db
   328  	expectUpdateTo := "0.17-updated"
   329  	go s.updateClusterArtifactVersion(cluster, "store", expectUpdateTo)
   330  
   331  	s.Eventually(func() bool {
   332  		// shipment, pallets should eventually (~requeue interal ish), get updated
   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  // create fake wireguard service and cluster, then check cluster becomes ready and the remote_access_ip value is set in the database
   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  			// check that specified cluster artifacts are added to shipment
   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  	// check for GKECluster creation
   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  		// update container cluster to be ready
   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  // checkNSOwnerReference check that namespace has cluster owner reference.
   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  	// insert the test cluster into the database so that setting infra_status works
   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  	// store clusters must have an explicit version assigned to them
   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) { // nolint:unparam
   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