From 937338215525c1a66102ceafd7905813c1561b3a Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Thu, 12 Nov 2015 22:33:21 -0800 Subject: [PATCH] Build: Use ant exec for starting elasticsearch in integ tests Currently elasticsearch in integ tests is started using an ant task on windows, or gradle exec on everything else. However, gradle exec has some flaws, one being Ctrl-C does not run finalizedBy tasks, which means interrupting integ tests will leak a jvm. This change makes all systems use ant exec. One caveat is, if there is any output by the jvm, we lose it in ant bit heaven. But this is no different than what we had with gradle. In the future, we should look at using a separate thread to pump streams from the elasticsearch process. closes #14701 --- .../gradle/test/ClusterFormationTasks.groovy | 100 ++++++++++-------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy index 847de12b995..b53420334ce 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy @@ -18,22 +18,17 @@ */ package org.elasticsearch.gradle.test -import org.gradle.internal.jvm.Jvm - -import java.nio.file.Paths - +import org.apache.tools.ant.DefaultLogger import org.apache.tools.ant.taskdefs.condition.Os import org.elasticsearch.gradle.VersionProperties -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.InvalidUserDataException -import org.gradle.api.Project -import org.gradle.api.Task +import org.gradle.api.* import org.gradle.api.file.FileCollection import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Delete import org.gradle.api.tasks.Exec +import java.nio.file.Paths + /** * A helper for creating tasks to build a cluster that is used by a task, and tear down the cluster when the task is finished. */ @@ -216,23 +211,57 @@ class ClusterFormationTasks { 'JAVA_HOME' : project.javaHome, 'ES_GC_OPTS': config.jvmArgs ] - List esProps = config.systemProperties.collect { key, value -> "-D${key}=${value}" } + List esProps = config.systemProperties.collect { key, value -> "-D${key}=${value}" } for (Map.Entry property : System.properties.entrySet()) { if (property.getKey().startsWith('es.')) { esProps.add("-D${property.getKey()}=${property.getValue()}") } } - Closure esPostStartActions = { ant, logger -> - ant.waitfor(maxwait: '30', maxwaitunit: 'second', checkevery: '500', checkeveryunit: 'millisecond', timeoutproperty: "failed${name.capitalize()}") { + String executable + List esArgs = [] + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + executable = 'cmd' + esArgs.add('/C') + esArgs.add('call') + } else { + executable = 'sh' + } + // running with cmd on windows will look for this with the .bat extension + esArgs.add(new File(home, 'bin/elasticsearch').toString()) + + // this closure is converted into ant nodes by groovy's AntBuilder + Closure antRunner = { + exec(executable: executable, spawn: config.daemonize, dir: cwd, taskname: 'elasticsearch') { + esEnv.each { key, value -> env(key: key, value: value) } + (esArgs + esProps).each { arg(value: it) } + } + waitfor(maxwait: '30', maxwaitunit: 'second', checkevery: '500', checkeveryunit: 'millisecond', timeoutproperty: "failed${name}") { and { resourceexists { - file file: pidFile.toString() + file(file: pidFile.toString()) } http(url: "http://localhost:${config.httpPort}") } } + } + + // this closure is the actual code to run elasticsearch + Closure elasticsearchRunner = { + ByteArrayOutputStream buffer = new ByteArrayOutputStream() + if (logger.isInfoEnabled() || config.daemonize == false) { + // run with piping streams directly out (even stderr to stdout since gradle would capture it) + runAntCommand(project, antRunner, System.out, System.err) + } else { + // buffer the output, we may not need to print it + PrintStream captureStream = new PrintStream(buffer, true, "UTF-8") + runAntCommand(project, antRunner, captureStream, captureStream) + } + if (ant.properties.containsKey("failed${name}".toString())) { + // the waitfor failed, so dump any output we got (may be empty if info logging, but that is ok) + logger.error(buffer.toString('UTF-8')) + // also dump the cluster's log file, it may be useful File logFile = new File(home, "logs/${clusterName}.log") if (logFile.exists()) { logFile.eachLine { line -> logger.error(line) } @@ -240,35 +269,10 @@ class ClusterFormationTasks { throw new GradleException('Failed to start elasticsearch') } } - File esScript = new File(home, 'bin/elasticsearch') - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - // elasticsearch.bat is spawned as it has no daemon mode - return project.tasks.create(name: name, type: DefaultTask, dependsOn: setup) << { - // Fall back to Ant exec task as Gradle Exec task does not support spawning yet - ant.exec(executable: 'cmd', spawn: config.daemonize, dir: cwd) { - esEnv.each { key, value -> env(key: key, value: value) } - (['/C', 'call', esScript] + esProps).each { arg(value: it) } - } - esPostStartActions(ant, logger) - } - } else { - List esExecutable = [esScript] - if(config.daemonize) { - esExecutable.add("-d") - } - - return project.tasks.create(name: name, type: Exec, dependsOn: setup) { - workingDir cwd - executable 'sh' - args esExecutable - args esProps - environment esEnv - doLast { - esPostStartActions(ant, logger) - } - } - } + + Task start = project.tasks.create(name: name, type: DefaultTask, dependsOn: setup) + start.doLast(elasticsearchRunner) + return start } /** Adds a task to check if the process with the given pidfile is actually elasticsearch */ @@ -347,4 +351,16 @@ class ClusterFormationTasks { static File pidFile(File dir) { return new File(dir, 'es.pid') } + + /** Runs an ant command, sending output to the given out and error streams */ + static void runAntCommand(Project project, Closure command, PrintStream outputStream, PrintStream errorStream) { + DefaultLogger listener = new DefaultLogger( + errorPrintStream: errorStream, + outputPrintStream: outputStream, + messageOutputLevel: org.apache.tools.ant.Project.MSG_INFO) + + project.ant.project.addBuildListener(listener) + project.configure(project.ant, command) + project.ant.project.removeBuildListener(listener) + } }