/* * 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 com.sun.jna.Native import com.sun.jna.WString import org.apache.tools.ant.taskdefs.condition.Os import org.opensearch.gradle.Version import org.opensearch.gradle.VersionProperties import org.gradle.api.Project import org.opensearch.gradle.test.JNAKernel32Library import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths /** * A container for the files and configuration associated with a single node in a test cluster. */ class NodeInfo { /** Gradle project this node is part of */ Project project /** common configuration for all nodes, including this one */ ClusterConfiguration config /** node number within the cluster, for creating unique names and paths */ int nodeNum /** name of the cluster this node is part of */ String clusterName /** root directory all node files and operations happen under */ File baseDir /** shared data directory all nodes share */ File sharedDir /** the pid file the node will use */ File pidFile /** a file written by opensearch containing the ports of each bound address for http */ File httpPortsFile /** a file written by opensearch containing the ports of each bound address for transport */ File transportPortsFile /** opensearch home dir */ File homeDir /** config directory */ File pathConf /** data directory (as an Object, to allow lazy evaluation) */ Object dataDir /** THE config file */ File configFile /** working directory for the node process */ File cwd /** file that if it exists, indicates the node failed to start */ File failedMarker /** stdout/stderr log of the opensearch process for this node */ File startLog /** directory to install plugins from */ File pluginsTmpDir /** Major version of java this node runs with, or {@code null} if using the runtime java version */ Integer javaVersion /** environment variables to start the node with */ Map env /** arguments to start the node with */ List args /** Executable to run the bin/opensearch with, either cmd or sh */ String executable /** Path to the opensearch start script */ private Object opensearchScript /** script to run when running in the background */ private File wrapperScript /** buffer for ant output when starting this node */ ByteArrayOutputStream buffer = new ByteArrayOutputStream() /** the version of opensearch that this node runs */ Version nodeVersion /** true if the node is not the current version */ boolean isBwcNode /** Holds node configuration for part of a test cluster. */ NodeInfo(ClusterConfiguration config, int nodeNum, Project project, String prefix, String nodeVersion, File sharedDir) { this.config = config this.nodeNum = nodeNum this.project = project this.sharedDir = sharedDir if (config.clusterName != null) { clusterName = config.clusterName } else { clusterName = project.path.replace(':', '_').substring(1) + '_' + prefix } baseDir = new File(project.buildDir, "cluster/${prefix} node${nodeNum}") pidFile = new File(baseDir, 'opensearch.pid') this.nodeVersion = Version.fromString(nodeVersion) this.isBwcNode = this.nodeVersion.before(VersionProperties.getOpenSearch()) homeDir = new File(baseDir, "opensearch-${nodeVersion}") pathConf = new File(homeDir, 'config') if (config.dataDir != null) { dataDir = "${config.dataDir(nodeNum)}" } else { dataDir = new File(homeDir, "data") } configFile = new File(pathConf, 'opensearch.yml') // even for rpm/deb, the logs are under home because we dont start with real services File logsDir = new File(homeDir, 'logs') httpPortsFile = new File(logsDir, 'http.ports') transportPortsFile = new File(logsDir, 'transport.ports') cwd = new File(baseDir, "cwd") failedMarker = new File(cwd, 'run.failed') startLog = new File(cwd, 'run.log') pluginsTmpDir = new File(baseDir, "plugins tmp") args = [] if (Os.isFamily(Os.FAMILY_WINDOWS)) { executable = 'cmd' args.add('/C') args.add('"') // quote the entire command wrapperScript = new File(cwd, "run.bat") /* * We have to delay building the string as the path will not exist during configuration which will fail on Windows due to * getting the short name requiring the path to already exist. */ opensearchScript = "${-> binPath().resolve('opensearch.bat').toString()}" } else { executable = 'bash' wrapperScript = new File(cwd, "run") opensearchScript = binPath().resolve('opensearch') } if (config.daemonize) { if (Os.isFamily(Os.FAMILY_WINDOWS)) { /* * We have to delay building the string as the path will not exist during configuration which will fail on Windows due to * getting the short name requiring the path to already exist. */ args.add("${-> getShortPathName(wrapperScript.toString())}") } else { args.add("${wrapperScript}") } } else { args.add("${opensearchScript}") } if (this.nodeVersion.before("6.2.0")) { javaVersion = 8 } else if (this.nodeVersion.onOrAfter("6.2.0") && this.nodeVersion.before("6.3.0")) { javaVersion = 9 } else if (this.nodeVersion.onOrAfter("6.3.0") && this.nodeVersion.before("6.5.0")) { javaVersion = 10 } args.addAll("-E", "node.portsfile=true") env = [:] env.putAll(config.environmentVariables) for (Map.Entry property : System.properties.entrySet()) { if (property.key.startsWith('tests.opensearch.')) { args.add("-E") args.add("${property.key.substring('tests.opensearch.'.size())}=${property.value}") } } if (Os.isFamily(Os.FAMILY_WINDOWS)) { /* * We have to delay building the string as the path will not exist during configuration which will fail on Windows due to * getting the short name requiring the path to already exist. */ env.put('OPENSEARCH_PATH_CONF', "${-> getShortPathName(pathConf.toString())}") } else { env.put('OPENSEARCH_PATH_CONF', pathConf) } if (!System.properties.containsKey("tests.opensearch.path.data")) { if (Os.isFamily(Os.FAMILY_WINDOWS)) { /* * We have to delay building the string as the path will not exist during configuration which will fail on Windows due to * getting the short name requiring the path to already exist. This one is extra tricky because usually we rely on the node * creating its data directory on startup but we simply can not do that here because getting the short path name requires * the directory to already exist. Therefore, we create this directory immediately before getting the short name. */ args.addAll("-E", "path.data=${-> Files.createDirectories(Paths.get(dataDir.toString())); getShortPathName(dataDir.toString())}") } else { args.addAll("-E", "path.data=${-> dataDir.toString()}") } } if (Os.isFamily(Os.FAMILY_WINDOWS)) { args.add('"') // end the entire command, quoted } } Path binPath() { if (Os.isFamily(Os.FAMILY_WINDOWS)) { return Paths.get(getShortPathName(new File(homeDir, 'bin').toString())) } else { return Paths.get(new File(homeDir, 'bin').toURI()) } } static String getShortPathName(String path) { assert Os.isFamily(Os.FAMILY_WINDOWS) final WString longPath = new WString("\\\\?\\" + path) // first we get the length of the buffer needed final int length = JNAKernel32Library.getInstance().GetShortPathNameW(longPath, null, 0) if (length == 0) { throw new IllegalStateException("path [" + path + "] encountered error [" + Native.getLastError() + "]") } final char[] shortPath = new char[length] // knowing the length of the buffer, now we get the short name if (JNAKernel32Library.getInstance().GetShortPathNameW(longPath, shortPath, length) == 0) { throw new IllegalStateException("path [" + path + "] encountered error [" + Native.getLastError() + "]") } // we have to strip the \\?\ away from the path for cmd.exe return Native.toString(shortPath).substring(4) } /** Returns debug string for the command that started this node. */ String getCommandString() { String esCommandString = "\nNode ${nodeNum} configuration:\n" esCommandString += "|-----------------------------------------\n" esCommandString += "| cwd: ${cwd}\n" esCommandString += "| command: ${executable} ${args.join(' ')}\n" esCommandString += '| environment:\n' env.each { k, v -> esCommandString += "| ${k}: ${v}\n" } if (config.daemonize) { esCommandString += "|\n| [${wrapperScript.name}]\n" wrapperScript.eachLine('UTF-8', { line -> esCommandString += " ${line}\n"}) } esCommandString += '|\n| [opensearch.yml]\n' configFile.eachLine('UTF-8', { line -> esCommandString += "| ${line}\n" }) esCommandString += "|-----------------------------------------" return esCommandString } void writeWrapperScript() { String argsPasser = '"$@"' String exitMarker = "; if [ \$? != 0 ]; then touch run.failed; fi" if (Os.isFamily(Os.FAMILY_WINDOWS)) { argsPasser = '%*' exitMarker = "\r\n if \"%errorlevel%\" neq \"0\" ( type nul >> run.failed )" } wrapperScript.setText("\"${opensearchScript}\" ${argsPasser} > run.log 2>&1 ${exitMarker}", 'UTF-8') } /** Returns an address and port suitable for a uri to connect to this node over http */ String httpUri() { return httpPortsFile.readLines("UTF-8").get(0) } /** Returns an address and port suitable for a uri to connect to this node over transport protocol */ String transportUri() { return transportPortsFile.readLines("UTF-8").get(0) } /** Returns the file which contains the transport protocol ports for this node */ File getTransportPortsFile() { return transportPortsFile } /** Returns the data directory for this node */ File getDataDir() { if (!(dataDir instanceof File)) { return new File(dataDir) } return dataDir } }