1
0
Эх сурвалжийг харах

Refactor GCS test fixture to remove docker dependency (#94755)

Mark Vieira 2 жил өмнө
parent
commit
9502d39c96
22 өөрчлөгдсөн 449 нэмэгдсэн , 596 устгасан
  1. 26 117
      modules/repository-gcs/build.gradle
  2. 23 12
      modules/repository-gcs/src/internalClusterTest/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageThirdPartyTests.java
  3. 61 0
      modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/DefaultCredentialsRepositoryGcsClientYamlTestSuiteIT.java
  4. 22 0
      modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/LargeBlobRepositoryGcsClientYamlTestSuiteIT.java
  5. 46 0
      modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/RepositoryGcsClientYamlTestSuiteIT.java
  6. 0 14
      test/fixtures/gcs-fixture/Dockerfile
  7. 1 15
      test/fixtures/gcs-fixture/build.gradle
  8. 0 86
      test/fixtures/gcs-fixture/docker-compose.yml
  9. 26 20
      test/fixtures/gcs-fixture/src/main/java/fixture/gcs/GoogleCloudStorageHttpFixture.java
  10. 6 0
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalSpecBuilder.java
  11. 5 0
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalSpecBuilder.java
  12. 2 3
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/FileResource.java
  13. 6 2
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/Resource.java
  14. 8 3
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/StringResource.java
  15. 9 81
      x-pack/plugin/repositories-metering-api/qa/gcs/build.gradle
  16. 42 0
      x-pack/plugin/repositories-metering-api/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/repositories/metering/gcs/GCSRepositoriesMeteringIT.java
  17. 8 85
      x-pack/plugin/searchable-snapshots/qa/gcs/build.gradle
  18. 48 0
      x-pack/plugin/searchable-snapshots/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/searchablesnapshots/GCSSearchableSnapshotsIT.java
  19. 8 87
      x-pack/plugin/snapshot-based-recoveries/qa/gcs/build.gradle
  20. 45 0
      x-pack/plugin/snapshot-based-recoveries/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/snapshotbasedrecoveries/recovery/GCSSnapshotBasedRecoveryIT.java
  21. 8 71
      x-pack/plugin/snapshot-repo-test-kit/qa/gcs/build.gradle
  22. 49 0
      x-pack/plugin/snapshot-repo-test-kit/qa/gcs/src/javaRestTest/java/org/elasticsearch/repositories/blobstore/testkit/GCSSnapshotRepoTestKitIT.java

+ 26 - 117
modules/repository-gcs/build.gradle

@@ -1,14 +1,3 @@
-import org.apache.tools.ant.filters.ReplaceTokens
-import org.elasticsearch.gradle.internal.info.BuildParams
-import org.elasticsearch.gradle.internal.test.RestIntegTestTask
-import org.elasticsearch.gradle.internal.test.rest.LegacyYamlRestTestPlugin
-import org.elasticsearch.gradle.internal.test.InternalClusterTestPlugin
-
-import java.nio.file.Files
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
 /*
  * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
  * or more contributor license agreements. Licensed under the Elastic License
@@ -16,7 +5,15 @@ import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
  * in compliance with, at your election, the Elastic License 2.0 or the Server
  * Side Public License, v 1.
  */
-apply plugin: 'elasticsearch.legacy-yaml-rest-test'
+
+
+import org.apache.tools.ant.filters.ReplaceTokens
+import org.elasticsearch.gradle.internal.info.BuildParams
+import org.elasticsearch.gradle.internal.test.InternalClusterTestPlugin
+
+import java.nio.file.Files
+
+apply plugin: 'elasticsearch.internal-yaml-rest-test'
 apply plugin: 'elasticsearch.internal-cluster-test'
 
 esplugin {
@@ -60,6 +57,7 @@ dependencies {
   testImplementation "org.apache.httpcomponents:httpcore:${versions.httpcore}"
 
   testImplementation project(':test:fixtures:gcs-fixture')
+  yamlRestTestImplementation project(':test:fixtures:gcs-fixture')
 }
 
 restResources {
@@ -192,60 +190,23 @@ tasks.named("thirdPartyAudit").configure {
 }
 
 boolean useFixture = false
-
-def fixtureAddress = { fixture ->
-  assert useFixture: 'closure should not be used without a fixture'
-  int ephemeralPort = project(':test:fixtures:gcs-fixture').postProcessFixture.ext."test.fixtures.${fixture}.tcp.80"
-  assert ephemeralPort > 0
-  'http://127.0.0.1:' + ephemeralPort
-}
-
 String gcsServiceAccount = System.getenv("google_storage_service_account")
 String gcsBucket = System.getenv("google_storage_bucket")
 String gcsBasePath = System.getenv("google_storage_base_path")
-File serviceAccountFile = null
+File serviceAccountFile = gcsServiceAccount != null ? new File(gcsServiceAccount) : null
 
 if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
-  serviceAccountFile = new File(project.buildDir, 'generated-resources/service_account_test.json')
   gcsBucket = 'bucket'
   gcsBasePath = 'integration_test'
   useFixture = true
-
-  apply plugin: 'elasticsearch.test.fixtures'
-  testFixtures.useFixture(':test:fixtures:gcs-fixture', 'gcs-fixture')
-  testFixtures.useFixture(':test:fixtures:gcs-fixture', 'gcs-fixture-third-party')
-  testFixtures.useFixture(':test:fixtures:gcs-fixture', 'gcs-fixture-with-application-default-credentials')
-
 } else if (!gcsServiceAccount || !gcsBucket || !gcsBasePath) {
   throw new IllegalArgumentException("not all options specified to run tests against external GCS service are present")
-} else {
-  serviceAccountFile = new File(gcsServiceAccount)
 }
 
 def encodedCredentials = {
   Base64.encoder.encodeToString(Files.readAllBytes(serviceAccountFile.toPath()))
 }
 
-/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
-tasks.register("createServiceAccountFile") {
-  doLast {
-    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
-    keyPairGenerator.initialize(2048)
-    KeyPair keyPair = keyPairGenerator.generateKeyPair()
-    String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded())
-
-    serviceAccountFile.parentFile.mkdirs()
-    serviceAccountFile.setText("{\n" +
-            '  "type": "service_account",\n' +
-            '  "project_id": "integration_test",\n' +
-            '  "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
-            '  "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
-            '  "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
-            '  "client_id": "123456789101112130594"\n' +
-            '}', 'UTF-8')
-  }
-}
-
 Map<String, Object> expansions = [
   'bucket'   : gcsBucket,
   'base_path': gcsBasePath + "_integration_tests"
@@ -261,86 +222,34 @@ tasks.named("internalClusterTest").configure {
   exclude '**/GoogleCloudStorageThirdPartyTests.class'
 }
 
-tasks.named("yamlRestTest").configure {
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
+tasks.named("yamlRestTest") {
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
+  if (useFixture == false) {
+    systemProperty 'test.google.account', serviceAccountFile
+    // We can't run these test in parallel against a real bucket since the tests will step on each other
+    maxParallelForks = 1
   }
 }
 
-/*
- * We only use a small amount of data in these tests, which means that the resumable upload path is not tested. We add
- * an additional test that forces the large blob threshold to be small to exercise the resumable upload path.
- */
-def largeBlobYamlRestTest = tasks.register("largeBlobYamlRestTest", RestIntegTestTask) {
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
-  }
-  SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
-  SourceSet yamlRestTestSourceSet = sourceSets.getByName(LegacyYamlRestTestPlugin.SOURCE_SET_NAME)
-  setTestClassesDirs(yamlRestTestSourceSet.getOutput().getClassesDirs())
-  setClasspath(yamlRestTestSourceSet.getRuntimeClasspath())
-
-  // We have to wait for configure the cluster here as it might not have been created otherwise yet.
-  testClusters {
-    largeBlobYamlRestTest {
-      module tasks.named("explodedBundlePlugin")
-
-      // force large blob uploads by setting the threshold small, forcing this code path to be tested
-      systemProperty 'es.repository_gcs.large_blob_threshold_byte_size', '256'
-    }
-  }
-}
-
-def gcsThirdPartyTest = tasks.register("gcsThirdPartyTest", Test) {
-  SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
+def gcsThirdPartyTest = tasks.register("gcsThirdPartyUnitTest", Test) {
+  SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class)
   SourceSet internalTestSourceSet = sourceSets.getByName(InternalClusterTestPlugin.SOURCE_SET_NAME)
   setTestClassesDirs(internalTestSourceSet.getOutput().getClassesDirs())
   setClasspath(internalTestSourceSet.getRuntimeClasspath())
   include '**/GoogleCloudStorageThirdPartyTests.class'
   systemProperty 'tests.security.manager', false
   systemProperty 'test.google.bucket', gcsBucket
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
   nonInputProperties.systemProperty 'test.google.base', gcsBasePath + "_third_party_tests_" + BuildParams.testSeed
-  nonInputProperties.systemProperty 'test.google.account', "${-> encodedCredentials.call()}"
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
-    nonInputProperties.systemProperty 'test.google.endpoint', "${-> fixtureAddress('gcs-fixture-third-party')}"
-    nonInputProperties.systemProperty 'test.google.tokenURI', "${-> fixtureAddress('gcs-fixture-third-party')}/o/oauth2/token"
-  }
-}
-
-testClusters.matching {
-  it.name == "yamlRestTest" ||
-  it.name == "largeBlobYamlRestTest" ||
-  it.name == "gcsThirdPartyTest" }.configureEach {
-  keystore 'gcs.client.integration_test.credentials_file', serviceAccountFile, IGNORE_VALUE
-
-  if (useFixture) {
-    /* Use a closure on the string to delay evaluation until tests are executed */
-    setting 'gcs.client.integration_test.endpoint', { "${-> fixtureAddress('gcs-fixture')}" }, IGNORE_VALUE
-    setting 'gcs.client.integration_test.token_uri', { "${-> fixtureAddress('gcs-fixture')}/o/oauth2/token" }, IGNORE_VALUE
-  } else {
-    println "Using an external service to test the repository-gcs plugin"
+  if (useFixture == false) {
+    nonInputProperties.systemProperty 'test.google.account', "${-> encodedCredentials.call()}"
   }
 }
 
-
-// Application Default Credentials
-if (useFixture) {
-  tasks.register("yamlRestTestApplicationDefaultCredentials", RestIntegTestTask.class) {
-    SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
-    SourceSet yamlRestTestSourceSet = sourceSets.getByName(LegacyYamlRestTestPlugin.SOURCE_SET_NAME)
-    setTestClassesDirs(yamlRestTestSourceSet.getOutput().getClassesDirs())
-    setClasspath(yamlRestTestSourceSet.getRuntimeClasspath())
-  }
-  tasks.named("check").configure { dependsOn("yamlRestTestApplicationDefaultCredentials") }
-
-  testClusters.matching { it.name == "yamlRestTestApplicationDefaultCredentials" }.configureEach {
-    setting 'gcs.client.integration_test.endpoint', { "${-> fixtureAddress('gcs-fixture-with-application-default-credentials')}" }, IGNORE_VALUE
-    module tasks.named("explodedBundlePlugin")
-    environment 'GCE_METADATA_HOST', { "${-> fixtureAddress('gcs-fixture-with-application-default-credentials')}".replace("http://", "") }, IGNORE_VALUE
-  }
+tasks.register('gcsThirdPartyTest') {
+  dependsOn 'yamlRestTest', gcsThirdPartyTest
 }
 
-tasks.named("check").configure {
-  dependsOn(largeBlobYamlRestTest, gcsThirdPartyTest)
+tasks.named('check') {
+  dependsOn gcsThirdPartyTest
 }

+ 23 - 12
modules/repository-gcs/src/internalClusterTest/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageThirdPartyTests.java

@@ -8,13 +8,17 @@
 
 package org.elasticsearch.repositories.gcs;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
-import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.MockSecureSettings;
 import org.elasticsearch.common.settings.SecureSettings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.Booleans;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.repositories.AbstractThirdPartyRepositoryTestCase;
+import org.junit.ClassRule;
 
 import java.util.Base64;
 import java.util.Collection;
@@ -24,6 +28,10 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
 
 public class GoogleCloudStorageThirdPartyTests extends AbstractThirdPartyRepositoryTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    @ClassRule
+    public static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
 
     @Override
     protected Collection<Class<? extends Plugin>> getPlugins() {
@@ -34,12 +42,9 @@ public class GoogleCloudStorageThirdPartyTests extends AbstractThirdPartyReposit
     protected Settings nodeSettings() {
         Settings.Builder builder = Settings.builder().put(super.nodeSettings());
 
-        if (Strings.isNullOrEmpty(System.getProperty("test.google.endpoint")) == false) {
-            builder.put("gcs.client.default.endpoint", System.getProperty("test.google.endpoint"));
-        }
-
-        if (Strings.isNullOrEmpty(System.getProperty("test.google.tokenURI")) == false) {
-            builder.put("gcs.client.default.token_uri", System.getProperty("test.google.tokenURI"));
+        if (USE_FIXTURE) {
+            builder.put("gcs.client.default.endpoint", fixture.getAddress());
+            builder.put("gcs.client.default.token_uri", fixture.getAddress() + "/o/oauth2/token");
         }
 
         return builder.build();
@@ -47,14 +52,20 @@ public class GoogleCloudStorageThirdPartyTests extends AbstractThirdPartyReposit
 
     @Override
     protected SecureSettings credentials() {
-        assertThat(System.getProperty("test.google.account"), not(blankOrNullString()));
+        if (USE_FIXTURE == false) {
+            assertThat(System.getProperty("test.google.account"), not(blankOrNullString()));
+        }
         assertThat(System.getProperty("test.google.bucket"), not(blankOrNullString()));
 
         MockSecureSettings secureSettings = new MockSecureSettings();
-        secureSettings.setFile(
-            "gcs.client.default.credentials_file",
-            Base64.getDecoder().decode(System.getProperty("test.google.account"))
-        );
+        if (USE_FIXTURE) {
+            secureSettings.setFile("gcs.client.default.credentials_file", TestUtils.createServiceAccount(random()));
+        } else {
+            secureSettings.setFile(
+                "gcs.client.default.credentials_file",
+                Base64.getDecoder().decode(System.getProperty("test.google.account"))
+            );
+        }
         return secureSettings;
     }
 

+ 61 - 0
modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/DefaultCredentialsRepositoryGcsClientYamlTestSuiteIT.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.repositories.gcs;
+
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
+import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+public class DefaultCredentialsRepositoryGcsClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(
+        true,
+        "bucket",
+        "computeMetadata/v1/instance/service-accounts/default/token"
+    );
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .module("repository-gcs")
+        .setting("gcs.client.integration_test.endpoint", () -> fixture.getAddress())
+        .environment("GCE_METADATA_HOST", () -> fixture.getAddress().replace("http://", ""))
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
+
+    public DefaultCredentialsRepositoryGcsClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
+        super(testCandidate);
+    }
+
+    @BeforeClass
+    public static void checkFixtureEnabled() {
+        assumeTrue("Only run against test fixture", USE_FIXTURE);
+    }
+
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
+
+    @ParametersFactory
+    public static Iterable<Object[]> parameters() throws Exception {
+        return createParameters();
+    }
+}

+ 22 - 0
modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/LargeBlobRepositoryGcsClientYamlTestSuiteIT.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.repositories.gcs;
+
+import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
+
+public class LargeBlobRepositoryGcsClientYamlTestSuiteIT extends RepositoryGcsClientYamlTestSuiteIT {
+
+    static {
+        clusterConfig = c -> c.systemProperty("es.repository_gcs.large_blob_threshold_byte_size", "256");
+    }
+
+    public LargeBlobRepositoryGcsClientYamlTestSuiteIT(ClientYamlTestCandidate testCandidate) {
+        super(testCandidate);
+    }
+}

+ 46 - 0
modules/repository-gcs/src/yamlRestTest/java/org/elasticsearch/repositories/gcs/RepositoryGcsClientYamlTestSuiteIT.java

@@ -8,18 +8,64 @@
 
 package org.elasticsearch.repositories.gcs;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import com.carrotsearch.randomizedtesting.annotations.Name;
 import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider;
+import org.elasticsearch.test.cluster.util.resource.Resource;
 import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
 import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.nio.charset.StandardCharsets;
 
 public class RepositoryGcsClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
+
+    protected static LocalClusterConfigProvider clusterConfig = b -> {};
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .module("repository-gcs")
+        .setting("gcs.client.integration_test.endpoint", () -> fixture.getAddress(), s -> USE_FIXTURE)
+        .setting("gcs.client.integration_test.token_uri", () -> fixture.getAddress() + "/o/oauth2/token", s -> USE_FIXTURE)
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                c.keystore(
+                    "gcs.client.integration_test.credentials_file",
+                    Resource.fromString(() -> new String(TestUtils.createServiceAccount(random()), StandardCharsets.UTF_8))
+                );
+            } else {
+                c.keystore(
+                    "gcs.client.integration_test.credentials_file",
+                    Resource.fromFile(PathUtils.get(System.getProperty("test.google.account")))
+                );
+            }
+        })
+        .apply(() -> clusterConfig)
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
 
     public RepositoryGcsClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
         super(testCandidate);
     }
 
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
+
     @ParametersFactory
     public static Iterable<Object[]> parameters() throws Exception {
         return createParameters();

+ 0 - 14
test/fixtures/gcs-fixture/Dockerfile

@@ -1,14 +0,0 @@
-FROM openjdk:17.0.2
-
-ARG port
-ARG bucket
-ARG token
-
-ENV GCS_FIXTURE_PORT=${port}
-ENV GCS_FIXTURE_BUCKET=${bucket}
-ENV GCS_FIXTURE_TOKEN=${token}
-
-ENTRYPOINT exec java -classpath "/fixture/shared/*" \
-    fixture.gcs.GoogleCloudStorageHttpFixture 0.0.0.0 "$GCS_FIXTURE_PORT" "$GCS_FIXTURE_BUCKET" "$GCS_FIXTURE_TOKEN"
-
-EXPOSE $port

+ 1 - 15
test/fixtures/gcs-fixture/build.gradle

@@ -6,25 +6,11 @@
  * Side Public License, v 1.
  */
 apply plugin: 'elasticsearch.java'
-apply plugin: 'elasticsearch.test.fixtures'
 
 description = 'Fixture for Google Cloud Storage service'
 tasks.named("test").configure { enabled = false }
 
 dependencies {
   api project(':server')
-}
-
-def jarTask = tasks.named("jar")
-tasks.named("preProcessFixture").configure {
-  dependsOn jarTask, configurations.runtimeClasspath
-  inputs.files(jarTask, configurations.runtimeClasspath)
-  doLast {
-    file("${testFixturesDir}/shared").mkdirs()
-    project.copy {
-      from jar
-      from configurations.runtimeClasspath
-      into "${testFixturesDir}/shared"
-    }
-  }
+  api project(':test:framework')
 }

+ 0 - 86
test/fixtures/gcs-fixture/docker-compose.yml

@@ -1,86 +0,0 @@
-version: '3'
-services:
-  gcs-fixture:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-third-party:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-other:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-repositories-metering:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-repository-test-kit:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-snapshots-based-recoveries:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "o/oauth2/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"
-  gcs-fixture-with-application-default-credentials:
-    build:
-      context: .
-      args:
-        port: 80
-        bucket: "bucket"
-        token: "computeMetadata/v1/instance/service-accounts/default/token"
-      dockerfile: Dockerfile
-    volumes:
-      - ./testfixtures_shared/shared:/fixture/shared
-    ports:
-      - "80"

+ 26 - 20
test/fixtures/gcs-fixture/src/main/java/fixture/gcs/GoogleCloudStorageHttpFixture.java

@@ -9,37 +9,43 @@ package fixture.gcs;
 
 import com.sun.net.httpserver.HttpServer;
 
-import java.io.IOException;
+import org.junit.rules.ExternalResource;
+
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 
-public class GoogleCloudStorageHttpFixture {
+public class GoogleCloudStorageHttpFixture extends ExternalResource {
+
+    private final boolean enabled;
+    private final String bucket;
+    private final String token;
+    private HttpServer server;
 
-    private final HttpServer server;
+    public GoogleCloudStorageHttpFixture(boolean enabled, final String bucket, final String token) {
+        this.enabled = enabled;
+        this.bucket = bucket;
+        this.token = token;
+    }
 
-    private GoogleCloudStorageHttpFixture(final String address, final int port, final String bucket, final String token)
-        throws IOException {
-        this.server = HttpServer.create(new InetSocketAddress(InetAddress.getByName(address), port), 0);
-        server.createContext("/" + token, new FakeOAuth2HttpHandler());
-        server.createContext("/computeMetadata/v1/project/project-id", new FakeProjectIdHttpHandler());
-        server.createContext("/", new GoogleCloudStorageHttpHandler(bucket));
+    public String getAddress() {
+        return "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort();
     }
 
-    private void start() throws Exception {
-        try {
+    @Override
+    protected void before() throws Throwable {
+        if (enabled) {
+            this.server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+            server.createContext("/" + token, new FakeOAuth2HttpHandler());
+            server.createContext("/computeMetadata/v1/project/project-id", new FakeProjectIdHttpHandler());
+            server.createContext("/", new GoogleCloudStorageHttpHandler(bucket));
             server.start();
-            // wait to be killed
-            Thread.sleep(Long.MAX_VALUE);
-        } finally {
-            server.stop(0);
         }
     }
 
-    public static void main(final String[] args) throws Exception {
-        if (args == null || args.length != 4) {
-            throw new IllegalArgumentException("GoogleCloudStorageHttpFixture expects 4 arguments [address, port, bucket, token]");
+    @Override
+    protected void after() {
+        if (enabled) {
+            server.stop(0);
         }
-        GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(args[0], Integer.parseInt(args[1]), args[2], args[3]);
-        fixture.start();
     }
 }

+ 6 - 0
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalSpecBuilder.java

@@ -96,6 +96,12 @@ public abstract class AbstractLocalSpecBuilder<T extends LocalSpecBuilder<?>> im
         return cast(this);
     }
 
+    @Override
+    public T environment(String key, Supplier<String> supplier) {
+        this.environmentProviders.add(s -> Map.of(key, supplier.get()));
+        return cast(this);
+    }
+
     Map<String, String> getEnvironment() {
         return inherit(() -> parent.getEnvironment(), environment);
     }

+ 5 - 0
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalSpecBuilder.java

@@ -50,6 +50,11 @@ interface LocalSpecBuilder<T extends LocalSpecBuilder<?>> {
      */
     T environment(String key, String value);
 
+    /**
+     * Add a new node environment variable computed by the given supplier.
+     */
+    T environment(String key, Supplier<String> supplier);
+
     /**
      * Set the cluster {@link DistributionType}. By default, the {@link DistributionType#INTEG_TEST} distribution is used.
      */

+ 2 - 3
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/FileResource.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.test.cluster.util.resource;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
@@ -19,8 +18,8 @@ import java.nio.file.StandardOpenOption;
 public class FileResource implements Resource {
     private final Path file;
 
-    FileResource(File file) {
-        this.file = file.toPath();
+    FileResource(Path file) {
+        this.file = file;
     }
 
     @Override

+ 6 - 2
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/Resource.java

@@ -8,13 +8,13 @@
 
 package org.elasticsearch.test.cluster.util.resource;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
+import java.util.function.Supplier;
 
 public interface Resource {
     InputStream asStream();
@@ -23,11 +23,15 @@ public interface Resource {
         return new StringResource(text);
     }
 
+    static Resource fromString(Supplier<String> supplier) {
+        return new StringResource(supplier);
+    }
+
     static Resource fromClasspath(String path) {
         return new ClasspathResource(path);
     }
 
-    static Resource fromFile(File file) {
+    static Resource fromFile(Path file) {
         return new FileResource(file);
     }
 

+ 8 - 3
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/resource/StringResource.java

@@ -10,17 +10,22 @@ package org.elasticsearch.test.cluster.util.resource;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.util.function.Supplier;
 
 class StringResource implements Resource {
-    private final String text;
+    private final Supplier<String> text;
 
     StringResource(String text) {
-        this.text = text;
+        this.text = () -> text;
+    }
+
+    StringResource(Supplier<String> supplier) {
+        this.text = supplier;
     }
 
     @Override
     public InputStream asStream() {
-        return new ByteArrayInputStream(text.getBytes());
+        return new ByteArrayInputStream(text.get().getBytes());
     }
 
 }

+ 9 - 81
x-pack/plugin/repositories-metering-api/qa/gcs/build.gradle

@@ -5,39 +5,25 @@
  * in compliance with, at your election, the Elastic License 2.0 or the Server
  * Side Public License, v 1.
  */
-import org.elasticsearch.gradle.internal.info.BuildParams
-import org.apache.tools.ant.filters.ReplaceTokens
-
-import java.nio.file.Files
-import java.security.KeyPair
-import java.security.KeyPairGenerator
 
-import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
-
-apply plugin: 'elasticsearch.legacy-java-rest-test'
-apply plugin: 'elasticsearch.rest-resources'
+import org.elasticsearch.gradle.internal.info.BuildParams
 
-final Project fixture = project(':test:fixtures:gcs-fixture')
+apply plugin: 'elasticsearch.internal-java-rest-test'
 
 dependencies {
+  clusterModules project(':modules:repository-gcs')
+  clusterModules project(xpackModule('repositories-metering-api'))
   javaRestTestImplementation(testArtifact(project(xpackModule('repositories-metering-api'))))
-}
-
-restResources {
-  restApi {
-    include 'indices', 'bulk', 'snapshot', 'nodes', '_common', 'repositories-metering-api'
-  }
+  javaRestTestImplementation(project(':test:fixtures:gcs-fixture'))
 }
 
 boolean useFixture = false
-
 String gcsServiceAccount = System.getenv("google_storage_service_account")
 String gcsBucket = System.getenv("google_storage_bucket")
 String gcsBasePath = System.getenv("google_storage_base_path")
-
 File serviceAccountFile = null
+
 if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
-  serviceAccountFile = new File(project.buildDir, 'generated-resources/service_account_test.json')
   gcsBucket = 'bucket'
   gcsBasePath = 'integration_test'
   useFixture = true
@@ -47,70 +33,12 @@ if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
   serviceAccountFile = new File(gcsServiceAccount)
 }
 
-testClusters.configureEach {
-  setting 'xpack.security.enabled', 'false'
-}
-
-/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
-tasks.register("createServiceAccountFile") {
-  doLast {
-    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
-    keyPairGenerator.initialize(2048)
-    KeyPair keyPair = keyPairGenerator.generateKeyPair()
-    String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded())
-
-    serviceAccountFile.parentFile.mkdirs()
-    serviceAccountFile.setText("{\n" +
-      '  "type": "service_account",\n' +
-      '  "project_id": "integration_test",\n' +
-      '  "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
-      '  "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
-      '  "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
-      '  "client_id": "123456789101112130594"\n' +
-      '}', 'UTF-8')
-  }
-}
-
-def fixtureAddress = { f ->
-  assert useFixture: 'closure should not be used without a fixture'
-  int ephemeralPort = project(':test:fixtures:gcs-fixture').postProcessFixture.ext."test.fixtures.${f}.tcp.80"
-  assert ephemeralPort > 0
-  'http://127.0.0.1:' + ephemeralPort
-}
-
-Map<String, Object> expansions = [
-  'bucket'   : gcsBucket,
-  'base_path': gcsBasePath + "_integration_tests"
-]
-
-tasks.named("processTestResources").configure {
-  inputs.properties(expansions)
-  filter("tokens" : expansions, ReplaceTokens.class)
-}
-
-if (useFixture) {
-  apply plugin: 'elasticsearch.test.fixtures'
-  testFixtures.useFixture(fixture.path, 'gcs-fixture-repositories-metering')
-}
-
 tasks.named("javaRestTest").configure {
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
-  }
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
   systemProperty 'test.gcs.bucket', gcsBucket
   nonInputProperties.systemProperty 'test.gcs.base_path', gcsBasePath + "_repositories_metering" + BuildParams.testSeed
-}
-
-testClusters.matching { it.name == "javaRestTest" }.configureEach {
-  testDistribution = 'DEFAULT'
-
-  keystore 'gcs.client.repositories_metering.credentials_file', serviceAccountFile, IGNORE_VALUE
-  if (useFixture) {
-    /* Use a closure on the string to delay evaluation until tests are executed */
-    setting 'gcs.client.repositories_metering.endpoint', { "${-> fixtureAddress('gcs-fixture-repositories-metering')}" }, IGNORE_VALUE
-    setting 'gcs.client.repositories_metering.token_uri', { "${-> fixtureAddress('gcs-fixture-repositories-metering')}/o/oauth2/token" }, IGNORE_VALUE
-  } else {
-    println "Using an external service to test " + project.name
+  if (useFixture == false) {
+    systemProperty 'test.google.account', serviceAccountFile
   }
 }
 

+ 42 - 0
x-pack/plugin/repositories-metering-api/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/repositories/metering/gcs/GCSRepositoriesMeteringIT.java

@@ -6,13 +6,55 @@
  */
 package org.elasticsearch.xpack.repositories.metering.gcs;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.util.resource.Resource;
 import org.elasticsearch.xpack.repositories.metering.AbstractRepositoriesMeteringAPIRestTestCase;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
 
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 
 public class GCSRepositoriesMeteringIT extends AbstractRepositoriesMeteringAPIRestTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .module("repository-gcs")
+        .module("repositories-metering-api")
+        .setting("gcs.client.repositories_metering.endpoint", () -> fixture.getAddress(), s -> USE_FIXTURE)
+        .setting("gcs.client.repositories_metering.token_uri", () -> fixture.getAddress() + "/o/oauth2/token", s -> USE_FIXTURE)
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                c.keystore(
+                    "gcs.client.repositories_metering.credentials_file",
+                    Resource.fromString(() -> new String(TestUtils.createServiceAccount(random()), StandardCharsets.UTF_8))
+                );
+            } else {
+                c.keystore(
+                    "gcs.client.repositories_metering.credentials_file",
+                    Resource.fromFile(PathUtils.get(System.getProperty("test.google.account")))
+                );
+            }
+        })
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
+
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
 
     @Override
     protected String repositoryType() {

+ 8 - 85
x-pack/plugin/searchable-snapshots/qa/gcs/build.gradle

@@ -1,36 +1,22 @@
-import org.apache.tools.ant.filters.ReplaceTokens
 import org.elasticsearch.gradle.internal.info.BuildParams
 
-import java.nio.file.Files
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
-
-apply plugin: 'elasticsearch.legacy-java-rest-test'
-apply plugin: 'elasticsearch.rest-resources'
-
-final Project fixture = project(':test:fixtures:gcs-fixture')
+apply plugin: 'elasticsearch.internal-java-rest-test'
 
 dependencies {
+  clusterModules project(':modules:repository-gcs')
+  clusterModules project(':modules:lang-painless')
+  clusterModules project(xpackModule('searchable-snapshots'))
   javaRestTestImplementation(testArtifact(project(xpackModule('searchable-snapshots'))))
-}
-
-restResources {
-  restApi {
-    include 'indices', 'search', 'bulk', 'snapshot', 'nodes', '_common', 'searchable_snapshots'
-  }
+  javaRestTestImplementation(project(':test:fixtures:gcs-fixture'))
 }
 
 boolean useFixture = false
-
 String gcsServiceAccount = System.getenv("google_storage_service_account")
 String gcsBucket = System.getenv("google_storage_bucket")
 String gcsBasePath = System.getenv("google_storage_base_path")
 
 File serviceAccountFile = null
 if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
-  serviceAccountFile = new File(project.buildDir, 'generated-resources/service_account_test.json')
   gcsBucket = 'bucket'
   gcsBasePath = 'integration_test'
   useFixture = true
@@ -40,79 +26,16 @@ if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
   serviceAccountFile = new File(gcsServiceAccount)
 }
 
-/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
-tasks.register("createServiceAccountFile") {
-  doLast {
-    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
-    keyPairGenerator.initialize(2048)
-    KeyPair keyPair = keyPairGenerator.generateKeyPair()
-    String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded())
-
-    serviceAccountFile.parentFile.mkdirs()
-    serviceAccountFile.setText("{\n" +
-      '  "type": "service_account",\n' +
-      '  "project_id": "integration_test",\n' +
-      '  "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
-      '  "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
-      '  "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
-      '  "client_id": "123456789101112130594"\n' +
-      '}', 'UTF-8')
-  }
-}
-
-def fixtureAddress = { f ->
-  assert useFixture: 'closure should not be used without a fixture'
-  int ephemeralPort = project(':test:fixtures:gcs-fixture').postProcessFixture.ext."test.fixtures.${f}.tcp.80"
-  assert ephemeralPort > 0
-  'http://127.0.0.1:' + ephemeralPort
-}
-
-Map<String, Object> expansions = [
-  'bucket'   : gcsBucket,
-  'base_path': gcsBasePath + "_integration_tests"
-]
-
-tasks.named("processTestResources").configure {
-  inputs.properties(expansions)
-  filter("tokens" : expansions, ReplaceTokens.class)
-}
-
-if (useFixture) {
-  apply plugin: 'elasticsearch.test.fixtures'
-  testFixtures.useFixture(fixture.path, 'gcs-fixture-other')
-}
-
 tasks.named("javaRestTest").configure {
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
   systemProperty 'test.gcs.bucket', gcsBucket
   nonInputProperties.systemProperty 'test.gcs.base_path', gcsBasePath + "_searchable_snapshots_tests" + BuildParams.testSeed
 
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
+  if (useFixture == false) {
+    systemProperty 'test.google.account', serviceAccountFile
   }
 }
 
-testClusters.matching { it.name == "javaRestTest" }.configureEach {
-  testDistribution = 'DEFAULT'
-
-  keystore 'gcs.client.searchable_snapshots.credentials_file', serviceAccountFile, IGNORE_VALUE
-  if (useFixture) {
-    /* Use a closure on the string to delay evaluation until tests are executed */
-    setting 'gcs.client.searchable_snapshots.endpoint', { "${-> fixtureAddress('gcs-fixture-other')}" }, IGNORE_VALUE
-    setting 'gcs.client.searchable_snapshots.token_uri', { "${-> fixtureAddress('gcs-fixture-other')}/o/oauth2/token" }, IGNORE_VALUE
-  } else {
-    println "Using an external service to test " + project.name
-  }
-
-  setting 'xpack.license.self_generated.type', 'trial'
-
-  setting 'xpack.searchable.snapshot.shared_cache.size', '16MB'
-  setting 'xpack.searchable.snapshot.shared_cache.region_size', '256KB'
-  setting 'xpack.searchable_snapshots.cache_fetch_async_thread_pool.keep_alive', '0ms'
-
-  setting 'xpack.security.enabled', 'false'
-}
-
-
 tasks.register("gcsThirdPartyTest")  {
   dependsOn "javaRestTest"
 }

+ 48 - 0
x-pack/plugin/searchable-snapshots/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/searchablesnapshots/GCSSearchableSnapshotsIT.java

@@ -7,12 +7,60 @@
 
 package org.elasticsearch.xpack.searchablesnapshots;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.util.resource.Resource;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.nio.charset.StandardCharsets;
 
 import static org.hamcrest.Matchers.blankOrNullString;
 import static org.hamcrest.Matchers.not;
 
 public class GCSSearchableSnapshotsIT extends AbstractSearchableSnapshotsRestTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .module("repository-gcs")
+        .module("searchable-snapshots")
+        .module("lang-painless")
+        .setting("gcs.client.searchable_snapshots.endpoint", () -> fixture.getAddress(), s -> USE_FIXTURE)
+        .setting("gcs.client.searchable_snapshots.token_uri", () -> fixture.getAddress() + "/o/oauth2/token", s -> USE_FIXTURE)
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                c.keystore(
+                    "gcs.client.searchable_snapshots.credentials_file",
+                    Resource.fromString(() -> new String(TestUtils.createServiceAccount(random()), StandardCharsets.UTF_8))
+                );
+            } else {
+                c.keystore(
+                    "gcs.client.searchable_snapshots.credentials_file",
+                    Resource.fromFile(PathUtils.get(System.getProperty("test.google.account")))
+                );
+            }
+        })
+        .setting("xpack.license.self_generated.type", "trial")
+        .setting("xpack.searchable.snapshot.shared_cache.size", "16MB")
+        .setting("xpack.searchable.snapshot.shared_cache.region_size", "256KB")
+        .setting("xpack.searchable_snapshots.cache_fetch_async_thread_pool.keep_alive", "0ms")
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
+
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
 
     @Override
     protected String writeRepositoryType() {

+ 8 - 87
x-pack/plugin/snapshot-based-recoveries/qa/gcs/build.gradle

@@ -1,35 +1,21 @@
-import org.apache.tools.ant.filters.ReplaceTokens
 import org.elasticsearch.gradle.internal.info.BuildParams
 
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
-
-apply plugin: 'elasticsearch.legacy-java-rest-test'
-apply plugin: 'elasticsearch.rest-resources'
-
-final Project fixture = project(':test:fixtures:gcs-fixture')
+apply plugin: 'elasticsearch.internal-java-rest-test'
 
 dependencies {
+  clusterModules project(':modules:repository-gcs')
+  clusterModules project(xpackModule('snapshot-based-recoveries'))
   javaRestTestImplementation(testArtifact(project(xpackModule('snapshot-based-recoveries'))))
-}
-
-restResources {
-  restApi {
-    include 'indices', 'search', 'bulk', 'snapshot'
-  }
+  javaRestTestImplementation(project(':test:fixtures:gcs-fixture'))
 }
 
 boolean useFixture = false
-
 String gcsServiceAccount = System.getenv("google_storage_service_account")
 String gcsBucket = System.getenv("google_storage_bucket")
 String gcsBasePath = System.getenv("google_storage_base_path")
-
 File serviceAccountFile = null
+
 if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
-  serviceAccountFile = new File(project.buildDir, 'generated-resources/service_account_test.json')
   gcsBucket = 'bucket'
   gcsBasePath = 'integration_test'
   useFixture = true
@@ -39,81 +25,16 @@ if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
   serviceAccountFile = new File(gcsServiceAccount)
 }
 
-/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
-tasks.register("createServiceAccountFile") {
-  doLast {
-    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
-    keyPairGenerator.initialize(2048)
-    KeyPair keyPair = keyPairGenerator.generateKeyPair()
-    String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded())
-
-    serviceAccountFile.parentFile.mkdirs()
-    serviceAccountFile.setText(
-      """
-      {
-        "type": "service_account",
-        "project_id": "integration_test",
-        "private_key_id": "${UUID.randomUUID().toString()}",
-
-        "private_key": "-----BEGIN PRIVATE KEY-----\\n${encodedKey}\\n-----END PRIVATE KEY-----\\n",
-        "client_email": "integration_test@appspot.gserviceaccount.com",
-        "client_id": "123456789101112130594"
-      }
-    """
-    )
-  }
-}
-
-def fixtureAddress = { f ->
-  assert useFixture: 'closure should not be used without a fixture'
-  int ephemeralPort = project(':test:fixtures:gcs-fixture').postProcessFixture.ext."test.fixtures.${f}.tcp.80"
-  assert ephemeralPort > 0
-  'http://127.0.0.1:' + ephemeralPort
-}
-
-Map<String, Object> expansions = [
-  'bucket'   : gcsBucket,
-  'base_path': gcsBasePath + "_integration_tests"
-]
-
-tasks.named("processTestResources").configure {
-  inputs.properties(expansions)
-  filter("tokens" : expansions, ReplaceTokens.class)
-}
-
-if (useFixture) {
-  apply plugin: 'elasticsearch.test.fixtures'
-  testFixtures.useFixture(fixture.path, 'gcs-fixture-snapshots-based-recoveries')
-}
-
 tasks.named("javaRestTest").configure {
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
   systemProperty 'test.gcs.bucket', gcsBucket
   nonInputProperties.systemProperty 'test.gcs.base_path', gcsBasePath + "_snapshot_based_recoveries_tests" + BuildParams.testSeed
 
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
+  if (useFixture == false) {
+    systemProperty 'test.google.account', serviceAccountFile
   }
 }
 
-testClusters.matching { it.name == "javaRestTest" }.configureEach {
-  testDistribution = 'DEFAULT'
-  numberOfNodes = 3
-
-  setting 'xpack.license.self_generated.type', 'trial'
-
-  keystore 'gcs.client.snapshot_based_recoveries.credentials_file', serviceAccountFile, IGNORE_VALUE
-  if (useFixture) {
-    /* Use a closure on the string to delay evaluation until tests are executed */
-    setting 'gcs.client.snapshot_based_recoveries.endpoint', { "${-> fixtureAddress('gcs-fixture-snapshots-based-recoveries')}" }, IGNORE_VALUE
-    setting 'gcs.client.snapshot_based_recoveries.token_uri', { "${-> fixtureAddress('gcs-fixture-snapshots-based-recoveries')}/o/oauth2/token" },
-      IGNORE_VALUE
-  } else {
-    println "Using an external service to test " + project.name
-  }
-
-  setting 'xpack.security.enabled', 'false'
-}
-
 tasks.register("gcsThirdPartyTest")  {
   dependsOn "javaRestTest"
 }

+ 45 - 0
x-pack/plugin/snapshot-based-recoveries/qa/gcs/src/javaRestTest/java/org/elasticsearch/xpack/snapshotbasedrecoveries/recovery/GCSSnapshotBasedRecoveryIT.java

@@ -7,12 +7,57 @@
 
 package org.elasticsearch.xpack.snapshotbasedrecoveries.recovery;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.util.resource.Resource;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.nio.charset.StandardCharsets;
 
 import static org.hamcrest.Matchers.blankOrNullString;
 import static org.hamcrest.Matchers.not;
 
 public class GCSSnapshotBasedRecoveryIT extends AbstractSnapshotBasedRecoveryRestTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .nodes(3)
+        .module("repository-gcs")
+        .module("snapshot-based-recoveries")
+        .setting("gcs.client.snapshot_based_recoveries.endpoint", () -> fixture.getAddress(), s -> USE_FIXTURE)
+        .setting("gcs.client.snapshot_based_recoveries.token_uri", () -> fixture.getAddress() + "/o/oauth2/token", s -> USE_FIXTURE)
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                c.keystore(
+                    "gcs.client.snapshot_based_recoveries.credentials_file",
+                    Resource.fromString(() -> new String(TestUtils.createServiceAccount(random()), StandardCharsets.UTF_8))
+                );
+            } else {
+                c.keystore(
+                    "gcs.client.snapshot_based_recoveries.credentials_file",
+                    Resource.fromFile(PathUtils.get(System.getProperty("test.google.account")))
+                );
+            }
+        })
+        .setting("xpack.license.self_generated.type", "trial")
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
+
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
 
     @Override
     protected String repositoryType() {

+ 8 - 71
x-pack/plugin/snapshot-repo-test-kit/qa/gcs/build.gradle

@@ -7,34 +7,21 @@
 
 import org.elasticsearch.gradle.internal.info.BuildParams
 
-import java.nio.file.Files
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
-
-apply plugin: 'elasticsearch.legacy-java-rest-test'
-apply plugin: 'elasticsearch.rest-resources'
-
-final Project fixture = project(':test:fixtures:gcs-fixture')
+apply plugin: 'elasticsearch.internal-java-rest-test'
 
 dependencies {
+  clusterModules project(':modules:repository-gcs')
+  clusterModules project(xpackModule('snapshot-repo-test-kit'))
   javaRestTestImplementation testArtifact(project(xpackModule('snapshot-repo-test-kit')))
-}
-
-restResources {
-  restApi {
-    include 'indices', 'search', 'bulk', 'snapshot', 'nodes', '_common', 'snapshot_repo_test_kit'
-  }
+  javaRestTestImplementation project(':test:fixtures:gcs-fixture')
 }
 
 boolean useFixture = false
-
 String gcsServiceAccount = System.getenv("google_storage_service_account")
 String gcsBucket = System.getenv("google_storage_bucket")
 String gcsBasePath = System.getenv("google_storage_base_path")
-
 File serviceAccountFile = null
+
 if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
   serviceAccountFile = new File(project.buildDir, 'generated-resources/service_account_test.json')
   gcsBucket = 'bucket'
@@ -46,64 +33,14 @@ if (!gcsServiceAccount && !gcsBucket && !gcsBasePath) {
   serviceAccountFile = new File(gcsServiceAccount)
 }
 
-tasks.register("createServiceAccountFile") {
-  doLast {
-    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
-    keyPairGenerator.initialize(2048)
-    KeyPair keyPair = keyPairGenerator.generateKeyPair()
-    String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded())
-
-    serviceAccountFile.parentFile.mkdirs()
-    serviceAccountFile.setText("{\n" +
-      '  "type": "service_account",\n' +
-      '  "project_id": "integration_test",\n' +
-      '  "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
-      '  "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
-      '  "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
-      '  "client_id": "123456789101112130594"\n' +
-      '}', 'UTF-8')
-  }
-}
-
-def fixtureAddress = { f ->
-  assert useFixture: 'closure should not be used without a fixture'
-  int ephemeralPort = project(':test:fixtures:gcs-fixture').postProcessFixture.ext."test.fixtures.${f}.tcp.80"
-  assert ephemeralPort > 0
-  'http://127.0.0.1:' + ephemeralPort
-}
-
-Map<String, Object> expansions = [
-  'bucket'   : gcsBucket,
-  'base_path': gcsBasePath + "_integration_tests"
-]
-
-if (useFixture) {
-  apply plugin: 'elasticsearch.test.fixtures'
-  testFixtures.useFixture(fixture.path, 'gcs-fixture-repository-test-kit')
-}
-
 tasks.named("javaRestTest").configure {
+  systemProperty 'test.google.fixture', Boolean.toString(useFixture)
   systemProperty 'test.gcs.bucket', gcsBucket
   nonInputProperties.systemProperty 'test.gcs.base_path', gcsBasePath + "_repository_test_kit_tests" + BuildParams.testSeed
 
-  if (useFixture) {
-    dependsOn "createServiceAccountFile"
-  }
-}
-
-testClusters.matching { it.name == "javaRestTest" }.configureEach {
-  testDistribution = 'DEFAULT'
-
-  keystore 'gcs.client.repository_test_kit.credentials_file', serviceAccountFile, IGNORE_VALUE
-  if (useFixture) {
-    /* Use a closure on the string to delay evaluation until tests are executed */
-    setting 'gcs.client.repository_test_kit.endpoint', { "${-> fixtureAddress('gcs-fixture-repository-test-kit')}" }, IGNORE_VALUE
-    setting 'gcs.client.repository_test_kit.token_uri', { "${-> fixtureAddress('gcs-fixture-repository-test-kit')}/o/oauth2/token" }, IGNORE_VALUE
-    systemProperty 'test.repository_test_kit.skip_cas', 'true' // test fixture does not support CAS yet; TODO fix this
-  } else {
-    println "Using an external service to test " + project.name
+  if (useFixture == false) {
+    systemProperty 'test.google.account', serviceAccountFile
   }
-  setting 'xpack.security.enabled', 'false'
 }
 
 tasks.register("gcsThirdPartyTest")  {

+ 49 - 0
x-pack/plugin/snapshot-repo-test-kit/qa/gcs/src/javaRestTest/java/org/elasticsearch/repositories/blobstore/testkit/GCSSnapshotRepoTestKitIT.java

@@ -6,12 +6,61 @@
  */
 package org.elasticsearch.repositories.blobstore.testkit;
 
+import fixture.gcs.GoogleCloudStorageHttpFixture;
+import fixture.gcs.TestUtils;
+
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.Booleans;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.util.resource.Resource;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.nio.charset.StandardCharsets;
 
 import static org.hamcrest.Matchers.blankOrNullString;
 import static org.hamcrest.Matchers.not;
 
 public class GCSSnapshotRepoTestKitIT extends AbstractSnapshotRepoTestKitRestTestCase {
+    private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.google.fixture", "true"));
+
+    private static GoogleCloudStorageHttpFixture fixture = new GoogleCloudStorageHttpFixture(USE_FIXTURE, "bucket", "o/oauth2/token");
+
+    private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
+        .module("repository-gcs")
+        .module("snapshot-repo-test-kit")
+        .setting("gcs.client.repository_test_kit.endpoint", () -> fixture.getAddress(), s -> USE_FIXTURE)
+        .setting("gcs.client.repository_test_kit.token_uri", () -> fixture.getAddress() + "/o/oauth2/token", s -> USE_FIXTURE)
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                c.keystore(
+                    "gcs.client.repository_test_kit.credentials_file",
+                    Resource.fromString(() -> new String(TestUtils.createServiceAccount(random()), StandardCharsets.UTF_8))
+                );
+            } else {
+                c.keystore(
+                    "gcs.client.repository_test_kit.credentials_file",
+                    Resource.fromFile(PathUtils.get(System.getProperty("test.google.account")))
+                );
+            }
+        })
+        .apply(c -> {
+            if (USE_FIXTURE) {
+                // test fixture does not support CAS yet; TODO fix this
+                c.systemProperty("test.repository_test_kit.skip_cas", "true");
+            }
+        })
+        .build();
+
+    @ClassRule
+    public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
+
+    @Override
+    protected String getTestRestCluster() {
+        return cluster.getHttpAddresses();
+    }
 
     @Override
     protected String repositoryType() {