package integration import ( "context" "database/sql" "os" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "edge-infra.dev/pkg/edge/api/services" "edge-infra.dev/pkg/edge/api/services/artifacts" clustersvc "edge-infra.dev/pkg/edge/api/services/cluster" sqlquery "edge-infra.dev/pkg/edge/api/sql" "edge-infra.dev/pkg/edge/api/sql/plugin" sqlutils "edge-infra.dev/pkg/edge/api/sql/utils" "edge-infra.dev/pkg/edge/api/testutils/seededpostgres" "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/constants/api/fleet" "edge-infra.dev/pkg/lib/runtime/version" "edge-infra.dev/test/f2" "edge-infra.dev/test/f2/x/postgres" ) var ( f f2.Framework ) func TestMain(m *testing.M) { f = f2.New( context.Background(), f2.WithExtensions( postgres.New(postgres.ApplySeedModel()), )). Setup(func(ctx f2.Context) (f2.Context, error) { // TODO: set up a single shared postgres server or // defer setup to each feature test? return ctx, nil }) os.Exit(f.Run(m)) } func TestArtifactsService_AutoUpdates(t *testing.T) { latestVersionForTest := "0.16.0" seedData := seededpostgres.Seed // Extra seed data used to validate auto-update corner cases: // - UPDATEs in the CTES dont add to the rows affected return value, used to count number of affected clusters // - cluster fleet_version should only be updated for clusters with matching labels clusterEdgeID := uuid.New().String() seedData = append(seedData, plugin.Seed{ Name: "seed-clusters.tmpl", Priority: 4, Data: []sqlutils.Cluster{ { EdgeID: clusterEdgeID, ProjectID: "test-org", Active: true, Registered: true, SiteID: "test_bsl_site_id", Name: "test_auto_store_cluster", BannerID: "3396a52c-6a22-4049-9593-5a63b596a101", FleetVersion: "1.2.3-test", }, }, }, plugin.Seed{ Name: "seed-cluster-labels.tmpl", Priority: 6, Data: []sqlutils.ClusterLabel{ { ClusterEdgeID: clusterEdgeID, // store label. label_type = 'edge-fleet' and label_key = 'store'. required // for auto-update to find and set store:$version for this cluster LabelEdgeID: "e0ad2eca-03af-4fd1-91b0-36f98ca0c2cc", }, }, }, plugin.Seed{ Name: "seed-cluster-artifact-versions.tmpl", Priority: 21, Data: []sqlutils.ClusterArtifactVersion{ { ClusterEdgeID: clusterEdgeID, ArtifactName: fleet.Store, ArtifactVersion: "1.2.3-test", }, }, }, plugin.Seed{ Name: "seed-cluster-config.tmpl", Priority: 21, Data: []sqlutils.ClusterConfig{ { ClusterConfigEdgeID: uuid.NewString(), ClusterEdgeID: clusterEdgeID, ConfigKey: "auto_update_enabled", ConfigValue: "true", }, }, }, ) feat := f2.NewFeature("Artifact auto-updates"). Setup("Add Seed data", postgres.WithData(seedData)). Test("Service automatically applies latest version to clusters with auto-update enabled", func(ctx f2.Context, t *testing.T) f2.Context { pg := postgres.FromContextT(ctx, t) db := pg.DB() clusterLabelSvc := clustersvc.NewLabelService(db) svc := artifacts.NewArtifactsService(db, clusterLabelSvc) bsSvc := services.NewBootstrapService("", nil, db) // 6ae03a3b is a seeded store with auto-update enabled storeUUID := "6ae03a3b-654d-4140-ac50-69d8b93a94ea" // Sanity check that the seeded data has the expected initial state fleetType, err := clusterLabelSvc.FetchFleetType(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, fleet.Store, fleetType.String()) version, err := bsSvc.GetClusterFleetVersion(ctx, storeUUID) assert.NoError(t, err) // db trigger will update version to 0.15.0 upon insertion assert.Equal(t, "0.15.0", version) artifacts, err := svc.GetClusterArtifactVersions(ctx, storeUUID) assert.NoError(t, err) assert.Len(t, artifacts, 1) assert.Equal(t, types.ArtifactVersion{ Name: fleet.Store, Version: "0.0.0-test", }, artifacts[0]) // Auto-update store clusters to latest version. Expected n will need to be updated // if new cluster seeds are added assert.NoError(t, insertNewLatestArtifactVersion(db, fleet.Store, latestVersionForTest)) n, err := svc.UpdateClustersToLatestArtifactVersion(ctx, fleet.Store) assert.NoError(t, err) assert.Equal(t, 2, n) versionUpdated, err := bsSvc.GetClusterFleetVersion(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, latestVersionForTest, versionUpdated) artifactsUpdated, err := svc.GetClusterArtifactVersions(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, types.ArtifactVersion{ Name: fleet.Store, Version: latestVersionForTest, }, artifactsUpdated[0]) return ctx }). Test("Auto-update only affects clusters with a fleet type matching the new artifact", func(ctx f2.Context, t *testing.T) f2.Context { pg := postgres.FromContextT(ctx, t) db := pg.DB() svc := artifacts.NewArtifactsService(db, nil) bsSvc := services.NewBootstrapService("", nil, db) // 6ae03a3b-... is skipped because it is a store, and the artifact being updated is basic-store n, err := svc.UpdateClustersToLatestArtifactVersion(ctx, fleet.BasicStore) assert.NoError(t, err) assert.Equal(t, 0, n) // "test_auto_store_cluster" is not skipped because it was labeled as a store nonStoreClusterVersion, err := bsSvc.GetClusterFleetVersion(ctx, clusterEdgeID) assert.NoError(t, err) assert.Equal(t, "0.16.0", nonStoreClusterVersion) nonStoreClusterArtifactsUpdated, err := svc.GetClusterArtifactVersions(ctx, clusterEdgeID) assert.NoError(t, err) assert.Equal(t, types.ArtifactVersion{ Name: fleet.Store, Version: "0.16.0", }, nonStoreClusterArtifactsUpdated[0]) return ctx }).Feature() f.Test(t, feat) } func TestArtifactsService(t *testing.T) { seedData := seededpostgres.Seed feat := f2.NewFeature("Artifacts service"). Setup("Add Seed data", postgres.WithData(seedData)). Test("Service handles getting and updating cluster fleet versions", func(ctx f2.Context, t *testing.T) f2.Context { pg := postgres.FromContextT(ctx, t) db := pg.DB() clusterLabelSvc := clustersvc.NewLabelService(db) svc := artifacts.NewArtifactsService(db, clusterLabelSvc) bsSvc := services.NewBootstrapService("", nil, db) storeUUID := "dc8e59c3-6338-4c28-a776-f54e93a19ff4" // Sanity check that the seeded store cluster exists fleetType, err := clusterLabelSvc.FetchFleetType(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, fleet.Store, fleetType.String()) version, err := bsSvc.GetClusterFleetVersion(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, "0.14.0-seed", version) // New stores should have a single artifact, store artifacts, err := svc.GetClusterArtifactVersions(ctx, storeUUID) assert.NoError(t, err) assert.Len(t, artifacts, 1) assert.Equal(t, types.ArtifactVersion{ Name: fleet.Store, Version: "0.14.0-seed", }, artifacts[0]) // Updating "Edge version" of a store should move the fleet_version and store cluster_artifact_version assert.NoError(t, svc.UpdateClusterFleetVersionAndArtifact(ctx, storeUUID, "0.15"), ) versionUpdated, err := bsSvc.GetClusterFleetVersion(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, "0.15", versionUpdated) artifactsUpdated, err := svc.GetClusterArtifactVersions(ctx, storeUUID) assert.NoError(t, err) assert.Equal(t, types.ArtifactVersion{ Name: fleet.Store, Version: "0.15", }, artifactsUpdated[0]) return ctx }).Feature() f.Test(t, feat) } func TestArtifactsService_Migrations(t *testing.T) { feat := f2.NewFeature("Migration manager successfully runs artifact migrations"). Setup("Add Seed data", postgres.WithData(seededpostgres.Seed)). Test("Latest tags are converted to static versions", func(ctx f2.Context, t *testing.T) f2.Context { pg := postgres.FromContextT(ctx, t) db := pg.DB() row := db.QueryRowContext(ctx, "SELECT count(*) FROM clusters WHERE fleet_version = 'latest'") assert.NoError(t, row.Err()) var clusterCount int assert.NoError(t, row.Scan(&clusterCount)) assert.Equal(t, 0, clusterCount, "expected 0 clusters to have fleet_version = 'latest' after migration runs") row = db.QueryRowContext(ctx, "SELECT count(*) FROM cluster_artifact_versions WHERE artifact_version = 'latest'") assert.NoError(t, row.Err()) var artifactCount int assert.NoError(t, row.Scan(&artifactCount)) assert.Equal(t, 0, artifactCount, "expected 0 cluster_artifacts to have artifact_version = 'latest' after migration runs") return ctx }).Feature() f.Test(t, feat) } // insertNewLatestArtifactVersion adds the given artifact name and version to available_artifact_versions // and marks it as "latest" func insertNewLatestArtifactVersion(db *sql.DB, artifactName, artifactVersion string) error { v := version.New() v.SemVer = artifactVersion major, minor, patch, err := v.SemVerMajorMinorPatch() if err != nil { return err } if _, err = db.Exec(sqlquery.AddCurrentVersionToAvailableArtifacts, artifactName, artifactVersion, major, minor, patch); err != nil { return err } if _, err = db.Exec(sqlquery.UpdateLatestAvailableArtifact, artifactName, major, minor, patch); err != nil { return err } return nil }