From d136ecbac84c630620f069e81c56f92a407adc96 Mon Sep 17 00:00:00 2001 From: munishchouhan Date: Mon, 7 Oct 2024 16:43:42 +0200 Subject: [PATCH 1/4] added e2e test [e2e test] Signed-off-by: munishchouhan --- .github/workflows/build.yml | 20 ++- Makefile | 3 + build.gradle | 30 +++- .../ContainerControllerHttpE2ETest.groovy | 138 ++++++++++++++++++ src/e2eTest/resources/application-test.yml | 73 +++++++++ 5 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy create mode 100644 src/e2eTest/resources/application-test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 503bce24f..a55bb34a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,6 +72,23 @@ jobs: AZURECR_PAT: ${{ secrets.AZURECR_PAT }} GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }} + - name: Tests + if: "contains(github.event.head_commit.message, '[release]') || contains(github.event.head_commit.message, '[e2e test]')" + run: | + make e2eTest + env: + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + GITHUB_TOKEN: ${{ secrets.GH_SEQERA_TOKEN }} + AWS_ACCESS_KEY_ID: ${{secrets.TOWER_CI_AWS_ACCESS}} + AWS_SECRET_ACCESS_KEY: ${{secrets.TOWER_CI_AWS_SECRET}} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PAT: ${{ secrets.DOCKER_PAT }} + QUAY_USER: ${{ secrets.QUAY_USER }} + QUAY_PAT: ${{ secrets.QUAY_PAT }} + AZURECR_USER: ${{ secrets.AZURECR_USER }} + AZURECR_PAT: ${{ secrets.AZURECR_PAT }} + GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }} + - name: Cleanup build workspace if: always() run: | @@ -96,7 +113,8 @@ jobs: with: name: test-reports-jdk-${{ matrix.java_version }} path: | - **/build/reports/tests/test + **/build/reports/tests + - name : Publish code coverage report if: success() diff --git a/Makefile b/Makefile index 23895e870..5fb8134bc 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ compile: check: ./gradlew check +e2eTest: + ./gradlew e2eTest + image: ./gradlew jibDockerBuild diff --git a/build.gradle b/build.gradle index 85a016157..676b853ac 100644 --- a/build.gradle +++ b/build.gradle @@ -31,12 +31,12 @@ repositories { dependencies { annotationProcessor("io.micronaut:micronaut-http-validation") compileOnly("io.micronaut.data:micronaut-data-processor") - compileOnly("io.micronaut:micronaut-inject-groovy") + implementation("io.micronaut:micronaut-inject-groovy") compileOnly("io.micronaut:micronaut-http-validation") implementation("jakarta.persistence:jakarta.persistence-api:3.0.0") api 'io.seqera:lib-mail:1.0.0' api 'io.seqera:wave-api:0.13.0' - api 'io.seqera:wave-utils:0.14.0' + api 'io.seqera:wave-utils:0.14.1' implementation("io.micronaut:micronaut-http-client") implementation("io.micronaut:micronaut-jackson-databind") implementation("io.micronaut.groovy:micronaut-runtime-groovy") @@ -204,3 +204,29 @@ jacocoTestReport { })) } } + +configurations { + e2eTestImplementation.extendsFrom testImplementation, implementation + e2eTestRuntimeOnly.extendsFrom testRuntimeOnly, runtimeOnly + e2eTestAnnotationProcessor.extendsFrom testAnnotationProcessor +} + +sourceSets { + e2eTest { + java { + srcDirs = ['src/e2eTest/groovy'] + } + resources { + srcDirs = ['src/e2eTest/resources'] + } + compileClasspath += sourceSets.main.output + sourceSets.test.output + runtimeClasspath += sourceSets.main.output + sourceSets.test.output + } +} + +tasks.register('e2eTest', Test) { + group = 'verification' + testClassesDirs = sourceSets.e2eTest.output.classesDirs + classpath = sourceSets.e2eTest.runtimeClasspath + shouldRunAfter test +} diff --git a/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy new file mode 100644 index 000000000..712b501a3 --- /dev/null +++ b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy @@ -0,0 +1,138 @@ +/* + * Wave, containers provisioning service + * Copyright (c) 2024, Seqera Labs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.seqera.wave.controller + +import spock.lang.Specification +import io.micronaut.context.annotation.Property +import io.micronaut.http.HttpRequest +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.objectstorage.InputStreamMapper +import io.micronaut.objectstorage.ObjectStorageOperations +import io.micronaut.objectstorage.aws.AwsS3Configuration +import io.micronaut.objectstorage.aws.AwsS3Operations +import io.micronaut.test.annotation.MockBean +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.seqera.wave.api.BuildStatusResponse +import io.seqera.wave.api.PackagesSpec +import io.seqera.wave.api.SubmitContainerTokenRequest +import io.seqera.wave.api.SubmitContainerTokenResponse +import io.seqera.wave.core.RouteHandler +import io.seqera.wave.service.pairing.PairingService +import io.seqera.wave.service.pairing.PairingServiceImpl +import io.seqera.wave.test.AwsS3TestContainer +import io.seqera.wave.tower.client.TowerClient +import jakarta.inject.Inject +import jakarta.inject.Named +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.s3.S3Client + +/** + * @author Munish Chouhan + */ +@MicronautTest +@Property(name = 'wave.build.logs.bucket', value = 'test-bucket') +class ContainerControllerHttpE2ETest extends Specification implements AwsS3TestContainer { + + @Inject + @Client("/") + HttpClient httpClient + + @Inject + PairingService pairingService + + @Inject + TowerClient towerClient + + @Inject + RouteHandler routeHandler + + @MockBean(PairingServiceImpl) + PairingService mockPairingService(){ + Mock(PairingService) + } + + @MockBean(TowerClient) + TowerClient mockTowerClient() { + Mock(TowerClient) + } + + def s3Client = S3Client.builder() + .endpointOverride(URI.create("http://${awsS3HostName}:${awsS3Port}")) + .region(Region.EU_WEST_1) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("accesskey", "secretkey"))) + .forcePathStyle(true) + .build() + + @MockBean(ObjectStorageOperations) + @Named('build-logs') + ObjectStorageOperations mockObjectStorageOperations() { + AwsS3Configuration configuration = new AwsS3Configuration('build-logs') + configuration.setBucket("test-bucket") + return new AwsS3Operations(configuration, s3Client, Mock(InputStreamMapper)) + } + + def 'should build conda image then store conda lockfile and fetch conda lockfile' () { + given: + def request = new SubmitContainerTokenRequest( + packages: new PackagesSpec(channels: ['conda-forge'], entries: ['xz'], type: 'CONDA'), + buildRepository: "test/repository", + cacheRepository: "test/cache", + containerPlatform: "arm64" + + ) + and: + s3Client.createBucket { it.bucket("test-bucket") } + + when: + def res = httpClient + .toBlocking() + .exchange(HttpRequest.POST("/v1alpha2/container",request), SubmitContainerTokenResponse).body() + and: + awaitBuild(res.buildId) + and: + res = httpClient + .toBlocking() + .exchange(HttpRequest.GET("/v1alpha1/builds/$res.buildId/condalock"), String).body() + + then: + res.contains('conda create --name --file ') + } + + boolean awaitBuild(String buildId) { + long startTime = System.currentTimeMillis() + long timeout = 120000 + long checkInterval = 5000 + while (System.currentTimeMillis() - startTime < timeout) { + def res = httpClient + .toBlocking() + .exchange(HttpRequest.GET("/v1alpha1/builds/$buildId/status"), BuildStatusResponse) + .body() + + if (res.status == BuildStatusResponse.Status.COMPLETED) { + return true + } + sleep checkInterval + } + + return false + } +} diff --git a/src/e2eTest/resources/application-test.yml b/src/e2eTest/resources/application-test.yml new file mode 100644 index 000000000..71dfec879 --- /dev/null +++ b/src/e2eTest/resources/application-test.yml @@ -0,0 +1,73 @@ +micronaut: + server: + # Use a random port for testing to enable running tests with the application running as well as parallel execution + port: -1 + http: + client: + read-timeout: 90s + max-content-length: 20Mb + codec: + json: + additionalTypes: + - application/vnd.docker.distribution.manifest.list.v2+json + object-storage: + aws: + build-logs: + bucket: "${wave.build.logs.bucket}" +--- +datasources: + default: + url: "jdbc:h2:mem:test_mem" + driverClassName: "org.h2.Driver" + username: "sq" + password: "" + dialect: H2 + schema-generate: CREATE_DROP +--- +wave: + accounts: + foo: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + bar: "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7" + username: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" + registries: + default: docker.io + docker.io: + username: ${DOCKER_USER:test} + password: ${DOCKER_PAT:test} + quay.io: + username: ${QUAY_USER:test} + password: ${QUAY_PAT:test} + 195996028523.dkr.ecr.eu-west-1.amazonaws.com: + username: ${AWS_ACCESS_KEY_ID:test} + password: ${AWS_SECRET_ACCESS_KEY:test} + public.ecr.aws: + username: ${AWS_ACCESS_KEY_ID:test} + password: ${AWS_SECRET_ACCESS_KEY:test} + seqeralabs.azurecr.io: + username: ${AZURECR_USER:test} + password: ${AZURECR_PAT:test} + europe-southwest1-docker.pkg.dev: + credentials : ${GOOGLECR_KEYS:test} + quay.io/test/public/repo: + username: 'foo' + password: 'bar' + build: + workspace: 'build-workspace' + logs : + enabled : true + bucket : 'nextflow-ci' + prefix : 'wave-build/logs' + conda-lock-prefix: 'wave-build/conda-locks' + scan: + enabled: true +--- +logger: + levels: + io.micronaut.data.query: "DEBUG" +--- +redis : + pool : + enabled : false + health: + enabled: false +--- From 133b506b5943768ef9b361221ec39c8ae5a61f8a Mon Sep 17 00:00:00 2001 From: munishchouhan Date: Mon, 7 Oct 2024 16:52:23 +0200 Subject: [PATCH 2/4] minor change Signed-off-by: munishchouhan --- .github/workflows/build.yml | 2 +- build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a55bb34a8..602004145 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,7 +72,7 @@ jobs: AZURECR_PAT: ${{ secrets.AZURECR_PAT }} GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }} - - name: Tests + - name: E2E Tests if: "contains(github.event.head_commit.message, '[release]') || contains(github.event.head_commit.message, '[e2e test]')" run: | make e2eTest diff --git a/build.gradle b/build.gradle index 676b853ac..a4ea741f3 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ repositories { dependencies { annotationProcessor("io.micronaut:micronaut-http-validation") compileOnly("io.micronaut.data:micronaut-data-processor") - implementation("io.micronaut:micronaut-inject-groovy") + compileOnly("io.micronaut:micronaut-inject-groovy") compileOnly("io.micronaut:micronaut-http-validation") implementation("jakarta.persistence:jakarta.persistence-api:3.0.0") api 'io.seqera:lib-mail:1.0.0' @@ -206,9 +206,9 @@ jacocoTestReport { } configurations { + e2eTestCompileOnly.extendsFrom testCompileOnly, compileOnly e2eTestImplementation.extendsFrom testImplementation, implementation e2eTestRuntimeOnly.extendsFrom testRuntimeOnly, runtimeOnly - e2eTestAnnotationProcessor.extendsFrom testAnnotationProcessor } sourceSets { From ad19b956a5c83c0b175d72a13f4f23c5f9910120 Mon Sep 17 00:00:00 2001 From: munishchouhan Date: Mon, 7 Oct 2024 16:55:09 +0200 Subject: [PATCH 3/4] fixed tests [e2e test] Signed-off-by: munishchouhan --- .../groovy/io/seqera/wave/util/ContainerHelperTest.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/io/seqera/wave/util/ContainerHelperTest.groovy b/src/test/groovy/io/seqera/wave/util/ContainerHelperTest.groovy index a89e74ba5..056cdcf42 100644 --- a/src/test/groovy/io/seqera/wave/util/ContainerHelperTest.groovy +++ b/src/test/groovy/io/seqera/wave/util/ContainerHelperTest.groovy @@ -52,7 +52,7 @@ class ContainerHelperTest extends Specification { then: result =='''\ BootStrap: docker - From: mambaorg/micromamba:1.5.10-lunar + From: mambaorg/micromamba:1.5.10-noble %post micromamba install -y -n base -c conda-forge -c defaults -f https://foo.com/lock.yml micromamba install -y -n base foo::one bar::two @@ -79,7 +79,7 @@ class ContainerHelperTest extends Specification { then: result =='''\ - FROM mambaorg/micromamba:1.5.10-lunar + FROM mambaorg/micromamba:1.5.10-noble RUN \\ micromamba install -y -n base -c conda-forge -c defaults -f https://foo.com/lock.yml \\ && micromamba install -y -n base foo::one bar::two \\ @@ -107,7 +107,7 @@ class ContainerHelperTest extends Specification { then: result =='''\ BootStrap: docker - From: mambaorg/micromamba:1.5.10-lunar + From: mambaorg/micromamba:1.5.10-noble %files {{wave_context_dir}}/conda.yml /scratch/conda.yml %post @@ -135,7 +135,7 @@ class ContainerHelperTest extends Specification { then: result =='''\ - FROM mambaorg/micromamba:1.5.10-lunar + FROM mambaorg/micromamba:1.5.10-noble COPY --chown=$MAMBA_USER:$MAMBA_USER conda.yml /tmp/conda.yml RUN micromamba install -y -n base -f /tmp/conda.yml \\ && micromamba install -y -n base foo::one bar::two \\ From a6e7c3ba342c882930bb96741eddb90d29288514 Mon Sep 17 00:00:00 2001 From: munishchouhan Date: Mon, 7 Oct 2024 17:11:56 +0200 Subject: [PATCH 4/4] fixed tests [e2e test] Signed-off-by: munishchouhan --- .../wave/controller/ContainerControllerHttpE2ETest.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy index 712b501a3..5c6c51acb 100644 --- a/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy +++ b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy @@ -95,8 +95,7 @@ class ContainerControllerHttpE2ETest extends Specification implements AwsS3TestC def request = new SubmitContainerTokenRequest( packages: new PackagesSpec(channels: ['conda-forge'], entries: ['xz'], type: 'CONDA'), buildRepository: "test/repository", - cacheRepository: "test/cache", - containerPlatform: "arm64" + cacheRepository: "test/cache" ) and: