mirror of https://github.com/apache/jclouds.git
Issue 454: added wrapInInitScript option for running scripts on nodes
This commit is contained in:
parent
b9e60a8b4e
commit
d56bc68d1c
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.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.
|
||||
* ====================================================================
|
||||
*/
|
||||
|
||||
package org.jclouds.compute.callables;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.compute.domain.NodeMetadata;
|
||||
import org.jclouds.compute.options.RunScriptOptions;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.ssh.ExecResponse;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class RunScriptOnNodeUsingSsh implements RunScriptOnNode {
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
protected final Function<NodeMetadata, SshClient> sshFactory;
|
||||
protected final NodeMetadata node;
|
||||
protected final Statement statement;
|
||||
protected final boolean runAsRoot;
|
||||
|
||||
protected SshClient ssh;
|
||||
|
||||
@AssistedInject
|
||||
public RunScriptOnNodeUsingSsh(Function<NodeMetadata, SshClient> sshFactory, @Assisted NodeMetadata node,
|
||||
@Assisted Statement statement, @Assisted RunScriptOptions options) {
|
||||
this.sshFactory = checkNotNull(sshFactory, "sshFactory");
|
||||
this.node = checkNotNull(node, "node");
|
||||
this.statement = checkNotNull(statement, "statement");
|
||||
this.runAsRoot = options.shouldRunAsRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecResponse call() {
|
||||
checkState(ssh != null, "please call init() before invoking call");
|
||||
try {
|
||||
ssh.connect();
|
||||
ExecResponse returnVal;
|
||||
String command = (runAsRoot) ? execAsRoot(statement.render(OsFamily.UNIX)) : execScriptAsDefaultUser(statement
|
||||
.render(OsFamily.UNIX));
|
||||
returnVal = runCommand(command);
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace("<< %s[%s]", statement, returnVal);
|
||||
else
|
||||
logger.debug("<< %s(%d)", statement, returnVal.getExitCode());
|
||||
return returnVal;
|
||||
} finally {
|
||||
if (ssh != null)
|
||||
ssh.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunScriptOnNode init() {
|
||||
ssh = sshFactory.apply(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected ExecResponse runCommand(String command) {
|
||||
ExecResponse returnVal;
|
||||
logger.debug(">> running [%s] as %s@%s", command.replace(node.getAdminPassword() != null ? node
|
||||
.getAdminPassword() : "XXXXX", "XXXXX"), ssh.getUsername(), ssh.getHostAddress());
|
||||
returnVal = ssh.exec(command);
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public String execAsRoot(String command) {
|
||||
if (node.getCredentials().identity.equals("root")) {
|
||||
} else if (node.getAdminPassword() != null) {
|
||||
command = String.format("echo '%s'|sudo -S %s", node.getAdminPassword(), command);
|
||||
} else {
|
||||
command = "sudo " + command;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
protected String execScriptAsDefaultUser(String command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
public NodeMetadata getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this).add("node", node).add("name", statement).add("runAsRoot", runAsRoot)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -35,6 +35,7 @@ import org.jclouds.collect.Memoized;
|
|||
import org.jclouds.compute.callables.RunScriptOnNode;
|
||||
import org.jclouds.compute.callables.RunScriptOnNodeAsInitScriptUsingSsh;
|
||||
import org.jclouds.compute.callables.RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete;
|
||||
import org.jclouds.compute.callables.RunScriptOnNodeUsingSsh;
|
||||
import org.jclouds.compute.domain.ComputeMetadata;
|
||||
import org.jclouds.compute.domain.Hardware;
|
||||
import org.jclouds.compute.domain.Image;
|
||||
|
@ -81,7 +82,8 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
bind(new TypeLiteral<Function<TemplateOptions, Statement>>() {
|
||||
}).to(TemplateOptionsToStatement.class);
|
||||
|
||||
install(new FactoryModuleBuilder().implement(RunScriptOnNode.class, Names.named("blocking"),
|
||||
install(new FactoryModuleBuilder().implement(RunScriptOnNode.class, Names.named("direct"),
|
||||
RunScriptOnNodeUsingSsh.class).implement(RunScriptOnNode.class, Names.named("blocking"),
|
||||
RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.class).implement(RunScriptOnNode.class,
|
||||
Names.named("nonblocking"), RunScriptOnNodeAsInitScriptUsingSsh.class).build(
|
||||
RunScriptOnNodeFactoryImpl.Factory.class));
|
||||
|
@ -103,11 +105,14 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
|
||||
static interface Factory {
|
||||
|
||||
@Named("direct")
|
||||
RunScriptOnNode exec(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
|
||||
@Named("blocking")
|
||||
RunScriptOnNode blockOnComplete(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
RunScriptOnNode backgroundAndBlockOnComplete(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
|
||||
@Named("nonblocking")
|
||||
RunScriptOnNode dontBlockOnComplete(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
RunScriptOnNode background(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
}
|
||||
|
||||
private final Factory factory;
|
||||
|
@ -122,8 +127,9 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
checkNotNull(node, "node");
|
||||
checkNotNull(runScript, "runScript");
|
||||
checkNotNull(options, "options");
|
||||
return options.shouldBlockOnComplete() ? factory.blockOnComplete(node, runScript, options) : factory
|
||||
.dontBlockOnComplete(node, runScript, options);
|
||||
return !options.shouldWrapInInitScript() ? factory.exec(node, runScript, options)
|
||||
: (options.shouldBlockOnComplete() ? factory.backgroundAndBlockOnComplete(node, runScript, options)
|
||||
: factory.background(node, runScript, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -54,7 +54,7 @@ public class CreateSshClientOncePortIsListeningOnNode implements Function<NodeMe
|
|||
checkState(sshFactory != null, "ssh requested, but no SshModule configured");
|
||||
checkNotNull(node.getCredentials(), "no credentials found for node %s", node.getId());
|
||||
checkNotNull(node.getCredentials().identity, "no login identity found for node %s", node.getId());
|
||||
checkNotNull(node.getCredentials().credential, "no credential found for $s on node %s", node
|
||||
checkNotNull(node.getCredentials().credential, "no credential found for %s on node %s", node
|
||||
.getCredentials().identity, node.getId());
|
||||
IPSocket socket = ComputeServiceUtils.findReachableSocketOnNode(socketTester, node, node.getLoginPort());
|
||||
return sshFactory.create(socket, node.getCredentials());
|
||||
|
|
|
@ -118,6 +118,7 @@ public class RunScriptOptions {
|
|||
protected Credentials overridingCredentials;
|
||||
protected boolean runAsRoot = true;
|
||||
protected boolean blockOnComplete = true;
|
||||
protected boolean wrapInInitScript = true;
|
||||
|
||||
public RunScriptOptions withOverridingCredentials(Credentials overridingCredentials) {
|
||||
checkNotNull(overridingCredentials, "overridingCredentials");
|
||||
|
@ -142,6 +143,20 @@ public class RunScriptOptions {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* default true
|
||||
* <p/>
|
||||
*
|
||||
* @param wrapInInitScript
|
||||
* if the command is long-running, use this option to ensure it is wrapInInitScripted
|
||||
* properly. (ex. have jclouds wrap it an init script, nohup, etc)
|
||||
* @return
|
||||
*/
|
||||
public RunScriptOptions wrapInInitScript(boolean wrapInInitScript) {
|
||||
this.wrapInInitScript = wrapInInitScript;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RunScriptOptions blockOnComplete(boolean blockOnComplete) {
|
||||
this.blockOnComplete = blockOnComplete;
|
||||
return this;
|
||||
|
@ -198,6 +213,15 @@ public class RunScriptOptions {
|
|||
return blockOnComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to wait until the script has completed. By default, true.
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
public boolean shouldWrapInInitScript() {
|
||||
return wrapInInitScript;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
public static RunScriptOptions nameTask(String name) {
|
||||
|
@ -220,6 +244,11 @@ public class RunScriptOptions {
|
|||
return options.blockOnComplete(value);
|
||||
}
|
||||
|
||||
public static RunScriptOptions wrapInInitScript(boolean value) {
|
||||
RunScriptOptions options = new RunScriptOptions();
|
||||
return options.wrapInInitScript(value);
|
||||
}
|
||||
|
||||
public static RunScriptOptions blockOnPort(int port, int seconds) {
|
||||
RunScriptOptions options = new RunScriptOptions();
|
||||
return options.blockOnPort(port, seconds);
|
||||
|
@ -230,7 +259,8 @@ public class RunScriptOptions {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "[overridingCredentials=" + (overridingCredentials != null) + ", port:seconds=" + port + ":" + seconds
|
||||
+ ", runAsRoot=" + runAsRoot + ", blockOnComplete=" + blockOnComplete + "]";
|
||||
+ ", runAsRoot=" + runAsRoot + ", blockOnComplete=" + blockOnComplete + ", wrapInInitScript=" + wrapInInitScript
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import java.util.Properties;
|
|||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
@ -76,6 +77,7 @@ import org.jclouds.predicates.RetryablePredicate;
|
|||
import org.jclouds.predicates.SocketOpen;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.rest.RestContextFactory;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.ssh.ExecResponse;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshException;
|
||||
|
@ -236,6 +238,12 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
assert getRootCause(e).getMessage().contains("Auth fail") : e;
|
||||
}
|
||||
|
||||
for (Entry<? extends NodeMetadata, ExecResponse> response : client.runScriptOnNodesMatching(
|
||||
runningWithTag(tag), Statements.exec("echo hello"), overrideCredentialsWith(good).wrapInInitScript(false))
|
||||
.entrySet())
|
||||
assert response.getValue().getOutput().trim().equals("hello") : response.getKey() + ": "
|
||||
+ response.getValue();
|
||||
|
||||
runScriptWithCreds(tag, os, good);
|
||||
|
||||
checkNodes(nodes, tag);
|
||||
|
|
|
@ -110,12 +110,19 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
runScriptAndService(client1, 1);
|
||||
|
||||
expect(factory.create(new IPSocket("144.175.1.2", 22), new Credentials("root", "password2"))).andReturn(
|
||||
client2).times(2);
|
||||
client2).times(3);
|
||||
expect(factory.create(new IPSocket("144.175.1.2", 22), new Credentials("root", "romeo"))).andThrow(
|
||||
new SshException("Auth fail"));
|
||||
|
||||
// run script without backgrounding
|
||||
client2.connect();
|
||||
expect(client2.exec("echo hello\n")).andReturn(new ExecResponse("hello\n", "", 0));
|
||||
client2.disconnect();
|
||||
|
||||
client2.connect();
|
||||
try {
|
||||
runScript(client2, "runScriptWithCreds", Strings2.toStringAndClose(StubComputeServiceIntegrationTest.class
|
||||
runScript(client2, "runScriptWithCreds",
|
||||
Strings2.toStringAndClose(StubComputeServiceIntegrationTest.class
|
||||
.getResourceAsStream("/runscript.sh")), 2);
|
||||
} catch (IOException e) {
|
||||
Throwables.propagate(e);
|
||||
|
@ -134,20 +141,20 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
runScriptAndInstallSsh(client5, "bootstrap", 5);
|
||||
|
||||
expect(
|
||||
factory.create(eq(new IPSocket("144.175.1.1", 22)),
|
||||
eq(new Credentials("root", keyPair.get("private"))))).andReturn(client1);
|
||||
factory.create(eq(new IPSocket("144.175.1.1", 22)), eq(new Credentials("root", keyPair
|
||||
.get("private"))))).andReturn(client1);
|
||||
expect(
|
||||
factory.create(eq(new IPSocket("144.175.1.2", 22)),
|
||||
eq(new Credentials("root", keyPair.get("private"))))).andReturn(client2);
|
||||
factory.create(eq(new IPSocket("144.175.1.2", 22)), eq(new Credentials("root", keyPair
|
||||
.get("private"))))).andReturn(client2);
|
||||
expect(
|
||||
factory.create(eq(new IPSocket("144.175.1.3", 22)),
|
||||
eq(new Credentials("root", keyPair.get("private"))))).andReturn(client3);
|
||||
factory.create(eq(new IPSocket("144.175.1.3", 22)), eq(new Credentials("root", keyPair
|
||||
.get("private"))))).andReturn(client3);
|
||||
expect(
|
||||
factory.create(eq(new IPSocket("144.175.1.4", 22)),
|
||||
eq(new Credentials("root", keyPair.get("private"))))).andReturn(client4);
|
||||
factory.create(eq(new IPSocket("144.175.1.4", 22)), eq(new Credentials("root", keyPair
|
||||
.get("private"))))).andReturn(client4);
|
||||
expect(
|
||||
factory.create(eq(new IPSocket("144.175.1.5", 22)),
|
||||
eq(new Credentials("root", keyPair.get("private"))))).andReturn(client5);
|
||||
factory.create(eq(new IPSocket("144.175.1.5", 22)), eq(new Credentials("root", keyPair
|
||||
.get("private"))))).andReturn(client5);
|
||||
|
||||
helloAndJava(client2);
|
||||
helloAndJava(client3);
|
||||
|
|
Loading…
Reference in New Issue