diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db1a9eff2f..9ed843e7eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,6 +74,23 @@ jobs: AZURECR_PAT: ${{ secrets.AZURECR_PAT }} GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }} + - name: E2E 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: | @@ -98,7 +115,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 ab044536b1..d5efcd3551 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 4f35e4803d..fbaf5d27f2 100644 --- a/build.gradle +++ b/build.gradle @@ -224,3 +224,29 @@ jacocoTestReport { })) } } + +configurations { + e2eTestCompileOnly.extendsFrom testCompileOnly, compileOnly + e2eTestImplementation.extendsFrom testImplementation, implementation + e2eTestRuntimeOnly.extendsFrom testRuntimeOnly, runtimeOnly +} + +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 0000000000..5c6c51acb9 --- /dev/null +++ b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy @@ -0,0 +1,137 @@ +/* + * 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" + + ) + 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 0000000000..71dfec879b --- /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 +---