From 6ace40823966a41f11ac89eb3ce566636ab699a9 Mon Sep 17 00:00:00 2001 From: Mark Vieira Date: Wed, 3 May 2023 13:43:39 -0700 Subject: [PATCH] Refactor BWC handling to support more flexible testing scenarios (#95803) --- .../gradle/internal/InternalBwcGitPlugin.java | 18 +- .../InternalDistributionDownloadPlugin.java | 4 +- .../internal/info/GlobalBuildInfoPlugin.java | 11 +- .../test/rest/InternalJavaRestTestPlugin.java | 4 +- .../test/rest/RestTestBasePlugin.java | 4 +- test/test-clusters/build.gradle | 6 +- .../test/cluster/ClusterHandle.java | 5 + ....java => DefaultElasticsearchCluster.java} | 27 ++- .../test/cluster/ElasticsearchCluster.java | 4 +- .../AbstractLocalClusterSpecBuilder.java | 168 ++++++++++++++++++ .../local/DefaultLocalClusterSpecBuilder.java | 160 ++--------------- .../local/DefaultSettingsProvider.java | 1 - .../FipsEnabledClusterConfigProvider.java | 2 +- .../local/LocalClusterConfigProvider.java | 2 +- .../cluster/local/LocalClusterFactory.java | 65 ++++--- .../cluster/local/LocalClusterHandle.java | 4 +- .../test/cluster/local/LocalClusterSpec.java | 4 + .../local/LocalClusterSpecBuilder.java | 22 +-- 18 files changed, 291 insertions(+), 220 deletions(-) rename test/test-clusters/src/main/java/org/elasticsearch/test/cluster/{local/LocalElasticsearchCluster.java => DefaultElasticsearchCluster.java} (74%) create mode 100644 test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalClusterSpecBuilder.java diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalBwcGitPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalBwcGitPlugin.java index cd5455b1f2d11..407fa6ec289df 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalBwcGitPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalBwcGitPlugin.java @@ -84,11 +84,11 @@ public void apply(Project project) { TaskProvider addRemoteTaskProvider = tasks.register("addRemote", LoggedExec.class, addRemote -> { addRemote.dependsOn(findRemoteTaskProvider); addRemote.onlyIf("remote exists", task -> ((boolean) extraProperties.get("remoteExists")) == false); - addRemote.getWorkingDir().set(gitExtension.getCheckoutDir().get()); + addRemote.getWorkingDir().set(gitExtension.getCheckoutDir()); String remoteRepo = remote.get(); // for testing only we can override the base remote url String remoteRepoUrl = providerFactory.systemProperty("testRemoteRepo") - .getOrElse("https://github.com/" + remoteRepo + "/elasticsearch.git"); + .getOrElse("https://github.com/" + remoteRepo + "/" + project.getRootProject().getName()); addRemote.commandLine("git", "remote", "add", remoteRepo, remoteRepoUrl); }); @@ -105,8 +105,9 @@ public void apply(Project project) { }); fetchLatest.onlyIf("online and gitFetchLatest == true", t -> isOffline == false && gitFetchLatest.get()); fetchLatest.dependsOn(addRemoteTaskProvider); - fetchLatest.getWorkingDir().set(gitExtension.getCheckoutDir().get()); - fetchLatest.commandLine("git", "fetch", "--all"); + fetchLatest.getWorkingDir().set(gitExtension.getCheckoutDir()); + // Fetch latest from remotes, including tags, overriding any existing local refs + fetchLatest.commandLine("git", "fetch", "--all", "--tags", "--force"); }); String projectPath = project.getPath(); @@ -119,13 +120,20 @@ public void execute(Task task) { String bwcBranch = gitExtension.getBwcBranch().get(); final String refspec = providerFactory.systemProperty("bwc.refspec." + bwcBranch) .orElse(providerFactory.systemProperty("tests.bwc.refspec." + bwcBranch)) + .orElse( + providerFactory.provider( + () -> task.getExtensions().getExtraProperties().has("refspec") + ? task.getExtensions().getExtraProperties().get("refspec").toString() + : null + ) + ) .getOrElse(remote.get() + "/" + bwcBranch); String effectiveRefSpec = maybeAlignedRefSpec(task.getLogger(), refspec); task.getLogger().lifecycle("Performing checkout of {}...", refspec); LoggedExec.exec(execOperations, spec -> { spec.workingDir(checkoutDir); - spec.commandLine("git", "checkout", effectiveRefSpec); + spec.commandLine("git", "checkout", "--recurse-submodules", effectiveRefSpec); }); String checkoutHash = GitInfo.gitInfo(checkoutDir).getRevision(); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalDistributionDownloadPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalDistributionDownloadPlugin.java index ae719bc27a309..16c7bf6d32862 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalDistributionDownloadPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/InternalDistributionDownloadPlugin.java @@ -172,11 +172,11 @@ private static String distributionProjectName(ElasticsearchDistribution distribu return projectName + distribution.getType().getName(); } - private static class ProjectBasedDistributionDependency implements DistributionDependency { + public static class ProjectBasedDistributionDependency implements DistributionDependency { private Function function; - ProjectBasedDistributionDependency(Function function) { + public ProjectBasedDistributionDependency(Function function) { this.function = function; } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java index 9cc3e71192d40..e3fbf749f10b3 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java @@ -100,8 +100,7 @@ public void apply(Project project) { File runtimeJavaHome = findRuntimeJavaHome(); boolean isRuntimeJavaHomeSet = Jvm.current().getJavaHome().equals(runtimeJavaHome) == false; - File rootDir = project.getRootDir(); - GitInfo gitInfo = GitInfo.gitInfo(rootDir); + GitInfo gitInfo = GitInfo.gitInfo(project.getRootDir()); BuildParams.init(params -> { params.reset(); @@ -132,7 +131,13 @@ public void apply(Project project) { params.setInFipsJvm(Util.getBooleanProperty("tests.fips.enabled", false)); params.setIsSnapshotBuild(Util.getBooleanProperty("build.snapshot", true)); AtomicReference cache = new AtomicReference<>(); - params.setBwcVersions(providers.provider(() -> cache.updateAndGet(val -> val == null ? resolveBwcVersions(rootDir) : val))); + params.setBwcVersions( + providers.provider( + () -> cache.updateAndGet( + val -> val == null ? resolveBwcVersions(Util.locateElasticsearchWorkspace(project.getGradle())) : val + ) + ) + ); }); // Enforce the minimum compiler version diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/InternalJavaRestTestPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/InternalJavaRestTestPlugin.java index b73dbf66d5730..f3950c8646292 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/InternalJavaRestTestPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/InternalJavaRestTestPlugin.java @@ -35,7 +35,9 @@ public void apply(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); SourceSet javaTestSourceSet = sourceSets.create(SOURCE_SET_NAME); - project.getDependencies().add(javaTestSourceSet.getImplementationConfigurationName(), project.project(":test:test-clusters")); + if (project.findProject(":test:test-clusters") != null) { + project.getDependencies().add(javaTestSourceSet.getImplementationConfigurationName(), project.project(":test:test-clusters")); + } // setup the javaRestTest task // we use a StandloneRestIntegTestTask here so that the conventions of RestTestBasePlugin don't create a test cluster diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/RestTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/RestTestBasePlugin.java index d2b6607033131..3fa67f2067229 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/RestTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/RestTestBasePlugin.java @@ -161,7 +161,7 @@ public Void call(Object... args) { task.getExtensions().getExtraProperties().set("usesBwcDistribution", new Closure(task) { @Override public Void call(Object... args) { - if (args.length != 1 && args[0] instanceof Version == false) { + if (args.length != 1 || args[0] instanceof Version == false) { throw new IllegalArgumentException("Expected exactly one argument of type org.elasticsearch.gradle.Version"); } @@ -178,7 +178,7 @@ public Void call(Object... args) { providerFactory.provider(() -> bwcDistro.getExtracted().getSingleFile().getPath()) ); - if (version.before(BuildParams.getBwcVersions().getMinimumWireCompatibleVersion())) { + if (version.getMajor() > 0 && version.before(BuildParams.getBwcVersions().getMinimumWireCompatibleVersion())) { // If we are upgrade testing older versions we also need to upgrade to 7.last this.call(BuildParams.getBwcVersions().getMinimumWireCompatibleVersion()); } diff --git a/test/test-clusters/build.gradle b/test/test-clusters/build.gradle index f24416023edb1..d2c7633603f26 100644 --- a/test/test-clusters/build.gradle +++ b/test/test-clusters/build.gradle @@ -1,12 +1,10 @@ import org.elasticsearch.gradle.internal.conventions.util.Util apply plugin: 'elasticsearch.java' -apply plugin: 'com.github.johnrengelman.shadow' dependencies { - shadow "junit:junit:${versions.junit}" - shadow "org.apache.logging.log4j:log4j-api:${versions.log4j}" - + api "junit:junit:${versions.junit}" + implementation "org.apache.logging.log4j:log4j-api:${versions.log4j}" implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}" diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ClusterHandle.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ClusterHandle.java index f611601850a58..29d8689de501a 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ClusterHandle.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ClusterHandle.java @@ -106,4 +106,9 @@ public interface ClusterHandle extends Closeable { * @param version version to upgrade to */ void upgradeToVersion(Version version); + + /** + * Cleans up any resources created by this cluster. + */ + void close(); } diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalElasticsearchCluster.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/DefaultElasticsearchCluster.java similarity index 74% rename from test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalElasticsearchCluster.java rename to test/test-clusters/src/main/java/org/elasticsearch/test/cluster/DefaultElasticsearchCluster.java index 832f74a65090a..84bd0e5cf4d96 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalElasticsearchCluster.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/DefaultElasticsearchCluster.java @@ -6,23 +6,22 @@ * Side Public License, v 1. */ -package org.elasticsearch.test.cluster.local; +package org.elasticsearch.test.cluster; -import org.elasticsearch.test.cluster.ElasticsearchCluster; -import org.elasticsearch.test.cluster.local.distribution.LocalDistributionResolver; -import org.elasticsearch.test.cluster.local.distribution.ReleasedDistributionResolver; -import org.elasticsearch.test.cluster.local.distribution.SnapshotDistributionResolver; import org.elasticsearch.test.cluster.util.Version; import org.junit.runner.Description; import org.junit.runners.model.Statement; -public class LocalElasticsearchCluster implements ElasticsearchCluster { - private final DefaultLocalClusterSpecBuilder builder; - private LocalClusterSpec spec; - private LocalClusterHandle handle; +import java.util.function.Supplier; - public LocalElasticsearchCluster(DefaultLocalClusterSpecBuilder builder) { - this.builder = builder; +public class DefaultElasticsearchCluster implements ElasticsearchCluster { + private final Supplier specProvider; + private final ClusterFactory clusterFactory; + private H handle; + + public DefaultElasticsearchCluster(Supplier specProvider, ClusterFactory clusterFactory) { + this.specProvider = specProvider; + this.clusterFactory = clusterFactory; } @Override @@ -31,10 +30,8 @@ public Statement apply(Statement base, Description description) { @Override public void evaluate() throws Throwable { try { - spec = builder.buildClusterSpec(); - handle = new LocalClusterFactory( - new LocalDistributionResolver(new SnapshotDistributionResolver(new ReleasedDistributionResolver())) - ).create(spec); + S spec = specProvider.get(); + handle = clusterFactory.create(spec); handle.start(); base.evaluate(); } finally { diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ElasticsearchCluster.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ElasticsearchCluster.java index 02eb3fb73df63..6ff771b9ca1e6 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ElasticsearchCluster.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ElasticsearchCluster.java @@ -23,12 +23,12 @@ public interface ElasticsearchCluster extends TestRule, ClusterHandle { /** - * Creates a new {@link DefaultLocalClusterSpecBuilder} for defining a locally orchestrated cluster. Local clusters use a locally built + * Creates a new {@link LocalClusterSpecBuilder} for defining a locally orchestrated cluster. Local clusters use a locally built * Elasticsearch distribution. * * @return a builder for a local cluster */ - static LocalClusterSpecBuilder local() { + static LocalClusterSpecBuilder local() { return new DefaultLocalClusterSpecBuilder(); } diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalClusterSpecBuilder.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalClusterSpecBuilder.java new file mode 100644 index 0000000000000..5bae78889b226 --- /dev/null +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalClusterSpecBuilder.java @@ -0,0 +1,168 @@ +/* + * 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.test.cluster.local; + +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.LocalClusterSpec.LocalNodeSpec; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.cluster.local.model.User; +import org.elasticsearch.test.cluster.util.Version; +import org.elasticsearch.test.cluster.util.resource.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class AbstractLocalClusterSpecBuilder extends AbstractLocalSpecBuilder< + LocalClusterSpecBuilder> implements LocalClusterSpecBuilder { + + private String name = "test-cluster"; + private final List nodeBuilders = new ArrayList<>(); + private final List users = new ArrayList<>(); + private final List roleFiles = new ArrayList<>(); + private final List> lazyConfigProviders = new ArrayList<>(); + + public AbstractLocalClusterSpecBuilder() { + super(null); + } + + @Override + public AbstractLocalClusterSpecBuilder name(String name) { + this.name = name; + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder apply(LocalClusterConfigProvider configProvider) { + configProvider.apply(this); + return this; + } + + @Override + public LocalClusterSpecBuilder apply(Supplier configProvider) { + lazyConfigProviders.add(configProvider); + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder nodes(int nodes) { + if (nodes < nodeBuilders.size()) { + throw new IllegalArgumentException( + "Cannot shrink cluster to " + nodes + ". " + nodeBuilders.size() + " nodes already configured" + ); + } + + int newNodes = nodes - nodeBuilders.size(); + for (int i = 0; i < newNodes; i++) { + nodeBuilders.add(new DefaultLocalNodeSpecBuilder(this)); + } + + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder withNode(Consumer config) { + DefaultLocalNodeSpecBuilder builder = new DefaultLocalNodeSpecBuilder(this); + config.accept(builder); + nodeBuilders.add(builder); + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder node(int index, Consumer config) { + try { + DefaultLocalNodeSpecBuilder builder = nodeBuilders.get(index); + config.accept(builder); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "No node at index + " + index + " exists. Only " + nodeBuilders.size() + " nodes have been configured" + ); + } + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder user(String username, String password) { + this.users.add(new User(username, password)); + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder user(String username, String password, String role) { + this.users.add(new User(username, password, role)); + return this; + } + + @Override + public AbstractLocalClusterSpecBuilder rolesFile(Resource rolesFile) { + this.roleFiles.add(rolesFile); + return this; + } + + protected LocalClusterSpec buildClusterSpec() { + // Apply lazily provided configuration + lazyConfigProviders.forEach(s -> s.get().apply(this)); + + List clusterUsers = users.isEmpty() ? List.of(User.DEFAULT_USER) : users; + LocalClusterSpec clusterSpec = new LocalClusterSpec(name, clusterUsers, roleFiles); + List nodeSpecs; + + if (nodeBuilders.isEmpty()) { + // No node-specific configuration so assume a single-node cluster + nodeSpecs = List.of(new DefaultLocalNodeSpecBuilder(this).build(clusterSpec)); + } else { + nodeSpecs = nodeBuilders.stream().map(node -> node.build(clusterSpec)).toList(); + } + + clusterSpec.setNodes(nodeSpecs); + clusterSpec.validate(); + + return clusterSpec; + } + + public static class DefaultLocalNodeSpecBuilder extends AbstractLocalSpecBuilder implements LocalNodeSpecBuilder { + private String name; + + protected DefaultLocalNodeSpecBuilder(AbstractLocalSpecBuilder parent) { + super(parent); + } + + @Override + public DefaultLocalNodeSpecBuilder name(String name) { + this.name = name; + return this; + } + + private LocalNodeSpec build(LocalClusterSpec cluster) { + + return new LocalNodeSpec( + cluster, + name, + Optional.ofNullable(getVersion()).orElse(Version.CURRENT), + getSettingsProviders(), + getSettings(), + getEnvironmentProviders(), + getEnvironment(), + getModules(), + getPlugins(), + Optional.ofNullable(getDistributionType()).orElse(DistributionType.INTEG_TEST), + getFeatures(), + getKeystoreProviders(), + getKeystoreSettings(), + getKeystoreFiles(), + getKeystorePassword(), + getExtraConfigFiles(), + getSystemProperties(), + getSecrets() + ); + } + } +} diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultLocalClusterSpecBuilder.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultLocalClusterSpecBuilder.java index eae77f081a9b0..e8c5d7aac6e88 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultLocalClusterSpecBuilder.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultLocalClusterSpecBuilder.java @@ -8,168 +8,28 @@ package org.elasticsearch.test.cluster.local; +import org.elasticsearch.test.cluster.DefaultElasticsearchCluster; import org.elasticsearch.test.cluster.ElasticsearchCluster; -import org.elasticsearch.test.cluster.local.LocalClusterSpec.LocalNodeSpec; -import org.elasticsearch.test.cluster.local.distribution.DistributionType; -import org.elasticsearch.test.cluster.local.model.User; -import org.elasticsearch.test.cluster.util.Version; +import org.elasticsearch.test.cluster.local.distribution.LocalDistributionResolver; +import org.elasticsearch.test.cluster.local.distribution.ReleasedDistributionResolver; +import org.elasticsearch.test.cluster.local.distribution.SnapshotDistributionResolver; import org.elasticsearch.test.cluster.util.resource.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class DefaultLocalClusterSpecBuilder extends AbstractLocalSpecBuilder implements LocalClusterSpecBuilder { - private String name = "test-cluster"; - private final List nodeBuilders = new ArrayList<>(); - private final List users = new ArrayList<>(); - private final List roleFiles = new ArrayList<>(); - private final List> lazyConfigProviders = new ArrayList<>(); +public class DefaultLocalClusterSpecBuilder extends AbstractLocalClusterSpecBuilder { public DefaultLocalClusterSpecBuilder() { - super(null); + super(); this.apply(new FipsEnabledClusterConfigProvider()); this.settings(new DefaultSettingsProvider()); this.environment(new DefaultEnvironmentProvider()); this.rolesFile(Resource.fromClasspath("default_test_roles.yml")); } - @Override - public DefaultLocalClusterSpecBuilder name(String name) { - this.name = name; - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder apply(LocalClusterConfigProvider configProvider) { - configProvider.apply(this); - return this; - } - - @Override - public LocalClusterSpecBuilder apply(Supplier configProvider) { - lazyConfigProviders.add(configProvider); - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder nodes(int nodes) { - if (nodes < nodeBuilders.size()) { - throw new IllegalArgumentException( - "Cannot shrink cluster to " + nodes + ". " + nodeBuilders.size() + " nodes already configured" - ); - } - - int newNodes = nodes - nodeBuilders.size(); - for (int i = 0; i < newNodes; i++) { - nodeBuilders.add(new DefaultLocalNodeSpecBuilder(this)); - } - - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder withNode(Consumer config) { - DefaultLocalNodeSpecBuilder builder = new DefaultLocalNodeSpecBuilder(this); - config.accept(builder); - nodeBuilders.add(builder); - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder node(int index, Consumer config) { - try { - DefaultLocalNodeSpecBuilder builder = nodeBuilders.get(index); - config.accept(builder); - } catch (IndexOutOfBoundsException e) { - throw new IllegalArgumentException( - "No node at index + " + index + " exists. Only " + nodeBuilders.size() + " nodes have been configured" - ); - } - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder user(String username, String password) { - this.users.add(new User(username, password)); - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder user(String username, String password, String role) { - this.users.add(new User(username, password, role)); - return this; - } - - @Override - public DefaultLocalClusterSpecBuilder rolesFile(Resource rolesFile) { - this.roleFiles.add(rolesFile); - return this; - } - @Override public ElasticsearchCluster build() { - return new LocalElasticsearchCluster(this); - } - - LocalClusterSpec buildClusterSpec() { - // Apply lazily provided configuration - lazyConfigProviders.forEach(s -> s.get().apply(this)); - - List clusterUsers = users.isEmpty() ? List.of(User.DEFAULT_USER) : users; - LocalClusterSpec clusterSpec = new LocalClusterSpec(name, clusterUsers, roleFiles); - List nodeSpecs; - - if (nodeBuilders.isEmpty()) { - // No node-specific configuration so assume a single-node cluster - nodeSpecs = List.of(new DefaultLocalNodeSpecBuilder(this).build(clusterSpec)); - } else { - nodeSpecs = nodeBuilders.stream().map(node -> node.build(clusterSpec)).toList(); - } - - clusterSpec.setNodes(nodeSpecs); - clusterSpec.validate(); - - return clusterSpec; - } - - public static class DefaultLocalNodeSpecBuilder extends AbstractLocalSpecBuilder implements LocalNodeSpecBuilder { - private String name; - - protected DefaultLocalNodeSpecBuilder(AbstractLocalSpecBuilder parent) { - super(parent); - } - - @Override - public DefaultLocalNodeSpecBuilder name(String name) { - this.name = name; - return this; - } - - private LocalNodeSpec build(LocalClusterSpec cluster) { - - return new LocalNodeSpec( - cluster, - name, - Optional.ofNullable(getVersion()).orElse(Version.CURRENT), - getSettingsProviders(), - getSettings(), - getEnvironmentProviders(), - getEnvironment(), - getModules(), - getPlugins(), - Optional.ofNullable(getDistributionType()).orElse(DistributionType.INTEG_TEST), - getFeatures(), - getKeystoreProviders(), - getKeystoreSettings(), - getKeystoreFiles(), - getKeystorePassword(), - getExtraConfigFiles(), - getSystemProperties(), - getSecrets() - ); - } + return new DefaultElasticsearchCluster<>( + this::buildClusterSpec, + new LocalClusterFactory(new LocalDistributionResolver(new SnapshotDistributionResolver(new ReleasedDistributionResolver()))) + ); } } diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultSettingsProvider.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultSettingsProvider.java index a1c5e5892ef10..b2d9e47bb77bf 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultSettingsProvider.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultSettingsProvider.java @@ -21,7 +21,6 @@ public class DefaultSettingsProvider implements SettingsProvider { public Map get(LocalNodeSpec nodeSpec) { Map settings = new HashMap<>(); - settings.put("node.name", nodeSpec.getName()); settings.put("node.attr.testattr", "test"); settings.put("node.portsfile", "true"); settings.put("http.port", "0"); diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/FipsEnabledClusterConfigProvider.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/FipsEnabledClusterConfigProvider.java index 37a159bc81720..473456f6b0cc3 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/FipsEnabledClusterConfigProvider.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/FipsEnabledClusterConfigProvider.java @@ -13,7 +13,7 @@ public class FipsEnabledClusterConfigProvider implements LocalClusterConfigProvider { @Override - public void apply(LocalClusterSpecBuilder builder) { + public void apply(LocalClusterSpecBuilder builder) { if (isFipsEnabled()) { builder.configFile( "fips_java.security", diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterConfigProvider.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterConfigProvider.java index 7e802247709d0..a54f2fb6a8115 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterConfigProvider.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterConfigProvider.java @@ -10,5 +10,5 @@ public interface LocalClusterConfigProvider { - void apply(LocalClusterSpecBuilder builder); + void apply(LocalClusterSpecBuilder builder); } diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterFactory.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterFactory.java index 080b733e02d15..f0abf5ba42077 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterFactory.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterFactory.java @@ -68,28 +68,37 @@ public class LocalClusterFactory implements ClusterFactory new Node(baseWorkingDir, distributionResolver, s)).toList() + ); + } + + public static class Node { + private final ObjectMapper objectMapper; + private final Path baseWorkingDir; + private final DistributionResolver distributionResolver; private final LocalNodeSpec spec; + private final String name; private final Path workingDir; private final Path repoDir; private final Path dataDir; @@ -102,9 +111,17 @@ public class Node { private Process process = null; private DistributionDescriptor distributionDescriptor; - public Node(LocalNodeSpec spec) { + public Node(Path baseWorkingDir, DistributionResolver distributionResolver, LocalNodeSpec spec) { + this(baseWorkingDir, distributionResolver, spec, null); + } + + public Node(Path baseWorkingDir, DistributionResolver distributionResolver, LocalNodeSpec spec, String suffix) { + this.objectMapper = new ObjectMapper(); + this.baseWorkingDir = baseWorkingDir; + this.distributionResolver = distributionResolver; this.spec = spec; - this.workingDir = baseWorkingDir.resolve(spec.getName()); + this.name = suffix == null ? spec.getName() : spec.getName() + "-" + suffix; + this.workingDir = baseWorkingDir.resolve(name); this.repoDir = baseWorkingDir.resolve("repo"); this.dataDir = workingDir.resolve("data"); this.logsDir = workingDir.resolve("logs"); @@ -113,15 +130,15 @@ public Node(LocalNodeSpec spec) { } public synchronized void start(Version version) { - LOGGER.info("Starting Elasticsearch node '{}'", spec.getName()); + LOGGER.info("Starting Elasticsearch node '{}'", name); if (version != null) { spec.setVersion(version); } if (currentVersion == null || currentVersion.equals(spec.getVersion()) == false) { - LOGGER.info("Creating installation for node '{}' in {}", spec.getName(), workingDir); + LOGGER.info("Creating installation for node '{}' in {}", name, workingDir); distributionDescriptor = resolveDistribution(); - LOGGER.info("Distribution for node '{}': {}", spec.getName(), distributionDescriptor); + LOGGER.info("Distribution for node '{}': {}", name, distributionDescriptor); initializeWorkingDirectory(currentVersion != null); createConfigDirectory(); copyExtraConfigFiles(); // extra config files might be needed for running cli tools like plugin install @@ -147,6 +164,7 @@ public synchronized void start(Version version) { } public synchronized void stop(boolean forcibly) { + LOGGER.info("Shutting down node '{}'", name); if (process != null) { ProcessUtils.stopHandle(process.toHandle(), forcibly); ProcessReaper.instance().unregister(getServiceName()); @@ -200,6 +218,10 @@ public void deletePortsFiles() { } } + public String getName() { + return name; + } + public LocalNodeSpec getSpec() { return spec; } @@ -280,7 +302,7 @@ private void initializeWorkingDirectory(boolean preserveWorkingDirectory) { Files.createDirectories(logsDir); Files.createDirectories(tempDir); } catch (IOException e) { - throw new UncheckedIOException("Failed to create working directory for node '" + spec.getName() + "'", e); + throw new UncheckedIOException("Failed to create working directory for node '" + name + "'", e); } } @@ -325,6 +347,7 @@ private void writeConfiguration() { try { // Write settings to elasticsearch.yml Map finalSettings = new HashMap<>(); + finalSettings.put("node.name", name); finalSettings.put("path.repo", repoDir.toString()); finalSettings.put("path.data", dataDir.toString()); finalSettings.put("path.logs", logsDir.toString()); @@ -341,10 +364,12 @@ private void writeConfiguration() { ); // Copy additional configuration from distribution - try (Stream configFiles = Files.list(distributionDir.resolve("config"))) { + try (Stream configFiles = Files.walk(distributionDir.resolve("config"))) { for (Path file : configFiles.toList()) { - Path dest = configFile.getParent().resolve(file.getFileName()); + Path relativePath = distributionDir.resolve("config").relativize(file); + Path dest = configDir.resolve(relativePath); if (Files.exists(dest) == false) { + Files.createDirectories(dest.getParent()); Files.copy(file, dest); } } @@ -430,7 +455,7 @@ private void writeSecureSecretsFile() { private void configureSecurity() { if (spec.isSecurityEnabled()) { if (spec.getUsers().isEmpty() == false) { - LOGGER.info("Setting up roles.yml for node '{}'", spec.getName()); + LOGGER.info("Setting up roles.yml for node '{}'", name); Path destination = workingDir.resolve("config").resolve("roles.yml"); spec.getRolesFiles().forEach(rolesFile -> { @@ -445,7 +470,7 @@ private void configureSecurity() { }); } - LOGGER.info("Creating users for node '{}'", spec.getName()); + LOGGER.info("Creating users for node '{}'", name); for (User user : spec.getUsers()) { runToolScript( "elasticsearch-users", @@ -465,7 +490,7 @@ private void installPlugins() { if (spec.getPlugins().isEmpty() == false) { Pattern pattern = Pattern.compile("(.+)(?:-\\d\\.\\d\\.\\d-SNAPSHOT\\.zip)?"); - LOGGER.info("Installing plugins {} into node '{}", spec.getPlugins(), spec.getName()); + LOGGER.info("Installing plugins {} into node '{}", spec.getPlugins(), name); List pluginPaths = Arrays.stream(System.getProperty(TESTS_CLUSTER_PLUGINS_PATH_SYSPROP).split(File.pathSeparator)) .map(Path::of) .toList(); @@ -514,7 +539,7 @@ private void installPlugins() { private void installModules() { if (spec.getModules().isEmpty() == false) { - LOGGER.info("Installing modules {} into node '{}", spec.getModules(), spec.getName()); + LOGGER.info("Installing modules {} into node '{}", spec.getModules(), name); List modulePaths = Arrays.stream(System.getProperty(TESTS_CLUSTER_MODULES_PATH_SYSPROP).split(File.pathSeparator)) .map(Path::of) .toList(); @@ -661,12 +686,12 @@ private void runToolScript(String tool, String input, String... args) { } private String getServiceName() { - return baseWorkingDir.getFileName() + "-" + spec.getName(); + return baseWorkingDir.getFileName() + "-" + name; } @Override public String toString() { - return "{ cluster: '" + spec.getCluster().getName() + "', node: '" + spec.getName() + "' }"; + return "{ cluster: '" + spec.getCluster().getName() + "', node: '" + name + "' }"; } } } diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterHandle.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterHandle.java index 1578e1d26b37f..f3a066b648dea 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterHandle.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterHandle.java @@ -141,7 +141,7 @@ public String getRemoteClusterServerEndpoint(int index) { public void upgradeNodeToVersion(int index, Version version) { Node node = nodes.get(index); node.stop(false); - LOGGER.info("Upgrading node '{}' to version {}", node.getSpec().getName(), version); + LOGGER.info("Upgrading node '{}' to version {}", node.getName(), version); node.start(version); waitUntilReady(); } @@ -156,7 +156,7 @@ public void upgradeToVersion(Version version) { waitUntilReady(); } - private void waitUntilReady() { + protected void waitUntilReady() { writeUnicastHostsFile(); try { WaitForHttpResource wait = configureWaitForReady(); diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpec.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpec.java index a6c619ba95d5e..b1ca62c9d419f 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpec.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpec.java @@ -194,6 +194,10 @@ public boolean isMasterEligible() { return getSetting("node.roles", "master").contains("master"); } + public boolean hasRole(String role) { + return getSetting("node.roles", "[]").contains("search"); + } + /** * Return node configured setting or the provided default if no explicit value has been configured. This method returns all * settings, to include security settings provided to the keystore diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpecBuilder.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpecBuilder.java index 1f4086fd47fe8..0f184b9b0e2e2 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpecBuilder.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpecBuilder.java @@ -14,33 +14,33 @@ import java.util.function.Consumer; import java.util.function.Supplier; -public interface LocalClusterSpecBuilder extends LocalSpecBuilder { +public interface LocalClusterSpecBuilder extends LocalSpecBuilder> { /** * Sets the node name. By default, "test-cluster" is used. */ - LocalClusterSpecBuilder name(String name); + LocalClusterSpecBuilder name(String name); /** * Apply configuration from a {@link LocalClusterConfigProvider}. This configuration is applied eagerly. Subsequent calls to this * builder will override provider settings. */ - LocalClusterSpecBuilder apply(LocalClusterConfigProvider configProvider); + LocalClusterSpecBuilder apply(LocalClusterConfigProvider configProvider); /** * Apply configuration from a {@link LocalClusterConfigProvider} created by the given {@link Supplier}. This configuration is applied * lazily and will override existing builder settings. */ - LocalClusterSpecBuilder apply(Supplier configProvider); + LocalClusterSpecBuilder apply(Supplier configProvider); /** * Sets the number of nodes for the cluster. */ - LocalClusterSpecBuilder nodes(int nodes); + LocalClusterSpecBuilder nodes(int nodes); /** * Adds a new node to the cluster and configures the node. */ - LocalClusterSpecBuilder withNode(Consumer config); + LocalClusterSpecBuilder withNode(Consumer config); /** * Configures an existing node. @@ -48,22 +48,22 @@ public interface LocalClusterSpecBuilder extends LocalSpecBuilder config); + LocalClusterSpecBuilder node(int index, Consumer config); /** * Register a user using the default test role. */ - LocalClusterSpecBuilder user(String username, String password); + LocalClusterSpecBuilder user(String username, String password); /** * Register a user using the given role. */ - LocalClusterSpecBuilder user(String username, String password, String role); + LocalClusterSpecBuilder user(String username, String password, String role); /** * Register a roles file with cluster via the supplied {@link Resource}. */ - LocalClusterSpecBuilder rolesFile(Resource rolesFile); + LocalClusterSpecBuilder rolesFile(Resource rolesFile); - ElasticsearchCluster build(); + T build(); }