diff --git a/Makefile b/Makefile index 80fb36d485b..c8938a9ffff 100644 --- a/Makefile +++ b/Makefile @@ -46,15 +46,15 @@ launcher = rundeck-launcher/launcher/build/libs/rundeck-launcher-$(VERSION).jar -.PHONY: clean rundeck docs makedocs plugins war launcher +.PHONY: clean rundeck docs makedocs plugins war launcher core-release core-snapshot rundeck: $(launcher) @echo $(VERSION)-$(RELEASE) -rpm: docs $(launcher) $(plugs) +rpm: docs $(launcher) plugins cd packaging; $(MAKE) VERSION=$(VNUMBER) VNAME=$(VERSION) RELEASE=$(RELEASE) rpmclean rpm -deb: docs $(launcher) $(plugs) +deb: docs $(launcher) plugins cd packaging; $(MAKE) VERSION=$(VNUMBER) VNAME=$(VERSION) RELEASE=$(RELEASE) debclean deb makedocs: @@ -63,6 +63,12 @@ makedocs: $(core): $(CORE_FILES) cd core; ./gradlew $(PROXY_DEFS) -PbuildNum=$(RELEASE) clean check assemble javadoc +core-snapshot: $(CORE_FILES) + cd core; ./gradlew $(PROXY_DEFS) -Psnapshot -PbuildNum=$(RELEASE) uploadArchives + +core-release: $(CORE_FILES) + cd core; ./gradlew $(PROXY_DEFS) -Prelease -PbuildNum=$(RELEASE) uploadArchives + war: $(war) grails: $(GRAILS_HOME) @@ -95,10 +101,8 @@ $(war): $(core) $(RUNDECK_FILES) $(GRAILS_HOME) cd rundeckapp; $(GRAILS) test-app cd rundeckapp; yes | $(GRAILS) prod war -$(plugs): $(core) $(PLUGIN_FILES) - cd plugins && ./gradlew - -plugins: $(plugs) +plugins: $(core) $(PLUGIN_FILES) + cd plugins && ./gradlew docs: makedocs mkdir -p ./rundeckapp/web-app/docs diff --git a/core/build.gradle b/core/build.gradle index 05a4497ae04..f81ff708a3e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -5,12 +5,26 @@ description = 'The Rundeck Core API project' import org.apache.tools.ant.filters.ReplaceTokens +import org.gradle.plugins.signing.Sign apply from: "${rootDir}/gradle/java.gradle" archivesBaseName = 'rundeck-core' defaultTasks 'clean','assemble' +def isReleaseBuild +def isSnapshotBuild +def isDevBuild + +if(hasProperty("release")){ + isReleaseBuild=true +}else if(hasProperty("snapshot")){ + isSnapshotBuild=true + version=version.split('-')[0]+ '-SNAPSHOT' +}else{ + isDevBuild=true +} + repositories { mavenCentral() add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) { @@ -83,96 +97,161 @@ assemble { } uploadArchives { - repositories.mavenDeployer { - configuration = configurations.archives - pom.project { - artifactId archivesBaseName - groupId group - inceptionYear '2011' - packaging 'jar' - version version - name "RunDeck Core" - url 'http://rundeck.org' - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' + repositories.mavenDeployer { + configuration = configurations.archives + pom.project { + artifactId archivesBaseName + groupId "com.dtolabs.rundeck" + inceptionYear '2011' + packaging 'jar' + version version + name "RunDeck Core" + url 'http://rundeck.org' + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } } } - } } } -task createPom << { - pom { - project { - artifactId archivesBaseName - groupId group - inceptionYear '2011' - packaging 'jar' - version version - name "RunDeck Core" - url 'http://rundeck.org' - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' - } +//build a pom we reuse for both maven builds and release to sonatype +def buildpom=pom { + project { + artifactId archivesBaseName + groupId project.group + inceptionYear '2011' + packaging 'jar' + version version + name "Rundeck Core" + description "Core library for the Rundeck web console for command dispatching and job scheduling" + url 'http://rundeck.org' + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' } - properties{ - 'version'(version) - 'version_build'(buildNum) - 'version_ident'(version+'-'+buildNum) + } + properties{ + 'version'(version) + 'version_build'(buildNum) + 'version_ident'(version+'-'+buildNum) + } + scm { + url 'https://github.com/dtolabs/rundeck' + connection 'scm:git:git@github.com/dtolabs/rundeck.git' + developerConnection 'scm:git:git@github.com:dtolabs/rundeck.git' + } + developers { + developer { + id('gschueler') + name('Greg Schueler') + email('greg@dtosolutions.com') + } + } + parent { + groupId('org.sonatype.oss') + artifactId('oss-parent') + version('7') + } + build { + resources{ + resource{ + directory 'src/main/meta' + targetPath 'META-INF' + filtering=true + } + resource{ + directory 'src/main/resources' + } } - build { - resources{ - resource{ - directory 'src/main/meta' - targetPath 'META-INF' - filtering=true - } - resource{ - directory 'src/main/resources' + plugins{ + plugin{ + groupId 'org.apache.maven.plugins' + artifactId 'maven-compiler-plugin' + version '2.3.2' + configuration{ + 'source'('1.5') + 'target'('1.5') } } - plugins{ - plugin{ - groupId 'org.apache.maven.plugins' - artifactId 'maven-compiler-plugin' - version '2.3.2' - configuration{ - 'source'('1.5') - 'target'('1.5') + plugin{ + groupId 'org.apache.maven.plugins' + artifactId 'maven-surefire-plugin' + version '2.10' + configuration{ + systemPropertyVariables{ + 'rdeck.base'('${project.build.directory}/rdeck_base') } + redirectTestOutputToFile 'true' } - plugin{ - groupId 'org.apache.maven.plugins' - artifactId 'maven-surefire-plugin' - version '2.10' - configuration{ - systemPropertyVariables{ - 'rdeck.base'('${project.build.directory}/rdeck_base') - } - redirectTestOutputToFile 'true' - } - } - plugin{ - groupId 'org.apache.maven.plugins' - artifactId 'maven-jar-plugin' - version '2.3.2' - configuration{ - archive{ - manifestEntries{ - 'Rundeck-Version'(version) - 'Rundeck-Tools-Dependencies'(configurations.runtime.collect { "$it.name" }.join(" ")) - } - } + } + plugin{ + groupId 'org.apache.maven.plugins' + artifactId 'maven-jar-plugin' + version '2.3.2' + configuration{ + archive{ + manifestEntries{ + 'Rundeck-Version'(version) + 'Rundeck-Tools-Dependencies'(configurations.runtime.collect { "$it.name" }.join(" ")) + } } } } } } - }.writeTo("pom.xml") + } +} + +task createPom << { + buildpom.writeTo("pom.xml") +} + + +// prompt for PGP key passphrase if not set +gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.allTasks.any { it instanceof Sign } && !ext.hasProperty("signing.password") && !isDevBuild) { + // Use Java 6's console to read from the console (no good for a CI environment) + Console console = System.console() + console.printf "\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n" + + //def id = console.readLine("PGP Key Id: ") + //def file = console.readLine("PGP Secret Key Ring File (absolute path): ") + def password = console.readPassword("PGP Private Key Password: ") + + //allprojects { ext."signing.keyId" = id } + //allprojects { ext."signing.secretKeyRingFile" = file } + allprojects { ext."signing.password" = password } + + console.printf "\nThanks.\n\n" + } +} + +uploadArchives { + if(isDevBuild){ + repositories{ + mavenLocal() + } + }else{ + repositories.mavenDeployer { + if(isReleaseBuild){ + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + } + + configuration = configurations.archives + + repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') { + authentication(userName: sonatypeUsername, password: sonatypePassword) + } + snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') { + authentication(userName: sonatypeUsername, password: sonatypePassword) + } + pom=buildpom + } + } } diff --git a/core/pom.xml b/core/pom.xml index 535c6e7af26..a3f1bf70785 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -2,10 +2,16 @@ 4.0.0 - com.dtolabs.rundeck + + oss-parent + org.sonatype.oss + 7 + + org.rundeck rundeck-core 1.5-dev RunDeck Core + Core library for the Rundeck web console for command dispatching and job scheduling http://rundeck.org 2011 @@ -15,6 +21,18 @@ repo + + + gschueler + Greg Schueler + greg@dtosolutions.com + + + + scm:git:git@github.com/dtolabs/rundeck.git + scm:git:git@github.com:dtolabs/rundeck.git + https://github.com/dtolabs/rundeck + diff --git a/core/src/main/java/com/dtolabs/rundeck/core/CoreException.java b/core/src/main/java/com/dtolabs/rundeck/core/CoreException.java index b25a2c1b7a3..48afc79a41f 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/CoreException.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/CoreException.java @@ -16,13 +16,10 @@ package com.dtolabs.rundeck.core; -import org.apache.tools.ant.BuildException; - - /** * CoreException, base exception class for the core framework */ -public class CoreException extends BuildException { +public class CoreException extends RuntimeException { public CoreException(final String msg) { super(msg); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/NodesetFailureException.java b/core/src/main/java/com/dtolabs/rundeck/core/NodesetFailureException.java index c9a86895bbd..2517bf4539c 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/NodesetFailureException.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/NodesetFailureException.java @@ -23,6 +23,9 @@ */ package com.dtolabs.rundeck.core; +import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; + import java.util.Collection; import java.util.Map; @@ -32,7 +35,7 @@ * @author Greg Schueler greg@dtosolutions.com * @version $Revision$ */ -public class NodesetFailureException extends CoreException { +public class NodesetFailureException extends DispatcherException { /** * Exit code for a Nodeset failure exception */ @@ -44,7 +47,6 @@ public class NodesetFailureException extends CoreException { * @param retryMsg commandline for retrying the command for the failed nodes. */ private Collection nodeset; - private Map nodeFailures; /** * Create NodesetFailureException @@ -58,9 +60,9 @@ public NodesetFailureException(final Collection nodeset) { * Create NodesetFailureException * @param nodeset node names */ - public NodesetFailureException(final Map failures) { + public NodesetFailureException(final Map failures) { super("Execution failed on the following " + (null != failures ? failures.size() : 0) + " nodes: " + failures); - this.nodeFailures = failures; + setResultMap(failures); } /** @@ -71,19 +73,7 @@ public Collection getNodeset() { return nodeset; } - /** - * Set the nodeset - * @param nodeset node names collection - */ - public void setNodeset(final Collection nodeset) { - this.nodeset = nodeset; - } - - public Map getNodeFailures() { - return nodeFailures; - } - - public void setNodeFailures(Map nodeFailures) { - this.nodeFailures = nodeFailures; + public Map getNodeFailures() { + return getResultMap(); } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/cli/FailedNodesFilestore.java b/core/src/main/java/com/dtolabs/rundeck/core/cli/FailedNodesFilestore.java index 280807cc105..9169ad4a482 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/cli/FailedNodesFilestore.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/cli/FailedNodesFilestore.java @@ -16,17 +16,21 @@ package com.dtolabs.rundeck.core.cli; +import com.dtolabs.rundeck.core.cli.project.ProjectToolException; import com.dtolabs.rundeck.core.execution.FailedNodesListener; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; import com.dtolabs.rundeck.core.utils.NodeSet; -import com.dtolabs.rundeck.core.cli.project.ProjectToolException; +import org.apache.log4j.Logger; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.Collection; -import java.util.Properties; -import java.util.Map; import java.util.HashMap; - -import org.apache.log4j.Logger; +import java.util.Map; +import java.util.Properties; /** * Utility class for managing the failed nodes filestore @@ -107,7 +111,7 @@ public void nodesSucceeded() { } } - public void nodesFailed(final Map failedNodeNames) { + public void nodesFailed(final Map failedNodeNames) { if (null != failedNodesFile) { if (failedNodeNames.size() > 0) { //store failed node list into file, echo Commandline with nodelist diff --git a/core/src/main/java/com/dtolabs/rundeck/core/dispatcher/DataContextUtils.java b/core/src/main/java/com/dtolabs/rundeck/core/dispatcher/DataContextUtils.java index f55ff171fb3..3ef23cb4cc5 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/dispatcher/DataContextUtils.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/dispatcher/DataContextUtils.java @@ -125,6 +125,34 @@ public static String replaceDataReferences(final String input, final Map> merge(final Map> targetContext, + final Map> newContext) { + + final HashMap> result = deepCopy(targetContext); + for (final Map.Entry> entry : newContext.entrySet()) { + if (!targetContext.containsKey(entry.getKey())) { + result.put(entry.getKey(), new HashMap()); + } else { + result.put(entry.getKey(), new HashMap(targetContext.get(entry.getKey()))); + } + result.get(entry.getKey()).putAll(entry.getValue()); + } + return result; + } + + private static HashMap> deepCopy(Map> context) { + HashMap> map = new HashMap>(); + for (final Map.Entry> entry : context.entrySet()) { + map.put(entry.getKey(), new HashMap(entry.getValue())); + } + return map; + } + /** * Indicates that the value of a property reference could not be resolved. */ diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionContextImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionContextImpl.java index fa74f7b330e..67358d2413f 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionContextImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionContextImpl.java @@ -24,13 +24,20 @@ package com.dtolabs.rundeck.core.execution; import com.dtolabs.rundeck.core.common.Framework; +import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.common.INodeSet; import com.dtolabs.rundeck.core.common.NodeSetImpl; import com.dtolabs.rundeck.core.common.NodesSelector; +import com.dtolabs.rundeck.core.common.SelectorUtils; +import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeExecutionContext; import java.io.File; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** @@ -38,7 +45,7 @@ * * @author Greg Schueler greg@dtosolutions.com */ -public class ExecutionContextImpl implements ExecutionContext, StepExecutionContext { +public class ExecutionContextImpl implements ExecutionContext, StepExecutionContext, NodeExecutionContext { private String frameworkProject; private String user; private NodesSelector nodeSet; @@ -48,6 +55,7 @@ public class ExecutionContextImpl implements ExecutionContext, StepExecutionCont private int loglevel; private Map> dataContext; private Map> privateDataContext; + private Map>> nodeDataContext; private ExecutionListener executionListener; private Framework framework; private File nodesFile; @@ -59,6 +67,7 @@ public class ExecutionContextImpl implements ExecutionContext, StepExecutionCont private ExecutionContextImpl() { stepContext = new ArrayList(); nodes = new NodeSetImpl(); + nodeDataContext = new HashMap>>(); } public static Builder builder() { @@ -70,6 +79,12 @@ public static Builder builder(ExecutionContext context) { public static Builder builder(StepExecutionContext context) { return new Builder(context); } + + @Override + public Map>> getNodeDataContext() { + return nodeDataContext; + } + public static class Builder { private ExecutionContextImpl ctx; @@ -95,6 +110,10 @@ public Builder(final ExecutionContext original) { ctx.keepgoing = original.isKeepgoing(); ctx.nodeRankAttribute = original.getNodeRankAttribute(); ctx.nodeRankOrderAscending = original.isNodeRankOrderAscending(); + if(original instanceof NodeExecutionContext){ + NodeExecutionContext original1 = (NodeExecutionContext) original; + ctx.nodeDataContext.putAll(original1.getNodeDataContext()); + } } } @@ -126,6 +145,44 @@ public Builder nodes(INodeSet nodeSet) { return this; } + /** + * Set node set/selector to single node context, and optionally merge node-specific context data + */ + public Builder singleNodeContext(INodeEntry node, boolean setContextData) { + nodeSelector(SelectorUtils.singleNode(node.getNodename())); + nodes(NodeSetImpl.singleNodeSet(node)); + if(setContextData) { + //merge in any node-specific data context + nodeContextData(node); + + if (null != ctx.nodeDataContext && null != ctx.nodeDataContext.get(node.getNodename())) { + ctx.dataContext = DataContextUtils.merge(ctx.dataContext, + ctx.nodeDataContext.get(node.getNodename())); + } + } + return this; + } + + public Builder nodeContextData(INodeEntry node) { + ctx.dataContext = DataContextUtils.addContext("node", DataContextUtils.nodeData(node), ctx.dataContext); + return this; + } + + /** + * Add/replace a context data set + */ + public Builder setContext(final String key, final Map data) { + return dataContext(DataContextUtils.addContext(key, data, ctx.dataContext)); + } + /** + * merge a context data set + */ + public Builder mergeContext(final String key, final Map data) { + HashMap> tomerge = new HashMap>(); + tomerge.put(key, data); + return dataContext(DataContextUtils.merge(ctx.dataContext, tomerge)); + } + public Builder loglevel(int loglevel) { ctx.loglevel = loglevel; return this; @@ -191,6 +248,11 @@ public Builder pushContextStep(final int step) { return this; } + public Builder nodeDataContext(final String nodeName, final Map> dataContext) { + ctx.nodeDataContext.put(nodeName, dataContext); + return this; + } + public ExecutionContextImpl build() { return ctx; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionService.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionService.java index ae94ccb922e..f9684ef7ad1 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionService.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionService.java @@ -25,7 +25,9 @@ import com.dtolabs.rundeck.core.common.FrameworkSupportService; import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.execution.service.ExecutionServiceException; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; @@ -56,7 +58,8 @@ public interface ExecutionService extends FrameworkSupportService { * @return result * @deprecated use {@link #executeStep(com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext, StepExecutionItem)} */ - public ExecutionResult executeItem(StepExecutionContext context, StepExecutionItem item) throws ExecutionException; + public ExecutionResult executeItem(StepExecutionContext context, StepExecutionItem item) + throws ExecutionException, ExecutionServiceException; /** * Execute a workflow step item for the given context and return the result. @@ -65,9 +68,9 @@ public interface ExecutionService extends FrameworkSupportService { * * @param item item * - * @return result + * @return not-null result */ - public StepExecutionResult executeStep(StepExecutionContext context, StepExecutionItem item) throws ExecutionException; + public StepExecutionResult executeStep(StepExecutionContext context, StepExecutionItem item) throws StepException; /** * Interpret the execution item within the context for the given node. @@ -79,11 +82,13 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExec /** * Dispatch the command (execution item) to all the nodes within the context. */ - public DispatcherResult dispatchToNodes(StepExecutionContext context, NodeStepExecutionItem item) throws DispatcherException; + public DispatcherResult dispatchToNodes(StepExecutionContext context, NodeStepExecutionItem item) + throws DispatcherException, ExecutionServiceException; /** * Dispatch the command (execution item) to all the nodes within the context. */ - public DispatcherResult dispatchToNodes(StepExecutionContext context, Dispatchable item) throws DispatcherException; + public DispatcherResult dispatchToNodes(StepExecutionContext context, Dispatchable item) + throws DispatcherException, ExecutionServiceException; /** @@ -113,6 +118,5 @@ public String fileCopyScriptContent(final ExecutionContext context, String scrip /** * Execute a command within the context on the node. */ - public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) throws - ExecutionException; + public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) ; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionServiceImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionServiceImpl.java index e2cb2dc9003..87c43bbbe9c 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionServiceImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionServiceImpl.java @@ -23,23 +23,30 @@ */ package com.dtolabs.rundeck.core.execution; +import com.dtolabs.rundeck.core.CoreException; import com.dtolabs.rundeck.core.cli.ExecTool; import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; +import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; +import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; +import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; +import com.dtolabs.rundeck.core.execution.dispatch.NodeDispatcher; +import com.dtolabs.rundeck.core.execution.service.ExecutionServiceException; +import com.dtolabs.rundeck.core.execution.service.FileCopier; +import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.service.NodeExecutor; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; -import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; -import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; -import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; -import com.dtolabs.rundeck.core.execution.dispatch.NodeDispatcher; -import com.dtolabs.rundeck.core.execution.service.*; -import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutor; import com.dtolabs.rundeck.core.utils.FormattedOutputStream; import com.dtolabs.rundeck.core.utils.LogReformatter; import com.dtolabs.rundeck.core.utils.MapGenerator; @@ -63,7 +70,8 @@ public ExecutionServiceImpl(Framework framework) { this.framework = framework; } - public ExecutionResult executeItem(StepExecutionContext context, StepExecutionItem executionItem) throws ExecutionException { + public ExecutionResult executeItem(StepExecutionContext context, StepExecutionItem executionItem) + throws ExecutionException, ExecutionServiceException { if (null != context.getExecutionListener()) { context.getExecutionListener().beginStepExecution(context, executionItem); } @@ -89,7 +97,8 @@ public ExecutionResult executeItem(StepExecutionContext context, StepExecutionIt return baseExecutionResult; } - public StepExecutionResult executeStep(StepExecutionContext context, StepExecutionItem item) throws ExecutionException { + + public StepExecutionResult executeStep(StepExecutionContext context, StepExecutionItem item) throws StepException { if (null != context.getExecutionListener()) { context.getExecutionListener().beginStepExecution(context, item); } @@ -98,16 +107,14 @@ public StepExecutionResult executeStep(StepExecutionContext context, StepExecuti try { executor = framework.getStepExecutionService().getExecutorForItem(item); } catch (ExecutionServiceException e) { - throw new ExecutionException(e); + return new StepExecutionResultImpl(e, ServiceFailureReason.ServiceFailure, e.getMessage()); } - StepExecutionResult result=null; + StepExecutionResult result = null; final LogReformatter formatter = createLogReformatter(null, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); try { result = executor.executeWorkflowStep(context, item); - } catch (StepException e) { - throw new ExecutionException(e); } finally { loggingReformatter.resetOutputStreams(); if (null != context.getExecutionListener()) { @@ -117,6 +124,10 @@ public StepExecutionResult executeStep(StepExecutionContext context, StepExecuti return result; } + static enum ServiceFailureReason implements FailureReason{ + ServiceFailure + } + public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExecutionItem item, INodeEntry node) throws NodeStepException { @@ -124,24 +135,25 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, try { interpreter = framework.getNodeStepExecutorForItem(item); } catch (ExecutionServiceException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e, ServiceFailureReason.ServiceFailure, node.getNodename()); } if (null != context.getExecutionListener()) { context.getExecutionListener().beginExecuteNodeStep(context, item, node); } //create node context for node and substitute data references in command - final Map> nodeDataContext = - DataContextUtils.addContext("node", DataContextUtils.nodeData(node), context.getDataContext()); -// final String[] nodeCommand = DataContextUtils.replaceDataReferences(command, nodeDataContext); final LogReformatter formatter = createLogReformatter(node, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); NodeStepResult result = null; try { - final ExecutionContextImpl nodeContext = new ExecutionContextImpl.Builder(context).dataContext( - nodeDataContext).build(); + final ExecutionContextImpl nodeContext = new ExecutionContextImpl.Builder(context) + .singleNodeContext(node,true) + .build(); result = interpreter.executeNodeStep(nodeContext, item, node); + if (!result.isSuccess()) { + context.getExecutionListener().log(0, "Failed: " + result.toString()); + } } finally { loggingReformatter.resetOutputStreams(); if (null != context.getExecutionListener()) { @@ -152,17 +164,13 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, } public DispatcherResult dispatchToNodes(StepExecutionContext context, NodeStepExecutionItem item) throws - DispatcherException { + DispatcherException, + ExecutionServiceException { if (null != context.getExecutionListener()) { context.getExecutionListener().beginNodeDispatch(context, item); } - final NodeDispatcher dispatcher; - try { - dispatcher = framework.getNodeDispatcherForContext(context); - } catch (ExecutionServiceException e) { - throw new DispatcherException(e); - } + final NodeDispatcher dispatcher = framework.getNodeDispatcherForContext(context); DispatcherResult result = null; try { result = dispatcher.dispatch(context, item); @@ -175,17 +183,13 @@ public DispatcherResult dispatchToNodes(StepExecutionContext context, NodeStepEx } public DispatcherResult dispatchToNodes(StepExecutionContext context, Dispatchable item) throws - DispatcherException { + DispatcherException, + ExecutionServiceException { if (null != context.getExecutionListener()) { context.getExecutionListener().beginNodeDispatch(context, item); } - final NodeDispatcher dispatcher; - try { - dispatcher = framework.getNodeDispatcherForContext(context); - } catch (ExecutionServiceException e) { - throw new DispatcherException(e); - } + final NodeDispatcher dispatcher = framework.getNodeDispatcherForContext(context); DispatcherResult result = null; try { result = dispatcher.dispatch(context, item); @@ -208,7 +212,7 @@ public String fileCopyFileStream(ExecutionContext context, InputStream input, IN try { copier = framework.getFileCopierForNodeAndProject(node, context.getFrameworkProject()); } catch (ExecutionServiceException e) { - throw new FileCopierException(e); + throw new FileCopierException(e.getMessage(), ServiceFailureReason.ServiceFailure, e); } final LogReformatter formatter = createLogReformatter(node, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); @@ -233,7 +237,7 @@ public String fileCopyFile(ExecutionContext context, File file, try { copier = framework.getFileCopierForNodeAndProject(node, context.getFrameworkProject()); } catch (ExecutionServiceException e) { - throw new FileCopierException(e); + throw new FileCopierException(e.getMessage(), ServiceFailureReason.ServiceFailure, e); } final LogReformatter formatter = createLogReformatter(node, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); @@ -258,7 +262,7 @@ public String fileCopyScriptContent(ExecutionContext context, String script, try { copier = framework.getFileCopierForNodeAndProject(node, context.getFrameworkProject()); } catch (ExecutionServiceException e) { - throw new FileCopierException(e); + throw new FileCopierException(e.getMessage(), ServiceFailureReason.ServiceFailure, e); } final LogReformatter formatter = createLogReformatter(node, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); @@ -275,7 +279,7 @@ public String fileCopyScriptContent(ExecutionContext context, String script, } public NodeExecutorResult executeCommand(final ExecutionContext context, final String[] command, - final INodeEntry node) throws ExecutionException { + final INodeEntry node) { if (null != context.getExecutionListener()) { context.getExecutionListener().beginNodeExecution(context, command, node); @@ -284,20 +288,18 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S try { nodeExecutor = framework.getNodeExecutorForNodeAndProject(node, context.getFrameworkProject()); } catch (ExecutionServiceException e) { - throw new ExecutionException(e); + throw new CoreException(e); } //create node context for node and substitute data references in command - final Map> nodeDataContext = - DataContextUtils.addContext("node", DataContextUtils.nodeData(node), context.getDataContext()); - final String[] nodeCommand = DataContextUtils.replaceDataReferences(command, nodeDataContext); + final ExecutionContextImpl nodeContext = new ExecutionContextImpl.Builder(context).nodeContextData(node).build(); + + final String[] nodeCommand = DataContextUtils.replaceDataReferences(command, nodeContext.getDataContext()); final LogReformatter formatter = createLogReformatter(node, context.getExecutionListener()); final ThreadStreamFormatter loggingReformatter = new ThreadStreamFormatter(formatter).invoke(); NodeExecutorResult result = null; try { - final ExecutionContextImpl nodeContext = new ExecutionContextImpl.Builder(context).dataContext( - nodeDataContext).build(); result = nodeExecutor.executeCommand(nodeContext, nodeCommand, node); } finally { loggingReformatter.resetOutputStreams(); diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/FailedNodesListener.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/FailedNodesListener.java index 3e1b9ba526a..92a28ba5a88 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/FailedNodesListener.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/FailedNodesListener.java @@ -23,6 +23,8 @@ */ package com.dtolabs.rundeck.core.execution; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; + import java.util.Collection; import java.util.Map; @@ -38,7 +40,7 @@ public interface FailedNodesListener { * * @param names node names */ - public void nodesFailed(Map failures); + public void nodesFailed(Map failures); /** * Called if no nodes failed during execution. diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/Dispatchable.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/Dispatchable.java index c3fe235f24b..2ba16338e33 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/Dispatchable.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/Dispatchable.java @@ -25,7 +25,6 @@ import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.StatusResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; @@ -35,5 +34,5 @@ * @author Greg Schueler greg@dtosolutions.com */ public interface Dispatchable { - public NodeStepResult dispatch(ExecutionContext context, INodeEntry node) throws DispatcherException; + public NodeStepResult dispatch(ExecutionContext context, INodeEntry node) ; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherException.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherException.java index 07231641890..59eed4fbd69 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherException.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherException.java @@ -24,6 +24,10 @@ package com.dtolabs.rundeck.core.execution.dispatch; import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; + +import java.util.Map; + /** * DispatcherException is ... @@ -32,31 +36,17 @@ */ public class DispatcherException extends Exception { private INodeEntry node; + private Map resultMap; - public DispatcherException() { - } public DispatcherException(String s) { super(s); } - public DispatcherException(String s, Throwable throwable) { - super(s, throwable); - } - public DispatcherException(Throwable throwable) { super(throwable); } - public DispatcherException(INodeEntry node) { - this.node = node; - } - - public DispatcherException(String s, INodeEntry node) { - super(s); - this.node = node; - } - public DispatcherException(String s, Throwable throwable, INodeEntry node) { super(s, throwable); this.node = node; @@ -74,4 +64,12 @@ public INodeEntry getNode() { public void setNode(INodeEntry node) { this.node = node; } + + public Map getResultMap() { + return resultMap; + } + + protected void setResultMap(Map resultMap) { + this.resultMap = resultMap; + } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherResultImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherResultImpl.java index 45571a6a6f9..05ff467c968 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherResultImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/DispatcherResultImpl.java @@ -24,10 +24,10 @@ */ package com.dtolabs.rundeck.core.execution.dispatch; -import com.dtolabs.rundeck.core.execution.StatusResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; -import java.util.*; +import java.util.ArrayList; +import java.util.Map; /** @@ -77,10 +77,19 @@ public String toString() { if(null!=description){ return description; } - return "DispatcherResultImpl{" + - "results=" + results + - ", success=" + success + - ", description='" + description + '\'' + - '}'; + if(success) { + return "Dispatch successful (" + results.size() + " nodes)"; + }else{ + int i=0; + ArrayList names = new ArrayList(); + for (final String s : results.keySet()) { + NodeStepResult stepResult = results.get(s); + if(!stepResult.isSuccess()){ + i++; + names.add(s); + } + } + return "Dispatch failed on " + i + " nodes: " + names; + } } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/ParallelNodeDispatcher.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/ParallelNodeDispatcher.java index a85ce930ebc..3e51ea9f7cf 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/ParallelNodeDispatcher.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/ParallelNodeDispatcher.java @@ -23,14 +23,17 @@ */ package com.dtolabs.rundeck.core.execution.dispatch; -import com.dtolabs.rundeck.core.NodesetFailureException; import com.dtolabs.rundeck.core.cli.CallableWrapperTask; -import com.dtolabs.rundeck.core.common.*; +import com.dtolabs.rundeck.core.common.Framework; +import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.common.INodeSet; import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.FailedNodesListener; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; import com.dtolabs.rundeck.core.tasks.dispatch.NodeExecutionStatusTask; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -38,7 +41,13 @@ import org.apache.tools.ant.taskdefs.Parallel; import org.apache.tools.ant.taskdefs.Sequential; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeSet; import java.util.concurrent.Callable; /** @@ -93,7 +102,7 @@ public DispatcherResult dispatch(final StepExecutionContext context, parallelTask.setFailOnAny(!keepgoing); boolean success = false; final HashMap resultMap = new HashMap(); - final HashMap failureMap = new HashMap(); + final HashMap failureMap = new HashMap(); final Collection nodes1 = nodes.getNodes(); //reorder based on configured rank property and order final String rankProperty = null != context.getNodeRankAttribute() ? context.getNodeRankAttribute() : "nodename"; @@ -138,9 +147,7 @@ public DispatcherResult dispatch(final StepExecutionContext context, //extract status results failedListener.nodesFailed(failureMap); } - //now fail - //XXXX: needs to change from exception - throw new NodesetFailureException(failureMap); + return new DispatcherResultImpl(failureMap, false); } else if (null != failedListener && nodeNames.isEmpty()) { failedListener.nodesSucceeded(); } @@ -152,43 +159,65 @@ public DispatcherResult dispatch(final StepExecutionContext context, private Callable dispatchableCallable(final ExecutionContext context, final Dispatchable toDispatch, final HashMap resultMap, final INodeEntry node, - final Map failureMap) { + final Map failureMap) { return new Callable() { public Object call() throws Exception { - try { - final NodeStepResult dispatch = toDispatch.dispatch(context, node); - if (!dispatch.isSuccess()) { - failureMap.put(node.getNodename(), dispatch); - } - resultMap.put(node.getNodename(), dispatch); - return dispatch; - } catch (Throwable t) { - failureMap.put(node.getNodename(), t); - return null; + final NodeStepResult dispatch = toDispatch.dispatch(context, node); + if (!dispatch.isSuccess()) { + failureMap.put(node.getNodename(), dispatch); } + resultMap.put(node.getNodename(), dispatch); + return dispatch; } }; } - private Callable execItemCallable(final StepExecutionContext context, final NodeStepExecutionItem item, - final HashMap resultMap, final INodeEntry node, - final Map failureMap) { - return new Callable() { - public Object call() throws Exception { - try { - final NodeStepResult interpreterResult = framework.getExecutionService().executeNodeStep( - context, item, node); - if (!interpreterResult.isSuccess()) { - failureMap.put(node.getNodename(), interpreterResult); - } - resultMap.put(node.getNodename(), interpreterResult); - return interpreterResult; - } catch (Throwable t) { - failureMap.put(node.getNodename(), t); - return null; + static class ExecNodeStepCallable implements Callable{ + final StepExecutionContext context; + final NodeStepExecutionItem item; + final HashMap resultMap; + final INodeEntry node; + final Map failureMap; + final Framework framework; + + ExecNodeStepCallable(StepExecutionContext context, + NodeStepExecutionItem item, + HashMap resultMap, + INodeEntry node, + Map failureMap, + Framework framework) { + this.context = context; + this.item = item; + this.resultMap = resultMap; + this.node = node; + this.failureMap = failureMap; + this.framework = framework; + } + + @Override + public NodeStepResult call() { + try { + final NodeStepResult interpreterResult = framework.getExecutionService().executeNodeStep( + context, item, node); + if (!interpreterResult.isSuccess()) { + failureMap.put(node.getNodename(), interpreterResult); } + resultMap.put(node.getNodename(), interpreterResult); + return interpreterResult; + } catch (NodeStepException e) { + NodeStepResultImpl result = new NodeStepResultImpl(e, + e.getFailureReason(), + e.getMessage(), + node); + failureMap.put(node.getNodename(), result); + return result; } - }; + } + } + private ExecNodeStepCallable execItemCallable(final StepExecutionContext context, final NodeStepExecutionItem item, + final HashMap resultMap, final INodeEntry node, + final Map failureMap) { + return new ExecNodeStepCallable(context, item, resultMap, node, failureMap, framework); } /** diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/SequentialNodeDispatcher.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/SequentialNodeDispatcher.java index e58ac55ee78..64d93dddf87 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/SequentialNodeDispatcher.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/dispatch/SequentialNodeDispatcher.java @@ -24,16 +24,26 @@ package com.dtolabs.rundeck.core.execution.dispatch; import com.dtolabs.rundeck.core.Constants; -import com.dtolabs.rundeck.core.NodesetFailureException; -import com.dtolabs.rundeck.core.common.*; -import com.dtolabs.rundeck.core.execution.*; +import com.dtolabs.rundeck.core.common.Framework; +import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.common.INodeSet; +import com.dtolabs.rundeck.core.execution.FailedNodesListener; +import com.dtolabs.rundeck.core.execution.ServiceThreadBase; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.TreeSet; + /** * SequentialNodeDispatcher is ... @@ -49,31 +59,32 @@ public SequentialNodeDispatcher(Framework framework) { public DispatcherResult dispatch(final StepExecutionContext context, final NodeStepExecutionItem item) throws - DispatcherException { + DispatcherException { return dispatch(context, item, null); } public DispatcherResult dispatch(final StepExecutionContext context, final Dispatchable item) throws - DispatcherException { + DispatcherException { return dispatch(context, null, item); } public DispatcherResult dispatch(final StepExecutionContext context, final NodeStepExecutionItem item, final Dispatchable toDispatch) throws - DispatcherException { + DispatcherException { INodeSet nodes = framework.filterAuthorizedNodes(context.getFrameworkProject(), new HashSet(Arrays.asList("read", "run")), context.getNodes()); - if(nodes.getNodes().size()<1) { + if (nodes.getNodes().size() < 1) { throw new DispatcherException("No nodes matched"); } boolean keepgoing = context.isKeepgoing(); - context.getExecutionListener().log(4, "preparing for sequential execution on " + nodes.getNodes().size() + " nodes"); + context.getExecutionListener() + .log(4, "preparing for sequential execution on " + nodes.getNodes().size() + " nodes"); final HashSet nodeNames = new HashSet(nodes.getNodeNames()); - final HashMap failures = new HashMap(); + final HashMap failures = new HashMap(); FailedNodesListener failedListener = context.getExecutionListener().getFailedNodesListener(); if (null != failedListener) { failedListener.matchedNodes(nodeNames); @@ -84,23 +95,24 @@ public DispatcherResult dispatch(final StepExecutionContext context, final HashMap resultMap = new HashMap(); final Collection nodes1 = nodes.getNodes(); //reorder based on configured rank property and order - final String rankProperty = null != context.getNodeRankAttribute() ? context.getNodeRankAttribute() : "nodename"; + final String rankProperty = null != context.getNodeRankAttribute() ? context.getNodeRankAttribute() + : "nodename"; final boolean rankAscending = context.isNodeRankOrderAscending(); final INodeEntryComparator comparator = new INodeEntryComparator(rankProperty); final TreeSet orderedNodes = new TreeSet( rankAscending ? comparator : Collections.reverseOrder(comparator)); orderedNodes.addAll(nodes1); - Throwable caught=null; - INodeEntry failedNode=null; - for (final Object node1 : orderedNodes) { + Throwable caught = null; + INodeEntry failedNode = null; + for (final INodeEntry node : orderedNodes) { if (thread.isInterrupted() || thread instanceof ServiceThreadBase && ((ServiceThreadBase) thread).isAborted()) { interrupted = true; break; } - final INodeEntry node = (INodeEntry) node1; context.getExecutionListener().log(Constants.DEBUG_LEVEL, - "Executing command on node: " + node.getNodename() + ", " + node.toString()); + "Executing command on node: " + node.getNodename() + ", " + + node.toString()); try { if (thread.isInterrupted() @@ -109,50 +121,45 @@ public DispatcherResult dispatch(final StepExecutionContext context, break; } final NodeStepResult result; - final StepExecutionContext interimcontext = new ExecutionContextImpl.Builder(context).nodeSelector( - SelectorUtils.singleNode(node.getNodename())).build(); //execute the step or dispatchable if (null != item) { - result = framework.getExecutionService().executeNodeStep(interimcontext, item, node); + result = framework.getExecutionService().executeNodeStep(context, item, node); } else { - result = toDispatch.dispatch(interimcontext, node); + result = toDispatch.dispatch(context, node); } - if (null != result) { - resultMap.put(node.getNodename(), result); - } - if (null == result || !result.isSuccess()) { + resultMap.put(node.getNodename(), result); + if (!result.isSuccess()) { success = false; // context.getExecutionListener().log(Constants.ERR_LEVEL, // "Failed execution for node " + node.getNodename() + ": " + result); - if(null!=result) { - failures.put(node.getNodename(), result); - }else{ - failures.put(node.getNodename(), - "Failed execution, result was null"); - } + failures.put(node.getNodename(), result); if (!keepgoing) { - failedNode=node; + failedNode = node; break; } } else { nodeNames.remove(node.getNodename()); } - } catch (Throwable e) { + } catch (NodeStepException e) { success = false; - failures.put(node.getNodename(), "Error dispatching command to the node: " + e.getMessage()); + failures.put(node.getNodename(), + new NodeStepResultImpl(e, e.getFailureReason(), e.getMessage(), node) + ); context.getExecutionListener().log(Constants.ERR_LEVEL, - "Failed dispatching to node " + node.getNodename() + ": " + e.getMessage()); + "Failed dispatching to node " + node.getNodename() + ": " + + e.getMessage()); final StringWriter stringWriter = new StringWriter(); e.printStackTrace(new PrintWriter(stringWriter)); context.getExecutionListener().log(Constants.DEBUG_LEVEL, - "Failed dispatching to node " + node.getNodename() + ": " + stringWriter.toString()); + "Failed dispatching to node " + node.getNodename() + ": " + + stringWriter.toString()); if (!keepgoing) { - failedNode=node; - caught=e; + failedNode = node; + caught = e; break; } } @@ -172,8 +179,7 @@ public DispatcherResult dispatch(final StepExecutionContext context, failedListener.nodesFailed(failures); } //now fail - //XXX: needs to change from exception - throw new NodesetFailureException(failures); + return new DispatcherResultImpl(failures, false); } else if (null != failedListener && failures.isEmpty() && !interrupted) { failedListener.nodesSucceeded(); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/common/BaseFileCopier.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/common/BaseFileCopier.java index d938db45af5..aa707f3973e 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/common/BaseFileCopier.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/common/BaseFileCopier.java @@ -29,6 +29,7 @@ import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.script.ScriptfileUtils; import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.utils.Streams; import java.io.*; @@ -95,7 +96,8 @@ public static File writeScriptTempFile(final ExecutionContext context, final Fil return null; } } catch (IOException e) { - throw new FileCopierException("error writing script to tempfile: " + e.getMessage(), e); + throw new FileCopierException("error writing script to tempfile: " + e.getMessage(), + StepFailureReason.IOFailure, e); } // System.err.println("Wrote script content to file: " + tempfile); try { diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschNodeExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschNodeExecutor.java index f41b484ba6a..7ce7684a6bb 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschNodeExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschNodeExecutor.java @@ -30,7 +30,6 @@ import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.ExecutionListener; import com.dtolabs.rundeck.core.execution.impl.common.AntSupport; import com.dtolabs.rundeck.core.execution.service.NodeExecutor; @@ -39,13 +38,16 @@ import com.dtolabs.rundeck.core.execution.utils.LeadPipeOutputStream; import com.dtolabs.rundeck.core.execution.utils.Responder; import com.dtolabs.rundeck.core.execution.utils.ResponderTask; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; -import com.dtolabs.rundeck.core.plugins.configuration.Property; import com.dtolabs.rundeck.core.tasks.net.ExtSSHExec; import com.dtolabs.rundeck.core.tasks.net.SSHTaskBuilder; import com.dtolabs.rundeck.plugins.util.DescriptionBuilder; import com.jcraft.jsch.JSchException; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.log4j.Logger; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -53,11 +55,21 @@ import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.net.NoRouteToHostException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.text.MessageFormat; -import java.util.*; -import java.util.concurrent.*; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; + /** * JschNodeExecutor is ... * @@ -85,7 +97,7 @@ public class JschNodeExecutor implements NodeExecutor, Describable { public static final String DEFAULT_SSH_PASSWORD_OPTION = "option.sshPassword"; public static final String SUDO_OPT_PREFIX = "sudo-"; public static final String SUDO2_OPT_PREFIX = "sudo2-"; - public static final String NODE_ATTR_SUDO_PASSWORD_OPTION = "password-option"; + public static final String NODE_ATTR_SUDO_PASSWORD_OPTION = "password-option"; public static final String DEFAULT_SUDO_PASSWORD_OPTION = "option.sudoPassword"; public static final String DEFAULT_SUDO2_PASSWORD_OPTION = "option.sudo2Password"; public static final String NODE_ATTR_SSH_KEY_PASSPHRASE_OPTION = "ssh-key-passphrase-option"; @@ -139,17 +151,18 @@ public JschNodeExecutor(final Framework framework) { .build(); - public Description getDescription() { return DESC; } public NodeExecutorResult executeCommand(final ExecutionContext context, final String[] command, - final INodeEntry node) throws - ExecutionException { + final INodeEntry node) { if (null == node.getHostname() || null == node.extractHostname()) { - throw new ExecutionException( - "Hostname must be set to connect to remote node '" + node.getNodename() + "'"); + return NodeExecutorResultImpl.createFailure( + StepFailureReason.ConfigurationFailure, + "Hostname must be set to connect to remote node '" + node.getNodename() + "'", + node + ); } final ExecutionListener listener = context.getExecutionListener(); @@ -160,14 +173,15 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S final ExtSSHExec sshexec; //perform jsch sssh command final NodeSSHConnectionInfo nodeAuthentication = new NodeSSHConnectionInfo(node, framework, - context); + context); final int timeout = nodeAuthentication.getSSHTimeout(); try { sshexec = SSHTaskBuilder.build(node, command, project, context.getDataContext(), - nodeAuthentication, context.getLoglevel()); + nodeAuthentication, context.getLoglevel()); } catch (SSHTaskBuilder.BuilderException e) { - throw new ExecutionException(e); + return NodeExecutorResultImpl.createFailure(StepFailureReason.ConfigurationFailure, + e.getMessage(), node); } //Sudo support @@ -188,7 +202,7 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S responderInput.connect(jschOutput); jschInput.connect(responderOutput); } catch (IOException e) { - throw new ExecutionException(e); + return NodeExecutorResultImpl.createFailure(StepFailureReason.IOFailure, e.getMessage(), node); } //first sudo prompt responder @@ -226,7 +240,7 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S responderFuture = executor.submit(responderResultCallable); - }else { + } else { responderFuture = null; } if (null != context.getExecutionListener()) { @@ -236,37 +250,14 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S + node.getNodename() + ")"); } String errormsg = null; + FailureReason failureReason=null; try { sshexec.execute(); success = true; } catch (BuildException e) { - if (e.getMessage().contains("Timeout period exceeded, connection dropped")) { - errormsg = - "Failed execution for node: " + node.getNodename() + ": Execution Timeout period exceeded (after " - + timeout + "ms), connection dropped"; - } else if (null != e.getCause() && e.getCause() instanceof JSchException && ( - e.getCause().getMessage().contains("timeout:") || e.getCause().getMessage().contains( - "SocketTimeoutException") || e.getCause().getMessage().contains( - "java.net.ConnectException: Operation timed out"))) { - errormsg = "Failed execution for node: " + node.getNodename() + ": Connection Timeout (after " + timeout - + "ms): " + e.getMessage(); - } else if (null != e.getCause() && e.getCause() instanceof JSchException && e.getCause().getMessage() - .contains("Auth cancel")) { - String msgformat = FWK_PROP_AUTH_CANCEL_MSG_DEFAULT; - if (framework.getPropertyLookup().hasProperty(FWK_PROP_AUTH_CANCEL_MSG)) { - msgformat = framework.getProperty(FWK_PROP_AUTH_CANCEL_MSG); - } - errormsg = MessageFormat.format(msgformat, node.getNodename(), e.getMessage()); - }else if (null != e.getCause() && e.getCause() instanceof JSchException && e.getCause().getMessage() - .contains("Auth fail")) { - String msgformat = FWK_PROP_AUTH_FAIL_MSG_DEFAULT; - if (framework.getPropertyLookup().hasProperty(FWK_PROP_AUTH_FAIL_MSG)) { - msgformat = framework.getProperty(FWK_PROP_AUTH_FAIL_MSG); - } - errormsg = MessageFormat.format(msgformat, node.getNodename(), e.getMessage()); - } else { - errormsg = e.getMessage(); - } + final ExtractFailure extractJschFailure = extractFailure(e,node, timeout, framework); + errormsg = extractJschFailure.getErrormsg(); + failureReason = extractJschFailure.getReason(); context.getExecutionListener().log(0, errormsg); } shutdownAndAwaitTermination(executor); @@ -289,16 +280,86 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S } } final int resultCode = sshexec.getExitStatus(); - final String resultmsg = null != errormsg ? errormsg : null; - return new NodeExecutorResultImpl(success, node,resultCode) { - @Override - public String toString() { - return "[jsch-ssh] result was " + (isSuccess() ? "success" : "failure") + ", resultcode: " - + getResultCode() + (null != resultmsg ? ": " + resultmsg : ""); + if (success) { + return NodeExecutorResultImpl.createSuccess(node); + } else { + return NodeExecutorResultImpl.createFailure(failureReason, errormsg, node, resultCode); + } + } + static enum JschFailureReason implements FailureReason { + /** + * A problem with the SSH connection + */ + SSHProtocolFailure + } + + static ExtractFailure extractFailure(BuildException e, INodeEntry node, int timeout, Framework framework) { + String errormsg; + FailureReason failureReason; + + if (e.getMessage().contains("Timeout period exceeded, connection dropped")) { + errormsg = + "Failed execution for node: " + node.getNodename() + ": Execution Timeout period exceeded (after " + + timeout + "ms), connection dropped"; + failureReason = NodeStepFailureReason.ConnectionTimeout; + } else if (null != e.getCause() && e.getCause() instanceof JSchException) { + JSchException jSchException = (JSchException) e.getCause(); + return extractJschFailure(node, timeout, jSchException, framework); + } else if (e.getMessage().contains("Remote command failed with exit status")) { + errormsg = e.getMessage(); + failureReason = NodeStepFailureReason.NonZeroResultCode; + } else { + failureReason = StepFailureReason.Unknown; + errormsg = e.getMessage(); + } + return new ExtractFailure(errormsg, failureReason); + } + + static ExtractFailure extractJschFailure(final INodeEntry node, + final int timeout, + final JSchException jSchException, final Framework framework) { + String errormsg; + FailureReason reason; + + if (null == jSchException.getCause()) { + if (jSchException.getMessage().contains("Auth cancel")) { + + String msgformat = FWK_PROP_AUTH_CANCEL_MSG_DEFAULT; + if (framework.getPropertyLookup().hasProperty(FWK_PROP_AUTH_CANCEL_MSG)) { + msgformat = framework.getProperty(FWK_PROP_AUTH_CANCEL_MSG); + } + errormsg = MessageFormat.format(msgformat, node.getNodename(), jSchException.getMessage()); + reason = NodeStepFailureReason.AuthenticationFailure; + } else if (jSchException.getMessage().contains("Auth fail")) { + String msgformat = FWK_PROP_AUTH_FAIL_MSG_DEFAULT; + if (framework.getPropertyLookup().hasProperty(FWK_PROP_AUTH_FAIL_MSG)) { + msgformat = framework.getProperty(FWK_PROP_AUTH_FAIL_MSG); + } + errormsg = MessageFormat.format(msgformat, node.getNodename(), jSchException.getMessage()); + reason = NodeStepFailureReason.AuthenticationFailure; + } else { + reason = JschFailureReason.SSHProtocolFailure; + errormsg = jSchException.getMessage(); + } + } else { + Throwable cause = ExceptionUtils.getRootCause(jSchException); + errormsg = cause.getMessage(); + if (cause instanceof NoRouteToHostException) { + reason = NodeStepFailureReason.ConnectionFailure; + } else if (cause instanceof UnknownHostException) { + reason = NodeStepFailureReason.HostNotFound; + } else if (cause instanceof SocketTimeoutException) { + errormsg = "Connection Timeout (after " + timeout + "ms): " + cause.getMessage(); + reason = NodeStepFailureReason.ConnectionTimeout; + } else if (cause instanceof SocketException) { + reason = NodeStepFailureReason.ConnectionFailure; + } else { + reason = StepFailureReason.Unknown; } - }; + } + return new ExtractFailure(errormsg, reason); } /** @@ -422,16 +483,16 @@ public int getSSHTimeout() { } /** - * Return null if the input is null or empty or whitespace, otherwise return the input - * string trimmed. + * Return null if the input is null or empty or whitespace, otherwise return the input string trimmed. */ - public static String nonBlank(final String input){ - if(null==input|| "".equals(input.trim())){ + public static String nonBlank(final String input) { + if (null == input || "".equals(input.trim())) { return null; - }else { + } else { return input.trim(); } } + public String getUsername() { String user; if (null != nonBlank(node.getUsername()) || node.containsUserName()) { @@ -514,6 +575,7 @@ private static boolean resolveBooleanProperty(final String attribute, final bool private static class DisconnectResultHandler implements ResponderTask.ResultHandler, ExtSSHExec.DisconnectHolder { private ExtSSHExec.Disconnectable disconnectable; + public void setDisconnectable(final ExtSSHExec.Disconnectable disconnectable) { this.disconnectable = disconnectable; } @@ -537,7 +599,7 @@ public void handleResult(final boolean success, final String reason) { * lines, or 5000 milliseconds, then assume success (isFailOnResponseThreshold)
  • if seen, then fail
  • * */ - private static class SudoResponder implements Responder{ + private static class SudoResponder implements Responder { public static final String DEFAULT_DESCRIPTION = "Sudo execution password response"; private String sudoCommandPattern; private String inputSuccessPattern; @@ -565,21 +627,24 @@ private SudoResponder() { defaultSudoCommandPattern = DEFAULT_SUDO_COMMAND_PATTERN; configPrefix = SUDO_OPT_PREFIX; } - private SudoResponder(final String configPrefix, final String defaultSudoPasswordOption, final String defaultSudoCommandPattern) { + + private SudoResponder(final String configPrefix, + final String defaultSudoPasswordOption, + final String defaultSudoCommandPattern) { this(); - if(null!= configPrefix) { + if (null != configPrefix) { this.configPrefix = configPrefix; } - if(null!=defaultSudoPasswordOption){ - this.defaultSudoPasswordOption=defaultSudoPasswordOption; + if (null != defaultSudoPasswordOption) { + this.defaultSudoPasswordOption = defaultSudoPasswordOption; } - if(null!=defaultSudoCommandPattern){ - this.defaultSudoCommandPattern=defaultSudoCommandPattern; + if (null != defaultSudoCommandPattern) { + this.defaultSudoCommandPattern = defaultSudoCommandPattern; } } static SudoResponder create(final INodeEntry node, final Framework framework, final ExecutionContext context) { - return create(node, framework, context, null,null, null); + return create(node, framework, context, null, null, null); } static SudoResponder create(final INodeEntry node, @@ -587,7 +652,7 @@ static SudoResponder create(final INodeEntry node, final ExecutionContext context, final String configPrefix, final String defaultSudoPasswordOption, final String defaultSudoCommandPattern) { - final SudoResponder sudoResponder = new SudoResponder(configPrefix,defaultSudoPasswordOption, + final SudoResponder sudoResponder = new SudoResponder(configPrefix, defaultSudoPasswordOption, defaultSudoCommandPattern); sudoResponder.init(node, framework.getFrameworkProjectMgr().getFrameworkProject( context.getFrameworkProject()), framework, context); @@ -606,11 +671,17 @@ public boolean matchesCommandPattern(final String command) { private void init(final INodeEntry node, final FrameworkProject frameworkProject, final Framework framework, final ExecutionContext context) { - sudoEnabled = resolveBooleanProperty(configPrefix + NODE_ATTR_SUDO_COMMAND_ENABLED, false, node, frameworkProject, - framework); + sudoEnabled = resolveBooleanProperty(configPrefix + NODE_ATTR_SUDO_COMMAND_ENABLED, + false, + node, + frameworkProject, + framework); if (sudoEnabled) { - final String sudoPassOptname=resolveProperty(configPrefix + NODE_ATTR_SUDO_PASSWORD_OPTION, null, node, frameworkProject, - framework); + final String sudoPassOptname = resolveProperty(configPrefix + NODE_ATTR_SUDO_PASSWORD_OPTION, + null, + node, + frameworkProject, + framework); final String sudoPassword = NodeSSHConnectionInfo.evaluateSecureOption( null != sudoPassOptname ? sudoPassOptname : defaultSudoPasswordOption, context); inputString = (null != sudoPassword ? sudoPassword : "") + "\n"; @@ -753,4 +824,24 @@ public void setDescription(String description) { } + static class ExtractFailure { + + private String errormsg; + private FailureReason reason; + + private ExtractFailure(String errormsg, FailureReason reason) { + this.errormsg = errormsg; + this.reason = reason; + } + + public String getErrormsg() { + return errormsg; + } + + public FailureReason getReason() { + return reason; + } + + + } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschScpFileCopier.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschScpFileCopier.java index 975665a681e..df88393c295 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschScpFileCopier.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/jsch/JschScpFileCopier.java @@ -30,22 +30,21 @@ import com.dtolabs.rundeck.core.execution.impl.common.BaseFileCopier; import com.dtolabs.rundeck.core.execution.service.FileCopier; import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; -import com.dtolabs.rundeck.core.plugins.configuration.Property; import com.dtolabs.rundeck.core.tasks.net.SSHTaskBuilder; import com.dtolabs.rundeck.plugins.util.DescriptionBuilder; -import com.jcraft.jsch.JSchException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Echo; import org.apache.tools.ant.taskdefs.Sequential; -import java.io.*; -import java.text.MessageFormat; -import java.util.List; -import java.util.Map; +import java.io.File; +import java.io.InputStream; + /** * JschScpFileCopier is ... @@ -76,18 +75,18 @@ public JschScpFileCopier(Framework framework) { } public String copyFileStream(final ExecutionContext context, InputStream input, INodeEntry node) throws - FileCopierException { + FileCopierException { return copyFile(context, null, input, null, node); } public String copyFile(final ExecutionContext context, File scriptfile, INodeEntry node) throws - FileCopierException { + FileCopierException { return copyFile(context, scriptfile, null, null, node); } public String copyScriptContent(ExecutionContext context, String script, INodeEntry node) throws - FileCopierException { + FileCopierException { return copyFile(context, null, null, script, node); } @@ -108,14 +107,17 @@ private String copyFile(final ExecutionContext context, final File scriptfile, f // logger.debug("temp file for node " + node.getNodename() + ": " + temp.getAbsolutePath() + ", datacontext: " + dataContext); final Task scp; - final JschNodeExecutor.NodeSSHConnectionInfo nodeAuthentication = new JschNodeExecutor.NodeSSHConnectionInfo(node, - framework, context); + final JschNodeExecutor.NodeSSHConnectionInfo nodeAuthentication = new JschNodeExecutor.NodeSSHConnectionInfo( + node, + framework, + context); try { scp = SSHTaskBuilder.buildScp(node, project, remotefile, localTempfile, nodeAuthentication, - context.getLoglevel()); + context.getLoglevel()); } catch (SSHTaskBuilder.BuilderException e) { - throw new FileCopierException(e); + throw new FileCopierException("Configuration error: " + e.getMessage(), + StepFailureReason.ConfigurationFailure, e); } /** @@ -129,23 +131,19 @@ private String copyFile(final ExecutionContext context, final File scriptfile, f try { seq.execute(); } catch (BuildException e) { - if (null != e.getCause() && e.getCause() instanceof JSchException && e.getCause().getMessage().contains( - "Auth cancel")) { - String msgformat = JschNodeExecutor.FWK_PROP_AUTH_CANCEL_MSG_DEFAULT; - if (framework.getPropertyLookup().hasProperty(JschNodeExecutor.FWK_PROP_AUTH_CANCEL_MSG)) { - msgformat = framework.getProperty(JschNodeExecutor.FWK_PROP_AUTH_CANCEL_MSG); - } - errormsg = MessageFormat.format(msgformat, node.getNodename(), - e.getMessage()); - } else { - errormsg = e.getMessage(); - } + JschNodeExecutor.ExtractFailure failure = JschNodeExecutor.extractFailure(e, + node, + nodeAuthentication.getSSHTimeout(), + framework); + errormsg = failure.getErrormsg(); + FailureReason failureReason = failure.getReason(); + context.getExecutionListener().log(0, errormsg); context.getExecutionListener().log(0, errormsg); - throw new FileCopierException("[jsch-scp] Failed copying the file: " + errormsg, e); + throw new FileCopierException("[jsch-scp] Failed copying the file: " + errormsg, failureReason, e); } if (!localTempfile.delete()) { context.getExecutionListener().log(Constants.WARN_LEVEL, - "Unable to remove local temp file: " + localTempfile.getAbsolutePath()); + "Unable to remove local temp file: " + localTempfile.getAbsolutePath()); } return remotefile; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/local/LocalNodeExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/local/LocalNodeExecutor.java index 1af49aa0aa4..851b3233cb5 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/local/LocalNodeExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/impl/local/LocalNodeExecutor.java @@ -29,7 +29,6 @@ import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.ExecutionListener; -import com.dtolabs.rundeck.core.execution.dispatch.ParallelNodeDispatcher; import com.dtolabs.rundeck.core.execution.impl.common.AntSupport; import com.dtolabs.rundeck.core.execution.script.ExecTaskParameterGenerator; import com.dtolabs.rundeck.core.execution.script.ExecTaskParameterGeneratorImpl; @@ -37,11 +36,11 @@ import com.dtolabs.rundeck.core.execution.service.NodeExecutor; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; -import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.ExecTask; -import org.apache.tools.ant.taskdefs.Sequential; import org.apache.tools.ant.types.Commandline; import java.util.Map; @@ -62,8 +61,7 @@ public LocalNodeExecutor(final Framework framework) { } public NodeExecutorResult executeCommand(final ExecutionContext context, final String[] command, - final INodeEntry node) throws - ExecutionException { + final INodeEntry node) { final ExecutionListener listener = context.getExecutionListener(); final Project project = new Project(); AntSupport.addAntBuildListener(listener, project); @@ -73,8 +71,14 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S boolean success = false; final ExecTask execTask; //perform jsch sssh command - execTask = buildExecTask(project, parameterGenerator.generate(node, true, null, command), - context.getDataContext()); + try { + execTask = buildExecTask(project, parameterGenerator.generate(node, true, null, command), + context.getDataContext()); + } catch (ExecutionException e) { + return NodeExecutorResultImpl.createFailure(StepFailureReason.ConfigurationFailure, + e.getMessage(), + node); + } execTask.setResultProperty(propName); @@ -94,13 +98,12 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S } } final boolean status = 0==result; - return new NodeExecutorResultImpl(status,node,result) { - @Override - public String toString() { - return "[local node exec] result was " + (isSuccess() ? "success" : "failure") + ", resultcode: " - + getResultCode(); - } - }; + if(status) { + return NodeExecutorResultImpl.createSuccess(node); + }else { + return NodeExecutorResultImpl.createFailure(NodeStepFailureReason.NonZeroResultCode, + "Result code was " + result, node, result); + } } private ExecTask buildExecTask(Project project, ExecTaskParameters taskParameters, diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/FileCopierException.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/FileCopierException.java index ff50cb4a286..0056653d46b 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/FileCopierException.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/FileCopierException.java @@ -23,25 +23,28 @@ */ package com.dtolabs.rundeck.core.execution.service; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; + + /** * FileCopierException is ... * * @author Greg Schueler greg@dtosolutions.com */ public class FileCopierException extends Exception { - public FileCopierException() { - super(); - } + private FailureReason failureReason; - public FileCopierException(String msg) { + public FileCopierException(String msg, FailureReason failureReason) { super(msg); + this.failureReason = failureReason; } - public FileCopierException(Exception cause) { - super(cause); + public FileCopierException(String msg, FailureReason failureReason, Exception cause) { + super(msg, cause); + this.failureReason = failureReason; } - public FileCopierException(String msg, Exception cause) { - super(msg, cause); + public FailureReason getFailureReason() { + return failureReason; } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutor.java index 39c7dff353f..ba22b485071 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutor.java @@ -25,7 +25,7 @@ import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; + /** * NodeExecutor executes a command on a node. @@ -41,9 +41,6 @@ public interface NodeExecutor { * @param node the node to execute on * * @return a result - * - * @throws ExecutionException if there is an error performing the execution */ - public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) throws - ExecutionException; + public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResult.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResult.java index 30821db0ce8..abf3174bade 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResult.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResult.java @@ -36,4 +36,5 @@ public interface NodeExecutorResult extends NodeStepResult { * Return the exit/result code of the execution */ public int getResultCode(); + } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResultImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResultImpl.java index d2b93c4665b..267c25b9194 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResultImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/NodeExecutorResultImpl.java @@ -24,7 +24,7 @@ package com.dtolabs.rundeck.core.execution.service; import com.dtolabs.rundeck.core.common.INodeEntry; -import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; @@ -34,16 +34,44 @@ * @author Greg Schueler greg@dtosolutions.com */ public class NodeExecutorResultImpl extends NodeStepResultImpl implements NodeExecutorResult { + public static final String FAILURE_DATA_RESULT_CODE = "resultCode"; private int resultCode; - public NodeExecutorResultImpl(boolean success, INodeEntry node, int resultCode) { - super(success, node); + public static NodeExecutorResultImpl createSuccess(INodeEntry node) { + return new NodeExecutorResultImpl(node, 0); + } + + private NodeExecutorResultImpl(INodeEntry node, int resultCode) { + super(node); this.resultCode = resultCode; + getFailureData().put(FAILURE_DATA_RESULT_CODE, resultCode); + } + + public static NodeExecutorResultImpl createFailure(FailureReason reason, String message, + Exception exception, INodeEntry node, int resultCode) { + + return new NodeExecutorResultImpl(exception, node, resultCode, reason, message); + } + + public static NodeExecutorResultImpl createFailure(FailureReason reason, + String message, + INodeEntry node, + int resultCode) { + + return new NodeExecutorResultImpl(null, node, resultCode, reason, message); + } + + public static NodeExecutorResultImpl createFailure(FailureReason reason, String message, INodeEntry node) { + + return new NodeExecutorResultImpl(null, node, -1, reason, message); } - public NodeExecutorResultImpl(boolean success, Exception exception, INodeEntry node, int resultCode) { - super(success, exception, node); + private NodeExecutorResultImpl(Exception exception, + INodeEntry node, + int resultCode, final FailureReason reason, final String failureMessage) { + super(exception, reason, failureMessage, node); this.resultCode = resultCode; + getFailureData().put(FAILURE_DATA_RESULT_CODE, resultCode); } public int getResultCode() { @@ -72,9 +100,7 @@ public int hashCode() { @Override public String toString() { - return "NodeExecutorResultImpl{" + - "resultCode=" + resultCode + - ", success=" + isSuccess() + - '}'; + return isSuccess() ? "Succeeded" : getFailureReason() + ": " + getFailureMessage(); } + } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginFileCopier.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginFileCopier.java index 05afec2d089..0e55bd44e21 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginFileCopier.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginFileCopier.java @@ -28,9 +28,13 @@ import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.impl.common.BaseFileCopier; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.BaseScriptPlugin; import com.dtolabs.rundeck.core.plugins.PluginException; import com.dtolabs.rundeck.core.plugins.ScriptPluginProvider; +import com.dtolabs.rundeck.core.utils.ScriptExecUtil; import java.io.ByteArrayOutputStream; import java.io.File; @@ -87,6 +91,12 @@ public String copyScriptContent(final ExecutionContext executionContext, final S return copyFile(executionContext, null, null, s, node); } + static enum ScriptPluginFailureReason implements FailureReason { + /** + * Expected output from the plugin was missing + */ + ScriptPluginFileCopierOutputMissing + } /** * Internal copy method accepting file, inputstream or string @@ -97,8 +107,8 @@ String copyFile(final ExecutionContext executionContext, final File file, final final String pluginname = getProvider().getName(); final Map> localDataContext = createScriptDataContext( executionContext.getFramework(), - executionContext.getFrameworkProject(), - executionContext.getDataContext()); + executionContext.getFrameworkProject(), + executionContext.getDataContext()); //add node context data localDataContext.put("node", DataContextUtils.nodeData(node)); @@ -118,40 +128,38 @@ String copyFile(final ExecutionContext executionContext, final File file, final finalargs)); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int result = -1; try { - result = runScript(finalargs, - DataContextUtils.generateEnvVarsFromContext(localDataContext), - null, - byteArrayOutputStream, - System.err + final int result = ScriptExecUtil.runLocalCommand(finalargs, + DataContextUtils.generateEnvVarsFromContext(localDataContext), + null, + byteArrayOutputStream, + System.err ); + executionContext.getExecutionListener().log(3, "[" + pluginname + "]: result code: " + result); + if(result!=0){ + throw new FileCopierException("[" + pluginname + "]: external script failed with exit code: " + result, + NodeStepFailureReason.NonZeroResultCode); + } } catch (IOException e) { - executionContext.getExecutionListener().log(0, e.getMessage()); - e.printStackTrace(); + throw new FileCopierException(e.getMessage(), StepFailureReason.IOFailure); } catch (InterruptedException e) { - executionContext.getExecutionListener().log(0, e.getMessage()); - e.printStackTrace(); - } - final boolean success = result == 0; - executionContext.getExecutionListener().log(3, - "[" + pluginname + "]: result code: " + result + ", success: " - + success); - - if (!success) { - throw new FileCopierException("[" + pluginname + "]: external script failed with exit code: " + result); + Thread.currentThread().interrupt(); + throw new FileCopierException(e.getMessage(), StepFailureReason.Interrupted); } //load string of output from outputstream final String output = byteArrayOutputStream.toString(); if (null == output || output.length() < 1) { - throw new FileCopierException("[" + pluginname + "]: No output from external script"); + throw new FileCopierException("[" + pluginname + "]: No output from external script", + ScriptPluginFailureReason.ScriptPluginFileCopierOutputMissing + ); } //TODO: require any specific format for the data? //look for first line of output final String[] split1 = output.split("(\\r?\\n)"); if (split1.length < 1) { - throw new FileCopierException("[" + pluginname + "]: No output from external script"); + throw new FileCopierException("[" + pluginname + "]: No output from external script", + ScriptPluginFailureReason.ScriptPluginFileCopierOutputMissing); } final String remotefilepath = split1[0]; diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginNodeExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginNodeExecutor.java index 540c4919b40..2ec8e1267e9 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginNodeExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/service/ScriptPluginNodeExecutor.java @@ -27,14 +27,18 @@ import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.BaseScriptPlugin; import com.dtolabs.rundeck.core.plugins.PluginException; import com.dtolabs.rundeck.core.plugins.ScriptPluginProvider; +import com.dtolabs.rundeck.core.utils.ScriptExecUtil; import com.dtolabs.rundeck.core.utils.StringArrayUtil; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** @@ -57,7 +61,7 @@ static void validateScriptPlugin(final ScriptPluginProvider plugin) throws Plugi } public NodeExecutorResult executeCommand(final ExecutionContext executionContext, final String[] command, - final INodeEntry node) throws ExecutionException { + final INodeEntry node) { final ScriptPluginProvider plugin = getProvider(); final String pluginname = plugin.getName(); executionContext.getExecutionListener().log(3, @@ -79,30 +83,35 @@ public NodeExecutorResult executeCommand(final ExecutionContext executionContext int result = -1; try { - result = runScript(finalargs, - DataContextUtils.generateEnvVarsFromContext(localDataContext), - null, - System.out, - System.err + result = ScriptExecUtil.runLocalCommand(finalargs, + DataContextUtils.generateEnvVarsFromContext(localDataContext), + null, + System.out, + System.err ); + executionContext.getExecutionListener().log(3, + "[" + pluginname + "]: result code: " + result + ", success: " + + (0 == result)); + if(0!=result){ + return NodeExecutorResultImpl.createFailure(NodeStepFailureReason.NonZeroResultCode, + "Result code: " + result, node, result); + }else { + return NodeExecutorResultImpl.createSuccess(node); + } } catch (IOException e) { - executionContext.getExecutionListener().log(0,e.getMessage()); - e.printStackTrace(); + return NodeExecutorResultImpl.createFailure(StepFailureReason.IOFailure, + e.getMessage(), + e, + node, + result); } catch (InterruptedException e) { - executionContext.getExecutionListener().log(0, e.getMessage()); - e.printStackTrace(); + Thread.currentThread().interrupt(); + return NodeExecutorResultImpl.createFailure(StepFailureReason.Interrupted, + e.getMessage(), + e, + node, + result); } - final boolean success = result == 0; - executionContext.getExecutionListener().log(3, - "[" + pluginname + "]: result code: " + result + ", success: " - + success); - - return new NodeExecutorResultImpl(success, node, result) { - @Override - public String toString() { - return "[" + pluginname + "] success: " + isSuccess() + ", result code: " + getResultCode(); - } - }; } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/BaseWorkflowStrategy.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/BaseWorkflowStrategy.java index 924c65b507d..68d63bc4f18 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/BaseWorkflowStrategy.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/BaseWorkflowStrategy.java @@ -30,9 +30,20 @@ import com.dtolabs.rundeck.core.execution.*; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.NodeDispatchStepExecutor; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; /** * BaseWorkflowStrategy is ... @@ -49,15 +60,18 @@ public BaseWorkflowStrategy(final Framework framework) { static class BaseWorkflowExecutionResult implements WorkflowExecutionResult { private final List results; - private final Map> failures; + private final Map> failures; + private final Map stepFailures; private final boolean success; private final Exception orig; public BaseWorkflowExecutionResult(List results, - Map> failures, + Map> failures, + final Map stepFailures, boolean success, Exception orig) { this.results = results; this.failures = failures; + this.stepFailures=stepFailures; this.success = success; this.orig = orig; } @@ -66,7 +80,7 @@ public List getResultSet() { return results; } - public Map> getFailureMessages() { + public Map> getNodeFailures() { return failures; } @@ -81,13 +95,17 @@ public Exception getException() { @Override public String toString() { return "[Workflow " - + (null != getResultSet() && getResultSet().size() > 0 ? "results: " + getResultSet() : "") - + (null != getFailureMessages() && getFailureMessages().size() > 0 ? ", failures: " - + getFailureMessages() : "") +// + (null != getResultSet() && getResultSet().size() > 0 ? "results: " + getResultSet() : "") + + (null != getStepFailures() && getStepFailures().size() > 0 ? "step failures: " + getStepFailures() : "") + + (null != getNodeFailures() && getNodeFailures().size() > 0 ? ", Node failures: " + + getNodeFailures() : "") + (null != getException() ? ": exception: " + getException() : "") + "]"; } + public Map getStepFailures() { + return stepFailures; + } } public final WorkflowExecutionResult executeWorkflow(final StepExecutionContext executionContext, @@ -127,99 +145,47 @@ public abstract WorkflowExecutionResult executeWorkflowImpl(StepExecutionContext * @param failedMap List to add any messages if the item fails * @param c index of the WF item * @param cmd WF item descriptor - * @param keepgoing - * * @return true if the execution succeeds, false otherwise * - * @throws WorkflowStepFailureException if underlying WF item throws exception and the workflow is not "keepgoing", - * or the result from the execution includes an exception */ protected StepExecutionResult executeWFItem(final StepExecutionContext executionContext, - final Map failedMap, + final Map failedMap, final int c, - final StepExecutionItem cmd, final boolean keepgoing) throws - WorkflowStepFailureException { + final StepExecutionItem cmd) { - if(null!=executionContext.getExecutionListener()){ - executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, c + ": " + cmd.toString()); + if (null != executionContext.getExecutionListener()) { + executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, + c + ": Workflow step executing: " + cmd); } - StepExecutionResult result = null; - boolean itemsuccess; - Throwable wfstepthrowable = null; + StepExecutionResult result; try { - if(null!=executionContext.getExecutionListener()){ - executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, - "StepExecutionItem created, executing: " + cmd); - } - result = framework.getExecutionService() - .executeStep(ExecutionContextImpl.builder(executionContext) - .stepNumber(c) - .build(), - cmd); - itemsuccess = null != result && result.isSuccess(); - } catch (Throwable exc) { - if (keepgoing) { - //don't fail - if (null != executionContext.getExecutionListener()) { - executionContext.getExecutionListener().log(Constants.VERBOSE_LEVEL, - "Step " + c + "of the workflow failed: " + org.apache.tools.ant.util - .StringUtils.getStackTrace(exc)); - } - wfstepthrowable = exc; - itemsuccess = false; - } else { - - failedMap.put(c, exc.getMessage()); - throw new WorkflowStepFailureException( - "Step " + c + " of the workflow threw exception: " + exc.getMessage(), exc, c); + result = framework.getExecutionService().executeStep( + ExecutionContextImpl.builder(executionContext).stepNumber(c).build(), + cmd); + if (!result.isSuccess()) { + failedMap.put(c, result); } + } catch (StepException e) { + result = StepExecutionResultImpl.wrapStepException(e); + failedMap.put(c, result); } - Exception exception=null; - if(null!=result && (result instanceof ExceptionStatusResult)) { - exception = ((ExceptionStatusResult) result).getException(); - } - if (itemsuccess) { - if (null != executionContext.getExecutionListener()) { - executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, - c + ": StepExecutionItem finished, result: " + result); - } - } else if (keepgoing) { - //don't fail yet - failedMap.put(c, (null != wfstepthrowable ? wfstepthrowable.getMessage() - : (null != result && null != exception ? exception - : "no result"))); - executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, "Workflow continues"); - } else { - failedMap.put(c, (null != wfstepthrowable ? wfstepthrowable.getMessage() - : (null != result && null != exception ? exception - : "no result"))); - if (null != result && null != exception) { - throw new WorkflowStepFailureException( - "Step " + c + " of the workflow threw an exception: " + exception.getMessage(), - exception, c); - } else { - throw new WorkflowStepFailureException( - "Step " + c + " of the workflow failed with result: " + (result != null ? result : null), - result, - c); - } + if (null != executionContext.getExecutionListener()) { + executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, + c + ": Workflow step finished, result: " + result); } return result; } - - /** * Execute the sequence of ExecutionItems within the context, and with the given keepgoing value, return true if * successful */ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext executionContext, - final Map failedMap, + final Map failedMap, final List resultList, final List iWorkflowCmdItems, - final boolean keepgoing) throws - WorkflowStepFailureException { + final boolean keepgoing) { return executeWorkflowItemsForNodeSet(executionContext, failedMap, resultList, iWorkflowCmdItems, keepgoing, executionContext.getStepNumber()); } @@ -228,19 +194,17 @@ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext exec * successful */ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext executionContext, - final Map failedMap, + final Map failedMap, final List resultList, final List iWorkflowCmdItems, final boolean keepgoing, - final int beginStepIndex) throws - WorkflowStepFailureException { + final int beginStepIndex) { boolean workflowsuccess = true; final WorkflowExecutionListener wlistener = getWorkflowListener(executionContext); int c = beginStepIndex; for (final StepExecutionItem cmd : iWorkflowCmdItems) { boolean stepSuccess=false; - WorkflowStepFailureException stepFailure=null; if (null != wlistener) { wlistener.beginWorkflowItem(c, cmd); } @@ -249,18 +213,13 @@ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext exec NodeRecorder stepCaptureFailedNodesListener = new NodeRecorder(); StepExecutionContext stepContext = replaceFailedNodesListenerInContext(executionContext, stepCaptureFailedNodesListener); - Map nodeFailures; + Map nodeFailures; //execute the step item, and store the results StepExecutionResult stepResult=null; - Map stepFailedMap = new HashMap(); - try { - stepResult = executeWFItem(stepContext, stepFailedMap, c, cmd, keepgoing); - stepSuccess=stepResult.isSuccess(); - } catch (WorkflowStepFailureException e) { - stepFailure = e; - stepResult=e.getStatusResult(); - } + Map stepFailedMap = new HashMap(); + stepResult = executeWFItem(stepContext, stepFailedMap, c, cmd); + stepSuccess = stepResult.isSuccess(); nodeFailures = stepCaptureFailedNodesListener.getFailedNodes(); if(null!=executionContext.getExecutionListener() && null!=executionContext.getExecutionListener().getFailedNodesListener()) { @@ -292,18 +251,21 @@ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext exec } - StepExecutionResult handlerResult = null; - Map handlerFailedMap = new HashMap(); - WorkflowStepFailureException handlerFailure = null; - boolean handlerSuccess = false; - try { - handlerResult = executeWFItem(handlerExecContext, handlerFailedMap, c, handler, false); - handlerSuccess = handlerResult.isSuccess(); - } catch (WorkflowStepFailureException e) { - handlerFailure = e; - handlerResult=e.getStatusResult(); + if(null!=stepResult) { + //add step failure data to data context + handlerExecContext = addStepFailureContextData(stepResult, handlerExecContext); + + //extract node-specific failure and set as node-context data + handlerExecContext = addNodeStepFailureContextData(stepResult, handlerExecContext); } + Map handlerFailedMap = new HashMap(); + StepExecutionResult handlerResult = executeWFItem(handlerExecContext, + handlerFailedMap, + c, + handler); + boolean handlerSuccess = handlerResult.isSuccess(); + //handle success conditions: //1. if keepgoing=true, then status from handler overrides original step //2. keepgoing=false, then status is the same as the original step, unless @@ -314,7 +276,6 @@ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext exec } if(useHandlerResults){ stepSuccess = handlerSuccess; - stepFailure = handlerFailure; stepResult=handlerResult; stepFailedMap = handlerFailedMap; nodeFailures = handlerCaptureFailedNodesListener.getFailedNodes(); @@ -344,25 +305,87 @@ protected boolean executeWorkflowItemsForNodeSet(final StepExecutionContext exec } - if(null!=stepFailure && !keepgoing){ - throw stepFailure; - }else if(!stepSuccess && !keepgoing){ + if(!stepSuccess && !keepgoing) { break; } c++; } return workflowsuccess; } - private class noopExecutionListener extends ExecutionListenerOverrideBase{ - public noopExecutionListener(noopExecutionListener delegate){ - super(delegate); + + /** + * Add step result failure information to the data context + */ + private StepExecutionContext addStepFailureContextData(StepExecutionResult stepResult, + StepExecutionContext handlerExecContext) { + HashMap + resultData = new HashMap(); + if (null != stepResult.getFailureData()) { + //convert values to string + for (final Map.Entry entry : stepResult.getFailureData().entrySet()) { + resultData.put(entry.getKey(), entry.getValue().toString()); + } + } + FailureReason reason = stepResult.getFailureReason(); + if(null== reason){ + reason= StepFailureReason.Unknown; } - public void log(int level, String message) { + resultData.put("reason", reason.toString()); + String message = stepResult.getFailureMessage(); + if(null==message) { + message = "No message"; } + resultData.put("message", message); + //add to data context - public ExecutionListenerOverride createOverride() { - return new noopExecutionListener(this); + handlerExecContext = ExecutionContextImpl.builder(handlerExecContext). + setContext("result", resultData) + .build(); + return handlerExecContext; + } + + /** + * Add any node-specific step failure information to the node-specific data contexts + */ + private StepExecutionContext addNodeStepFailureContextData(StepExecutionResult dispatcherStepResult, + StepExecutionContext handlerExecContext) { + final Map resultMap; + if (NodeDispatchStepExecutor.isWrappedDispatcherResult(dispatcherStepResult)) { + DispatcherResult dispatcherResult = NodeDispatchStepExecutor.extractDispatcherResult(dispatcherStepResult); + resultMap = dispatcherResult.getResults(); + } else if (NodeDispatchStepExecutor.isWrappedDispatcherException(dispatcherStepResult)) { + DispatcherException exception = NodeDispatchStepExecutor.extractDispatcherException(dispatcherStepResult); + resultMap = exception.getResultMap(); + } else { + return handlerExecContext; + } + ExecutionContextImpl.Builder builder = ExecutionContextImpl.builder(handlerExecContext); + for (final Map.Entry dentry : resultMap.entrySet()) { + String nodename = dentry.getKey(); + NodeStepResult stepResult = dentry.getValue(); + HashMap resultData = new HashMap(); + if (null != stepResult.getFailureData()) { + //convert values to string + for (final Map.Entry entry : stepResult.getFailureData().entrySet()) { + resultData.put(entry.getKey(), entry.getValue().toString()); + } + } + FailureReason reason = stepResult.getFailureReason(); + if (null == reason) { + reason = StepFailureReason.Unknown; + } + resultData.put("reason", reason.toString()); + String message = stepResult.getFailureMessage(); + if (null == message) { + message = "No message"; + } + resultData.put("message", message); + //add to data context + HashMap> ndata = new HashMap>(); + ndata.put("result", resultData); + builder.nodeDataContext(nodename, ndata); } + return builder.build(); } private StepExecutionContext replaceFailedNodesListenerInContext(StepExecutionContext executionContext, @@ -379,68 +402,48 @@ private StepExecutionContext replaceFailedNodesListenerInContext(StepExecutionCo } /** - * Convert list of DispatcherResult items to map of Node name to Map of NodeStepResult items keyed by index in - * the list (0-first) - * - * @param resultList dispatcher result list - * - * @return map of node name to Map of NodeStepResult items keyed by index in the list (0-first) + * Convert map of step execution results keyed by step number, to a collection of step execution results + * keyed by node name */ -// protected HashMap> convertResults(final List resultList) { -// final HashMap> results = new HashMap>(); -// //iterate resultSet and place in map -// int i = 0; -// for (final StepExecutionResult result : resultList) { -// -// for (final String s : dispatcherResult.getResults().keySet()) { -// final StatusResult interpreterResult = dispatcherResult.getResults().get(s); -// if (!results.containsKey(s)) { -// results.put(s, new ArrayList()); -// } -// results.get(s).add(interpreterResult); -// } -// i++; -// } -// return results; -// } + protected Map> convertFailures( + final Map failedMap) { - /** - * Convert map of integer to failure object to map of node name to collection o string. - */ - protected Map> convertFailures(Map failedMap) { - final Map> failures = new HashMap>(); - for (final Map.Entry entry : failedMap.entrySet()) { - final Object o = entry.getValue(); - if (o instanceof DispatcherResult) { + final Map> failures + = new HashMap>(); + for (final Map.Entry entry : failedMap.entrySet()) { + final StepExecutionResult o = entry.getValue(); + + if (NodeDispatchStepExecutor.isWrappedDispatcherResult(o)) { //indicates dispatcher returned node results - DispatcherResult dispatcherResult = (DispatcherResult) o; + final DispatcherResult dispatcherResult = NodeDispatchStepExecutor.extractDispatcherResult(o); for (final String s : dispatcherResult.getResults().keySet()) { - final StatusResult interpreterResult = dispatcherResult.getResults().get(s); + final NodeStepResult interpreterResult = dispatcherResult.getResults().get(s); if (!failures.containsKey(s)) { - failures.put(s, new ArrayList()); + failures.put(s, new ArrayList()); } - failures.get(s).add(interpreterResult.toString()); + failures.get(s).add(interpreterResult); } - } else if (o instanceof DispatcherException) { - DispatcherException e = (DispatcherException) o; + } else if (NodeDispatchStepExecutor.isWrappedDispatcherException(o)) { + DispatcherException e = NodeDispatchStepExecutor.extractDispatcherException(o); final INodeEntry node = e.getNode(); - final String key = null != node ? node.getNodename() : "?"; - if (!failures.containsKey(key)) { - failures.put(key, new ArrayList()); - } - failures.get(key).add(e.getMessage()); - } else if (o instanceof Exception) { - Exception e = (Exception) o; - if (!failures.containsKey("?")) { - failures.put("?", new ArrayList()); - } - failures.get("?").add(e.getMessage()); - } else { - if (!failures.containsKey("?")) { - failures.put("?", new ArrayList()); + if (null != node) { + //dispatch failed for a specific node + final String key = node.getNodename(); + if (!failures.containsKey(key)) { + failures.put(key, new ArrayList()); + } + failures.get(key).add(e.getResultMap().get(node.getNodename())); + } else { + //dispatch failed for a set of nodes + for (final String s : e.getResultMap().keySet()) { + final NodeStepResult interpreterResult = e.getResultMap().get(s); + if (!failures.containsKey(s)) { + failures.put(s, new ArrayList()); + } + failures.get(s).add(interpreterResult); + } } - failures.get("?").add(o.toString()); } } return failures; diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeFirstWorkflowStrategy.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeFirstWorkflowStrategy.java index 9c3321bbbba..d34cdfe7fb3 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeFirstWorkflowStrategy.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeFirstWorkflowStrategy.java @@ -24,19 +24,22 @@ package com.dtolabs.rundeck.core.execution.workflow; import com.dtolabs.rundeck.core.Constants; -import com.dtolabs.rundeck.core.CoreException; import com.dtolabs.rundeck.core.NodesetEmptyException; -import com.dtolabs.rundeck.core.common.*; +import com.dtolabs.rundeck.core.common.Framework; +import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.common.INodeSet; +import com.dtolabs.rundeck.core.common.NodesSelector; import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; import com.dtolabs.rundeck.core.execution.HasSourceResult; -import com.dtolabs.rundeck.core.execution.StepExecutionItem; import com.dtolabs.rundeck.core.execution.StatusResult; +import com.dtolabs.rundeck.core.execution.StepExecutionItem; import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResultImpl; import com.dtolabs.rundeck.core.execution.service.ExecutionServiceException; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.NodeDispatchStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutor; @@ -68,7 +71,10 @@ public WorkflowExecutionResult executeWorkflowImpl(final StepExecutionContext ex boolean wfsuccess = true; final ArrayList results = new ArrayList(); - final Map> failures = new HashMap>(); + final Map> failures + = new HashMap>(); + final Map stepFailures = new HashMap(); + try { final NodesSelector nodeSelector = executionContext.getNodeSelector(); @@ -82,42 +88,45 @@ public WorkflowExecutionResult executeWorkflowImpl(final StepExecutionContext ex //each dispatched sequence should be wrapped in a separate dispatch //and each non-dispatched step performed separately final List sections = splitWorkflowDispatchedSections(workflow); - int stepCount=1; - if(sections.size()>1){ + int stepCount = 1; + if (sections.size() > 1) { logger.debug("Split workflow into " + sections.size() + " sections"); } - assert sections.size()>=1; - if(sections.size()<1) { + assert sections.size() >= 1; + if (sections.size() < 1) { throw new IllegalStateException(); } - for (final IWorkflow flowsection: sections) { - boolean sectionSuccess=true; + for (final IWorkflow flowsection : sections) { + boolean sectionSuccess = true; StepExecutor stepExecutor = framework.getStepExecutionService() .getExecutorForItem(flowsection.getCommands().get(0)); - if(stepExecutor.isNodeDispatchStep(flowsection.getCommands().get(0))) { + if (stepExecutor.isNodeDispatchStep(flowsection.getCommands().get(0))) { sectionSuccess = executeWFSectionNodeDispatch(executionContext, stepCount, results, failures, + stepFailures, flowsection ); - }else { + } else { //execute each item sequentially sectionSuccess = executeWFSection(executionContext, results, failures, + stepFailures, stepCount, flowsection.getCommands(), flowsection.isKeepgoing()); } if (!sectionSuccess && !item.getWorkflow().isKeepgoing()) { - throw new WorkflowFailureException("Some steps in the workflow failed: " + failures); + wfsuccess = false; + break; } - if(!sectionSuccess){ - wfsuccess=false; + if (!sectionSuccess) { + wfsuccess = false; } stepCount += flowsection.getCommands().size(); } @@ -130,34 +139,33 @@ public WorkflowExecutionResult executeWorkflowImpl(final StepExecutionContext ex } catch (ExecutionServiceException e) { exception = e; wfsuccess = false; - } catch (WorkflowStepFailureException e) { - exception=e; - wfsuccess=false; - } catch (WorkflowFailureException e) { - exception = e; - wfsuccess = false; } final boolean success = wfsuccess; final Exception fexception = exception; - return new BaseWorkflowExecutionResult(results, failures, success, fexception); + return new BaseWorkflowExecutionResult(results, failures,stepFailures, success, fexception); } /** * Execute a workflow section that should be dispatched across nodes + * * @return true if the section was succesful */ private boolean executeWFSectionNodeDispatch(StepExecutionContext executionContext, int stepCount, List results, - Map> failures, + Map> failures, + final Map stepFailures, IWorkflow flowsection) throws ExecutionServiceException, DispatcherException { logger.debug("Node dispatch for " + flowsection.getCommands().size() + " steps"); final DispatcherResult dispatch; final WorkflowExecutionItem innerLoopItem = createInnerLoopItem(flowsection); final WorkflowExecutor executor = framework.getWorkflowExecutionService().getExecutorForItem(innerLoopItem); - final Dispatchable dispatchedWorkflow = new DispatchedWorkflow(executor, innerLoopItem, stepCount, executionContext.getStepContext()); + final Dispatchable dispatchedWorkflow = new DispatchedWorkflow(executor, + innerLoopItem, + stepCount, + executionContext.getStepContext()); //dispatch the sequence of dispatched items to each node dispatch = framework.getExecutionService().dispatchToNodes( ExecutionContextImpl.builder(executionContext) @@ -166,127 +174,136 @@ private boolean executeWFSectionNodeDispatch(StepExecutionContext executionConte dispatchedWorkflow); logger.debug("Node dispatch result: " + dispatch); - extractWFDispatcherResult(dispatch, results, failures, flowsection.getCommands().size()); + extractWFDispatcherResult(dispatch, results, failures, stepFailures,flowsection.getCommands().size(),stepCount); return dispatch.isSuccess(); } /** - * invert the result of a DispatcherResult which contains a WorkflowResult + * invert the result of a DispatcherResult where each NodeStepResult contains a WorkflowResult */ - private void extractWFDispatcherResult(DispatcherResult dispatcherResult, - List results, - Map> failures, + private void extractWFDispatcherResult(final DispatcherResult dispatcherResult, + final List results, + final Map> failures, + final Map stepFailures, + int index, int max) { - ArrayList> full = new ArrayList>(max); + ArrayList> mergedStepResults = new ArrayList>(max); ArrayList successes = new ArrayList(max); - HashMap> im = new HashMap>(); + HashMap> mergedStepFailures + = new HashMap>(); + + //Convert a dispatcher result to a list of StepExecutionResults. //each result for node in the dispatcheresult contains a workflow result - //unroll each workflow result, append the result of each step into map of node results + //unroll each workflow result, append the result of each step into map of node results + //merge each step result with the mergedStepResults //DispatcherResult contains map {nodename: NodeStepResult} for (final String nodeName : dispatcherResult.getResults().keySet()) { final NodeStepResult stepResult = dispatcherResult.getResults().get(nodeName); //This NodeStepResult is produced by the DispatchedWorkflow wrapper - WorkflowExecutionResult result = DispatchedWorkflow.extractStepResult(stepResult); + WorkflowExecutionResult result = DispatchedWorkflow.extractWorkflowResult(stepResult); if (null == failures.get(nodeName)) { - failures.put(nodeName, new ArrayList()); + failures.put(nodeName, new ArrayList()); } - final Collection strings = result.getFailureMessages().get(nodeName); - if(null!=strings && strings.size()>0){ - failures.get(nodeName).addAll(strings); + //extract failures for this node + final Collection thisNodeFailures = result.getNodeFailures().get(nodeName); + if (null != thisNodeFailures && thisNodeFailures.size() > 0) { + failures.get(nodeName).addAll(thisNodeFailures); } - if (null != result.getException()) { - failures.get(nodeName).add(result.getException().getMessage()); + + //extract failures by step (for this node) + Map perStepFailures = DispatchedWorkflow.extractStepFailures(result, + stepResult.getNode()); + for (final Map.Entry entry : perStepFailures.entrySet()) { + Integer stepNum = entry.getKey(); + NodeStepResult value = entry.getValue(); + if (null == mergedStepFailures.get(stepNum)) { + mergedStepFailures.put(stepNum, new HashMap()); + } + mergedStepFailures.get(stepNum).put(nodeName, value); } //The WorkflowExecutionResult has a list of StepExecutionResults produced by NodeDispatchStepExecutor - int i=0; - for (final StepExecutionResult partStepResult : result.getResultSet()) { - - while (full.size() <= i) { - full.add(new HashMap()); + List results1 = DispatchedWorkflow.extractNodeStepResults(result, stepResult.getNode()); + int i = 0; + for (final NodeStepResult nodeStepResult : results1) { + while (mergedStepResults.size() <= i) { + mergedStepResults.add(new HashMap()); } while (successes.size() <= i) { successes.add(Boolean.TRUE); } - HashMap map = full.get(i); - - if(NodeDispatchStepExecutor.isWrappedDispatcherResult(partStepResult)){ - DispatcherResult subresult = NodeDispatchStepExecutor.extractDispatcherResult(partStepResult); + HashMap map = mergedStepResults.get(i); - NodeStepResult result1 = subresult.getResults().get(nodeName); - map.put(nodeName, result1); + map.put(nodeName, nodeStepResult); - if (!result1.isSuccess()) { - successes.set(i, false); - failures.get(nodeName).add(result1.toString()); - } - }else if (null!=partStepResult && !partStepResult.isSuccess()) { + if (!nodeStepResult.isSuccess()) { successes.set(i, false); - if (null!=partStepResult.getException()) { - failures.get(nodeName).add(partStepResult.getException().toString()); - } else { - failures.get(nodeName).add(partStepResult.toString()); - } +// failures.get(nodeName).add(nodeStepResult); } - i++; } - - } //add a new wrapped DispatcherResults for each original step - int x=0; - for (final HashMap map : full) { + int x = 0; + for (final HashMap map : mergedStepResults) { Boolean success = successes.get(x); DispatcherResult r = new DispatcherResultImpl(map, null != success ? success : false); results.add(NodeDispatchStepExecutor.wrapDispatcherResult(r)); x++; } + //merge failures for each step + for (final Integer integer : mergedStepFailures.keySet()) { + Map map = mergedStepFailures.get(integer); + + DispatcherResult r = new DispatcherResultImpl(map, false); + stepFailures.put(integer, NodeDispatchStepExecutor.wrapDispatcherResult(r)); + } } /** * Execute non-dispatch steps of a workflow + * * @return success if all steps were successful */ private boolean executeWFSection(StepExecutionContext executionContext, List results, - Map> failures, + Map> failures, + final Map stepFailures, int stepCount, - final List commands, final boolean keepgoing) - throws WorkflowStepFailureException { - Map failedMap = new HashMap(); + final List commands, final boolean keepgoing) { - boolean workflowsuccess=executeWorkflowItemsForNodeSet(executionContext, - failedMap, - results, - commands, - keepgoing, stepCount); + boolean workflowsuccess = executeWorkflowItemsForNodeSet(executionContext, + stepFailures, + results, + commands, + keepgoing, stepCount); - logger.debug("Aggregate results: " + workflowsuccess + " " + results + ", " + failedMap); - Map> localFailure = convertFailures(failedMap); + logger.debug("Aggregate results: " + workflowsuccess + " " + results + ", " + stepFailures); + Map> localFailure = convertFailures(stepFailures); mergeFailure(failures, localFailure); return workflowsuccess; } - private void mergeFailure(Map> destination, Map> source) { + private void mergeFailure(Map> destination, Map> source) { for (final String s : source.keySet()) { if (null == destination.get(s)) { - destination.put(s, new ArrayList()); + destination.put(s, new ArrayList()); } destination.get(s).addAll(source.get(s)); } } - private void mergeResult(HashMap> destination, HashMap> source) { + private void mergeResult(HashMap> destination, + HashMap> source) { for (final String s : source.keySet()) { - if(null== destination.get(s)) { + if (null == destination.get(s)) { destination.put(s, new ArrayList()); } destination.get(s).addAll(source.get(s)); @@ -304,10 +321,14 @@ private void validateNodeSet(ExecutionContext executionContext, NodesSelector no } } + static enum Reason implements FailureReason { + WorkflowSequenceFailures + } + /** * Workflow execution logic to dispatch an entire workflow sequence to a single node. */ - static class DispatchedWorkflow implements Dispatchable{ + static class DispatchedWorkflow implements Dispatchable { WorkflowExecutor executor; WorkflowExecutionItem workflowItem; int beginStep; @@ -323,22 +344,27 @@ static class DispatchedWorkflow implements Dispatchable{ this.stack = stack; } - public NodeStepResult dispatch(final ExecutionContext context, - final INodeEntry node) throws DispatcherException { - //XXX: not necessary, use passed in context, will be in single node context already + public NodeStepResult dispatch(final ExecutionContext context, final INodeEntry node) { final ExecutionContextImpl newcontext = new ExecutionContextImpl.Builder(context) - .nodeSelector(SelectorUtils.singleNode(node.getNodename())) - .nodes(NodeSetImpl.singleNodeSet(node)) + .singleNodeContext(node, true) .stepNumber(beginStep) .stepContext(stack) .build(); WorkflowExecutionResult result = executor.executeWorkflow(newcontext, workflowItem); - NodeStepResultImpl result1 = new NodeStepResultImpl(result.isSuccess(), node); + NodeStepResultImpl result1; + if (result.isSuccess()) { + result1 = new NodeStepResultImpl(node); + } else { + result1 = new NodeStepResultImpl(null, + Reason.WorkflowSequenceFailures, + "Sequence failed", + node); + } result1.setSourceResult(result); return result1; } - static WorkflowExecutionResult extractStepResult(NodeStepResult dispatcherResult) { + static WorkflowExecutionResult extractWorkflowResult(NodeStepResult dispatcherResult) { assert dispatcherResult instanceof HasSourceResult; if (!(dispatcherResult instanceof HasSourceResult)) { throw new IllegalArgumentException("Cannot extract source result from dispatcher result"); @@ -352,7 +378,59 @@ static WorkflowExecutionResult extractStepResult(NodeStepResult dispatcherResult WorkflowExecutionResult wfresult = (WorkflowExecutionResult) sourceResult; return wfresult; } + static List extractNodeStepResults(NodeStepResult dispatcherResult, INodeEntry node) { + return extractNodeStepResults(extractWorkflowResult(dispatcherResult), node); + } + static List extractNodeStepResults(WorkflowExecutionResult result, INodeEntry node) { + ArrayList results = new ArrayList(); + for (final StepExecutionResult executionResult : result.getResultSet()) { + + if (NodeDispatchStepExecutor.isWrappedDispatcherResult(executionResult)) { + DispatcherResult dispatcherResult + = NodeDispatchStepExecutor.extractDispatcherResult(executionResult); + NodeStepResult stepResult = dispatcherResult.getResults().get(node.getNodename()); + if(null!=stepResult){ + results.add(stepResult); + } + }else if (NodeDispatchStepExecutor.isWrappedDispatcherException(executionResult)) { + DispatcherException exception + = NodeDispatchStepExecutor.extractDispatcherException(executionResult); + NodeStepResult stepResult = exception.getResultMap().get(node.getNodename()); + if(null!=stepResult){ + results.add(stepResult); + } + } + } + return results; + } + static Map extractStepFailures(NodeStepResult dispatcherResult, INodeEntry node) { + return extractStepFailures(extractWorkflowResult(dispatcherResult), node); + } + static Map extractStepFailures(WorkflowExecutionResult result, INodeEntry node) { + Map results = new HashMap(); + for (final Map.Entry entry : result.getStepFailures().entrySet()) { + int num = entry.getKey(); + StepExecutionResult executionResult = entry.getValue(); + if (NodeDispatchStepExecutor.isWrappedDispatcherResult(executionResult)) { + DispatcherResult dispatcherResult + = NodeDispatchStepExecutor.extractDispatcherResult(executionResult); + NodeStepResult stepResult = dispatcherResult.getResults().get(node.getNodename()); + if (null != stepResult) { + results.put(num, stepResult); + } + } else if (NodeDispatchStepExecutor.isWrappedDispatcherException(executionResult)) { + DispatcherException exception + = NodeDispatchStepExecutor.extractDispatcherException(executionResult); + NodeStepResult stepResult = exception.getResultMap().get(node.getNodename()); + if (null != stepResult) { + results.put(num, stepResult); + } + } + } + return results; + } } + /** * Splits a workflow into a sequence of sub-workflows, separated along boundaries of node-dispatch sets. */ @@ -363,8 +441,8 @@ private List splitWorkflowDispatchedSections(IWorkflow workflow) thro StepExecutor executor = framework.getStepExecutionService().getExecutorForItem(item); if (executor.isNodeDispatchStep(item)) { dispatchItems.add(item); - }else{ - if(dispatchItems.size()>0) { + } else { + if (dispatchItems.size() > 0) { //add workflow section sections.add(new WorkflowImpl(dispatchItems, workflow.getThreadcount(), @@ -379,7 +457,7 @@ private List splitWorkflowDispatchedSections(IWorkflow workflow) thro workflow.getStrategy())); } } - if (null!=dispatchItems && dispatchItems.size() > 0) { + if (null != dispatchItems && dispatchItems.size() > 0) { //add workflow section sections.add(new WorkflowImpl(dispatchItems, workflow.getThreadcount(), @@ -390,7 +468,6 @@ private List splitWorkflowDispatchedSections(IWorkflow workflow) thro } - /** * Create workflowExecutionItem suitable for inner loop of node-first strategy */ diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeRecorder.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeRecorder.java index 07fee14346d..7e9e15525d0 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeRecorder.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/NodeRecorder.java @@ -24,6 +24,7 @@ package com.dtolabs.rundeck.core.execution.workflow; import com.dtolabs.rundeck.core.execution.FailedNodesListener; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; import java.util.*; @@ -34,16 +35,16 @@ */ public class NodeRecorder implements FailedNodesListener { private HashSet matchedNodes; - private HashMap failedNodes; + private HashMap failedNodes; private boolean success=false; public NodeRecorder() { matchedNodes =new HashSet(); - failedNodes=new HashMap(); + failedNodes=new HashMap(); success=false; } - public void nodesFailed(final Map failures) { + public void nodesFailed(final Map failures) { failedNodes.putAll(failures); } @@ -69,7 +70,7 @@ public HashSet getSuccessfulNodes() { * Return the set of failed nodes * @return */ - public HashMap getFailedNodes() { + public HashMap getFailedNodes() { return failedNodes; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/StepFirstWorkflowStrategy.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/StepFirstWorkflowStrategy.java index 8851dba8b21..2325a9f68fc 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/StepFirstWorkflowStrategy.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/StepFirstWorkflowStrategy.java @@ -25,10 +25,14 @@ import com.dtolabs.rundeck.core.Constants; import com.dtolabs.rundeck.core.common.Framework; -import com.dtolabs.rundeck.core.execution.*; +import com.dtolabs.rundeck.core.execution.StepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * StepFirstWorkflowStrategy iterates over the workflow steps and dispatches each one to all nodes matching the filter. @@ -51,8 +55,8 @@ public WorkflowExecutionResult executeWorkflowImpl(final StepExecutionContext ex boolean workflowsuccess = false; Exception exception = null; final IWorkflow workflow = item.getWorkflow(); - final Map failedList = new HashMap(); - final List resultList = new ArrayList(); + final Map stepFailures = new HashMap(); + final List stepResults = new ArrayList(); try { executionContext.getExecutionListener().log(Constants.DEBUG_LEVEL, "NodeSet: " + executionContext.getNodeSelector()); @@ -64,22 +68,15 @@ public WorkflowExecutionResult executeWorkflowImpl(final StepExecutionContext ex if (iWorkflowCmdItems.size() < 1) { executionContext.getExecutionListener().log(Constants.WARN_LEVEL, "Workflow has 0 items"); } - workflowsuccess = executeWorkflowItemsForNodeSet(executionContext, failedList, resultList, + workflowsuccess = executeWorkflowItemsForNodeSet(executionContext, stepFailures, stepResults, iWorkflowCmdItems, workflow.isKeepgoing()); - if (!workflowsuccess) { - throw new WorkflowFailureException("Some steps in the workflow failed: " + failedList); - } } catch (RuntimeException e) { exception = e; - } catch (WorkflowStepFailureException e) { - exception = e; - } catch (WorkflowFailureException e) { - exception = e; } final boolean success = workflowsuccess; final Exception orig = exception; - final Map> failures = convertFailures(failedList); - return new BaseWorkflowExecutionResult(resultList, failures, success, orig); + final Map> nodeFailures = convertFailures(stepFailures); + return new BaseWorkflowExecutionResult(stepResults, nodeFailures, stepFailures, success, orig); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowExecutionResult.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowExecutionResult.java index 54740718c0e..86ac1be6098 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowExecutionResult.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowExecutionResult.java @@ -24,7 +24,6 @@ package com.dtolabs.rundeck.core.execution.workflow; import com.dtolabs.rundeck.core.execution.ExceptionStatusResult; -import com.dtolabs.rundeck.core.execution.StatusResult; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; import java.util.Collection; @@ -46,5 +45,9 @@ public interface WorkflowExecutionResult extends ExceptionStatusResult { /** * Return map of workflow item failures, keyed by node name */ - public Map> getFailureMessages(); + public Map> getNodeFailures(); + /** + * Return map of workflow item failures, keyed by node name + */ + public Map getStepFailures(); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowFailureException.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowFailureException.java deleted file mode 100644 index 789f194fe30..00000000000 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowFailureException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2011 DTO Solutions, Inc. (http://dtosolutions.com) - * - * Licensed 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. - */ - -/* -* WorkflowFailureException.java -* -* User: Greg Schueler greg@dtosolutions.com -* Created: 3/24/11 9:45 AM -* -*/ -package com.dtolabs.rundeck.core.execution.workflow; - -import com.dtolabs.rundeck.core.execution.ExecutionException; - -import java.util.*; - -/** -* WorkflowFailureException is ... -* -* @author Greg Schueler greg@dtosolutions.com -*/ -public class WorkflowFailureException extends ExecutionException { - public WorkflowFailureException(String s) { - super(s); - } - - public WorkflowFailureException(Exception cause) { - super(cause); - } - - public WorkflowFailureException(String msg, Exception cause) { - super(msg, cause); - } -} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowStepFailureException.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowStepFailureException.java deleted file mode 100644 index 7f76c9e000b..00000000000 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/WorkflowStepFailureException.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2011 DTO Solutions, Inc. (http://dtosolutions.com) - * - * Licensed 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. - */ - -/* -* WorkflowStepFailureException.java -* -* User: Greg Schueler greg@dtosolutions.com -* Created: 3/24/11 9:45 AM -* -*/ -package com.dtolabs.rundeck.core.execution.workflow; - -import com.dtolabs.rundeck.core.execution.ExecutionResult; -import com.dtolabs.rundeck.core.execution.StatusResult; -import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; - -import java.util.*; - -/** -* WorkflowStepFailureException is ... -* -* @author Greg Schueler greg@dtosolutions.com -*/ -public class WorkflowStepFailureException extends Exception { - final private StepExecutionResult executionResult; - final private int workflowStep; - - public WorkflowStepFailureException(final String s, final StepExecutionResult executionResult, final int workflowStep) { - super(s); - this.executionResult = executionResult; - this.workflowStep = workflowStep; - } - - public WorkflowStepFailureException(final String s, final Throwable throwable, final int workflowStep) { - super(s, throwable); - this.executionResult = null; - this.workflowStep = workflowStep; - } - - public StepExecutionResult getStatusResult() { - return executionResult; - } - - public int getWorkflowStep() { - return workflowStep; - } -} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/FailureReason.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/FailureReason.java new file mode 100644 index 00000000000..c5a78d7bcc6 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/FailureReason.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* FailureReason.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/13/12 10:50 AM +* +*/ +package com.dtolabs.rundeck.core.execution.workflow.steps; + +/** + * A base interface for enum failure reasons, implementations should provide a {@link #toString()} implementation + * returning a single word reason. + * + * @author Greg Schueler greg@dtosolutions.com + */ +public interface FailureReason { + +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/NodeDispatchStepExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/NodeDispatchStepExecutor.java index ed78574a789..0be89e6a917 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/NodeDispatchStepExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/NodeDispatchStepExecutor.java @@ -24,11 +24,12 @@ */ package com.dtolabs.rundeck.core.execution.workflow.steps; +import com.dtolabs.rundeck.core.CoreException; import com.dtolabs.rundeck.core.common.Framework; -import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.StepExecutionItem; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; +import com.dtolabs.rundeck.core.execution.service.ExecutionServiceException; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; @@ -60,45 +61,87 @@ public StepExecutionResult executeWorkflowStep(final StepExecutionContext contex return wrapDispatcherResult(framework.getExecutionService().dispatchToNodes(context, item)); } catch (DispatcherException e) { return wrapDispatcherException(e); + } catch (ExecutionServiceException e) { + //internal error if failure using node dispatchers + throw new CoreException(e); } } + static enum Reason implements FailureReason{ + /** + * Node dispatch failed on at least one node + */ + NodeDispatchFailure + } /** * Return a StepExecutionResult based on the DispatcherResult, that can later be extracted. */ public static StepExecutionResult wrapDispatcherException(final DispatcherException dispatcherResult) { - final StepExecutionResultImpl result = new NodeDispatchStepExecutorExceptionResult(dispatcherResult); + final StepExecutionResultImpl result = new NodeDispatchStepExecutorExceptionResult(dispatcherResult, + Reason.NodeDispatchFailure, + dispatcherResult.getMessage()); + return result; } + /** * Return a StepExecutionResult based on the DispatcherResult, that can later be extracted. */ public static StepExecutionResult wrapDispatcherResult(final DispatcherResult dispatcherResult) { - final StepExecutionResultImpl result = new NodeDispatchStepExecutorResult(dispatcherResult.isSuccess(), - dispatcherResult); - result.setSourceResult(dispatcherResult); + final StepExecutionResultImpl result; + if(dispatcherResult.isSuccess()) { + result = NodeDispatchStepExecutorResult.success(dispatcherResult); + }else{ + result = NodeDispatchStepExecutorResult.failure(dispatcherResult, + null, + Reason.NodeDispatchFailure, + "Node dispatch failed"); + } return result; } static class NodeDispatchStepExecutorResult extends StepExecutionResultImpl{ DispatcherResult dispatcherResult; - - NodeDispatchStepExecutorResult(final boolean success, DispatcherResult dispatcherResult) { - super(success); + static NodeDispatchStepExecutorResult success(DispatcherResult dispatcherResult) { + return new NodeDispatchStepExecutorResult(dispatcherResult); + } + static NodeDispatchStepExecutorResult failure(DispatcherResult dispatcherResult, + final Exception exception, + final FailureReason reason, final String message){ + return new NodeDispatchStepExecutorResult(dispatcherResult, exception, reason, message); + } + NodeDispatchStepExecutorResult(DispatcherResult dispatcherResult) { + super(); + this.dispatcherResult = dispatcherResult; + setSourceResult(dispatcherResult); + } + NodeDispatchStepExecutorResult(DispatcherResult dispatcherResult, + final Exception exception, final FailureReason reason, final String message) { + super(exception, reason, message); this.dispatcherResult = dispatcherResult; setSourceResult(dispatcherResult); } public DispatcherResult getDispatcherResult(){ return dispatcherResult; } + + @Override + public String toString() { + if (null!=dispatcherResult) { + return dispatcherResult.toString(); + } else { + return super.toString(); + } + } + } static class NodeDispatchStepExecutorExceptionResult extends StepExecutionResultImpl{ DispatcherException dispatcherResult; - NodeDispatchStepExecutorExceptionResult(DispatcherException dispatcherResult) { - super(false); + NodeDispatchStepExecutorExceptionResult(DispatcherException dispatcherResult, + final FailureReason reason, final String message) { + super(dispatcherResult, reason, message); this.dispatcherResult = dispatcherResult; - setException(dispatcherResult); } public DispatcherException getDispatcherException(){ return dispatcherResult; diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/ScriptPluginStepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/ScriptPluginStepPlugin.java index b92cbacec4d..6be63108289 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/ScriptPluginStepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/ScriptPluginStepPlugin.java @@ -25,6 +25,7 @@ package com.dtolabs.rundeck.core.execution.workflow.steps; import com.dtolabs.rundeck.core.common.Framework; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.BaseScriptPlugin; import com.dtolabs.rundeck.core.plugins.PluginException; import com.dtolabs.rundeck.core.plugins.ScriptPluginProvider; @@ -61,7 +62,8 @@ static void validateScriptPlugin(final ScriptPluginProvider plugin) throws Plugi } @Override - public boolean executeStep(final PluginStepContext executionContext, final Map config) throws StepException { + public void executeStep(final PluginStepContext executionContext, final Map config) + throws StepException { final ScriptPluginProvider plugin = getProvider(); final String pluginname = plugin.getName(); executionContext.getLogger() @@ -73,14 +75,16 @@ public boolean executeStep(final PluginStepContext executionContext, final Mapgreg@dtosolutions.com */ public class StepException extends Exception { - public StepException() { - super(); - } + protected FailureReason failureReason; - public StepException(String msg) { + public StepException(String msg, FailureReason reason) { super(msg); + this.failureReason = reason; } - public StepException(Throwable cause) { + public StepException(Throwable cause, FailureReason reason) { super(cause); + this.failureReason = reason; } - public StepException(String msg, Throwable cause) { + public StepException(String msg, Throwable cause, FailureReason reason) { super(msg, cause); + this.failureReason = reason; + } + + public FailureReason getFailureReason() { + return failureReason; } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResult.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResult.java index 23558ec8588..d38e5ffb53a 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResult.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResult.java @@ -25,7 +25,8 @@ package com.dtolabs.rundeck.core.execution.workflow.steps; import com.dtolabs.rundeck.core.execution.ExceptionStatusResult; -import com.dtolabs.rundeck.core.execution.StatusResult; + +import java.util.Map; /** @@ -34,4 +35,10 @@ * @author Greg Schueler greg@dtosolutions.com */ public interface StepExecutionResult extends ExceptionStatusResult { + public Map getResultData(); + public Map getFailureData(); + + public FailureReason getFailureReason(); + public String getFailureMessage(); + } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResultImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResultImpl.java index 7d91bc76f0e..b94c91fa434 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResultImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutionResultImpl.java @@ -28,6 +28,9 @@ import com.dtolabs.rundeck.core.execution.HasSourceResult; import com.dtolabs.rundeck.core.execution.StatusResult; +import java.util.HashMap; +import java.util.Map; + /** * StepExecutionResultImpl is ... @@ -38,14 +41,26 @@ public class StepExecutionResultImpl implements StepExecutionResult, HasSourceRe private boolean success; private Exception exception; private StatusResult sourceResult; - - public StepExecutionResultImpl(boolean success) { - this.success = success; + private Map resultData; + private Map failureData; + private FailureReason failureReason; + private String failureMessage; + + public StepExecutionResultImpl() { + this.success = true; + resultData = new HashMap(); + failureData = new HashMap(); } - public StepExecutionResultImpl(boolean success, Exception exception) { - this.success = success; + public StepExecutionResultImpl(Exception exception, FailureReason failureReason, String failureMessage) { + this(); + this.success=false; this.exception = exception; + this.failureReason=failureReason; + this.failureMessage=failureMessage; + } + public static StepExecutionResultImpl wrapStepException(StepException e) { + return new StepExecutionResultImpl(e, e.getFailureReason(), e.getMessage()); } public boolean isSuccess() { @@ -73,6 +88,16 @@ public boolean equals(Object o) { if (success != result.success) { return false; } if (exception != null ? !exception.equals(result.exception) : result.exception != null) { return false; } +// if (sourceResult != null ? !sourceResult.equals(result.sourceResult) : result.sourceResult != null) { +// return false; +// } + if (failureData != null ? !failureData.equals(result.failureData) + : result.failureData != null) { + return false; + } + if (resultData != null ? !resultData.equals(result.resultData) : result.resultData != null) { + return false; + } return true; } @@ -81,15 +106,19 @@ public boolean equals(Object o) { public int hashCode() { int result = (success ? 1 : 0); result = 31 * result + (exception != null ? exception.hashCode() : 0); +// result = 31 * result + (sourceResult != null ? sourceResult.hashCode() : 0); + result = 31 * result + (resultData != null ? resultData.hashCode() : 0); + result = 31 * result + (failureData != null ? failureData.hashCode() : 0); return result; } @Override public String toString() { - return "StepExecutionResultImpl{" + - "success=" + success + - ", exception=" + exception + - '}'; + if (success ) { + return (null != sourceResult ? sourceResult.toString() : "Step successful"); + }else { + return failureReason + ": " + failureMessage; + } } public StatusResult getSourceResult() { @@ -99,4 +128,28 @@ public StatusResult getSourceResult() { public void setSourceResult(StatusResult sourceResult) { this.sourceResult = sourceResult; } + + public Map getResultData() { + return resultData; + } + + public void setResultData(Map resultData) { + this.resultData = resultData; + } + + public Map getFailureData() { + return failureData; + } + + public void setFailureData(Map failureData) { + this.failureData = failureData; + } + + public FailureReason getFailureReason() { + return failureReason; + } + + public String getFailureMessage() { + return failureMessage; + } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutor.java index 82011fff30c..4fc51e1e16c 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepExecutor.java @@ -24,9 +24,7 @@ */ package com.dtolabs.rundeck.core.execution.workflow.steps; -import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.StepExecutionItem; -import com.dtolabs.rundeck.core.execution.StatusResult; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; @@ -37,5 +35,7 @@ */ public interface StepExecutor { public boolean isNodeDispatchStep(StepExecutionItem item); - StepExecutionResult executeWorkflowStep(StepExecutionContext executionContext, StepExecutionItem item) throws StepException; + + StepExecutionResult executeWorkflowStep(StepExecutionContext executionContext, StepExecutionItem item) + throws StepException; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepFailureReason.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepFailureReason.java new file mode 100644 index 00000000000..6f9fda39f02 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepFailureReason.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* StepFailureReason.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/18/12 11:04 AM +* +*/ +package com.dtolabs.rundeck.core.execution.workflow.steps; + +/** +* Failure causes for workflow steps +* +* @author Greg Schueler greg@dtosolutions.com +*/ +public enum StepFailureReason implements FailureReason{ + /** + * A misconfiguration caused a failure + */ + ConfigurationFailure, + /** + * A process was interrupted + */ + Interrupted, + /** + * An IO error + */ + IOFailure, + /** + * Plugin failed + */ + PluginFailed, + /** + * Cause was not identified + */ + Unknown, +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepPluginAdapter.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepPluginAdapter.java index 5e2e4739051..e9be966e443 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepPluginAdapter.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/StepPluginAdapter.java @@ -76,8 +76,8 @@ public boolean isNodeDispatchStep(StepExecutionItem item) { @Override public StepExecutionResult executeWorkflowStep(final StepExecutionContext executionContext, - final StepExecutionItem item) - throws StepException { + final StepExecutionItem item) throws StepException + { Map instanceConfiguration = getStepConfiguration(item); if (null != instanceConfiguration) { instanceConfiguration = DataContextUtils.replaceDataReferences(instanceConfiguration, @@ -91,8 +91,12 @@ public StepExecutionResult executeWorkflowStep(final StepExecutionContext execut ); final PluginStepContext stepContext = PluginStepContextImpl.from(executionContext); final Map config = PluginAdapterUtility.configureProperties(resolver, getDescription(), plugin); - final boolean success = plugin.executeStep(stepContext, config); - return new StepExecutionResultImpl(success); + try { + plugin.executeStep(stepContext, config); + } catch (RuntimeException e) { + return new StepExecutionResultImpl(e, StepFailureReason.PluginFailed, e.getMessage()); + } + return new StepExecutionResultImpl(); } private Map getStepConfiguration(StepExecutionItem item) { diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeExecutionContext.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeExecutionContext.java new file mode 100644 index 00000000000..6e12489b9c3 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeExecutionContext.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* NodeExecutionContext.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/14/12 11:44 AM +* +*/ +package com.dtolabs.rundeck.core.execution.workflow.steps.node; + +import com.dtolabs.rundeck.core.execution.ExecutionContext; + +import java.util.Map; + + +/** + * NodeExecutionContext supports node-specific context data + * + * @author Greg Schueler greg@dtosolutions.com + */ +public interface NodeExecutionContext extends ExecutionContext { + /** + * Return the node specific context data keyed by node name + */ + public Map>> getNodeDataContext(); +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepException.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepException.java index d1f7742fcf9..1a8f1a38456 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepException.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepException.java @@ -23,34 +23,35 @@ */ package com.dtolabs.rundeck.core.execution.workflow.steps.node; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; /** - * NodeStepException is ... + * Represents an exception when executing a node step * * @author Greg Schueler greg@dtosolutions.com */ public class NodeStepException extends StepException { private final String nodeName; - - public NodeStepException(String s, String nodeName) { - super(s); + public NodeStepException(String s, FailureReason reason, String nodeName) { + super(s, reason); this.nodeName = nodeName; } - public NodeStepException(String s, Throwable throwable, String nodeName) { - super(s, throwable); + public NodeStepException(String s, Throwable throwable, FailureReason reason, String nodeName) { + super(s, throwable, reason); this.nodeName = nodeName; } - public NodeStepException(Throwable throwable, String nodeName) { - super(throwable); + public NodeStepException(Throwable throwable, FailureReason reason, String nodeName) { + super(throwable, reason); this.nodeName = nodeName; } public String getNodeName() { return nodeName; } + } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepFailureReason.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepFailureReason.java new file mode 100644 index 00000000000..0e336720bf3 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepFailureReason.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* NodeStepFailureReason.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/18/12 11:05 AM +* +*/ +package com.dtolabs.rundeck.core.execution.workflow.steps.node; + +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; + + +/** + * Failure reasons for node steps + * + * @author Greg Schueler greg@dtosolutions.com + */ +public enum NodeStepFailureReason implements FailureReason { + /** + * Host name could not be resolved + */ + HostNotFound, + /** + * Timeout on connection + */ + ConnectionTimeout, + /** + * Connection unsuccessful + */ + ConnectionFailure, + /** + * Authentication unsuccessful + */ + AuthenticationFailure, + /** + * Command or script execution result code was not zero + */ + NonZeroResultCode, +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepPluginAdapter.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepPluginAdapter.java index be07c8cfa7b..7138d353a20 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepPluginAdapter.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepPluginAdapter.java @@ -31,17 +31,18 @@ import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; import com.dtolabs.rundeck.core.execution.workflow.steps.PluginAdapterUtility; import com.dtolabs.rundeck.core.execution.workflow.steps.PluginStepContextImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.PropertyResolver; import com.dtolabs.rundeck.core.execution.workflow.steps.PropertyResolverFactory; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; import com.dtolabs.rundeck.core.utils.Converter; import com.dtolabs.rundeck.plugins.ServiceNameConstants; import com.dtolabs.rundeck.plugins.step.NodeStepPlugin; import com.dtolabs.rundeck.plugins.step.PluginStepContext; -import com.dtolabs.rundeck.core.execution.workflow.steps.PropertyResolver; import com.dtolabs.rundeck.plugins.util.DescriptionBuilder; -import java.util.*; +import java.util.Map; /** @@ -77,7 +78,6 @@ public NodeStepExecutor convert(final NodeStepPlugin plugin) { public static final Convert CONVERTER = new Convert(); - @Override public NodeStepResult executeNodeStep(final StepExecutionContext context, final NodeStepExecutionItem item, @@ -96,8 +96,20 @@ public NodeStepResult executeNodeStep(final StepExecutionContext context, providerName); final PluginStepContext pluginContext = PluginStepContextImpl.from(context); final Map config = PluginAdapterUtility.configureProperties(resolver, getDescription(), plugin); - final boolean success = plugin.executeNodeStep(pluginContext, config, node); - return new NodeStepResultImpl(success, node); + try { + plugin.executeNodeStep(pluginContext, config, node); + } catch (RuntimeException e) { + return new NodeStepResultImpl(e, + StepFailureReason.PluginFailed, + e.getMessage(), + node); + } catch (NodeStepException e){ + return new NodeStepResultImpl(e, + e.getFailureReason(), + e.getMessage(), + node); + } + return new NodeStepResultImpl(node); } private Map getStepConfiguration(StepExecutionItem item) { diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResult.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResult.java index 2c1ba13d79f..eb8cb7fc0e7 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResult.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResult.java @@ -24,15 +24,17 @@ package com.dtolabs.rundeck.core.execution.workflow.steps.node; import com.dtolabs.rundeck.core.common.INodeEntry; -import com.dtolabs.rundeck.core.execution.StatusResult; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; /** - * NodeStepResult is ... + * A result of a node step execution * * @author Greg Schueler greg@dtosolutions.com */ public interface NodeStepResult extends StepExecutionResult { + /** + * Return the node + */ public INodeEntry getNode(); } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResultImpl.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResultImpl.java index 203d940a875..b1522216af8 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResultImpl.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/NodeStepResultImpl.java @@ -25,10 +25,9 @@ package com.dtolabs.rundeck.core.execution.workflow.steps.node; import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResultImpl; -import java.util.*; - /** * NodeStepResultImpl is ... @@ -38,13 +37,23 @@ public class NodeStepResultImpl extends StepExecutionResultImpl implements NodeStepResult { private INodeEntry node; - public NodeStepResultImpl(boolean success, INodeEntry node) { - super(success); + /** + * Create a success result + */ + public NodeStepResultImpl(INodeEntry node) { + super(); this.node = node; } - public NodeStepResultImpl(boolean success, Exception exception, INodeEntry node) { - super(success, exception); + /** + * Create a failure result + */ + public NodeStepResultImpl( + Exception exception, + FailureReason failureReason, + String failureMessage, + INodeEntry node) { + super(exception, failureReason, failureMessage); this.node = node; } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/RemoteScriptNodeStepPluginAdapter.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/RemoteScriptNodeStepPluginAdapter.java index 99b312aa3f9..c47db5f6113 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/RemoteScriptNodeStepPluginAdapter.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/RemoteScriptNodeStepPluginAdapter.java @@ -28,7 +28,6 @@ import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ConfiguredStepExecutionItem; import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.ExecutionService; import com.dtolabs.rundeck.core.execution.StepExecutionItem; import com.dtolabs.rundeck.core.execution.service.FileCopierException; @@ -37,6 +36,7 @@ import com.dtolabs.rundeck.core.execution.workflow.steps.PluginStepContextImpl; import com.dtolabs.rundeck.core.execution.workflow.steps.PropertyResolver; import com.dtolabs.rundeck.core.execution.workflow.steps.PropertyResolverFactory; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.impl.ScriptFileNodeStepExecutor; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; @@ -83,6 +83,8 @@ public NodeStepExecutor convert(final RemoteScriptNodeStepPlugin plugin) { public static final Convert CONVERTER = new Convert(); + + @Override public NodeStepResult executeNodeStep(final StepExecutionContext context, final NodeStepExecutionItem item, @@ -104,7 +106,12 @@ public NodeStepResult executeNodeStep(final StepExecutionContext context, Description description = getDescription(); final Map config = PluginAdapterUtility.configureProperties(resolver, description, plugin); - final GeneratedScript script = plugin.generateScript(pluginContext, config, node); + final GeneratedScript script; + try { + script = plugin.generateScript(pluginContext, config, node); + } catch (RuntimeException e) { + return new NodeStepResultImpl(e, StepFailureReason.PluginFailed, e.getMessage(), node); + } //get all plugin config properties, and add to the data context used when executing the remote script final Map allconfig = PluginAdapterUtility.mapDescribedProperties(resolver, description); @@ -112,12 +119,10 @@ public NodeStepResult executeNodeStep(final StepExecutionContext context, for (final Map.Entry objectEntry : allconfig.entrySet()) { stringconfig.put(objectEntry.getKey(), objectEntry.getValue().toString()); } - final Map> configDataContext = - DataContextUtils.addContext("config", stringconfig, context.getDataContext()); return executeRemoteScript(ExecutionContextImpl .builder(context) - .dataContext(configDataContext) + .setContext("config", stringconfig) .build(), node, script); @@ -130,17 +135,13 @@ static NodeStepResult executeRemoteScript(final StepExecutionContext context, final ExecutionService executionService = context.getFramework().getExecutionService(); if (null != script.getCommand()) { //execute the command - try { - return executionService.executeCommand(context, script.getCommand(), node); - } catch (ExecutionException e) { - throw new NodeStepException(e, node.getNodename()); - } + return executionService.executeCommand(context, script.getCommand(), node); } else if (null != script.getScript()) { final String filepath; //result file path try { filepath = executionService.fileCopyScriptContent(context, script.getScript(), node); } catch (FileCopierException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e.getMessage(), e, e.getFailureReason(), node.getNodename()); } return ScriptFileNodeStepExecutor.executeRemoteScript(context, context.getFramework(), @@ -153,7 +154,7 @@ static NodeStepResult executeRemoteScript(final StepExecutionContext context, try { filepath = executionService.fileCopyFile(context, fileScript.getScriptFile(), node); } catch (FileCopierException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e.getMessage(), e, e.getFailureReason(), node.getNodename()); } return ScriptFileNodeStepExecutor.executeRemoteScript(context, context.getFramework(), @@ -164,7 +165,10 @@ static NodeStepResult executeRemoteScript(final StepExecutionContext context, fileScript.isInterpreterArgsQuoted()); } else { - return new NodeStepResultImpl(false, node); + return new NodeStepResultImpl(null, + StepFailureReason.ConfigurationFailure, + "Generated script must have a command or script defined", + node); } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptBasedRemoteScriptNodeStepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptBasedRemoteScriptNodeStepPlugin.java index a591e482f65..0c5bc1079ba 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptBasedRemoteScriptNodeStepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptBasedRemoteScriptNodeStepPlugin.java @@ -66,7 +66,7 @@ static void validateScriptPlugin(final ScriptPluginProvider plugin) throws Plugi @Override public GeneratedScript generateScript(final PluginStepContext context, final Map configuration, - final INodeEntry entry) throws NodeStepException { + final INodeEntry entry) { final ScriptPluginProvider provider = getProvider(); final String args = provider.getScriptArgs(); final String[] argsarr; diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptPluginNodeStepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptPluginNodeStepPlugin.java index ccb6fa325d4..c8d7129acf2 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptPluginNodeStepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/ScriptPluginNodeStepPlugin.java @@ -26,6 +26,7 @@ import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.plugins.BaseScriptPlugin; import com.dtolabs.rundeck.core.plugins.PluginException; import com.dtolabs.rundeck.core.plugins.ScriptPluginProvider; @@ -63,7 +64,7 @@ static void validateScriptPlugin(final ScriptPluginProvider plugin) throws Plugi } @Override - public boolean executeNodeStep(final PluginStepContext executionContext, + public void executeNodeStep(final PluginStepContext executionContext, final Map configuration, final INodeEntry node) throws NodeStepException { @@ -80,15 +81,20 @@ public boolean executeNodeStep(final PluginStepContext executionContext, try { result = runPluginScript(executionContext, System.out, System.err, getFramework(), configuration); } catch (IOException e) { - e.printStackTrace(); + throw new NodeStepException(e.getMessage(), + StepFailureReason.IOFailure, + node.getNodename()); } catch (InterruptedException e) { - e.printStackTrace(); + Thread.currentThread().interrupt(); + throw new NodeStepException(e.getMessage(), + StepFailureReason.Interrupted, + node.getNodename()); + } + executionContext.getLogger().log(3, "[" + pluginname + "]: result code: " + result); + if (result != 0) { + throw new NodeStepException("Script result code was: " + result, + NodeStepFailureReason.NonZeroResultCode, + node.getNodename()); } - boolean success = result == 0; - - executionContext.getLogger().log(3, - "[" + pluginname + "]: result code: " + result + ", success: " - + success); - return success; } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ExecNodeStepExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ExecNodeStepExecutor.java index 771909e1143..171d7544ae1 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ExecNodeStepExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ExecNodeStepExecutor.java @@ -25,7 +25,6 @@ import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeEntry; -import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; @@ -48,16 +47,10 @@ public ExecNodeStepExecutor(Framework framework) { private Framework framework; - public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExecutionItem item, INodeEntry node) throws - NodeStepException { + public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExecutionItem item, INodeEntry node) + throws NodeStepException { final ExecCommand cmd = (ExecCommand) item; - NodeExecutorResult result; - try { - result = framework.getExecutionService().executeCommand(context, cmd.getCommand(), node); - } catch (Exception e) { - throw new NodeStepException(e, node.getNodename()); - } - return result; + return framework.getExecutionService().executeCommand(context, cmd.getCommand(), node); } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptFileNodeStepExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptFileNodeStepExecutor.java index 7f8f7e2d6c1..477f9645242 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptFileNodeStepExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptFileNodeStepExecutor.java @@ -26,7 +26,6 @@ import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.ExecutionService; import com.dtolabs.rundeck.core.execution.service.FileCopierException; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; @@ -35,7 +34,7 @@ import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; -import com.dtolabs.rundeck.core.plugins.BaseScriptPlugin; +import com.dtolabs.rundeck.core.utils.ScriptExecUtil; import java.io.File; @@ -69,7 +68,7 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExec filepath = executionService.fileCopyFileStream(context, script.getScriptAsStream(), node); } } catch (FileCopierException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e.getMessage(), e, e.getFailureReason(), node.getNodename()); } return executeRemoteScript(context, context.getFramework(), node, script.getArgs(), filepath); @@ -110,33 +109,29 @@ public static NodeStepResult executeRemoteScript(final ExecutionContext context, final String filepath, final String scriptInterpreter, final boolean interpreterargsquoted) throws NodeStepException { - try { - /** - * TODO: Avoid this horrific hack. Discover how to get SCP task to preserve the execute bit. - */ - if (!"windows".equalsIgnoreCase(node.getOsFamily())) { - //perform chmod+x for the file + /** + * TODO: Avoid this horrific hack. Discover how to get SCP task to preserve the execute bit. + */ + if (!"windows".equalsIgnoreCase(node.getOsFamily())) { + //perform chmod+x for the file - final NodeExecutorResult nodeExecutorResult = framework.getExecutionService().executeCommand( - context, new String[]{"chmod", "+x", filepath}, node); - if (!nodeExecutorResult.isSuccess()) { - return nodeExecutorResult; - } + final NodeExecutorResult nodeExecutorResult = framework.getExecutionService().executeCommand( + context, new String[]{"chmod", "+x", filepath}, node); + if (!nodeExecutorResult.isSuccess()) { + return nodeExecutorResult; } + } - //replace data references - final String[] newargs = BaseScriptPlugin.createScriptArgs(context.getDataContext(), - null, - args, - scriptInterpreter, - interpreterargsquoted, - filepath); - //XXX: windows specific call? + //replace data references + final String[] newargs = ScriptExecUtil.createScriptArgs(context.getDataContext(), + null, + args, + scriptInterpreter, + interpreterargsquoted, + filepath); + //XXX: windows specific call? - return framework.getExecutionService().executeCommand(context, newargs, node); - //TODO: remove remote temp file after exec? - } catch (ExecutionException e) { - throw new NodeStepException(e, node.getNodename()); - } + return framework.getExecutionService().executeCommand(context, newargs, node); + //TODO: remove remote temp file after exec? } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptURLNodeStepExecutor.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptURLNodeStepExecutor.java index c14c248460c..0e226346761 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptURLNodeStepExecutor.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/ScriptURLNodeStepExecutor.java @@ -30,11 +30,12 @@ import com.dtolabs.rundeck.core.common.impl.URLFileUpdater; import com.dtolabs.rundeck.core.common.impl.URLFileUpdaterBuilder; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.ExecutionService; import com.dtolabs.rundeck.core.execution.service.FileCopierException; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutor; @@ -87,6 +88,12 @@ private static String hashURL(final String url) { } return Integer.toString(url.hashCode()); } + static enum Reason implements FailureReason{ + /** + * Failed to download required URL + */ + URLDownloadFailure + } public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExecutionItem item, INodeEntry node) throws NodeStepException { @@ -104,7 +111,7 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExec try { url = new URL(finalUrl); } catch (MalformedURLException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e, StepFailureReason.ConfigurationFailure, node.getNodename()); } if(null!=context.getExecutionListener()){ context.getExecutionListener().log(4, "Requesting URL: " + url.toExternalForm()); @@ -138,7 +145,10 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExec } catch (UpdateUtils.UpdateException e) { if (!destinationTempFile.isFile() || destinationTempFile.length() < 1) { throw new NodeStepException( - "Error requesting URL Script: " + cleanUrl + ": " + e.getMessage(), e,node.getNodename()); + "Error requesting URL Script: " + cleanUrl + ": " + e.getMessage(), + e, + Reason.URLDownloadFailure, + node.getNodename()); } else { logger.error( "Error requesting URL script: " + cleanUrl + ": " + e.getMessage(), e); @@ -149,39 +159,35 @@ public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExec try { filepath = executionService.fileCopyFile(context, destinationTempFile, node); } catch (FileCopierException e) { - throw new NodeStepException(e, node.getNodename()); + throw new NodeStepException(e.getMessage(), e, StepFailureReason.IOFailure, node.getNodename()); } - try { - /** - * TODO: Avoid this horrific hack. Discover how to get SCP task to preserve the execute bit. - */ - if (!"windows".equalsIgnoreCase(node.getOsFamily())) { - //perform chmod+x for the file - - final NodeExecutorResult nodeExecutorResult = framework.getExecutionService().executeCommand( - context, new String[]{"chmod", "+x", filepath}, node); - if (!nodeExecutorResult.isSuccess()) { - return nodeExecutorResult; - } - } + /** + * TODO: Avoid this horrific hack. Discover how to get SCP task to preserve the execute bit. + */ + if (!"windows".equalsIgnoreCase(node.getOsFamily())) { + //perform chmod+x for the file - final String[] args = script.getArgs(); - //replace data references - String[] newargs = null; - if (null != args && args.length > 0) { - newargs = new String[args.length + 1]; - final String[] replargs = DataContextUtils.replaceDataReferences(args, nodeDataContext); - newargs[0] = filepath; - System.arraycopy(replargs, 0, newargs, 1, replargs.length); - } else { - newargs = new String[]{filepath}; + final NodeExecutorResult nodeExecutorResult = framework.getExecutionService().executeCommand( + context, new String[]{"chmod", "+x", filepath}, node); + if (!nodeExecutorResult.isSuccess()) { + return nodeExecutorResult; } + } - return framework.getExecutionService().executeCommand(context, newargs, node); - } catch (ExecutionException e) { - throw new NodeStepException(e, node.getNodename()); + final String[] args = script.getArgs(); + //replace data references + String[] newargs = null; + if (null != args && args.length > 0) { + newargs = new String[args.length + 1]; + final String[] replargs = DataContextUtils.replaceDataReferences(args, nodeDataContext); + newargs[0] = filepath; + System.arraycopy(replargs, 0, newargs, 1, replargs.length); + } else { + newargs = new String[]{filepath}; } + + return framework.getExecutionService().executeCommand(context, newargs, node); } public static final Converter urlPathEncoder = new Converter() { diff --git a/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseProviderRegistryService.java b/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseProviderRegistryService.java index e441ad4abcc..94287f1201d 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseProviderRegistryService.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseProviderRegistryService.java @@ -24,7 +24,6 @@ package com.dtolabs.rundeck.core.plugins; import com.dtolabs.rundeck.core.common.Framework; -import com.dtolabs.rundeck.core.common.FrameworkSupportService; import com.dtolabs.rundeck.core.common.ProviderService; import com.dtolabs.rundeck.core.execution.service.ExecutionServiceException; import com.dtolabs.rundeck.core.execution.service.MissingProviderException; @@ -68,7 +67,7 @@ public void registerInstance(String name, T object) { */ public T providerOfType(final String providerName) throws ExecutionServiceException { if (null == providerName) { - throw new IllegalArgumentException("provider name was null for Service: " + getName()); + throw new NullPointerException("provider name was null for Service: " + getName()); } if (null == instanceregistry.get(providerName)) { T instance = createProviderInstanceOfType(providerName); diff --git a/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseScriptPlugin.java b/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseScriptPlugin.java index e8c25fef592..b643e033c72 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseScriptPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/plugins/BaseScriptPlugin.java @@ -26,14 +26,12 @@ import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; +import com.dtolabs.rundeck.core.utils.ScriptExecUtil; import com.dtolabs.rundeck.plugins.step.PluginStepContext; -import com.dtolabs.utils.Streams; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintStream; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -50,40 +48,10 @@ protected BaseScriptPlugin(final ScriptPluginProvider provider, final Framework } /** - * Run a command with environment variables in a working dir, and copy the streams - */ - protected int runScript(final String[] command, - final Map envMap, final File workingdir, - final OutputStream outputStream, final OutputStream errorStream) - throws IOException, InterruptedException { - final String[] envarr = createEnvironmentArray(envMap); - - final Runtime runtime = Runtime.getRuntime(); - final Process exec = runtime.exec(command, envarr, workingdir); - final Thread errthread = Streams.copyStreamThread(exec.getErrorStream(), errorStream); - final Thread outthread = Streams.copyStreamThread(exec.getInputStream(), outputStream); - errthread.start(); - outthread.start(); - exec.getOutputStream().close(); - final int result = exec.waitFor(); - errthread.join(); - outthread.join(); - return result; - } + * Runs the script configured for the script plugin and channels the output to two streams. - /** - * Create the environment array for executing via {@link Runtime}. - */ - private String[] createEnvironmentArray(final Map envMap) { - final ArrayList envlist = new ArrayList(); - for (final Map.Entry entry : envMap.entrySet()) { - envlist.add(entry.getKey() + "=" + entry.getValue()); - } - return envlist.toArray(new String[envlist.size()]); - } - - /** - * Runs the script configured for the script plugin and channels the output to two streams. the + * @throws IOException if any IO exception occurs + * @throws InterruptedException if interrupted while waiting for the command to finish */ protected int runPluginScript(final PluginStepContext executionContext, final PrintStream outputStream, @@ -100,11 +68,11 @@ protected int runPluginScript(final PluginStepContext executionContext, executionContext.getLogger().log(3, "[" + getProvider().getName() + "] executing: " + Arrays.asList( finalargs)); - return runScript(finalargs, - DataContextUtils.generateEnvVarsFromContext(localDataContext), - null, - outputStream, - errorStream + return ScriptExecUtil.runLocalCommand(finalargs, + DataContextUtils.generateEnvVarsFromContext(localDataContext), + null, + outputStream, + errorStream ); } @@ -152,53 +120,9 @@ protected String[] createScriptArgs(final Map> local final boolean interpreterargsquoted = plugin.getInterpreterArgsQuoted(); - return createScriptArgs(localDataContext, - scriptargs, null, scriptinterpreter, interpreterargsquoted, - scriptfile.getAbsolutePath()); + return ScriptExecUtil.createScriptArgs(localDataContext, + scriptargs, null, scriptinterpreter, interpreterargsquoted, + scriptfile.getAbsolutePath()); } - /** - * Generate argument array for a script file invocation - * - * @param localDataContext data context properties to expand among the args - * @param scriptargs arguments to the script file - * @param scriptargsarr arguments to the script file as an array - * @param scriptinterpreter interpreter invocation for the file, or null to invoke it directly - * @param interpreterargsquoted if true, pass the script file and args as a single argument to the interpreter - * @param filepath remote filepath for the script - */ - public static String[] createScriptArgs(final Map> localDataContext, - final String scriptargs, - final String[] scriptargsarr, - final String scriptinterpreter, - final boolean interpreterargsquoted, final String filepath) { - final ArrayList arglist = new ArrayList(); - if (null != scriptinterpreter) { - arglist.addAll(Arrays.asList(scriptinterpreter.split(" "))); - } - if (null != scriptinterpreter && interpreterargsquoted) { - final StringBuilder sbuf = new StringBuilder(filepath); - if (null != scriptargs) { - sbuf.append(" "); - sbuf.append(DataContextUtils.replaceDataReferences(scriptargs, localDataContext)); - } else if (null != scriptargsarr) { - - final String[] strings = DataContextUtils.replaceDataReferences(scriptargsarr, localDataContext); - for (final String string : strings) { - sbuf.append(" "); - sbuf.append(string); - } - } - arglist.add(sbuf.toString()); - } else { - arglist.add(filepath); - if (null != scriptargs) { - arglist.addAll(Arrays.asList(DataContextUtils.replaceDataReferences(scriptargs.split(" "), - localDataContext))); - } else if (null != scriptargsarr) { - arglist.addAll(Arrays.asList(DataContextUtils.replaceDataReferences(scriptargsarr, localDataContext))); - } - } - return arglist.toArray(new String[arglist.size()]); - } } diff --git a/core/src/main/java/com/dtolabs/rundeck/core/utils/ScriptExecUtil.java b/core/src/main/java/com/dtolabs/rundeck/core/utils/ScriptExecUtil.java new file mode 100644 index 00000000000..75f83cc05a4 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/utils/ScriptExecUtil.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* ScriptExecUtil.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/13/12 5:05 PM +* +*/ +package com.dtolabs.rundeck.core.utils; + +import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; +import com.dtolabs.utils.Streams; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + + +/** + * Provides methods for running scripts/commands. + * + * @author Greg Schueler greg@dtosolutions.com + */ +public class ScriptExecUtil { + + /** + * Run a command with environment variables in a working dir, and copy the streams + * + * @param command the command array to run + * @param envMap the environment variables to pass in + * @param workingdir optional working dir location (or null) + * @param outputStream stream for stdout + * @param errorStream stream for stderr + * + * @return the exit code of the command + * + * @throws IOException if any IO exception occurs + * @throws InterruptedException if interrupted while waiting for the command to finish + */ + public static int runLocalCommand(final String[] command, + final Map envMap, final File workingdir, + final OutputStream outputStream, final OutputStream errorStream) + throws IOException, InterruptedException { + final String[] envarr = createEnvironmentArray(envMap); + + final Runtime runtime = Runtime.getRuntime(); + final Process exec = runtime.exec(command, envarr, workingdir); + final Streams.StreamCopyThread errthread = Streams.copyStreamThread(exec.getErrorStream(), errorStream); + final Streams.StreamCopyThread outthread = Streams.copyStreamThread(exec.getInputStream(), outputStream); + errthread.start(); + outthread.start(); + exec.getOutputStream().close(); + final int result = exec.waitFor(); + errthread.join(); + outthread.join(); + if (null != outthread.getException()) { + throw outthread.getException(); + } + if (null != errthread.getException()) { + throw errthread.getException(); + } + return result; + } + + /** + * Create the environment array for executing via {@link Runtime}. + */ + private static String[] createEnvironmentArray(final Map envMap) { + final ArrayList envlist = new ArrayList(); + for (final Map.Entry entry : envMap.entrySet()) { + envlist.add(entry.getKey() + "=" + entry.getValue()); + } + return envlist.toArray(new String[envlist.size()]); + } + + /** + * Generate argument array for a script file invocation + * + * @param localDataContext data context properties to expand among the args + * @param scriptargs arguments to the script file + * @param scriptargsarr arguments to the script file as an array + * @param scriptinterpreter interpreter invocation for the file, or null to invoke it directly + * @param interpreterargsquoted if true, pass the script file and args as a single argument to the interpreter + * @param filepath remote filepath for the script + */ + public static String[] createScriptArgs(final Map> localDataContext, + final String scriptargs, + final String[] scriptargsarr, + final String scriptinterpreter, + final boolean interpreterargsquoted, final String filepath) { + final ArrayList arglist = new ArrayList(); + if (null != scriptinterpreter) { + arglist.addAll(Arrays.asList(scriptinterpreter.split(" "))); + } + if (null != scriptinterpreter && interpreterargsquoted) { + final StringBuilder sbuf = new StringBuilder(filepath); + if (null != scriptargs) { + sbuf.append(" "); + sbuf.append(DataContextUtils.replaceDataReferences(scriptargs, localDataContext)); + } else if (null != scriptargsarr) { + + final String[] strings = DataContextUtils.replaceDataReferences(scriptargsarr, localDataContext); + for (final String string : strings) { + sbuf.append(" "); + sbuf.append(string); + } + } + arglist.add(sbuf.toString()); + } else { + arglist.add(filepath); + if (null != scriptargs) { + arglist.addAll(Arrays.asList(DataContextUtils.replaceDataReferences(scriptargs.split(" "), + localDataContext))); + } else if (null != scriptargsarr) { + arglist.addAll(Arrays.asList(DataContextUtils.replaceDataReferences(scriptargsarr, localDataContext))); + } + } + return arglist.toArray(new String[arglist.size()]); + } +} diff --git a/core/src/main/java/com/dtolabs/rundeck/plugins/step/NodeStepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/plugins/step/NodeStepPlugin.java index fd7b8f38725..2853c74f70d 100644 --- a/core/src/main/java/com/dtolabs/rundeck/plugins/step/NodeStepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/plugins/step/NodeStepPlugin.java @@ -45,7 +45,7 @@ public interface NodeStepPlugin { * * @throws NodeStepException if an error occurs */ - public boolean executeNodeStep(final PluginStepContext context, + public void executeNodeStep(final PluginStepContext context, final Map configuration, final INodeEntry entry) throws NodeStepException; diff --git a/core/src/main/java/com/dtolabs/rundeck/plugins/step/RemoteScriptNodeStepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/plugins/step/RemoteScriptNodeStepPlugin.java index 47480e3997d..cbbec891d0d 100644 --- a/core/src/main/java/com/dtolabs/rundeck/plugins/step/RemoteScriptNodeStepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/plugins/step/RemoteScriptNodeStepPlugin.java @@ -47,6 +47,5 @@ public interface RemoteScriptNodeStepPlugin { */ public GeneratedScript generateScript(final PluginStepContext context, final Map configuration, - final INodeEntry entry) - throws NodeStepException; + final INodeEntry entry) throws NodeStepException; } diff --git a/core/src/main/java/com/dtolabs/rundeck/plugins/step/StepPlugin.java b/core/src/main/java/com/dtolabs/rundeck/plugins/step/StepPlugin.java index 92ffde9a226..792db36c336 100644 --- a/core/src/main/java/com/dtolabs/rundeck/plugins/step/StepPlugin.java +++ b/core/src/main/java/com/dtolabs/rundeck/plugins/step/StepPlugin.java @@ -41,8 +41,8 @@ public interface StepPlugin { * @param context the plugin step context * @param configuration Any configuration property values not otherwise applied to the plugin * - * @throws StepException if an error occurs + * @throws StepException if an error occurs, the failureReason should indicate the reason */ - public boolean executeStep(final PluginStepContext context, final Map configuration) + public void executeStep(final PluginStepContext context, final Map configuration) throws StepException; } diff --git a/core/src/test/java/com/dtolabs/rundeck/core/cli/TestExecTool.java b/core/src/test/java/com/dtolabs/rundeck/core/cli/TestExecTool.java index 9c9f72158d8..66a28f300ba 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/cli/TestExecTool.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/cli/TestExecTool.java @@ -24,29 +24,41 @@ */ -import com.dtolabs.rundeck.core.CoreException; import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.FrameworkProject; import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.dispatcher.*; -import com.dtolabs.rundeck.core.execution.*; +import com.dtolabs.rundeck.core.execution.ExecutionContext; +import com.dtolabs.rundeck.core.execution.ExecutionListener; +import com.dtolabs.rundeck.core.execution.StepExecutionItem; +import com.dtolabs.rundeck.core.execution.dispatch.DispatcherException; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionService; -import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; -import com.dtolabs.rundeck.core.execution.workflow.steps.node.impl.ExecCommandExecutionItem; -import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.impl.ExecCommandExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.impl.ScriptFileCommandExecutionItem; -import com.dtolabs.rundeck.core.utils.NodeSet; import com.dtolabs.rundeck.core.tools.AbstractBaseTest; import com.dtolabs.rundeck.core.utils.FileUtils; +import com.dtolabs.rundeck.core.utils.NodeSet; import junit.framework.Test; import junit.framework.TestSuite; -import java.io.*; -import java.util.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; public class TestExecTool extends AbstractBaseTest { ExecTool main; @@ -445,6 +457,7 @@ public testExecutor1(Framework framework) { this.framework = framework; } + @Override public NodeStepResult executeNodeStep(StepExecutionContext context, NodeStepExecutionItem item, INodeEntry node) throws NodeStepException { testContext=context; @@ -554,7 +567,7 @@ public void testRunActionShouldLogResult() throws Exception { //set return result - testExecutor1.returnResult = new NodeStepResultImpl(true,null) { + testExecutor1.returnResult = new NodeStepResultImpl(null) { @Override public String toString() { return "test1ResultString"; @@ -597,7 +610,7 @@ public void testRunActionShouldLogResultFailure() throws Exception { getFrameworkInstance()); cis.registerClass("exec", testExecutor1.class); //set return result - testExecutor1.returnResult = new NodeStepResultImpl(false,null) { + testExecutor1.returnResult = new NodeStepResultImpl(null,null,null,null) { public String toString() { return "test failure result"; } @@ -652,7 +665,7 @@ public void testRunAction() throws Exception{ try { main.runAction(); fail("run shouldn't succeed"); - } catch (CoreException e) { + } catch (NullPointerException e) { assertNotNull(e); e.printStackTrace(System.err); } @@ -664,7 +677,7 @@ public void testRunAction() throws Exception{ } //set return result - testExecutor1.returnResult=new NodeStepResultImpl(true,null){ + testExecutor1.returnResult=new NodeStepResultImpl(null){ public String toString() { return "testResult1"; } diff --git a/core/src/test/java/com/dtolabs/rundeck/core/dispatcher/TestDataContextUtils.java b/core/src/test/java/com/dtolabs/rundeck/core/dispatcher/TestDataContextUtils.java index 21fe1bc935b..b42e9a6581a 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/dispatcher/TestDataContextUtils.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/dispatcher/TestDataContextUtils.java @@ -573,4 +573,32 @@ public void testAddContext() throws Exception { assertEquals(0, map.get("test2").size()); } } + + public void testMerge(){ + final HashMap> origContext = new HashMap>(); + final HashMap test1data = new HashMap(); + test1data.put("key1", "value1"); + origContext.put("test1", test1data); + final HashMap test2data = new HashMap(); + test2data.put("key2", "value2"); + origContext.put("test2", test2data); + + + final HashMap> newContext = new HashMap>(); + final HashMap test3data = new HashMap(); + test3data.put("key1", "value2"); + newContext.put("test1", test3data); + final Map> result = DataContextUtils.merge(origContext, newContext); + + assertNotNull(result); + assertEquals(2, result.size()); + assertNotNull(result.get("test1")); + assertEquals(1, result.get("test1").size()); + assertNotNull(result.get("test1").get("key1")); + assertEquals("value2", result.get("test1").get("key1")); + assertNotNull(result.get("test2")); + assertEquals(1, result.get("test2").size()); + assertNotNull(result.get("test2").get("key2")); + assertEquals("value2", result.get("test2").get("key2")); + } } \ No newline at end of file diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/TestExecutionContextImpl.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/TestExecutionContextImpl.java new file mode 100644 index 00000000000..15486e7fd1a --- /dev/null +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/TestExecutionContextImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* TestExecutionContextImpl.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/17/12 10:09 AM +* +*/ +package com.dtolabs.rundeck.core.execution; + +import com.dtolabs.rundeck.core.common.NodeEntryImpl; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + + +/** + * TestExecutionContextImpl is ... + * + * @author Greg Schueler greg@dtosolutions.com + */ +public class TestExecutionContextImpl extends TestCase { + public void testSingleNodeContext() { + + Map> map = new HashMap>(); + Map ctx1 = new HashMap(); + ctx1.put("test", "value"); + map.put("ctx1", ctx1); + ExecutionContextImpl imp = + ExecutionContextImpl.builder().dataContext(map).build(); + + assertNotNull(imp.getDataContext()); + assertNull(imp.getDataContext().get("node")); + assertNotNull(imp.getDataContext().get("ctx1")); + assertEquals("value", imp.getDataContext().get("ctx1").get("test")); + + NodeEntryImpl testNode = new NodeEntryImpl("testNode"); + testNode.setDescription("desc 1"); + testNode.setHostname("host1"); + testNode.setUsername("user1"); + + + ExecutionContextImpl imp2 = + ExecutionContextImpl.builder(imp).singleNodeContext(testNode, true).build(); + + assertNotNull(imp2.getDataContext()); + assertNotNull(imp2.getDataContext().get("node")); + assertEquals("desc 1", imp2.getDataContext().get("node").get("description")); + assertEquals("host1", imp2.getDataContext().get("node").get("hostname")); + assertEquals("testNode", imp2.getDataContext().get("node").get("name")); + assertNotNull(imp2.getDataContext().get("ctx1")); + assertEquals("value", imp2.getDataContext().get("ctx1").get("test")); + } +} diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestBaseWorkflowStrategy.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestBaseWorkflowStrategy.java index 35b9e719cd7..964eee022ad 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestBaseWorkflowStrategy.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestBaseWorkflowStrategy.java @@ -32,7 +32,6 @@ import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; -import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResultImpl; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutor; @@ -48,7 +47,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** @@ -58,7 +61,7 @@ */ public class TestBaseWorkflowStrategy extends AbstractBaseTest { - public static final String TEST_PROJECT="TestBaseWorkflowStrategy"; + public static final String TEST_PROJECT = "TestBaseWorkflowStrategy"; Framework testFramework; String testnode; @@ -89,12 +92,12 @@ protected void tearDown() throws Exception { FileUtils.deleteDir(projectdir); } - public static class testWorkflowStrategy extends BaseWorkflowStrategy{ + public static class testWorkflowStrategy extends BaseWorkflowStrategy { private WorkflowExecutionResult result; - private int execIndex=0; + private int execIndex = 0; private List results; - private List> inputs; - private int executeWfItemCalled=0; + private List> inputs; + private int executeWfItemCalled = 0; public testWorkflowStrategy(Framework framework) { super(framework); @@ -111,10 +114,9 @@ public WorkflowExecutionResult executeWorkflowImpl(StepExecutionContext executio @Override protected StepExecutionResult executeWFItem(final StepExecutionContext executionContext, - final Map failedMap, + final Map failedMap, final int c, - final StepExecutionItem cmd, final boolean keepgoing) - throws WorkflowStepFailureException { + final StepExecutionItem cmd) { executeWfItemCalled++; HashMap input = new HashMap(); input.put("context", executionContext); @@ -122,32 +124,18 @@ protected StepExecutionResult executeWFItem(final StepExecutionContext execution input.put("c", c); input.put("stack", executionContext.getStepContext()); input.put("cmd", cmd); - input.put("keepgoing", keepgoing); inputs.add(input); int ndx = execIndex++; final Object o = results.get(ndx); if (o instanceof Boolean) { - return new StepExecutionResultImpl((Boolean) o); - } else if (o instanceof WorkflowStepFailureException) { - throw (WorkflowStepFailureException) o; + return ((Boolean) o) ? new StepExecutionResultImpl() : + new StepExecutionResultImpl(null, null, "false result"); } else if (o instanceof String) { - throw new WorkflowStepFailureException((String) o, new StepExecutionResult() { - public Exception getException() { - return null; - } - - public DispatcherResult getResultObject() { - return null; - } - - public boolean isSuccess() { - return false; - } - }, c); + return new StepExecutionResultImpl(null, null, (String) o); } else { fail("Unexpected result at index " + ndx + ": " + o); - return new StepExecutionResultImpl(false); + return new StepExecutionResultImpl(null, null, "Unexpected result at index " + ndx + ": " + o); } } @@ -187,12 +175,12 @@ void assertExecWFItems(final List items, strategy.getResults().addAll(returnResults); - final Map map = new HashMap(); + final Map map = new HashMap(); final List resultList = new ArrayList(); final boolean keepgoing = wfKeepgoing; - boolean itemsSuccess=false; - boolean sawException=false; + boolean itemsSuccess = false; + boolean sawException = false; try { final StepExecutionContext context = new ExecutionContextImpl.Builder() @@ -206,10 +194,6 @@ void assertExecWFItems(final List items, .build(); itemsSuccess = strategy.executeWorkflowItemsForNodeSet(context, map, resultList, items, keepgoing); assertFalse(expectStepException); - } catch (WorkflowStepFailureException e) { - e.printStackTrace(); - assertTrue("Unexpected step exception: " + e.getMessage(), expectStepException); - sawException = true; } catch (NodeFileParserException e) { e.printStackTrace(); fail(); @@ -219,402 +203,401 @@ void assertExecWFItems(final List items, assertEquals(expectedSuccess, itemsSuccess); assert expected.size() == strategy.getInputs().size(); - int i=0; + int i = 0; for (final Map expectedMap : expected) { final Map map1 = strategy.getInputs().get(i); - assertEquals("ExpectedMap index " + i + " value c",expectedMap.get("c"), map1.get("c")); - assertEquals("ExpectedMap index " + i + " value cmd",expectedMap.get("cmd"), map1.get("cmd")); - assertEquals("ExpectedMap index "+i+" value keepgoing",expectedMap.get("keepgoing"), map1.get("keepgoing")); + assertEquals("ExpectedMap index " + i + " value c", expectedMap.get("c"), map1.get("c")); + assertEquals("ExpectedMap index " + i + " value cmd", expectedMap.get("cmd"), map1.get("cmd")); i++; } } + private List mkTestItems(StepExecutionItem... item) { return Arrays.asList(item); } - public void testExecuteWorkflowItemsForNodeSet() throws Exception { - { - //test success 1 item + public void testExecuteWorkflowItemsForNodeSetSuccessful() throws Exception { + //test success 1 item - final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); - testCmd1.type = "test1"; - StepExecutor object = new StepExecutor() { - @Override - public boolean isNodeDispatchStep(StepExecutionItem item) { - return true; - } + final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); + testCmd1.type = "test1"; + StepExecutor object = new StepExecutor() { + @Override + public boolean isNodeDispatchStep(StepExecutionItem item) { + return true; + } - @Override - public StepExecutionResult executeWorkflowStep(StepExecutionContext executionContext, - StepExecutionItem item) - throws StepException { - assertEquals(1, executionContext.getStepNumber()); - return null; - } - }; - getFrameworkInstance().getStepExecutionService().registerInstance("test1", object); - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); + @Override + public StepExecutionResult executeWorkflowStep(StepExecutionContext executionContext, + StepExecutionItem item) { + assertEquals(1, executionContext.getStepNumber()); + return null; + } + }; + getFrameworkInstance().getStepExecutionService().registerInstance("test1", object); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1), + true, + Arrays.asList((Object) true), false + ); - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1), - true, - Arrays.asList((Object) true), false - ); + } - } + public void testExecuteWorkflowItemsForNodeSetFailureNoKeepgoing() throws Exception { - { + //test failure 1 item no keepgoing - //test failure 1 item no keepgoing + final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); - final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1), + false, + Arrays.asList((Object) false), false + ); + } - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1), - false, - Arrays.asList((Object) false), false - ); - } + public void testExecuteWorkflowItemsForNodeSetFailureNoKeepgoing2() throws Exception { - { - //test failure 1 exception no keepgoing + //test failure 1 exception no keepgoing - final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); + final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1), - false, - Arrays.asList((Object) "Failure"), - true - ); + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1), + false, + Arrays.asList((Object) "Failure"), + false + ); - } - { - //test failure 1 exception yes keepgoing + } + public void testExecuteWorkflowItemsForNodeSetFailureKeepgoing() throws Exception { - final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); + //test failure 1 exception yes keepgoing - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", true); - assertExecWFItems( - mkTestItems(testCmd1), - true, - Arrays.asList(expectResult1), - false, - Arrays.asList((Object) "Failure"), false - ); + final testWorkflowCmdItem testCmd1 = new testWorkflowCmdItem(); + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", true); + + assertExecWFItems( + mkTestItems(testCmd1), + true, + Arrays.asList(expectResult1), + false, + Arrays.asList((Object) "Failure"), false + ); - } } - public void testExecuteWorkflowItemsForNodeSetFailureHandler() throws Exception { - { - //test success 1 item, no failure handler + public void testExecuteWorkflowItemsForNodeSetFailureHandlerSuccessNoHandler() throws Exception { + //test success 1 item, no failure handler - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1), - true, - Arrays.asList((Object) true), false - ); + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1), + true, + Arrays.asList((Object) true), false + ); - } + } + public void testExecuteWorkflowItemsForNodeSetFailureHandlerFailureNoKeepgoingWithHandler() throws Exception { - { - //test failure 1 item no keepgoing, with failure handler (keepgoing=false) + //test failure 1 item no keepgoing, with failure handler (keepgoing=false) - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1,expectResult2), - false, - Arrays.asList((Object) false,true),//item1 fails, handler succeeds - false - ); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1, expectResult2), + false, + Arrays.asList((Object) false, true),//item1 fails, handler succeeds + false + ); - } - { - //test failure 1 item no keepgoing throw exception, with failure handler (keepgoing=false) + } + + public void testExecuteWorkflowItemsForNodeSetFailureHandlerFailureNoKeepgoingWithHandler2() throws Exception { + //test failure 1 item no keepgoing throw exception, with failure handler (keepgoing=false) - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; - assertExecWFItems( - mkTestItems(testCmd1), - false, - Arrays.asList(expectResult1, expectResult2), - false, - Arrays.asList((Object) "Failure", true),//item1 fails, handler succeeds - true - ); + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1), + false, + Arrays.asList(expectResult1, expectResult2), + false, + Arrays.asList((Object) "Failure", true),//item1 fails, handler succeeds + false + ); - } } + public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoing() throws Exception { - { - //test failure 1 item yes keepgoing, with failure handler (keepgoing=false) - - - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", true); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - - assertExecWFItems( - mkTestItems(testCmd1), - true, - Arrays.asList(expectResult1, expectResult2), - true, - Arrays.asList((Object) false, true),//item1 fails, handler succeeds - false - ); - - } - - { - //test failure 1 item yes keepgoing throw exception, with failure handler (keepgoing=false) - - - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", true); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - - assertExecWFItems( - mkTestItems(testCmd1), - true, - Arrays.asList(expectResult1, expectResult2), - true, - Arrays.asList((Object) "Failure", true),//item1 fails, handler succeeds - false - ); - - } - - { - //test failure 2 items yes keepgoing throw exception, with failure handler (keepgoing=false) - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", true); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - final Map expectResult3 = new HashMap(); - expectResult3.put("c", 2); - expectResult3.put("cmd", testCmd2); - expectResult3.put("keepgoing", true); - - assertExecWFItems( - mkTestItems(testCmd1, testCmd2), - true, - Arrays.asList(expectResult1, expectResult2, expectResult3), - true, - Arrays.asList((Object) "Failure",true, true),//item1 fails, handler succeeds, item2 succeeds - false - ); - - } - { - //test failure 2 items yes keepgoing throw exception, with failure handler (keepgoing=false) - - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = false; - final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", true); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - final Map expectResult3 = new HashMap(); - expectResult3.put("c", 2); - expectResult3.put("cmd", testCmd2); - expectResult3.put("keepgoing", true); - - assertExecWFItems( - mkTestItems(testCmd1, testCmd2), - true, - Arrays.asList(expectResult1, expectResult2, expectResult3), - false, - Arrays.asList((Object) "Failure", true, false),//item1 fails, handler succeeds, item2 fails - false - ); + //test failure 1 item yes keepgoing, with failure handler (keepgoing=false) + + + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", true); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1), + true, + Arrays.asList(expectResult1, expectResult2), + true, + Arrays.asList((Object) false, true),//item1 fails, handler succeeds + false + ); + + } + + public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoing2() throws Exception { + //test failure 1 item yes keepgoing throw exception, with failure handler (keepgoing=false) + + + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", true); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1), + true, + Arrays.asList(expectResult1, expectResult2), + true, + Arrays.asList((Object) "Failure", true),//item1 fails, handler succeeds + false + ); + + } + + public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoing3() throws Exception { + //test failure 2 items yes keepgoing throw exception, with failure handler (keepgoing=false) + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; + final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", true); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + final Map expectResult3 = new HashMap(); + expectResult3.put("c", 2); + expectResult3.put("cmd", testCmd2); + expectResult3.put("keepgoing", true); + + assertExecWFItems( + mkTestItems(testCmd1, testCmd2), + true, + Arrays.asList(expectResult1, expectResult2, expectResult3), + true, + Arrays.asList((Object) "Failure", true, true),//item1 fails, handler succeeds, item2 succeeds + false + ); + + } + + public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoing4() throws Exception { + //test failure 2 items yes keepgoing throw exception, with failure handler (keepgoing=false) + + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = false; + final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", true); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + final Map expectResult3 = new HashMap(); + expectResult3.put("c", 2); + expectResult3.put("cmd", testCmd2); + expectResult3.put("keepgoing", true); + + assertExecWFItems( + mkTestItems(testCmd1, testCmd2), + true, + Arrays.asList(expectResult1, expectResult2, expectResult3), + false, + Arrays.asList((Object) "Failure", true, false),//item1 fails, handler succeeds, item2 fails + false + ); - } } public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoingOnSuccess() throws Exception { - { - //test failure 1 item no keepgoing, with failure handler (keepgoing=true) - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = true; - final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - final Map expectResult3 = new HashMap(); - expectResult3.put("c", 2); - expectResult3.put("cmd", testCmd2); - expectResult3.put("keepgoing", false); - - assertExecWFItems( - mkTestItems(testCmd1, testCmd2), - false, - Arrays.asList(expectResult1, expectResult2, expectResult3), - true, - Arrays.asList((Object) false, true, true),//item1 fails, handler succeeds, item2 succeeds - false - ); - - } - { - //test failure 1 item no keepgoing, with failure handler (keepgoing=true) fails - final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); - testCmd1.failureHandler = null; - final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); - testCmd1.failureHandler = testCmdHandler1; - testCmdHandler1.keepgoingOnSuccess = true; - final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); - - final Map expectResult1 = new HashMap(); - expectResult1.put("c", 1); - expectResult1.put("cmd", testCmd1); - expectResult1.put("keepgoing", false); - final Map expectResult2 = new HashMap(); - expectResult2.put("c", 1); - expectResult2.put("cmd", testCmdHandler1); - expectResult2.put("keepgoing", false); - - assertExecWFItems( - mkTestItems(testCmd1, testCmd2), - false, - Arrays.asList(expectResult1, expectResult2), - false, - Arrays.asList((Object) false, false, "should not be executed"),//item1 fails, handler fails, item2 succeeds - false - ); + //test failure 1 item no keepgoing, with failure handler (keepgoing=true) + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = true; + final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + final Map expectResult3 = new HashMap(); + expectResult3.put("c", 2); + expectResult3.put("cmd", testCmd2); + expectResult3.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1, testCmd2), + false, + Arrays.asList(expectResult1, expectResult2, expectResult3), + true, + Arrays.asList((Object) false, true, true),//item1 fails, handler succeeds, item2 succeeds + false + ); + + } + + public void testExecuteWorkflowItemsForNodeSetFailureHandlerKeepgoingOnSuccessHandlerFails() throws Exception { + //test failure 1 item no keepgoing, with failure handler (keepgoing=true) fails + final testHandlerWorkflowCmdItem testCmd1 = new testHandlerWorkflowCmdItem(); + testCmd1.failureHandler = null; + final testWorkflowCmdItem testCmdHandler1 = new testWorkflowCmdItem(); + testCmd1.failureHandler = testCmdHandler1; + testCmdHandler1.keepgoingOnSuccess = true; + final testWorkflowCmdItem testCmd2 = new testWorkflowCmdItem(); + + final Map expectResult1 = new HashMap(); + expectResult1.put("c", 1); + expectResult1.put("cmd", testCmd1); + expectResult1.put("keepgoing", false); + final Map expectResult2 = new HashMap(); + expectResult2.put("c", 1); + expectResult2.put("cmd", testCmdHandler1); + expectResult2.put("keepgoing", false); + + assertExecWFItems( + mkTestItems(testCmd1, testCmd2), + false, + Arrays.asList(expectResult1, expectResult2), + false, + Arrays.asList((Object) false, false, "should not be executed"),//item1 fails, handler fails, item2 succeeds + false + ); - } } - static class testWorkflowCmdItem implements NodeStepExecutionItem,HandlerExecutionItem { + static class testWorkflowCmdItem implements NodeStepExecutionItem, HandlerExecutionItem { private String type; private String nodeStepType; int flag = -1; @@ -644,7 +627,7 @@ public String getNodeStepType() { } } - static class testHandlerWorkflowCmdItem implements StepExecutionItem,HasFailureHandler { + static class testHandlerWorkflowCmdItem implements StepExecutionItem, HasFailureHandler { private String type; private StepExecutionItem failureHandler; int flag = -1; @@ -676,13 +659,13 @@ static class testInterpreter implements NodeStepExecutor { boolean shouldThrowException = false; public NodeStepResult executeNodeStep(StepExecutionContext executionContext, - NodeStepExecutionItem executionItem, INodeEntry iNodeEntry) throws - NodeStepException { + NodeStepExecutionItem executionItem, INodeEntry iNodeEntry) throws + NodeStepException { executionItemList.add(executionItem); executionContextList.add(executionContext); nodeEntryList.add(iNodeEntry); if (shouldThrowException) { - throw new NodeStepException("testInterpreter test exception", iNodeEntry.getNodename()); + throw new NodeStepException("testInterpreter test exception", null, iNodeEntry.getNodename()); } System.out.println("return index: (" + index + ") in size: " + resultList.size()); return resultList.get(index++); diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestNodeFirstWorkflowStrategy.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestNodeFirstWorkflowStrategy.java index d96d5a68753..a0df9a68629 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestNodeFirstWorkflowStrategy.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestNodeFirstWorkflowStrategy.java @@ -27,15 +27,20 @@ import com.dtolabs.rundeck.core.common.FrameworkProject; import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.common.SelectorUtils; -import com.dtolabs.rundeck.core.execution.*; +import com.dtolabs.rundeck.core.execution.ExecutionContext; +import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; +import com.dtolabs.rundeck.core.execution.ExecutionListenerOverride; +import com.dtolabs.rundeck.core.execution.FailedNodesListener; +import com.dtolabs.rundeck.core.execution.StatusResult; +import com.dtolabs.rundeck.core.execution.StepExecutionItem; +import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; +import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionItem; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutionService; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; -import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; -import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; -import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResultImpl; import com.dtolabs.rundeck.core.tools.AbstractBaseTest; import com.dtolabs.rundeck.core.utils.FileUtils; @@ -216,36 +221,12 @@ public NodeStepResult executeNodeStep(StepExecutionContext executionContext, executionContextList.add(executionContext); nodeEntryList.add(iNodeEntry); if (shouldThrowException) { - throw new NodeStepException("testInterpreter test exception", iNodeEntry.getNodename()); + throw new NodeStepException("testInterpreter test exception", null,iNodeEntry.getNodename()); } return resultList.get(index++); } } - static class testResult implements NodeStepResult { - boolean success; - int flag; - INodeEntry node; - Exception exception; - - testResult(boolean success, int flag) { - this.success = success; - this.flag = flag; - } - - public boolean isSuccess() { - return success; - } - - public INodeEntry getNode() { - return node; - } - - public Exception getException() { - return exception; - } - } - public void testMultipleNodes() throws Exception{ { @@ -282,9 +263,9 @@ public void testMultipleNodes() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result node 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -371,11 +352,11 @@ public void testMultipleNodesExtFile() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result node 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 3 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -574,7 +555,7 @@ private void assertRankedNodeResult(final ArrayList expected, final Bool for (final String s : expected) { //set resturn result - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); } final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -637,13 +618,13 @@ public void testMultipleItemsAndNodes() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result node 1 step 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 step 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 1 step 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 step 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestStepFirstWorkflowStrategy.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestStepFirstWorkflowStrategy.java index 5cc4ce81256..4ccdf30146a 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestStepFirstWorkflowStrategy.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/TestStepFirstWorkflowStrategy.java @@ -28,6 +28,7 @@ import com.dtolabs.rundeck.core.execution.dispatch.Dispatchable; import com.dtolabs.rundeck.core.execution.dispatch.DispatcherResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.NodeDispatchStepExecutor; import com.dtolabs.rundeck.core.execution.workflow.steps.StepExecutionResult; import com.dtolabs.rundeck.core.execution.workflow.steps.node.*; @@ -211,18 +212,21 @@ public NodeStepResult executeNodeStep(StepExecutionContext executionContext, executionContextList.add(executionContext); nodeEntryList.add(iNodeEntry); if (shouldThrowException) { - throw new NodeStepException("testInterpreter test exception",iNodeEntry.getNodename()); + throw new NodeStepException("testInterpreter test exception",null,iNodeEntry.getNodename()); } System.out.println("return index: (" + index + ") in size: " + resultList.size()); return resultList.get(index++); } } - static class testResult implements NodeStepResult { + static enum Reason implements FailureReason{ + Test + } + static class testResult extends NodeStepResultImpl { boolean success; int flag; INodeEntry node; - testResult(boolean success, int flag) { + super(null,success?null: TestStepFirstWorkflowStrategy.Reason.Test,success?null:"test failure",null); this.success = success; this.flag = flag; } @@ -338,13 +342,9 @@ public void testExecuteWorkflow() throws Exception { assertEquals(0, interpreterMock.executionItemList.size()); assertNotNull("threw exception: " + result.getException(), result.getException()); assertTrue("threw exception: " + result.getException(), - result.getException() instanceof WorkflowStepFailureException); - StatusResult result1 = ((WorkflowStepFailureException) result.getException()).getStatusResult(); -// assertNotNull("should not be null: " + result.getException(), result1); -// assertTrue("not right type:" + result1, result1 instanceof ExceptionStatusResult); -// ExceptionStatusResult eresult = (ExceptionStatusResult) result1; + result.getException() instanceof NullPointerException); assertEquals("threw exception: " + result.getException(), - "Step 1 of the workflow threw an exception: Failed dispatching to node test1: provider name was null for Service: WorkflowNodeStep", + "provider name was null for Service: WorkflowNodeStep", result.getException().getMessage()); } @@ -386,7 +386,7 @@ public String getScript() { // interpreterService.registerInstance(JobExecutionItem.COMMAND_TYPE, failMock); //set resturn result - interpreterMock.resultList.add(new NodeStepResultImpl(true,null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -454,7 +454,7 @@ public String[] getCommand() { // interpreterService.registerInstance(JobExecutionItem.COMMAND_TYPE, failMock); //set resturn result - interpreterMock.resultList.add(new NodeStepResultImpl(true,null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -706,16 +706,9 @@ public String[] getArgs() { result.getException().printStackTrace(System.out); } assertFalse(result.isSuccess()); - assertNotNull("threw exception: " + result.getException(), result.getException()); - assertTrue("threw exception: " + result.getException(), result.getException() instanceof WorkflowStepFailureException); - WorkflowStepFailureException wfsfe = (WorkflowStepFailureException) result.getException(); - assertEquals(2, wfsfe.getWorkflowStep()); - assertNotNull(wfsfe.getStatusResult()); - //thrown after NodeDispatchStepExecutor returns false, extract DispatcherResult from StepExecutionResult - StatusResult result1 = wfsfe.getStatusResult(); - assertTrue("wrong type: " + result1.getClass(), result1 instanceof StepExecutionResult); - final DispatcherResult executionResult - = NodeDispatchStepExecutor.extractDispatcherResult((StepExecutionResult) result1); + assertNull("threw exception: " + result.getException(), result.getException()); + StepExecutionResult result1 = result.getResultSet().get(1); + final DispatcherResult executionResult = NodeDispatchStepExecutor.extractDispatcherResult(result1); assertNotNull(executionResult.getResults()); assertEquals(1, executionResult.getResults().size()); assertNotNull(executionResult.getResults().get(testnode)); @@ -863,9 +856,7 @@ public String[] getArgs() { result.getException().printStackTrace(System.err); } assertFalse(result.isSuccess()); - assertNotNull("threw exception: " + result.getException(), result.getException()); - assertTrue("threw exception: " + result.getException(), result.getException() instanceof WorkflowFailureException); - WorkflowFailureException wfsfe = (WorkflowFailureException) result.getException(); + assertNull("threw exception: " + result.getException(), result.getException()); assertNotNull(result.getResultSet()); final List test1 = result.getResultSet(); @@ -1026,14 +1017,10 @@ public String[] getArgs() { result.getException().printStackTrace(System.err); } assertFalse(result.isSuccess()); - assertNotNull("threw exception: " + result.getException(), result.getException()); - assertTrue("threw exception: " + result.getException(), - result.getException() instanceof WorkflowStepFailureException); - WorkflowStepFailureException wfsfe = (WorkflowStepFailureException) result.getException(); - assertEquals(1, wfsfe.getWorkflowStep()); - assertNotNull(wfsfe.getStatusResult()); + assertNull("threw exception: " + result.getException(), result.getException()); + StepExecutionResult result1 = result.getResultSet().get(0); final DispatcherResult executionResult - = NodeDispatchStepExecutor.extractDispatcherResult(wfsfe.getStatusResult()); + = NodeDispatchStepExecutor.extractDispatcherResult(result1); assertNotNull(executionResult.getResults()); assertEquals(1, executionResult.getResults().size()); assertNotNull(executionResult.getResults().get(testnode)); @@ -1213,10 +1200,7 @@ public String toString() { result.getException().printStackTrace(System.err); } assertFalse(result.isSuccess()); - assertNotNull("threw exception: " + result.getException(), result.getException()); - assertTrue("threw exception: " + result.getException(), - result.getException() instanceof WorkflowFailureException); - WorkflowFailureException wfsfe = (WorkflowFailureException) result.getException(); + assertNull("threw exception: " + result.getException(), result.getException()); assertNotNull(result.getResultSet()); final List test1 = result.getResultSet(); @@ -1567,7 +1551,7 @@ public void testGenericItem() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -1633,9 +1617,9 @@ public void testMultipleNodes() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result node 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); @@ -1726,13 +1710,13 @@ public void testMultipleItemsAndNodes() throws Exception{ interpreterService.registerInstance(WorkflowExecutionItem.COMMAND_TYPE_STEP_FIRST, failMock); //set resturn result node 1 step 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 step 1 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 1 step 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); //set resturn result node 2 step 2 - interpreterMock.resultList.add(new NodeStepResultImpl(true, null)); + interpreterMock.resultList.add(new NodeStepResultImpl(null)); final WorkflowExecutionResult result = strategy.executeWorkflow(context, executionItem); diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/TestNodeStepExecutorService.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/TestNodeStepExecutorService.java index 709159f3d13..40b8097dc9f 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/TestNodeStepExecutorService.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/TestNodeStepExecutorService.java @@ -207,7 +207,7 @@ public String getNodeStepType() { } }); fail("Should have thrown exception"); - } catch (IllegalArgumentException e) { + } catch (NullPointerException e) { assertNotNull(e); } } diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestExecNodeStepExecutor.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestExecNodeStepExecutor.java index 02e8a0e76d6..d728b67ff11 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestExecNodeStepExecutor.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestExecNodeStepExecutor.java @@ -30,7 +30,6 @@ import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; import com.dtolabs.rundeck.core.execution.ExecutionException; -import com.dtolabs.rundeck.core.execution.ExecutionListener; import com.dtolabs.rundeck.core.execution.service.NodeExecutor; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; @@ -39,12 +38,10 @@ import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; import com.dtolabs.rundeck.core.tools.AbstractBaseTest; import com.dtolabs.rundeck.core.utils.FileUtils; -import com.dtolabs.rundeck.core.utils.NodeSet; import java.io.File; import java.io.IOException; import java.util.Arrays; -import java.util.Map; /** * TestExecNodeStepExecutor is ... @@ -86,8 +83,7 @@ public static class testNodeExecutor implements NodeExecutor{ String[] testCommand; INodeEntry testNode; NodeExecutorResult testResult; - public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) throws - ExecutionException { + public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) { this.testContext=context; this.testCommand=command; this.testNode=node; @@ -129,7 +125,7 @@ public String[] getCommand() { assertTrue(Arrays.deepEquals(strings, testexec.testCommand)); assertEquals(test1, testexec.testNode); } - testexec.testResult= new NodeExecutorResultImpl(true, null, -2); + testexec.testResult= NodeExecutorResultImpl.createSuccess(test1); { final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -137,7 +133,8 @@ public String[] getCommand() { assertTrue(interpreterResult.isSuccess()); assertTrue(interpreterResult instanceof NodeExecutorResult); NodeExecutorResult result = (NodeExecutorResult) interpreterResult; - assertEquals(-2, result.getResultCode()); + assertEquals(0, result.getResultCode()); + assertEquals(test1, result.getNode()); // assertEquals(context, testexec.testContext); assertTrue(Arrays.deepEquals(strings, testexec.testCommand)); @@ -182,7 +179,7 @@ public String[] getCommand() { assertTrue(Arrays.deepEquals(strings, testexec.testCommand)); assertEquals(test1, testexec.testNode); } - testexec.testResult = new NodeExecutorResultImpl(true, null, -2); + testexec.testResult = NodeExecutorResultImpl.createSuccess(test1); { final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -190,7 +187,8 @@ public String[] getCommand() { assertTrue(interpreterResult.isSuccess()); assertTrue(interpreterResult instanceof NodeExecutorResult); NodeExecutorResult result = (NodeExecutorResult) interpreterResult; - assertEquals(-2, result.getResultCode()); + assertEquals(0, result.getResultCode()); + assertEquals(test1, result.getNode()); // assertEquals(context, testexec.testContext); assertTrue(Arrays.deepEquals(strings, testexec.testCommand)); diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptFileNodeStepExecutor.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptFileNodeStepExecutor.java index 279141722c8..cfca37f17d1 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptFileNodeStepExecutor.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptFileNodeStepExecutor.java @@ -29,21 +29,27 @@ import com.dtolabs.rundeck.core.common.NodeEntryImpl; import com.dtolabs.rundeck.core.execution.ExecutionContext; import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; -import com.dtolabs.rundeck.core.execution.ExecutionException; -import com.dtolabs.rundeck.core.execution.ExecutionListener; -import com.dtolabs.rundeck.core.execution.service.*; +import com.dtolabs.rundeck.core.execution.service.FileCopier; +import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.service.FileCopierService; +import com.dtolabs.rundeck.core.execution.service.NodeExecutor; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorService; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; import com.dtolabs.rundeck.core.tools.AbstractBaseTest; import com.dtolabs.rundeck.core.utils.FileUtils; -import com.dtolabs.rundeck.core.utils.NodeSet; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.List; /** * TestScriptFileNodeStepExecutor is ... @@ -78,7 +84,9 @@ public void tearDown() throws Exception { File projectdir = new File(getFrameworkProjectsBase(), PROJ_NAME); FileUtils.deleteDir(projectdir); } - + static enum TestReason implements FailureReason{ + Test + } public static class testFileCopier implements FileCopier { String testResult; ExecutionContext testContext; @@ -92,7 +100,7 @@ public String copyFileStream(ExecutionContext context, InputStream input, INodeE testNode = node; testInput = input; if(throwException) { - throw new FileCopierException("copyFileStream test"); + throw new FileCopierException("copyFileStream test", TestReason.Test); } return testResult; } @@ -104,7 +112,7 @@ public String copyFile(ExecutionContext context, File file, INodeEntry node) thr testNode = node; testFile = file; if (throwException) { - throw new FileCopierException("copyFile test"); + throw new FileCopierException("copyFile test", TestReason.Test); } return testResult; } @@ -118,7 +126,7 @@ public String copyScriptContent(ExecutionContext context, String script, INodeEn testScript = script; if (throwException) { - throw new FileCopierException("copyScriptContent test"); + throw new FileCopierException("copyScriptContent test", TestReason.Test); } return testResult; } @@ -131,8 +139,7 @@ public static class multiTestNodeExecutor implements NodeExecutor { List testResult = new ArrayList(); int index = 0; - public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) throws - ExecutionException { + public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) { this.testContext.add(context); this.testCommand.add(command); this.testNode.add(node); @@ -176,8 +183,8 @@ public String getScript() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult=nodeExecutorResults; testcopier.testResult="/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -248,8 +255,8 @@ public String[] getArgs() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult = nodeExecutorResults; testcopier.testResult = "/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -323,7 +330,9 @@ public String getScript() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(false, null, 1)); + nodeExecutorResults.add(NodeExecutorResultImpl.createFailure(NodeStepFailureReason.NonZeroResultCode, + "failed", + null)); testexec.testResult=nodeExecutorResults; testcopier.testResult = "/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -387,7 +396,7 @@ public String getScript() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult=nodeExecutorResults; testcopier.testResult = "/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -450,8 +459,8 @@ public String getServerScriptFilePath() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult = nodeExecutorResults; testcopier.testResult = "/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -524,8 +533,8 @@ public InputStream getScriptAsStream() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult = nodeExecutorResults; testcopier.testResult = "/test/file/path"; final NodeStepResult interpreterResult = interpret.executeNodeStep(context, command, test1); @@ -596,8 +605,8 @@ public InputStream getScriptAsStream() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult = nodeExecutorResults; //set filecopier to throw exception diff --git a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptURLNodeStepExecutor.java b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptURLNodeStepExecutor.java index 0d9061838b6..fcee27d7dda 100644 --- a/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptURLNodeStepExecutor.java +++ b/core/src/test/java/com/dtolabs/rundeck/core/execution/workflow/steps/node/impl/TestScriptURLNodeStepExecutor.java @@ -33,13 +33,18 @@ import com.dtolabs.rundeck.core.execution.ExecutionContextImpl; import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.StepExecutionItem; -import com.dtolabs.rundeck.core.execution.ExecutionListener; -import com.dtolabs.rundeck.core.execution.service.*; +import com.dtolabs.rundeck.core.execution.service.FileCopier; +import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.service.FileCopierService; +import com.dtolabs.rundeck.core.execution.service.NodeExecutor; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; +import com.dtolabs.rundeck.core.execution.service.NodeExecutorService; import com.dtolabs.rundeck.core.execution.workflow.StepExecutionContext; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepResult; import com.dtolabs.rundeck.core.tools.AbstractBaseTest; import com.dtolabs.rundeck.core.utils.FileUtils; -import com.dtolabs.rundeck.core.utils.NodeSet; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; @@ -48,7 +53,10 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * TestScriptURLNodeStepExecutor is ... @@ -88,6 +96,9 @@ public void tearDown() throws Exception { public void testInterpretCommand() throws Exception { } + static enum TestReason implements FailureReason{ + Test + } public static class testFileCopier implements FileCopier { String testResult; @@ -102,7 +113,7 @@ public String copyFileStream(ExecutionContext context, InputStream input, INodeE testNode = node; testInput = input; if (throwException) { - throw new FileCopierException("copyFileStream test"); + throw new FileCopierException("copyFileStream test",TestReason.Test); } return testResult; } @@ -114,7 +125,7 @@ public String copyFile(ExecutionContext context, File file, INodeEntry node) thr testNode = node; testFile = file; if (throwException) { - throw new FileCopierException("copyFile test"); + throw new FileCopierException("copyFile test", TestReason.Test); } return testResult; } @@ -128,7 +139,7 @@ public String copyScriptContent(ExecutionContext context, String script, INodeEn testScript = script; if (throwException) { - throw new FileCopierException("copyScriptContent test"); + throw new FileCopierException("copyScriptContent test", TestReason.Test); } return testResult; } @@ -141,8 +152,7 @@ public static class multiTestNodeExecutor implements NodeExecutor { List testResult = new ArrayList(); int index = 0; - public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) throws - ExecutionException { + public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) { this.testContext.add(context); this.testCommand.add(command); this.testNode.add(node); @@ -249,8 +259,8 @@ public boolean isKeepgoingOnSuccess() { }; { final ArrayList nodeExecutorResults = new ArrayList(); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 1)); - nodeExecutorResults.add(new NodeExecutorResultImpl(true, null, 2)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); + nodeExecutorResults.add(NodeExecutorResultImpl.createSuccess(null)); testexec.testResult = nodeExecutorResults; testcopier.testResult = "/test/file/path"; final test1 interaction = new TestScriptURLNodeStepExecutor.test1(); diff --git a/docs/en/manual/05-job-workflows.md b/docs/en/manual/05-job-workflows.md index 598b596af21..f48793d49bc 100644 --- a/docs/en/manual/05-job-workflows.md +++ b/docs/en/manual/05-job-workflows.md @@ -262,6 +262,13 @@ return success.) It is a good practice, when you are defining Error Handlers, to **always** have them fail (e.g. scripts/commands return a non-zero exit-code), unless you specifically want them to be used for Recovery. +### Context information + +When the Error-handler step is executed, its execution context will contain some information about the nature +of the failure that occurred for the original step. + +See the section on [Context Variables](#context-variables) for more information. + ## Save the changes Once the Workflow steps have been defined and order, changes are @@ -291,6 +298,26 @@ Node context variables: * `node.os-*`: OS properties of the Node: `name`,`version`,`arch`,`family` * `node.*`: All Node attributes defined on the Node. +Additional Error-handler context variables: + +* `result.reason`: A code indicating the reason the step failed + * Common reason code strings used by node execution of commands or scripts: + * `NonZeroResultCode` - the execution returned a non-zero code + * `SSHProtocolFailure` - SSH protocol failure + * `HostNotFound` - host not found + * `ConnectionTimeout` - connection timeout + * `ConnectionFailure` - connection failure (e.g. refused) + * `IOFailure` - IO error + * `AuthenticationFailure` - authentication was refused or incorrect + * Reason code strings used by Job references + * `JobFailed` - referenced Job workflow failed + * `NotFound` - referenced Job not found + * `Unauthorized` - referenced Job not authorized + * `InvalidOptions` - referenced Job input options invalid + * `NoMatchedNodes` - referenced Job node dispatch filters had no match +* `result.message`: A string describing the failure +* `result.resultCode`: Exit code from an execution (if available) + Option context variables are referred to as `option.NAME` (more about [Job Options](job-options.html) in the next chapter.) ### Context Variable Usage diff --git a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleNodeStepPlugin.java b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleNodeStepPlugin.java index 110e0eddb83..183e863fab5 100644 --- a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleNodeStepPlugin.java +++ b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleNodeStepPlugin.java @@ -25,6 +25,8 @@ package com.dtolabs.rundeck.plugin.example; import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; import com.dtolabs.rundeck.core.plugins.Plugin; import com.dtolabs.rundeck.plugins.ServiceNameConstants; import com.dtolabs.rundeck.plugins.step.NodeStepPlugin; @@ -108,6 +110,12 @@ public void buildWith(DescriptionBuilder builder) { .build() ); } + /** + * This enum lists the known reasons this plugin might fail + */ + static enum Reason implements FailureReason{ + PancakeReason + } /** * The {@link #performNodeStep(com.dtolabs.rundeck.plugins.step.PluginStepContext, @@ -116,16 +124,19 @@ public void buildWith(DescriptionBuilder builder) { * about the step number and context. *

    * The {@link INodeEntry} parameter is the node that should be executed on. Your plugin should make use of the - * node's attributes (such has "hostname" or any others required by your plugin) to perform the appropriate - * action. + * node's attributes (such has "hostname" or any others required by your plugin) to perform the appropriate action. */ - @Override - public boolean executeNodeStep(final PluginStepContext context, final Map configuration, final INodeEntry entry) { + public void executeNodeStep(final PluginStepContext context, + final Map configuration, + final INodeEntry entry) throws NodeStepException { - System.out.println("Stub node step executing on node: " + entry.getNodename()); - System.out.println("Stub step extra config: " + configuration); - System.out.println("Stub step num: " + context.getStepNumber()); - System.out.println("Stub step context: " + context.getStepContext()); - return true; + System.out.println("Example node step executing on node: " + entry.getNodename()); + System.out.println("Example step extra config: " + configuration); + System.out.println("Example step num: " + context.getStepNumber()); + System.out.println("Example step context: " + context.getStepContext()); + if ("true".equals(configuration.get("pancake"))) { + //throw exception indicating the cause of the error + throw new NodeStepException("pancake was true", Reason.PancakeReason, entry.getNodename()); + } } } diff --git a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleRemoteScriptNodeStepPlugin.java b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleRemoteScriptNodeStepPlugin.java index b8867db4d31..6c7af5aa27b 100644 --- a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleRemoteScriptNodeStepPlugin.java +++ b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleRemoteScriptNodeStepPlugin.java @@ -135,7 +135,6 @@ public class ExampleRemoteScriptNodeStepPlugin implements RemoteScriptNodeStepPl * Rundeck. All fields which were annotated as properties can be modified/removed. In this example, the Plugin * description is changed, and the "money" field property is altered to define a custom validator for the field. */ - @Override public void buildWith(final DescriptionBuilder builder) { //override the annotated description of this plugin builder.title("Example Remote Script Node Step"); @@ -160,7 +159,6 @@ public void buildWith(final DescriptionBuilder builder) { * set a custom validator for the property */ .validator(new PropertyValidator() { - @Override public boolean isValid(String s) throws ValidationException { try { final int i = Integer.parseInt(s); @@ -195,7 +193,6 @@ public boolean isValid(String s) throws ValidationException { *

    * The {@link GeneratedScriptBuilder} provides a factory for returning the correct type. */ - @Override public GeneratedScript generateScript(final PluginStepContext context, final Map configuration, final INodeEntry entry) { diff --git a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleStepPlugin.java b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleStepPlugin.java index 6feb043463b..91c5fa1cf1b 100644 --- a/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleStepPlugin.java +++ b/examples/example-java-step-plugin/src/main/java/com/dtolabs/rundeck/plugin/example/ExampleStepPlugin.java @@ -24,6 +24,8 @@ */ package com.dtolabs.rundeck.plugin.example; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepException; import com.dtolabs.rundeck.core.plugins.Plugin; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; @@ -65,7 +67,6 @@ public class ExampleStepPlugin implements StepPlugin, Describable { * using annotations such that the property values will be automatically bound to the plugin class instance * fields */ - @Override public Description getDescription() { return DescriptionBuilder.builder() .name(SERVICE_PROVIDER_NAME) @@ -96,9 +97,9 @@ public Description getDescription() { .build() ) .property(PropertyBuilder.builder() - .integer("mant") - .title("Mant") - .description("How manty?") + .integer("many") + .title("Many") + .description("How many?") .required(false) .defaultValue("2") .build() @@ -122,18 +123,27 @@ public Description getDescription() { .build(); } + /** + * This enum lists the known reasons this plugin might fail + */ + static enum Reason implements FailureReason{ + ExampleReason + } + /** * Here is the meat of the plugin implementation, which should perform the appropriate logic for your plugin. *

    * The {@link PluginStepContext} provides access to the appropriate Nodes, the configuration of the plugin, and * details about the step number and context. */ - @Override - public boolean executeStep(final PluginStepContext context, final Map configuration) { - System.out.println("Stub step executing on nodes: " + context.getNodes().getNodeNames()); - System.out.println("Stub step configuration: " + configuration); - System.out.println("Stub step num: " + context.getStepNumber()); - System.out.println("Stub step context: " + context.getStepContext()); - return true; + public void executeStep(final PluginStepContext context, final Map configuration) throws + StepException{ + System.out.println("Example step executing on nodes: " + context.getNodes().getNodeNames()); + System.out.println("Example step configuration: " + configuration); + System.out.println("Example step num: " + context.getStepNumber()); + System.out.println("Example step context: " + context.getStepContext()); + if ("true".equals(configuration.get("lampkin"))) { + throw new StepException("lampkin was true", Reason.ExampleReason); + } } } diff --git a/gradle.properties b/gradle.properties index c7417588b8d..e2328b90046 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,6 @@ # Rundeck Build Properties ##################################################################### -group = com.dtolabs.rundeck +group = org.rundeck currentVersion = 1.5 grailsVersion = 1.3.7 diff --git a/plugins/build.gradle b/plugins/build.gradle index b37309d6654..e7363226b0b 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -2,9 +2,6 @@ * The Rundeck plugins parent project */ -description = "The plug-ins project defines defaults for it's children" - -apply plugin: 'eclipse' eclipse.project.name = "${project.getParent().eclipse.project.name}:plugins"; @@ -50,11 +47,12 @@ task createPom << { modules{ module 'stub-plugin' module 'script-plugin' + module 'localexec-plugin' } dependencies{ dependency{ artifactId 'rundeck-core' - groupId 'com.dtolabs.rundeck' + groupId 'org.rundeck' version version } } diff --git a/plugins/localexec-plugin/build.gradle b/plugins/localexec-plugin/build.gradle new file mode 100644 index 00000000000..18bcdc8cf15 --- /dev/null +++ b/plugins/localexec-plugin/build.gradle @@ -0,0 +1,54 @@ +ext.pluginClassNames='com.dtolabs.rundeck.plugin.localexec.LocalExecNodeStepPlugin' +jar { + manifest { + attributes 'Rundeck-Plugin-Classnames': pluginClassNames + } +} + +apply plugin: 'idea' +apply plugin: 'maven' + +task createPom << { + pom { + project { + artifactId 'rundeck-localexec-plugin' + groupId group + inceptionYear '2011' + packaging 'jar' + version version + name "RunDeck LocalExec Node Step Plugin" + url 'http://rundeck.org' + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + parent{ + groupId project.group + artifactId "rundeck-bundled-plugins" + version(version) + } + build{ + plugins{ + plugin{ + groupId 'org.apache.maven.plugins' + artifactId 'maven-jar-plugin' + version '2.3.2' + configuration{ + archive{ + manifestEntries{ + 'Rundeck-Plugin-Classnames'(pluginClassNames) + 'Rundeck-Plugin-Version'(rundeckPluginVersion) + 'Rundeck-Plugin-Archive'('true') + 'Rundeck-Plugin-File-Version'(version) + } + } + } + } + } + } + } + }.writeTo("pom.xml") +} diff --git a/plugins/localexec-plugin/pom.xml b/plugins/localexec-plugin/pom.xml new file mode 100644 index 00000000000..229572a3f5c --- /dev/null +++ b/plugins/localexec-plugin/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + rundeck-bundled-plugins + org.rundeck + 1.5-dev + + org.rundeck + rundeck-localexec-plugin + 1.5-dev + RunDeck LocalExec Node Step Plugin + http://rundeck.org + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + maven-jar-plugin + 2.3.2 + + + + com.dtolabs.rundeck.plugin.localexec.LocalExecNodeStepPlugin + 1.1 + true + 1.5-dev + + + + + + + diff --git a/plugins/localexec-plugin/src/main/java/com/dtolabs/rundeck/plugin/localexec/LocalExecNodeStepPlugin.java b/plugins/localexec-plugin/src/main/java/com/dtolabs/rundeck/plugin/localexec/LocalExecNodeStepPlugin.java new file mode 100644 index 00000000000..99f384a61d3 --- /dev/null +++ b/plugins/localexec-plugin/src/main/java/com/dtolabs/rundeck/plugin/localexec/LocalExecNodeStepPlugin.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012 DTO Labs, Inc. (http://dtolabs.com) + * + * Licensed 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. + * + */ + +/* +* LocalExecNodeStepPlugin.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 12/13/12 4:54 PM +* +*/ +package com.dtolabs.rundeck.plugin.localexec; + +import com.dtolabs.rundeck.core.common.INodeEntry; +import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; +import com.dtolabs.rundeck.core.plugins.Plugin; +import com.dtolabs.rundeck.core.utils.ScriptExecUtil; +import com.dtolabs.rundeck.plugins.ServiceNameConstants; +import com.dtolabs.rundeck.plugins.descriptions.PluginDescription; +import com.dtolabs.rundeck.plugins.descriptions.PluginProperty; +import com.dtolabs.rundeck.plugins.step.NodeStepPlugin; +import com.dtolabs.rundeck.plugins.step.PluginStepContext; + +import java.io.IOException; +import java.util.Map; + + +/** + * LocalExecNodeStepPlugin is ... + * + * @author Greg Schueler greg@dtosolutions.com + */ +@Plugin(service = ServiceNameConstants.WorkflowNodeStep, name = LocalExecNodeStepPlugin.PROVIDER_NAME) +@PluginDescription(title = "Local Command", description = "Run a command locally on the server") +public class LocalExecNodeStepPlugin implements NodeStepPlugin { + public static final String PROVIDER_NAME = "localexec"; + + @PluginProperty(title = "Command", description = "The command (runs locally)", required = true) + private String command; + + @Override + public void executeNodeStep(PluginStepContext context, Map map, INodeEntry entry) + throws NodeStepException { + if(null==command || "".equals(command.trim())) { + throw new NodeStepException("Command is not set", + StepFailureReason.ConfigurationFailure, + entry.getNodename()); + } + String[] split = command.split(" "); + Map> nodeData = DataContextUtils.addContext("node", + DataContextUtils.nodeData(entry), + context.getDataContext()); + String[] finalCommand = DataContextUtils.replaceDataReferences(split, nodeData); + Map env = DataContextUtils.generateEnvVarsFromContext(nodeData); + final int result; + try { + result = ScriptExecUtil.runLocalCommand(finalCommand, env, null, System.out, System.err); + if(result!=0) { + throw new NodeStepException("Result code was " + result, + NodeStepFailureReason.NonZeroResultCode, + entry.getNodename()); + } + } catch (IOException e) { + throw new NodeStepException(e, StepFailureReason.IOFailure, entry.getNodename()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new NodeStepException(e, StepFailureReason.Interrupted, entry.getNodename()); + } + } +} diff --git a/plugins/pom.xml b/plugins/pom.xml index cc2168aaf17..06dc0da0f6a 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - com.dtolabs.rundeck + org.rundeck rundeck-bundled-plugins 1.5-dev pom @@ -24,7 +24,7 @@ - 1.0 + 1.1 true 1.5-dev @@ -44,10 +44,11 @@ stub-plugin script-plugin + localexec-plugin - com.dtolabs.rundeck + org.rundeck rundeck-core 1.5-dev diff --git a/plugins/script-plugin/build.gradle b/plugins/script-plugin/build.gradle index 5fbefb38099..be8b77c6a46 100644 --- a/plugins/script-plugin/build.gradle +++ b/plugins/script-plugin/build.gradle @@ -43,6 +43,9 @@ task createPom << { archive{ manifestEntries{ 'Rundeck-Plugin-Classnames'(pluginClassNames) + 'Rundeck-Plugin-Version'(rundeckPluginVersion) + 'Rundeck-Plugin-Archive'('true') + 'Rundeck-Plugin-File-Version'(version) } } } diff --git a/plugins/script-plugin/pom.xml b/plugins/script-plugin/pom.xml index 5779910a11a..da28f48c73e 100644 --- a/plugins/script-plugin/pom.xml +++ b/plugins/script-plugin/pom.xml @@ -4,10 +4,10 @@ 4.0.0 rundeck-bundled-plugins - com.dtolabs.rundeck + org.rundeck 1.5-dev - com.dtolabs.rundeck + org.rundeck rundeck-script-plugin 1.5-dev RunDeck Script Plugin @@ -29,6 +29,9 @@ com.dtolabs.rundeck.plugin.script.ScriptFileCopier,com.dtolabs.rundeck.plugin.script.ScriptNodeExecutor + 1.1 + true + 1.5-dev diff --git a/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptFileCopier.java b/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptFileCopier.java index 1ef74a395c1..d3f8e73d04c 100644 --- a/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptFileCopier.java +++ b/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptFileCopier.java @@ -31,15 +31,28 @@ import com.dtolabs.rundeck.core.execution.impl.common.BaseFileCopier; import com.dtolabs.rundeck.core.execution.service.FileCopier; import com.dtolabs.rundeck.core.execution.service.FileCopierException; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.Plugin; -import com.dtolabs.rundeck.core.plugins.configuration.*; +import com.dtolabs.rundeck.core.plugins.configuration.AbstractBaseDescription; +import com.dtolabs.rundeck.core.plugins.configuration.Describable; +import com.dtolabs.rundeck.core.plugins.configuration.Description; +import com.dtolabs.rundeck.core.plugins.configuration.Property; +import com.dtolabs.rundeck.core.plugins.configuration.PropertyUtil; +import com.dtolabs.rundeck.plugins.ServiceNameConstants; import com.dtolabs.utils.Streams; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * ExternalScriptFileCopier plugin provides the FileCopier service by allowing an external script to handle the @@ -62,7 +75,7 @@ * * @author Greg Schueler greg@dtosolutions.com */ -@Plugin (name = "script-copy", service = "FileCopier") +@Plugin(name = "script-copy", service = ServiceNameConstants.FileCopier) public class ScriptFileCopier implements FileCopier, Describable { public static String SERVICE_PROVIDER_NAME = "script-copy"; public static String SCRIPT_ATTRIBUTE = "script-copy"; @@ -87,17 +100,17 @@ public class ScriptFileCopier implements FileCopier, Describable { static { properties.add(PropertyUtil.string(CONFIG_COMMAND, "Command", - "Shell command to execute the file copy", - true, null)); + "Shell command to execute the file copy", + true, null)); properties.add(PropertyUtil.string(CONFIG_FILEPATH, "Remote Filepath", - "Remote filepath destination for the script.", - false, null)); + "Remote filepath destination for the script.", + false, null)); properties.add(PropertyUtil.string(CONFIG_INTERPRETER, "Interpreter", - "Shell or interpreter to pass the command string to. Not required.", - false, null)); + "Shell or interpreter to pass the command string to. Not required.", + false, null)); properties.add(PropertyUtil.string(CONFIG_DIRECTORY, "Directory", - "Directory to execute within", - false, null)); + "Directory to execute within", + false, null)); final Map mapping = new HashMap(); mapping.put(CONFIG_COMMAND, SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY); @@ -147,7 +160,7 @@ public String copyFileStream(final ExecutionContext executionContext, final Inpu * Copy existing file */ public String copyFile(final ExecutionContext executionContext, final File file, final INodeEntry node) throws - FileCopierException { + FileCopierException { return copyFile(executionContext, file, null, null, node); } @@ -156,17 +169,20 @@ public String copyFile(final ExecutionContext executionContext, final File file, */ public String copyScriptContent(final ExecutionContext executionContext, final String s, final INodeEntry node) throws - FileCopierException { + FileCopierException { return copyFile(executionContext, null, null, s, node); } + static enum Reason implements FailureReason { + ScriptFileCopierPluginExpectedOutputMissing + } /** * Internal copy method accepting file, inputstream or string */ String copyFile(final ExecutionContext executionContext, final File file, final InputStream input, final String content, final INodeEntry node) throws - FileCopierException { + FileCopierException { File workingdir = null; String scriptargs; @@ -177,7 +193,7 @@ String copyFile(final ExecutionContext executionContext, final File file, final final Framework framework = executionContext.getFramework(); //look for specific property scriptargs = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY); + SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY); if (null != node.getAttributes().get(SCRIPT_ATTRIBUTE)) { @@ -188,11 +204,13 @@ String copyFile(final ExecutionContext executionContext, final File file, final "[script-copy file copier] no attribute " + SCRIPT_ATTRIBUTE + " was found on node: " + node .getNodename() + ", and no " + SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY - + " property was configured for the project or framework."); + + " property was configured for the project or framework.", + StepFailureReason.ConfigurationFailure + ); } dirstring = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_COPY_DEFAULT_DIR_PROPERTY); + SCRIPT_COPY_DEFAULT_DIR_PROPERTY); if (null != node.getAttributes().get(DIR_ATTRIBUTE)) { dirstring = node.getAttributes().get(DIR_ATTRIBUTE); } @@ -201,7 +219,7 @@ String copyFile(final ExecutionContext executionContext, final File file, final } attrRemoteFilepath = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_COPY_DEFAULT_REMOTE_FILEPATH_PROPERTY); + SCRIPT_COPY_DEFAULT_REMOTE_FILEPATH_PROPERTY); if (null != node.getAttributes().get(REMOTE_FILEPATH_ATTRIBUTE)) { attrRemoteFilepath = node.getAttributes().get(REMOTE_FILEPATH_ATTRIBUTE); } @@ -226,7 +244,7 @@ String copyFile(final ExecutionContext executionContext, final File file, final } final Map> newDataContext = DataContextUtils.addContext("file-copy", scptexec, - nodeContext); + nodeContext); final String copiedFilepath; if (null != attrRemoteFilepath) { @@ -239,7 +257,7 @@ String copyFile(final ExecutionContext executionContext, final File file, final final Process exec; String remoteShell = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_COPY_DEFAULT_REMOTE_SHELL); + SCRIPT_COPY_DEFAULT_REMOTE_SHELL); if (null != node.getAttributes().get(SHELL_ATTRIBUTE)) { remoteShell = node.getAttributes().get(SHELL_ATTRIBUTE); } @@ -247,14 +265,14 @@ String copyFile(final ExecutionContext executionContext, final File file, final try { if (null != remoteShell) { exec = ScriptUtil.execShellProcess(executionContext.getExecutionListener(), workingdir, scriptargs, - nodeContext, newDataContext, remoteShell, "script-copy"); + nodeContext, newDataContext, remoteShell, "script-copy"); } else { exec = ScriptUtil.execProcess(executionContext.getExecutionListener(), workingdir, scriptargs, - nodeContext, - newDataContext, "script-copy"); + nodeContext, + newDataContext, "script-copy"); } } catch (IOException e) { - throw new FileCopierException(e); + throw new FileCopierException(e.getMessage(), StepFailureReason.IOFailure, e); } final Thread errthread; @@ -281,12 +299,14 @@ String copyFile(final ExecutionContext executionContext, final File file, final } success = 0 == result; } catch (InterruptedException e) { - throw new FileCopierException(e); + Thread.currentThread().interrupt(); + throw new FileCopierException(e.getMessage(), StepFailureReason.Interrupted, e); } catch (IOException e) { - throw new FileCopierException(e); + throw new FileCopierException(e.getMessage(), StepFailureReason.IOFailure, e); } if (!success) { - throw new FileCopierException("[script-copy]: external script failed with exit code: " + result); + throw new FileCopierException("[script-copy]: external script failed with exit code: " + result, + NodeStepFailureReason.NonZeroResultCode); } if (null != copiedFilepath) { @@ -295,13 +315,15 @@ String copyFile(final ExecutionContext executionContext, final File file, final //load string of output from outputstream final String output = byteArrayOutputStream.toString(); if (null == output || output.length() < 1) { - throw new FileCopierException("[script-copy]: No output from external script"); + throw new FileCopierException("[script-copy]: No output from external script", + Reason.ScriptFileCopierPluginExpectedOutputMissing); } //TODO: require any specific format for the data? //look for first line of output final String[] split1 = output.split("(\\r?\\n)"); if (split1.length < 1) { - throw new FileCopierException("[script-copy]: No output from external script"); + throw new FileCopierException("[script-copy]: No output from external script", + Reason.ScriptFileCopierPluginExpectedOutputMissing); } final String remotefilepath = split1[0]; diff --git a/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptNodeExecutor.java b/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptNodeExecutor.java index 51c0e5a1000..dc3bc3a4c4a 100644 --- a/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptNodeExecutor.java +++ b/plugins/script-plugin/src/main/java/com/dtolabs/rundeck/plugin/script/ScriptNodeExecutor.java @@ -28,18 +28,30 @@ import com.dtolabs.rundeck.core.common.INodeEntry; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.service.NodeExecutor; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason; +import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason; import com.dtolabs.rundeck.core.plugins.Plugin; -import com.dtolabs.rundeck.core.plugins.configuration.*; +import com.dtolabs.rundeck.core.plugins.configuration.AbstractBaseDescription; +import com.dtolabs.rundeck.core.plugins.configuration.Describable; +import com.dtolabs.rundeck.core.plugins.configuration.Description; +import com.dtolabs.rundeck.core.plugins.configuration.Property; +import com.dtolabs.rundeck.core.plugins.configuration.PropertyUtil; import com.dtolabs.rundeck.core.utils.StringArrayUtil; +import com.dtolabs.rundeck.plugins.ServiceNameConstants; import com.dtolabs.utils.Streams; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** *

    ExternalScriptExecutor is a {@link NodeExecutor} that delegates execution to an external script. The external @@ -60,7 +72,7 @@ * * @author Greg Schueler greg@dtosolutions.com */ -@Plugin (name = "script-exec", service = "NodeExecutor") +@Plugin(name = "script-exec", service = ServiceNameConstants.NodeExecutor) public class ScriptNodeExecutor implements NodeExecutor, Describable { public static String SERVICE_PROVIDER_NAME = "script-exec"; public static String SCRIPT_ATTRIBUTE = "script-exec"; @@ -82,14 +94,14 @@ public class ScriptNodeExecutor implements NodeExecutor, Describable { static { properties.add(PropertyUtil.string(CONFIG_COMMAND, "Command", - "Shell command to execute", - true, null)); + "Shell command to execute", + true, null)); properties.add(PropertyUtil.string(CONFIG_INTERPRETER, "Interpreter", - "Shell or interpreter to pass the command string to. Not required.", - false, null)); + "Shell or interpreter to pass the command string to. Not required.", + false, null)); properties.add(PropertyUtil.string(CONFIG_DIRECTORY, "Directory", - "Directory to execute within", - false, null)); + "Directory to execute within", + false, null)); final Map mapping = new HashMap(); @@ -123,7 +135,7 @@ public Map getPropertiesMapping() { }; public NodeExecutorResult executeCommand(final ExecutionContext executionContext, final String[] command, - final INodeEntry node) throws ExecutionException { + final INodeEntry node) { File workingdir = null; String scriptargs; String dirstring; @@ -132,22 +144,25 @@ public NodeExecutorResult executeCommand(final ExecutionContext executionContext final Framework framework = executionContext.getFramework(); //look for specific property scriptargs = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_EXEC_DEFAULT_COMMAND_PROPERTY); + SCRIPT_EXEC_DEFAULT_COMMAND_PROPERTY); if (null != node.getAttributes().get(SCRIPT_ATTRIBUTE)) { scriptargs = node.getAttributes().get(SCRIPT_ATTRIBUTE); } if (null == scriptargs) { - throw new ExecutionException( - "[script-exec node executor] no script-exec attribute " + SCRIPT_ATTRIBUTE + " was found on node: " - + node - .getNodename() + ", and no " + SCRIPT_EXEC_DEFAULT_COMMAND_PROPERTY - + " property was configured for the project or framework."); + return NodeExecutorResultImpl.createFailure(StepFailureReason.ConfigurationFailure, + "[script-exec node executor] no script-exec attribute " + + SCRIPT_ATTRIBUTE + " was found on node: " + + node + .getNodename() + ", and no " + + SCRIPT_EXEC_DEFAULT_COMMAND_PROPERTY + + " property was configured for the project or framework.", + node); } dirstring = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_EXEC_DEFAULT_DIR_PROPERTY); + SCRIPT_EXEC_DEFAULT_DIR_PROPERTY); if (null != node.getAttributes().get(DIR_ATTRIBUTE)) { dirstring = node.getAttributes().get(DIR_ATTRIBUTE); } @@ -164,33 +179,34 @@ public NodeExecutorResult executeCommand(final ExecutionContext executionContext scptexec.put("dir", workingdir.getAbsolutePath()); } final Map> newDataContext = DataContextUtils.addContext("exec", scptexec, - dataContext); + dataContext); final Process exec; String remoteShell = framework.getProjectProperty(executionContext.getFrameworkProject(), - SCRIPT_EXEC_DEFAULT_REMOTE_SHELL); + SCRIPT_EXEC_DEFAULT_REMOTE_SHELL); if (null != node.getAttributes().get(SHELL_ATTRIBUTE)) { remoteShell = node.getAttributes().get(SHELL_ATTRIBUTE); } try { if (null != remoteShell) { exec = ScriptUtil.execShellProcess(executionContext.getExecutionListener(), workingdir, scriptargs, - dataContext, newDataContext, remoteShell, "script-exec"); + dataContext, newDataContext, remoteShell, "script-exec"); } else { exec = ScriptUtil.execProcess(executionContext.getExecutionListener(), workingdir, scriptargs, - dataContext, - newDataContext, "script-exec"); + dataContext, + newDataContext, "script-exec"); } } catch (IOException e) { - throw new ExecutionException(e); + return NodeExecutorResultImpl.createFailure(StepFailureReason.IOFailure, e.getMessage(), e, node, -1); } int result = -1; boolean success = false; Thread errthread; Thread outthread; - + FailureReason reason; + String message; try { errthread = Streams.copyStreamThread(exec.getErrorStream(), System.err); outthread = Streams.copyStreamThread(exec.getInputStream(), System.out); @@ -201,19 +217,26 @@ public NodeExecutorResult executeCommand(final ExecutionContext executionContext errthread.join(); outthread.join(); success = 0 == result; + executionContext.getExecutionListener().log(3, + "[script-exec]: result code: " + result + ", success: " + + success); + if (success) { + return NodeExecutorResultImpl.createSuccess(node); + } + reason = NodeStepFailureReason.NonZeroResultCode; + message = "Result code was " + result; } catch (InterruptedException e) { - e.printStackTrace(System.err); + Thread.currentThread().interrupt(); + reason = StepFailureReason.Interrupted; + message = e.getMessage(); } catch (IOException e) { e.printStackTrace(System.err); + reason = StepFailureReason.IOFailure; + message = e.getMessage(); } executionContext.getExecutionListener().log(3, - "[script-exec]: result code: " + result + ", success: " + success); - return new NodeExecutorResultImpl(success, node, result) { - @Override - public String toString() { - return "[script-exec] success: " + isSuccess() + ", result code: " + getResultCode(); - } - }; + "[script-exec]: result code: " + result + ", success: " + success); + return NodeExecutorResultImpl.createFailure(reason, message, node, result); } public Description getDescription() { diff --git a/plugins/stub-plugin/build.gradle b/plugins/stub-plugin/build.gradle index 590c78d7467..c9697b4a1cf 100644 --- a/plugins/stub-plugin/build.gradle +++ b/plugins/stub-plugin/build.gradle @@ -43,6 +43,9 @@ task createPom << { archive{ manifestEntries{ 'Rundeck-Plugin-Classnames'(pluginClassNames) + 'Rundeck-Plugin-Version'(rundeckPluginVersion) + 'Rundeck-Plugin-Archive'('true') + 'Rundeck-Plugin-File-Version'(version) } } } diff --git a/plugins/stub-plugin/pom.xml b/plugins/stub-plugin/pom.xml index 0d786fd2223..f50bb85cb53 100644 --- a/plugins/stub-plugin/pom.xml +++ b/plugins/stub-plugin/pom.xml @@ -4,10 +4,10 @@ 4.0.0 rundeck-bundled-plugins - com.dtolabs.rundeck + org.rundeck 1.5-dev - com.dtolabs.rundeck + org.rundeck rundeck-stub-plugin 1.5-dev RunDeck Stub Plugin @@ -29,6 +29,9 @@ com.dtolabs.rundeck.plugin.stub.StubFileCopier,com.dtolabs.rundeck.plugin.stub.StubNodeExecutor + 1.1 + true + 1.5-dev diff --git a/plugins/stub-plugin/src/main/java/com/dtolabs/rundeck/plugin/stub/StubNodeExecutor.java b/plugins/stub-plugin/src/main/java/com/dtolabs/rundeck/plugin/stub/StubNodeExecutor.java index d032ba370eb..11fbf106d85 100644 --- a/plugins/stub-plugin/src/main/java/com/dtolabs/rundeck/plugin/stub/StubNodeExecutor.java +++ b/plugins/stub-plugin/src/main/java/com/dtolabs/rundeck/plugin/stub/StubNodeExecutor.java @@ -25,35 +25,32 @@ import com.dtolabs.rundeck.core.Constants; import com.dtolabs.rundeck.core.common.INodeEntry; -import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; import com.dtolabs.rundeck.core.execution.ExecutionContext; -import com.dtolabs.rundeck.core.execution.ExecutionException; import com.dtolabs.rundeck.core.execution.service.NodeExecutor; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult; import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl; +import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason; import com.dtolabs.rundeck.core.plugins.Plugin; import com.dtolabs.rundeck.core.plugins.configuration.AbstractBaseDescription; import com.dtolabs.rundeck.core.plugins.configuration.Describable; import com.dtolabs.rundeck.core.plugins.configuration.Description; -import com.dtolabs.rundeck.core.plugins.configuration.Property; import com.dtolabs.rundeck.core.utils.StringArrayUtil; +import com.dtolabs.rundeck.plugins.ServiceNameConstants; -import java.util.List; /** * StubNodeExecutor is ... * * @author Greg Schueler greg@dtosolutions.com */ -@Plugin (name="stub",service = "NodeExecutor") +@Plugin(name = "stub", service = ServiceNameConstants.NodeExecutor) public class StubNodeExecutor implements NodeExecutor, Describable { public static final String SERVICE_PROVIDER_NAME = "stub"; private static final String STUB_EXEC_SUCCESS = "stub-exec-success"; private static final String STUB_RESULT_CODE = "stub-result-code"; public NodeExecutorResult executeCommand(final ExecutionContext context, final String[] command, - final INodeEntry node) throws - ExecutionException { + final INodeEntry node) { //replace data context in args int tcode = 0; boolean tsuccess = true; @@ -62,7 +59,7 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S tcode = Integer.parseInt(node.getAttributes().get(STUB_RESULT_CODE)); } catch (NumberFormatException e) { context.getExecutionListener().log(Constants.WARN_LEVEL, - "[stub] (failed to parse stub-result-code for node)"); + "[stub] (failed to parse stub-result-code for node)"); } } if (null != node.getAttributes() && null != node.getAttributes().get(STUB_EXEC_SUCCESS)) { @@ -70,25 +67,27 @@ public NodeExecutorResult executeCommand(final ExecutionContext context, final S tsuccess = Boolean.parseBoolean(node.getAttributes().get(STUB_EXEC_SUCCESS)); } catch (NumberFormatException e) { context.getExecutionListener().log(Constants.WARN_LEVEL, - "[stub] (failed to parse " + STUB_EXEC_SUCCESS + " for node)"); + "[stub] (failed to parse " + STUB_EXEC_SUCCESS + " for node)"); } } - if(tsuccess){ + if (tsuccess) { context.getExecutionListener().log(Constants.WARN_LEVEL, - "[stub] execute on node " + node.getNodename() + ": " + StringArrayUtil.asString(command, " ")); - }else{ + "[stub] execute on node " + node.getNodename() + ": " + + StringArrayUtil.asString(command, " ")); + return NodeExecutorResultImpl.createSuccess(node); + } else { context.getExecutionListener().log(Constants.ERR_LEVEL, - "[stub] fail on node " + node.getNodename() + ": " + StringArrayUtil.asString(command, " ")); + "[stub] fail on node " + node.getNodename() + ": " + + StringArrayUtil.asString(command, " ")); + return NodeExecutorResultImpl.createFailure(Reason.Intentional, "Intentional failure", node); } + } - return new NodeExecutorResultImpl(tsuccess,node, tcode) { - public String toString() { - return "[Stub result: success? "+isSuccess()+", result code: "+getResultCode()+"]"; - } - }; + static enum Reason implements FailureReason { + Intentional } - static final Description DESC= new AbstractBaseDescription(){ + static final Description DESC = new AbstractBaseDescription() { public String getName() { return SERVICE_PROVIDER_NAME; } @@ -101,6 +100,7 @@ public String getDescription() { return "Prints the command instead of executing it. (Useful for mocking processes.)"; } }; + public Description getDescription() { return DESC; } diff --git a/pom.xml b/pom.xml index 15d8853be2d..be5f1f0eca6 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - com.dtolabs.rundeck + org.rundeck rundeck-project pom 1.5-dev diff --git a/rundeck-launcher/build.gradle b/rundeck-launcher/build.gradle index d1cde3009cb..efec9ad202d 100644 --- a/rundeck-launcher/build.gradle +++ b/rundeck-launcher/build.gradle @@ -2,7 +2,7 @@ * The Rundeck Launch parent build file */ -description = 'The parent launcher project provides defaults for it\'s children' +description = "The parent launcher project provides defaults for it's children" apply from: "${rootDir}/gradle/java.gradle" @@ -19,6 +19,7 @@ subprojects{ addArtifactPattern "$projectDir/../../rundeckapp/target/[module]-[revision](-[classifier]).[ext]" addArtifactPattern "$projectDir/../../plugins/script-plugin/build/libs/[module]-[revision](-[classifier]).[ext]" addArtifactPattern "$projectDir/../../plugins/stub-plugin/build/libs/[module]-[revision](-[classifier]).[ext]" + addArtifactPattern "$projectDir/../../plugins/localexec-plugin/build/libs/[module]-[revision](-[classifier]).[ext]" descriptor = 'optional' checkmodified = true } diff --git a/rundeck-launcher/launcher/build.gradle b/rundeck-launcher/launcher/build.gradle index 67ba65c0714..956e0ef9838 100644 --- a/rundeck-launcher/launcher/build.gradle +++ b/rundeck-launcher/launcher/build.gradle @@ -30,12 +30,13 @@ dependencies { ) warBundle( - [group: 'com.dtolabs.rundeck', name: 'rundeck', version: version, ext:'war'] + [group: 'org.rundeck', name: 'rundeck', version: version,ext:'war'] ) pluginFiles( - [group: 'com.dtolabs.rundeck', name: 'rundeck-script-plugin', version: version,ext:'jar'], - [group: 'com.dtolabs.rundeck', name: 'rundeck-stub-plugin', version: version,ext:'jar'] + [group: 'org.rundeck', name: 'rundeck-script-plugin', version: version,ext:'jar'], + [group: 'org.rundeck', name: 'rundeck-stub-plugin', version: version,ext:'jar'], + [group: 'org.rundeck', name: 'rundeck-localexec-plugin', version: version, ext: 'jar'] ) jettyServerLib(project(path:":rundeck-launcher:rundeck-jetty-server", configuration:'runtime')) { @@ -192,14 +193,14 @@ task createPom << { type 'jar' } } - (configurations.jettyServerLib.dependencies).each{ dep -> - dependency{ - groupId dep.group - artifactId dep.name - version dep.version - type 'jar' - } - } +// (configurations.jettyServerLib.dependencies).each{ dep -> +// dependency{ +// groupId dep.group +// artifactId dep.name +// version dep.version +// type 'jar' +// } +// } } } }.writeTo("pom.xml") diff --git a/rundeck-launcher/launcher/pom.xml b/rundeck-launcher/launcher/pom.xml index de542269875..0d2056229e1 100644 --- a/rundeck-launcher/launcher/pom.xml +++ b/rundeck-launcher/launcher/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - com.dtolabs.rundeck + org.rundeck rundeck-launcher 1.5-dev Rundeck Launcher @@ -64,28 +64,28 @@ - com.dtolabs.rundeck + org.rundeck rundeck 1.5-dev war - com.dtolabs.rundeck + org.rundeck rundeck-script-plugin 1.5-dev - com.dtolabs.rundeck + org.rundeck rundeck-stub-plugin 1.5-dev - com.dtolabs.rundeck - rundeck-jetty-server + org.rundeck + rundeck-localexec-plugin 1.5-dev - com.dtolabs.rundeck + org.rundeck rundeck-jetty-server 1.5-dev compile diff --git a/rundeck-launcher/pom.xml b/rundeck-launcher/pom.xml index 76e0f66c6e6..dae6bb6bd3a 100644 --- a/rundeck-launcher/pom.xml +++ b/rundeck-launcher/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - com.dtolabs.rundeck + org.rundeck rundeck-launcher-parent 1.5-dev pom diff --git a/rundeck-launcher/rundeck-jetty-server/pom.xml b/rundeck-launcher/rundeck-jetty-server/pom.xml index f0bb1629eb4..0ed82b33e76 100644 --- a/rundeck-launcher/rundeck-jetty-server/pom.xml +++ b/rundeck-launcher/rundeck-jetty-server/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - com.dtolabs.rundeck + org.rundeck rundeck-jetty-server 1.5-dev RunDeck Jetty Server diff --git a/rundeckapp/grails-app/conf/BuildConfig.groovy b/rundeckapp/grails-app/conf/BuildConfig.groovy index 5db4744b204..0239f7977af 100644 --- a/rundeckapp/grails-app/conf/BuildConfig.groovy +++ b/rundeckapp/grails-app/conf/BuildConfig.groovy @@ -40,12 +40,12 @@ grails.project.dependency.resolution = { compile 'org.yaml:snakeyaml:1.9', 'org.apache.ant:ant:1.7.1', 'org.apache.ant:ant-jsch:1.7.1', 'com.jcraft:jsch:0.1.45','log4j:log4j:1.2.16','commons-collections:commons-collections:3.2.1', 'commons-codec:commons-codec:1.5', 'com.fasterxml.jackson.core:jackson-databind:2.0.2', - "com.dtolabs.rundeck:rundeck-core:${appVersion}" + "org.rundeck:rundeck-core:${appVersion}" runtime 'org.yaml:snakeyaml:1.9', 'org.apache.ant:ant:1.7.1', 'org.apache.ant:ant-launcher:1.7.1', 'org.apache.ant:ant-jsch:1.7.1','com.jcraft:jsch:0.1.45', 'org.springframework:spring-test:3.0.5.RELEASE', 'log4j:log4j:1.2.16' ,'commons-collections:commons-collections:3.2.1','commons-codec:commons-codec:1.5', - 'com.fasterxml.jackson.core:jackson-databind:2.0.2', "com.dtolabs.rundeck:rundeck-jetty-server:${appVersion}" + 'com.fasterxml.jackson.core:jackson-databind:2.0.2', "org.rundeck:rundeck-jetty-server:${appVersion}" } grails.plugin.location.'webrealms' = "webrealms" diff --git a/rundeckapp/grails-app/controllers/rundeck/controllers/ExecutionController.groovy b/rundeckapp/grails-app/controllers/rundeck/controllers/ExecutionController.groovy index 0815d59368b..c4a70b34da2 100644 --- a/rundeckapp/grails-app/controllers/rundeck/controllers/ExecutionController.groovy +++ b/rundeckapp/grails-app/controllers/rundeck/controllers/ExecutionController.groovy @@ -497,7 +497,7 @@ class ExecutionController { def datamap = [ time: it.time.toString(), level: it.level, - log: it.mesg.trim(), + log: it.mesg.replaceAll(/\r?\n$/,''), user: it.user, // module: it.module, command: it.command, @@ -540,7 +540,7 @@ class ExecutionController { response.addHeader('X-Rundeck-ExecOutput-TotalSize', totsize.toString()) render(contentType:"text/plain"){ entry.each{ - out< ScheduledExecution se = ScheduledExecution.get(id) if (!frameworkService.authorizeProjectJobAll(executionContext.getFramework(), se, [AuthConstants.ACTION_RUN], se.project)) { - def msg= "Unauthorized to execute job ${jitem.jobIdentifier}: ${se.extid}" - executionContext.getExecutionListener().log(0,"Job ref [${jitem.jobIdentifier}] failed: " + msg); - result = new StepExecutionResultImpl(false,msg) + def msg= "Unauthorized to execute job [${jitem.jobIdentifier}}: ${se.extid}" + executionContext.getExecutionListener().log(0, msg); + result = new StepExecutionResultImpl(null,JobReferenceFailureReason.Unauthorized,msg) return } // se.refresh() @@ -1812,10 +1802,10 @@ class ExecutionService implements ApplicationContextAware, StepExecutor{ //validate the option values try { validateOptionValues(se, evalPlainOpts + evalSecOpts + evalSecAuthOpts) - } catch (Exception e) { - def msg = "Failed to execute job ${jitem.jobIdentifier}: ${se.extid}: ${e.message}" - executionContext.getExecutionListener().log(0, "Job ref [${jitem.jobIdentifier}] failed: " + msg); - result = new StepExecutionResultImpl(false, msg) + } catch (ExecutionServiceValidationException e) { + executionContext.getExecutionListener().log(0, "Option input was not valid for [${jitem.jobIdentifier}]: ${e.message}"); + def msg = "Invalid options: ${e.errors.keySet()}" + result = new StepExecutionResultImpl(e,JobReferenceFailureReason.InvalidOptions, msg) return } @@ -1840,14 +1830,24 @@ class ExecutionService implements ApplicationContextAware, StepExecutor{ if(null!=result){ return result } + + if (newContext.getNodes().getNodeNames().size()<1){ + def msg = "No nodes matched for the filters: " + newContext.getNodeSelector() + executionContext.getExecutionListener().log(0, msg) + throw new StepException(JobReferenceFailureReason.NoMatchedNodes, msg) + } + def WorkflowExecutionService service = executionContext.getFramework().getWorkflowExecutionService() - final WorkflowExecutionResult wresult = service.getExecutorForItem(newExecItem).executeWorkflow(newContext, newExecItem) - if(!wresult || !wresult.isSuccess()){ - System.err.println("Job ref [${jitem.jobIdentifier}] failed: "+ wresult); + def wresult = service.getExecutorForItem(newExecItem).executeWorkflow(newContext, newExecItem) + + if(!wresult || !wresult.success ){ + result = new StepExecutionResultImpl(null, JobReferenceFailureReason.JobFailed, "Job [${jitem.jobIdentifier}] failed") + }else{ + result=new StepExecutionResultImpl() } - result=new StepExecutionResultImpl(wresult.isSuccess()) - result.sourceResult=wresult + result.sourceResult = wresult + } finally { if (unbindrequest) { RequestContextHolder.setRequestAttributes (null) diff --git a/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy b/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy index 02e66083531..263cdb0acb8 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ProjectService.groovy @@ -1,24 +1,25 @@ package rundeck.services -import java.util.zip.ZipOutputStream +import com.dtolabs.rundeck.app.support.BuilderUtil import com.dtolabs.rundeck.core.common.Framework import com.dtolabs.rundeck.core.common.FrameworkProject - -import groovy.xml.MarkupBuilder +import com.dtolabs.rundeck.util.XmlParserUtil import com.dtolabs.rundeck.util.ZipBuilder -import java.util.zip.ZipInputStream import com.dtolabs.rundeck.util.ZipReader -import java.text.SimpleDateFormat +import groovy.xml.MarkupBuilder +import rundeck.BaseReport +import rundeck.ExecReport +import rundeck.Execution +import rundeck.ScheduledExecution +import rundeck.codecs.JobsXMLCodec + import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.jar.Attributes import java.util.jar.JarOutputStream import java.util.jar.Manifest -import java.util.jar.Attributes -import com.dtolabs.rundeck.app.support.BuilderUtil -import com.dtolabs.rundeck.util.XmlParserUtil -import rundeck.ScheduledExecution -import rundeck.BaseReport -import rundeck.Execution -import rundeck.ExecReport +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream class ProjectService { def grailsApplication diff --git a/rundeckapp/grails-app/services/rundeck/services/ScheduledExecutionService.groovy b/rundeckapp/grails-app/services/rundeck/services/ScheduledExecutionService.groovy index 03e62a4e20f..be3d2aa746c 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ScheduledExecutionService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ScheduledExecutionService.groovy @@ -1,42 +1,23 @@ package rundeck.services -import javax.security.auth.Subject; - -import org.quartz.Scheduler -import org.quartz.JobDetail -import org.quartz.Trigger -import org.quartz.CronTrigger -import org.quartz.TriggerUtils - -import org.quartz.JobExecutionContext -import org.quartz.InterruptableJob - +import com.dtolabs.rundeck.app.support.ScheduledExecutionQuery import com.dtolabs.rundeck.core.common.Framework - import com.dtolabs.rundeck.server.authorization.AuthConstants import org.apache.log4j.Logger import org.apache.log4j.MDC - -import org.quartz.CronExpression -import org.quartz.SchedulerException -import java.text.SimpleDateFormat +import org.quartz.* import org.springframework.context.MessageSource -import java.text.MessageFormat +import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.servlet.support.RequestContextUtils +import rundeck.* +import rundeck.controllers.EditOptsController +import rundeck.controllers.WorkflowController +import rundeck.quartzjobs.ExecutionJob +import javax.security.auth.Subject import javax.servlet.http.HttpSession -import org.springframework.web.context.request.RequestContextHolder -import rundeck.WorkflowStep -import com.dtolabs.rundeck.app.support.ScheduledExecutionQuery -import rundeck.CommandExec -import rundeck.ScheduledExecution -import rundeck.Workflow -import rundeck.JobExec -import rundeck.Execution -import rundeck.Option -import rundeck.Notification -import rundeck.controllers.WorkflowController -import rundeck.controllers.EditOptsController +import java.text.MessageFormat +import java.text.SimpleDateFormat /** * ScheduledExecutionService manages scheduling jobs with the Quartz scheduler @@ -656,21 +637,7 @@ class ScheduledExecutionService /*implements ApplicationContextAware*/{ * @return ScheduledExecution found or null */ def ScheduledExecution getByIDorUUID(anid){ - def found=null - if(anid instanceof Long){ - return ScheduledExecution.get(anid) - }else if(anid instanceof String){ - //attempt to parse as long id - try { - def long idlong = Long.parseLong(anid) - found = ScheduledExecution.get(idlong) - } catch (NumberFormatException e) { - } - if (!found) { - found=ScheduledExecution.findByUuid(anid) - } - } - return found + ScheduledExecution.getByIdOrUUID(anid) } /** diff --git a/rundeckapp/grails-app/utils/JobsXMLCodec.groovy b/rundeckapp/grails-app/utils/rundeck/codecs/JobsXMLCodec.groovy similarity index 98% rename from rundeckapp/grails-app/utils/JobsXMLCodec.groovy rename to rundeckapp/grails-app/utils/rundeck/codecs/JobsXMLCodec.groovy index 6005f6d02b3..c92d23cdebe 100644 --- a/rundeckapp/grails-app/utils/JobsXMLCodec.groovy +++ b/rundeckapp/grails-app/utils/rundeck/codecs/JobsXMLCodec.groovy @@ -1,3 +1,4 @@ +package rundeck.codecs /* * Copyright 2010 DTO Labs, Inc. (http://dtolabs.com) * @@ -106,7 +107,7 @@ class JobsXMLCodec { //perform structure conversions for expected input for populating ScheduledExecution - if(!map.context){ + if(!map.context|| !(map.context instanceof Map)){ throw new JobXMLException("'context' element not found") } map.project=map.context.remove('project') @@ -136,7 +137,10 @@ class JobsXMLCodec { } } //convert options:[option:[]] into options:[] - if(map.context?.options?.option){ + if(map.context?.options && !(map.context?.options instanceof Map)){ + throw new JobXMLException("'context/options' element is not valid") + } + if(map.context?.options && map.context?.options?.option){ final def opts = map.context.options.remove('option') map.remove('context') map.options=[:] diff --git a/rundeckapp/grails-app/utils/JobsYAMLCodec.groovy b/rundeckapp/grails-app/utils/rundeck/codecs/JobsYAMLCodec.groovy similarity index 98% rename from rundeckapp/grails-app/utils/JobsYAMLCodec.groovy rename to rundeckapp/grails-app/utils/rundeck/codecs/JobsYAMLCodec.groovy index 07108f135fd..ef1ef2a74f5 100644 --- a/rundeckapp/grails-app/utils/JobsYAMLCodec.groovy +++ b/rundeckapp/grails-app/utils/rundeck/codecs/JobsYAMLCodec.groovy @@ -1,3 +1,5 @@ +package rundeck.codecs + import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.DumperOptions import org.yaml.snakeyaml.constructor.SafeConstructor diff --git a/rundeckapp/grails-app/utils/MarkdownCodec.groovy b/rundeckapp/grails-app/utils/rundeck/codecs/MarkdownCodec.groovy similarity index 97% rename from rundeckapp/grails-app/utils/MarkdownCodec.groovy rename to rundeckapp/grails-app/utils/rundeck/codecs/MarkdownCodec.groovy index 3e3cc7c0218..a1781dea0cf 100644 --- a/rundeckapp/grails-app/utils/MarkdownCodec.groovy +++ b/rundeckapp/grails-app/utils/rundeck/codecs/MarkdownCodec.groovy @@ -1,3 +1,4 @@ +package rundeck.codecs /* * Copyright 2010 DTO Labs, Inc. (http://dtolabs.com) * diff --git a/rundeckapp/grails-app/views/execution/_execDetailsWorkflow.gsp b/rundeckapp/grails-app/views/execution/_execDetailsWorkflow.gsp index 385cbf1cf01..7a10186022f 100644 --- a/rundeckapp/grails-app/views/execution/_execDetailsWorkflow.gsp +++ b/rundeckapp/grails-app/views/execution/_execDetailsWorkflow.gsp @@ -136,24 +136,24 @@