/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.opensearch.gradle.test import org.opensearch.gradle.Version import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.tasks.Input /** Configuration for an opensearch cluster, used for integration tests. */ class ClusterConfiguration { private final Project project @Input String distribution = 'archive' @Input int numNodes = 1 @Input int numBwcNodes = 0 @Input Version bwcVersion = null @Input int httpPort = 0 @Input int transportPort = 0 /** * An override of the data directory. Input is the node number and output * is the override data directory. */ @Input Closure dataDir = null /** Optional override of the cluster name. */ @Input String clusterName = null @Input boolean daemonize = true @Input boolean debug = false /** * Configuration of the setting {@code discovery.zen.minimum_master_nodes} on the nodes. * In case of more than one node, this defaults to the number of nodes */ @Input Closure minimumMasterNodes = { if (bwcVersion != null && bwcVersion.before("6.5.0")) { return numNodes > 1 ? numNodes : -1 } else { return numNodes > 1 ? numNodes.intdiv(2) + 1 : -1 } } /** * Whether the initial_master_nodes setting should be automatically derived from the nodes * in the cluster. Only takes effect if all nodes in the cluster understand this setting * and the discovery type is not explicitly set. */ @Input boolean autoSetInitialMasterNodes = true /** * Whether the file-based discovery provider should be automatically setup based on * the nodes in the cluster. Only takes effect if no other hosts provider is already * configured. */ @Input boolean autoSetHostsProvider = true @Input String jvmArgs = "-Xms" + System.getProperty('tests.heap.size', '512m') + " " + "-Xmx" + System.getProperty('tests.heap.size', '512m') + " " + System.getProperty('tests.jvm.argline', '') /** * Should the shared environment be cleaned on cluster startup? Defaults * to {@code true} so we run with a clean cluster but some tests wish to * preserve snapshots between clusters so they set this to true. */ @Input boolean cleanShared = true /** * A closure to call which returns the unicast host to connect to for cluster formation. * * This allows multi node clusters, or a new cluster to connect to an existing cluster. * The closure takes three arguments, the NodeInfo for the first node in the cluster, * the NodeInfo for the node current being configured, an AntBuilder which may be used * to wait on conditions before returning. */ @Input Closure unicastTransportUri = { NodeInfo seedNode, NodeInfo node, AntBuilder ant -> if (seedNode == node) { return null } ant.waitfor(maxwait: '40', maxwaitunit: 'second', checkevery: '500', checkeveryunit: 'millisecond', timeoutproperty: "failed.${seedNode.transportPortsFile.path}") { resourceexists { file(file: seedNode.transportPortsFile.toString()) } } if (ant.properties.containsKey("failed.${seedNode.transportPortsFile.path}".toString())) { throw new GradleException("Failed to locate seed node transport file [${seedNode.transportPortsFile}]: " + "timed out waiting for it to be created after 40 seconds") } return seedNode.transportUri() } /** * A closure to call which returns a manually supplied list of unicast seed hosts. */ @Input Closure> otherUnicastHostAddresses = { Collections.emptyList() } /** * A closure to call before the cluster is considered ready. The closure is passed the node info, * as well as a groovy AntBuilder, to enable running ant condition checks. The default wait * condition is for http on the http port. */ @Input Closure waitCondition = { NodeInfo node, AntBuilder ant -> File tmpFile = new File(node.cwd, 'wait.success') String waitUrl = "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow" ant.echo(message: "==> [${new Date()}] checking health: ${waitUrl}", level: 'info') // checking here for wait_for_nodes to be >= the number of nodes because its possible // this cluster is attempting to connect to nodes created by another task (same cluster name), // so there will be more nodes in that case in the cluster state ant.get(src: waitUrl, dest: tmpFile.toString(), ignoreerrors: true, // do not fail on error, so logging buffers can be flushed by the wait task retries: 10) return tmpFile.exists() } /** * The maximum number of seconds to wait for nodes to complete startup, which includes writing * the ports files for the transports and the pid file. This wait time occurs before the wait * condition is executed. */ @Input int nodeStartupWaitSeconds = 30 public ClusterConfiguration(Project project) { this.project = project } // **Note** for systemProperties, settings, keystoreFiles etc: // value could be a GString that is evaluated to just a String // there are cases when value depends on task that is not executed yet on configuration stage Map systemProperties = new HashMap<>() Map environmentVariables = new HashMap<>() Map settings = new HashMap<>() Map keystoreSettings = new HashMap<>() Map keystoreFiles = new HashMap<>() // map from destination path, to source file Map extraConfigFiles = new HashMap<>() LinkedHashMap plugins = new LinkedHashMap<>() List modules = new ArrayList<>() LinkedHashMap setupCommands = new LinkedHashMap<>() List dependencies = new ArrayList<>() @Input void systemProperty(String property, Object value) { systemProperties.put(property, value) } @Input void environment(String variable, Object value) { environmentVariables.put(variable, value) } @Input void setting(String name, Object value) { settings.put(name, value) } @Input void keystoreSetting(String name, String value) { keystoreSettings.put(name, value) } /** * Adds a file to the keystore. The name is the secure setting name, and the sourceFile * is anything accepted by project.file() */ @Input void keystoreFile(String name, Object sourceFile) { keystoreFiles.put(name, sourceFile) } @Input void plugin(String path) { Project pluginProject = project.project(path) plugins.put(pluginProject.name, pluginProject) } @Input void mavenPlugin(String name, String mavenCoords) { plugins.put(name, mavenCoords) } /** Add a module to the cluster. The project must be an opensearchplugin and have a single zip default artifact. */ @Input void module(Project moduleProject) { modules.add(moduleProject) } @Input void setupCommand(String name, Object... args) { setupCommands.put(name, args) } /** * Add an extra configuration file. The path is relative to the config dir, and the sourceFile * is anything accepted by project.file() */ @Input void extraConfigFile(String path, Object sourceFile) { if (path == 'opensearch.yml') { throw new GradleException('Overwriting opensearch.yml is not allowed, add additional settings using cluster { setting "foo", "bar" }') } extraConfigFiles.put(path, sourceFile) } /** Add dependencies that must be run before the first task setting up the cluster. */ @Input void dependsOn(Object... deps) { dependencies.addAll(deps) } }