mirror of https://github.com/apache/jclouds.git
Issue 723:Allow for asynchronous script execution and client script execution handling
This commit is contained in:
parent
80e37b5a1b
commit
9e0aaa949b
|
@ -22,6 +22,7 @@ import java.util.Map;
|
|||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jclouds.compute.callables.ScriptStillRunningException;
|
||||
import org.jclouds.compute.domain.ComputeMetadata;
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.compute.domain.Hardware;
|
||||
|
@ -32,11 +33,14 @@ import org.jclouds.compute.domain.TemplateBuilder;
|
|||
import org.jclouds.compute.internal.BaseComputeService;
|
||||
import org.jclouds.compute.options.RunScriptOptions;
|
||||
import org.jclouds.compute.options.TemplateOptions;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
|
||||
import org.jclouds.domain.Location;
|
||||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
||||
/**
|
||||
|
@ -333,12 +337,35 @@ public interface ComputeService {
|
|||
* if the node is not found
|
||||
* @throws IllegalStateException
|
||||
* if the node is not in running state
|
||||
* @throws ScriptStillRunningException
|
||||
* if the script was still running after {@link Timeouts#scriptComplete}
|
||||
*
|
||||
* @see org.jclouds.compute.predicates.NodePredicates#runningWithTag(String)
|
||||
* @see org.jclouds.scriptbuilder.domain.Statements
|
||||
*/
|
||||
ExecResponse runScriptOnNode(String id, Statement runScript, RunScriptOptions options);
|
||||
|
||||
/**
|
||||
* Run the script on a specific node in the background, typically as {@code nohup}
|
||||
*
|
||||
* @param id
|
||||
* node the script is to be executed on
|
||||
* @param runScript
|
||||
* statement containing the script to run
|
||||
* @param options
|
||||
* nullable options to how to run the script, whether to override credentials
|
||||
* @return map with node identifiers and corresponding responses
|
||||
* @throws NoSuchElementException
|
||||
* if the node is not found
|
||||
* @throws IllegalStateException
|
||||
* if the node is not in running state
|
||||
*
|
||||
* @see org.jclouds.compute.predicates.NodePredicates#runningWithTag(String)
|
||||
* @see org.jclouds.scriptbuilder.domain.Statements
|
||||
*/
|
||||
@Beta
|
||||
ListenableFuture<ExecResponse> submitScriptOnNode(String id, Statement runScript, RunScriptOptions options);
|
||||
|
||||
/**
|
||||
* @see #runScriptOnNode(String, Statement, RunScriptOptions)
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.compute.callables;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.compute.predicates.ScriptStatusReturnsZero;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
import org.jclouds.scriptbuilder.InitBuilder;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.util.concurrent.AbstractFuture;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
/**
|
||||
* A future that works in tandem with a task that was invoked by {@link InitBuilder}
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class BlockUntilInitScriptStatusIsZeroThenReturnOutput extends AbstractFuture<ExecResponse> {
|
||||
|
||||
public static interface Factory {
|
||||
BlockUntilInitScriptStatusIsZeroThenReturnOutput create(SudoAwareInitManager commandRunner);
|
||||
}
|
||||
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final ExecutorService userThreads;
|
||||
private final SudoAwareInitManager commandRunner;
|
||||
private final RetryablePredicate<String> notRunningAnymore;
|
||||
|
||||
private boolean shouldCancel;
|
||||
|
||||
@Inject
|
||||
public BlockUntilInitScriptStatusIsZeroThenReturnOutput(
|
||||
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
|
||||
final ScriptStatusReturnsZero stateRunning, @Assisted final SudoAwareInitManager commandRunner) {
|
||||
this.commandRunner = checkNotNull(commandRunner, "commandRunner");
|
||||
this.userThreads = checkNotNull(userThreads, "userThreads");
|
||||
// arbitrarily high value, but Long.MAX_VALUE doesn't work!
|
||||
this.notRunningAnymore = new RetryablePredicate<String>(new Predicate<String>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(String arg0) {
|
||||
return commandRunner.runAction(arg0).getOutput().trim().equals("");
|
||||
}
|
||||
|
||||
}, TimeUnit.DAYS.toMillis(1)) {
|
||||
/**
|
||||
* make sure we stop the retry loop if someone cancelled the future, this keeps threads
|
||||
* from being consumed on dead tasks
|
||||
*/
|
||||
@Override
|
||||
protected boolean atOrAfter(Date end) {
|
||||
if (shouldCancel)
|
||||
Throwables.propagate(new TimeoutException("cancelled"));
|
||||
return super.atOrAfter(end);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* in case login credentials or user changes at runtime.
|
||||
*/
|
||||
public void setSshClient(SshClient client) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a thread that will either set the result of the future or the exception that took
|
||||
* place
|
||||
*/
|
||||
@PostConstruct
|
||||
BlockUntilInitScriptStatusIsZeroThenReturnOutput init() {
|
||||
userThreads.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
boolean complete = notRunningAnymore.apply("status");
|
||||
String stdout = commandRunner.runAction("tail").getOutput();
|
||||
String stderr = commandRunner.runAction("tailerr").getOutput();
|
||||
// TODO make ScriptBuilder save exit status on nuhup
|
||||
logger.debug("<< complete(%s) status(%s)", commandRunner.getStatement().getInstanceName(), complete);
|
||||
set(new ExecResponse(stdout, stderr, complete && !shouldCancel ? 0 : -1));
|
||||
} catch (Exception e) {
|
||||
setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interruptTask() {
|
||||
logger.debug("<< cancelled(%s)", commandRunner.getStatement().getInstanceName());
|
||||
commandRunner.runAction("stop");
|
||||
shouldCancel = true;
|
||||
super.interruptTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("running task[%s]", commandRunner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((commandRunner == null) ? 0 : commandRunner.hashCode());
|
||||
result = prime * result + ((logger == null) ? 0 : logger.hashCode());
|
||||
result = prime * result + ((notRunningAnymore == null) ? 0 : notRunningAnymore.hashCode());
|
||||
result = prime * result + (shouldCancel ? 1231 : 1237);
|
||||
result = prime * result + ((userThreads == null) ? 0 : userThreads.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BlockUntilInitScriptStatusIsZeroThenReturnOutput other = (BlockUntilInitScriptStatusIsZeroThenReturnOutput) obj;
|
||||
if (commandRunner == null) {
|
||||
if (other.commandRunner != null)
|
||||
return false;
|
||||
} else if (!commandRunner.equals(other.commandRunner))
|
||||
return false;
|
||||
if (logger == null) {
|
||||
if (other.logger != null)
|
||||
return false;
|
||||
} else if (!logger.equals(other.logger))
|
||||
return false;
|
||||
if (notRunningAnymore == null) {
|
||||
if (other.notRunningAnymore != null)
|
||||
return false;
|
||||
} else if (!notRunningAnymore.equals(other.notRunningAnymore))
|
||||
return false;
|
||||
if (shouldCancel != other.shouldCancel)
|
||||
return false;
|
||||
if (userThreads == null) {
|
||||
if (other.userThreads != null)
|
||||
return false;
|
||||
} else if (!userThreads.equals(other.userThreads))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecResponse get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException,
|
||||
ExecutionException {
|
||||
try {
|
||||
return super.get(timeout, unit);
|
||||
} catch (TimeoutException e) {
|
||||
throw new ScriptStillRunningException(e.getMessage(), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.jclouds.compute.options.RunScriptOptions;
|
|||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Separates out how one implements the ability to run a script on a node.
|
||||
|
@ -36,11 +37,10 @@ import com.google.common.annotations.Beta;
|
|||
public interface RunScriptOnNode extends Callable<ExecResponse> {
|
||||
|
||||
public interface Factory {
|
||||
RunScriptOnNode create(NodeMetadata node, String script);
|
||||
|
||||
RunScriptOnNode create(NodeMetadata node, Statement script);
|
||||
|
||||
RunScriptOnNode create(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
|
||||
ListenableFuture<ExecResponse> submit(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,9 +42,7 @@ import org.jclouds.scriptbuilder.statements.login.AdminAccess;
|
|||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshException;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
@ -54,21 +52,15 @@ import com.google.inject.assistedinject.AssistedInject;
|
|||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
|
||||
public class RunScriptOnNodeAsInitScriptUsingSsh extends SudoAwareInitManager implements RunScriptOnNode {
|
||||
public static final String PROPERTY_INIT_SCRIPT_PATTERN = "jclouds.compute.init-script-pattern";
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
protected final Function<NodeMetadata, SshClient> sshFactory;
|
||||
protected NodeMetadata node;
|
||||
protected final InitBuilder init;
|
||||
protected final boolean runAsRoot;
|
||||
protected final String initFile;
|
||||
|
||||
protected SshClient ssh;
|
||||
|
||||
/**
|
||||
*
|
||||
* determines the naming convention of init scripts.
|
||||
*
|
||||
* ex. {@code /tmp/init-%s}
|
||||
|
@ -80,24 +72,25 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
|
|||
@AssistedInject
|
||||
public RunScriptOnNodeAsInitScriptUsingSsh(Function<NodeMetadata, SshClient> sshFactory,
|
||||
@Assisted NodeMetadata node, @Assisted Statement script, @Assisted RunScriptOptions options) {
|
||||
this.sshFactory = checkNotNull(sshFactory, "sshFactory");
|
||||
this.node = checkNotNull(node, "node");
|
||||
String name = options.getTaskName();
|
||||
super(sshFactory, options.shouldRunAsRoot(), checkNotNull(node, "node"), init(script, options.getTaskName()));
|
||||
this.initFile = String.format(initScriptPattern, options.getTaskName());
|
||||
}
|
||||
|
||||
private static InitBuilder init(Statement script, String name) {
|
||||
if (name == null) {
|
||||
if (checkNotNull(script, "script") instanceof InitBuilder)
|
||||
name = InitBuilder.class.cast(script).getInstanceName();
|
||||
else
|
||||
name = "jclouds-script-" + System.currentTimeMillis();
|
||||
}
|
||||
this.init = checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script)
|
||||
: createInitScript(name, script);
|
||||
this.initFile = String.format(initScriptPattern, name);
|
||||
this.runAsRoot = options.shouldRunAsRoot();
|
||||
return checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script) : createInitScript(
|
||||
name, script);
|
||||
}
|
||||
|
||||
public static InitBuilder createInitScript(String name, Statement script) {
|
||||
String path = "/tmp/" + name;
|
||||
return new InitBuilder(name, path, path, Collections.<String, String> emptyMap(), Collections.singleton(script));
|
||||
@Override
|
||||
public RunScriptOnNodeAsInitScriptUsingSsh init() {
|
||||
super.init();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,10 +105,9 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunScriptOnNode init() {
|
||||
ssh = sshFactory.apply(node);
|
||||
return this;
|
||||
public static InitBuilder createInitScript(String name, Statement script) {
|
||||
String path = "/tmp/" + name;
|
||||
return new InitBuilder(name, path, path, Collections.<String, String> emptyMap(), Collections.singleton(script));
|
||||
}
|
||||
|
||||
public void refreshSshIfNewAdminCredentialsConfigured(AdminAccess input) {
|
||||
|
@ -167,55 +159,4 @@ public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode {
|
|||
ssh.exec(String.format("ln -fs %s %s", initFile, init.getInstanceName()));
|
||||
}
|
||||
|
||||
protected ExecResponse runAction(String action) {
|
||||
ExecResponse returnVal;
|
||||
String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action);
|
||||
returnVal = runCommand(command);
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace("<< %s[%s]", action, returnVal);
|
||||
else
|
||||
logger.debug("<< %s(%d)", action, returnVal.getExitCode());
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
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 execScriptAsRoot(String action) {
|
||||
String command;
|
||||
if (node.getCredentials().identity.equals("root")) {
|
||||
command = "./" + init.getInstanceName() + " " + action;
|
||||
} else if (node.getAdminPassword() != null) {
|
||||
command = String.format("echo '%s'|sudo -S ./%s %s", node.getAdminPassword(), init.getInstanceName(), action);
|
||||
} else {
|
||||
command = "sudo ./" + init.getInstanceName() + " " + action;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
protected String execScriptAsDefaultUser(String action) {
|
||||
return "./" + init.getInstanceName() + " " + action;
|
||||
}
|
||||
|
||||
public NodeMetadata getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this).add("node", node).add("name", init.getInstanceName())
|
||||
.add("runAsRoot", runAsRoot).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement getStatement() {
|
||||
return init;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,18 +20,19 @@ package org.jclouds.compute.callables;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.compute.domain.NodeMetadata;
|
||||
import org.jclouds.compute.options.RunScriptOptions;
|
||||
import org.jclouds.compute.predicates.ScriptStatusReturnsZero.CommandUsingClient;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
/**
|
||||
|
@ -39,29 +40,38 @@ import com.google.inject.assistedinject.Assisted;
|
|||
* @author Adrian Cole
|
||||
*/
|
||||
public class RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete extends RunScriptOnNodeAsInitScriptUsingSsh {
|
||||
protected final Predicate<CommandUsingClient> runScriptNotRunning;
|
||||
protected final Timeouts timeouts;
|
||||
protected final BlockUntilInitScriptStatusIsZeroThenReturnOutput.Factory statusFactory;
|
||||
|
||||
@Inject
|
||||
public RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete(
|
||||
@Named("SCRIPT_COMPLETE") Predicate<CommandUsingClient> runScriptNotRunning,
|
||||
BlockUntilInitScriptStatusIsZeroThenReturnOutput.Factory statusFactory, Timeouts timeouts,
|
||||
Function<NodeMetadata, SshClient> sshFactory, @Assisted NodeMetadata node, @Assisted Statement script,
|
||||
@Assisted RunScriptOptions options) {
|
||||
super(sshFactory, node, script, options);
|
||||
this.runScriptNotRunning = checkNotNull(runScriptNotRunning, "runScriptNotRunning");
|
||||
this.statusFactory = checkNotNull(statusFactory, "statusFactory");
|
||||
this.timeouts = checkNotNull(timeouts, "timeouts");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecResponse doCall() {
|
||||
ExecResponse returnVal = super.doCall();
|
||||
boolean complete = runScriptNotRunning.apply(new CommandUsingClient("./" + init.getInstanceName() + " status",
|
||||
ssh));
|
||||
logger.debug("<< complete(%s)", complete);
|
||||
if (logger.isDebugEnabled() || returnVal.getExitCode() != 0) {
|
||||
logger.debug("<< stdout from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(),
|
||||
ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tail").getOutput());
|
||||
logger.debug("<< stderr from %s as %s@%s\n%s", init.getInstanceName(), ssh.getUsername(),
|
||||
ssh.getHostAddress(), ssh.exec("./" + init.getInstanceName() + " tailerr").getOutput());
|
||||
try {
|
||||
return future().get(timeouts.scriptComplete, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
Throwables.propagate(e);
|
||||
return null;
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public BlockUntilInitScriptStatusIsZeroThenReturnOutput future() {
|
||||
ExecResponse returnVal = super.doCall();
|
||||
if (returnVal.getExitCode() != 0)
|
||||
logger.warn("task: %s had non-zero exit status: %s", init.getInstanceName(), returnVal);
|
||||
return statusFactory.create(this).init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete init() {
|
||||
return RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.class.cast(super.init());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.compute.callables;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class ScriptStillRunningException extends TimeoutException implements
|
||||
Supplier<BlockUntilInitScriptStatusIsZeroThenReturnOutput> {
|
||||
|
||||
/** The serialVersionUID */
|
||||
private static final long serialVersionUID = -7265376839848564663L;
|
||||
|
||||
private final BlockUntilInitScriptStatusIsZeroThenReturnOutput delegate;
|
||||
|
||||
public ScriptStillRunningException(String message, BlockUntilInitScriptStatusIsZeroThenReturnOutput delegate) {
|
||||
super(checkNotNull(message, "message"));
|
||||
this.delegate = checkNotNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockUntilInitScriptStatusIsZeroThenReturnOutput get() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.compute.callables;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.compute.domain.NodeMetadata;
|
||||
import org.jclouds.compute.domain.NodeMetadataBuilder;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.scriptbuilder.InitBuilder;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class SudoAwareInitManager {
|
||||
@Resource
|
||||
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
|
||||
protected Logger logger = Logger.NULL;
|
||||
protected NodeMetadata node;
|
||||
protected final InitBuilder init;
|
||||
protected final boolean runAsRoot;
|
||||
protected final Function<NodeMetadata, SshClient> sshFactory;
|
||||
protected SshClient ssh;
|
||||
|
||||
public SudoAwareInitManager(Function<NodeMetadata, SshClient> sshFactory, boolean runAsRoot, NodeMetadata node,
|
||||
InitBuilder init) {
|
||||
this.sshFactory = checkNotNull(sshFactory, "sshFactory");
|
||||
this.runAsRoot = runAsRoot;
|
||||
this.node = node;
|
||||
this.init = init;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public SudoAwareInitManager init() {
|
||||
ssh = sshFactory.apply(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void refreshSshIfNewAdminCredentialsConfigured(AdminAccess input) {
|
||||
if (input.getAdminCredentials() != null && input.shouldGrantSudoToAdminUser()) {
|
||||
ssh.disconnect();
|
||||
logger.debug(">> reconnecting as %s@%s", input.getAdminCredentials().identity, ssh.getHostAddress());
|
||||
ssh = sshFactory.apply(node = NodeMetadataBuilder.fromNodeMetadata(node).adminPassword(null).credentials(
|
||||
input.getAdminCredentials()).build());
|
||||
ssh.connect();
|
||||
}
|
||||
}
|
||||
|
||||
public ExecResponse runAction(String action) {
|
||||
ExecResponse returnVal;
|
||||
String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action);
|
||||
returnVal = runCommand(command);
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace("<< %s[%s]", action, returnVal);
|
||||
else
|
||||
logger.debug("<< %s(%d)", action, returnVal.getExitCode());
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public 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 execScriptAsRoot(String action) {
|
||||
String command;
|
||||
if (node.getCredentials().identity.equals("root")) {
|
||||
command = "./" + init.getInstanceName() + " " + action;
|
||||
} else if (node.getAdminPassword() != null) {
|
||||
command = String.format("echo '%s'|sudo -S ./%s %s", node.getAdminPassword(), init.getInstanceName(), action);
|
||||
} else {
|
||||
command = "sudo ./" + init.getInstanceName() + " " + action;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
protected String execScriptAsDefaultUser(String action) {
|
||||
return "./" + init.getInstanceName() + " " + action;
|
||||
}
|
||||
|
||||
public NodeMetadata getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this).add("node", node).add("name", init.getInstanceName()).add("runAsRoot",
|
||||
runAsRoot).toString();
|
||||
}
|
||||
|
||||
public InitBuilder getStatement() {
|
||||
return init;
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import javax.inject.Named;
|
|||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.collect.Memoized;
|
||||
import org.jclouds.compute.callables.BlockUntilInitScriptStatusIsZeroThenReturnOutput;
|
||||
import org.jclouds.compute.callables.RunScriptOnNode;
|
||||
import org.jclouds.compute.callables.RunScriptOnNodeAsInitScriptUsingSsh;
|
||||
import org.jclouds.compute.callables.RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete;
|
||||
|
@ -53,7 +54,6 @@ import org.jclouds.location.config.LocationModule;
|
|||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -83,25 +83,26 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
bind(new TypeLiteral<Function<TemplateOptions, Statement>>() {
|
||||
}).to(TemplateOptionsToStatement.class);
|
||||
|
||||
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));
|
||||
install(new FactoryModuleBuilder().implement(RunScriptOnNodeUsingSsh.class, Names.named("direct"),
|
||||
RunScriptOnNodeUsingSsh.class).implement(RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.class,
|
||||
Names.named("blocking"), RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete.class).implement(
|
||||
RunScriptOnNodeAsInitScriptUsingSsh.class, Names.named("nonblocking"),
|
||||
RunScriptOnNodeAsInitScriptUsingSsh.class).build(RunScriptOnNodeFactoryImpl.Factory.class));
|
||||
|
||||
install(new PersistNodeCredentialsModule());
|
||||
|
||||
bind(RunScriptOnNode.Factory.class).to(RunScriptOnNodeFactoryImpl.class);
|
||||
|
||||
install(new FactoryModuleBuilder().implement(new TypeLiteral<Callable<Void>>() {
|
||||
}, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.class)
|
||||
.implement(new TypeLiteral<Function<NodeMetadata, Void>>() {
|
||||
}, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.class)
|
||||
.build(CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory.class));
|
||||
}, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.class).implement(
|
||||
new TypeLiteral<Function<NodeMetadata, Void>>() {
|
||||
}, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.class).build(
|
||||
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory.class));
|
||||
|
||||
install(new FactoryModuleBuilder().implement(new TypeLiteral<Callable<RunScriptOnNode>>() {
|
||||
}, InitializeRunScriptOnNodeOrPlaceInBadMap.class).build(InitializeRunScriptOnNodeOrPlaceInBadMap.Factory.class));
|
||||
|
||||
install(new FactoryModuleBuilder().build(BlockUntilInitScriptStatusIsZeroThenReturnOutput.Factory.class));
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
@ -110,13 +111,14 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
static interface Factory {
|
||||
|
||||
@Named("direct")
|
||||
RunScriptOnNode exec(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
RunScriptOnNodeUsingSsh exec(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
|
||||
@Named("blocking")
|
||||
RunScriptOnNode backgroundAndBlockOnComplete(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
RunScriptOnNodeAsInitScriptUsingSshAndBlockUntilComplete backgroundAndBlockOnComplete(NodeMetadata node,
|
||||
Statement script, RunScriptOptions options);
|
||||
|
||||
@Named("nonblocking")
|
||||
RunScriptOnNode background(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
RunScriptOnNodeAsInitScriptUsingSsh background(NodeMetadata node, Statement script, RunScriptOptions options);
|
||||
}
|
||||
|
||||
private final Factory factory;
|
||||
|
@ -132,18 +134,18 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
checkNotNull(runScript, "runScript");
|
||||
checkNotNull(options, "options");
|
||||
return !options.shouldWrapInInitScript() ? factory.exec(node, runScript, options) : (options
|
||||
.shouldBlockOnComplete() ? factory.backgroundAndBlockOnComplete(node, runScript, options) : factory
|
||||
.background(node, runScript, options));
|
||||
.shouldBlockOnComplete() ? factory.backgroundAndBlockOnComplete(node, runScript, options) : factory
|
||||
.background(node, runScript, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunScriptOnNode create(NodeMetadata node, String script) {
|
||||
return create(node, Statements.exec(checkNotNull(script, "script")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunScriptOnNode create(NodeMetadata node, Statement script) {
|
||||
return create(node, script, RunScriptOptions.NONE);
|
||||
public BlockUntilInitScriptStatusIsZeroThenReturnOutput submit(NodeMetadata node, Statement script,
|
||||
RunScriptOptions options) {
|
||||
checkNotNull(node, "node");
|
||||
checkNotNull(script, "script");
|
||||
checkNotNull(options, "options");
|
||||
options.shouldWrapInInitScript();
|
||||
return factory.backgroundAndBlockOnComplete(node, script, options).init().future();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,8 +175,8 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
}
|
||||
|
||||
/**
|
||||
* supplies how the tag is encoded into the name. A string of hex characters
|
||||
* is the last argument and tag is the first
|
||||
* supplies how the tag is encoded into the name. A string of hex characters is the last argument
|
||||
* and tag is the first
|
||||
*/
|
||||
@Provides
|
||||
@Named("NAMING_CONVENTION")
|
||||
|
@ -209,14 +211,14 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
@Singleton
|
||||
@Memoized
|
||||
protected Supplier<Set<? extends Image>> supplyImageCache(@Named(PROPERTY_SESSION_INTERVAL) long seconds,
|
||||
final Supplier<Set<? extends Image>> imageSupplier) {
|
||||
final Supplier<Set<? extends Image>> imageSupplier) {
|
||||
return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<Set<? extends Image>>(authException,
|
||||
seconds, new Supplier<Set<? extends Image>>() {
|
||||
@Override
|
||||
public Set<? extends Image> get() {
|
||||
return imageSupplier.get();
|
||||
}
|
||||
});
|
||||
seconds, new Supplier<Set<? extends Image>>() {
|
||||
@Override
|
||||
public Set<? extends Image> get() {
|
||||
return imageSupplier.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -243,14 +245,14 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
@Singleton
|
||||
@Memoized
|
||||
protected Supplier<Set<? extends Hardware>> supplySizeCache(@Named(PROPERTY_SESSION_INTERVAL) long seconds,
|
||||
final Supplier<Set<? extends Hardware>> hardwareSupplier) {
|
||||
final Supplier<Set<? extends Hardware>> hardwareSupplier) {
|
||||
return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<Set<? extends Hardware>>(authException,
|
||||
seconds, new Supplier<Set<? extends Hardware>>() {
|
||||
@Override
|
||||
public Set<? extends Hardware> get() {
|
||||
return hardwareSupplier.get();
|
||||
}
|
||||
});
|
||||
seconds, new Supplier<Set<? extends Hardware>>() {
|
||||
@Override
|
||||
public Set<? extends Hardware> get() {
|
||||
return hardwareSupplier.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -99,6 +99,7 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -613,6 +614,32 @@ public class BaseComputeService implements ComputeService {
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<ExecResponse> submitScriptOnNode(String id, final Statement runScript,
|
||||
RunScriptOptions options) {
|
||||
NodeMetadata node = this.getNodeMetadata(id);
|
||||
if (node == null)
|
||||
throw new NoSuchElementException(id);
|
||||
if (node.getState() != NodeState.RUNNING)
|
||||
throw new IllegalStateException("node " + id
|
||||
+ " needs to be running before executing a script on it. current state: " + node.getState());
|
||||
initAdminAccess.visit(runScript);
|
||||
final NodeMetadata node1 = updateNodeWithCredentialsIfPresent(node, options);
|
||||
ListenableFuture<ExecResponse> response = runScriptOnNodeFactory.submit(node, runScript, options);
|
||||
response.addListener(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
persistNodeCredentials.ifAdminAccess(runScript).apply(node1);
|
||||
}
|
||||
|
||||
}, executor);
|
||||
return response;
|
||||
}
|
||||
|
||||
private Iterable<? extends RunScriptOnNode> transformNodesIntoInitializedScriptRunners(
|
||||
Iterable<? extends NodeMetadata> nodes, Statement script, RunScriptOptions options,
|
||||
Map<NodeMetadata, Exception> badNodes) {
|
||||
|
|
|
@ -34,21 +34,20 @@ import com.google.common.base.Predicate;
|
|||
* @author Adrian Cole
|
||||
*/
|
||||
@Singleton
|
||||
public class ScriptStatusReturnsZero implements
|
||||
Predicate<ScriptStatusReturnsZero.CommandUsingClient> {
|
||||
public class ScriptStatusReturnsZero implements Predicate<ScriptStatusReturnsZero.CommandUsingClient> {
|
||||
|
||||
@Resource
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
@Override
|
||||
public boolean apply(CommandUsingClient commandUsingClient) {
|
||||
logger.trace("looking for [%s] state on %s@%s", commandUsingClient.command,
|
||||
commandUsingClient.client.getUsername(), commandUsingClient.client.getHostAddress());
|
||||
logger.trace("looking for [%s] state on %s@%s", commandUsingClient.command, commandUsingClient.client
|
||||
.getUsername(), commandUsingClient.client.getHostAddress());
|
||||
ExecResponse response = refresh(commandUsingClient);
|
||||
while (response.getExitCode() == -1)
|
||||
response = refresh(commandUsingClient);
|
||||
logger.trace("%s@%s: looking for exit code 0: currently: %s", commandUsingClient.client
|
||||
.getUsername(), commandUsingClient.client.getHostAddress(), response.getExitCode());
|
||||
logger.trace("%s@%s: looking for exit code 0: currently: %s", commandUsingClient.client.getUsername(),
|
||||
commandUsingClient.client.getHostAddress(), response.getExitCode());
|
||||
return 0 == response.getExitCode();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
package org.jclouds.compute;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Predicates.and;
|
||||
import static com.google.common.base.Predicates.not;
|
||||
|
@ -50,11 +51,12 @@ import java.io.FileNotFoundException;
|
|||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
@ -99,6 +101,7 @@ import com.google.common.base.Supplier;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Module;
|
||||
|
||||
|
@ -126,7 +129,6 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
protected String endpoint;
|
||||
protected String apiversion;
|
||||
|
||||
|
||||
protected Properties setupProperties() {
|
||||
Properties overrides = new Properties();
|
||||
overrides.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true");
|
||||
|
@ -251,13 +253,13 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
|
||||
for (Entry<? extends NodeMetadata, ExecResponse> response : client.runScriptOnNodesMatching(
|
||||
runningInGroup(group), Statements.exec("hostname"),
|
||||
wrapInInitScript(false).runAsRoot(false).overrideCredentialsWith(good)).entrySet()){
|
||||
wrapInInitScript(false).runAsRoot(false).overrideCredentialsWith(good)).entrySet()) {
|
||||
checkResponseEqualsHostname(response.getValue(), response.getKey());
|
||||
}
|
||||
|
||||
// test single-node execution
|
||||
ExecResponse response = client.runScriptOnNode(node.getId(), "hostname", wrapInInitScript(false)
|
||||
.runAsRoot(false));
|
||||
ExecResponse response = client.runScriptOnNode(node.getId(), "hostname", wrapInInitScript(false).runAsRoot(
|
||||
false));
|
||||
checkResponseEqualsHostname(response, node);
|
||||
OperatingSystem os = node.getOperatingSystem();
|
||||
|
||||
|
@ -279,10 +281,18 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
checkNodes(nodes, group);
|
||||
|
||||
// test adding AdminAccess later changes the default boot user, in this case to foo
|
||||
response = client.runScriptOnNode(node.getId(), AdminAccess.builder().adminUsername("foo").build(), nameTask("adminUpdate"));
|
||||
ListenableFuture<ExecResponse> future = client.submitScriptOnNode(node.getId(), AdminAccess.builder()
|
||||
.adminUsername("foo").build(), nameTask("adminUpdate"));
|
||||
|
||||
response = client.runScriptOnNode(node.getId(), "echo $USER", wrapInInitScript(false)
|
||||
.runAsRoot(false));
|
||||
response = future.get(3, TimeUnit.MINUTES);
|
||||
|
||||
assert response.getExitCode() == 0 : node.getId() + ": " + response;
|
||||
|
||||
weCanCancelTasks(node);
|
||||
|
||||
assert response.getExitCode() == 0 : node.getId() + ": " + response;
|
||||
|
||||
response = client.runScriptOnNode(node.getId(), "echo $USER", wrapInInitScript(false).runAsRoot(false));
|
||||
|
||||
assert response.getOutput().trim().equals("foo") : node.getId() + ": " + response;
|
||||
|
||||
|
@ -291,6 +301,32 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void weCanCancelTasks(NodeMetadata node) throws InterruptedException, ExecutionException {
|
||||
ListenableFuture<ExecResponse> future;
|
||||
future = client.submitScriptOnNode(node.getId(), Statements.exec("sleep 300"), nameTask("sleeper"));
|
||||
ExecResponse response = null;
|
||||
try {
|
||||
response = future.get(1, TimeUnit.MILLISECONDS);
|
||||
assert false : node.getId() + ": " + response;
|
||||
} catch (TimeoutException e) {
|
||||
assert !future.isDone();
|
||||
response = client.runScriptOnNode(node.getId(), Statements.exec("./sleeper status"), wrapInInitScript(false)
|
||||
.runAsRoot(false));
|
||||
assert !response.getOutput().trim().equals("") : node.getId() + ": " + response;
|
||||
future.cancel(true);
|
||||
response = client.runScriptOnNode(node.getId(), Statements.exec("./sleeper status"), wrapInInitScript(false)
|
||||
.runAsRoot(false));
|
||||
assert response.getOutput().trim().equals("") : node.getId() + ": " + response;
|
||||
try {
|
||||
future.get();
|
||||
assert false : future;
|
||||
} catch (CancellationException e1) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkResponseEqualsHostname(ExecResponse execResponse, NodeMetadata node1) {
|
||||
assert execResponse.getOutput().trim().equals(node1.getHostname()) : node1 + ": " + execResponse;
|
||||
}
|
||||
|
@ -407,7 +443,7 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
protected Map<? extends NodeMetadata, ExecResponse> runScriptWithCreds(final String group, OperatingSystem os,
|
||||
Credentials creds) throws RunScriptOnNodesException {
|
||||
return client.runScriptOnNodesMatching(runningInGroup(group), buildScript(os), overrideCredentialsWith(creds)
|
||||
.nameTask("runScriptWithCreds"));
|
||||
.nameTask("runScriptWithCreds"));
|
||||
}
|
||||
|
||||
protected void checkNodes(Iterable<? extends NodeMetadata> nodes, String group) throws IOException {
|
||||
|
@ -461,8 +497,8 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
}
|
||||
|
||||
protected void assertNodeZero(Collection<? extends NodeMetadata> metadataSet) {
|
||||
assert metadataSet.size() == 0 : format("nodes left in set: [%s] which didn't match set: [%s]",
|
||||
metadataSet, nodes);
|
||||
assert metadataSet.size() == 0 : format("nodes left in set: [%s] which didn't match set: [%s]", metadataSet,
|
||||
nodes);
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "testGet")
|
||||
|
@ -484,8 +520,7 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
public boolean apply(NodeMetadata input) {
|
||||
boolean returnVal = input.getState() == NodeState.SUSPENDED;
|
||||
if (!returnVal)
|
||||
getAnonymousLogger().warning(
|
||||
format("node %s in state %s%n", input.getId(), input.getState()));
|
||||
getAnonymousLogger().warning(format("node %s in state %s%n", input.getId(), input.getState()));
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
|
@ -533,7 +568,7 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
for (NodeMetadata node : filter(client.listNodesDetailsMatching(all()), inGroup(group))) {
|
||||
assert node.getState() == NodeState.TERMINATED : node;
|
||||
assert context.getCredentialStore().get("node#" + node.getId()) == null : "credential should have been null for "
|
||||
+ "node#" + node.getId();
|
||||
+ "node#" + node.getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,21 +634,22 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
try {
|
||||
ImmutableMap<String, String> userMetadata = ImmutableMap.<String, String> of("Name", group);
|
||||
long startSeconds = currentTimeMillis();
|
||||
NodeMetadata node = getOnlyElement(client.createNodesInGroup(group, 1,
|
||||
inboundPorts(22, 8080).blockOnPort(22, 300)
|
||||
.userMetadata(userMetadata)));
|
||||
NodeMetadata node = getOnlyElement(client.createNodesInGroup(group, 1, inboundPorts(22, 8080).blockOnPort(22,
|
||||
300).userMetadata(userMetadata)));
|
||||
final String nodeId = node.getId();
|
||||
long createSeconds = (currentTimeMillis() - startSeconds) / 1000;
|
||||
|
||||
checkUserMetadataInNodeEquals(node, userMetadata);
|
||||
|
||||
getAnonymousLogger().info(
|
||||
format("<< available node(%s) os(%s) in %ss", node.getId(), node.getOperatingSystem(), createSeconds));
|
||||
getAnonymousLogger()
|
||||
.info(
|
||||
format("<< available node(%s) os(%s) in %ss", node.getId(), node.getOperatingSystem(),
|
||||
createSeconds));
|
||||
|
||||
startSeconds = currentTimeMillis();
|
||||
|
||||
// note this is a dependency on the template resolution so we have the right process per
|
||||
// operating system. moreover, we wish this to run as root, so that it can change ip
|
||||
// operating system. moreover, we wish this to run as root, so that it can change ip
|
||||
// tables rules and setup our admin user
|
||||
client.runScriptOnNode(nodeId, installAdminUserJBossAndOpenPorts(node.getOperatingSystem()),
|
||||
nameTask("configure-jboss"));
|
||||
|
@ -621,9 +657,9 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
long configureSeconds = (currentTimeMillis() - startSeconds) / 1000;
|
||||
|
||||
getAnonymousLogger().info(
|
||||
format("<< configured node(%s) with %s in %ss", nodeId,
|
||||
client.runScriptOnNode(nodeId, "java -fullversion", runAsRoot(false).wrapInInitScript(false)).getOutput().trim(),
|
||||
configureSeconds));
|
||||
format("<< configured node(%s) with %s in %ss", nodeId, client.runScriptOnNode(nodeId,
|
||||
"java -fullversion", runAsRoot(false).wrapInInitScript(false)).getOutput().trim(),
|
||||
configureSeconds));
|
||||
|
||||
trackAvailabilityOfJBossProcessOnNode(new Supplier<ExecResponse>() {
|
||||
|
||||
|
@ -664,7 +700,7 @@ public abstract class BaseComputeServiceLiveTest {
|
|||
|
||||
protected void checkUserMetadataInNodeEquals(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
|
||||
assert node.getUserMetadata().equals(userMetadata) : String.format("node userMetadata did not match %s %s",
|
||||
userMetadata, node);
|
||||
userMetadata, node);
|
||||
}
|
||||
|
||||
public void testListImages() throws Exception {
|
||||
|
|
|
@ -85,7 +85,6 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
// restart of jboss
|
||||
expect(socketOpen.apply(new IPSocket("144.175.1.1", 8080))).andReturn(true).times(2);
|
||||
|
||||
|
||||
replay(socketOpen);
|
||||
|
||||
preciseSocketTester = socketTester = new RetryablePredicate<IPSocket>(socketOpen, 1, 1, TimeUnit.MILLISECONDS);
|
||||
|
@ -178,9 +177,8 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
|
||||
client2New.connect();
|
||||
try {
|
||||
runScript(client2New, "adminUpdate",
|
||||
Strings2.toStringAndClose(StubComputeServiceIntegrationTest.class
|
||||
.getResourceAsStream("/runscript_adminUpdate.sh")), 2);
|
||||
runScript(client2New, "adminUpdate", Strings2.toStringAndClose(StubComputeServiceIntegrationTest.class
|
||||
.getResourceAsStream("/runscript_adminUpdate.sh")), 2);
|
||||
} catch (IOException e) {
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
|
@ -193,7 +191,6 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
expect(client2Foo.exec("echo $USER\n")).andReturn(new ExecResponse("foo\n", "", 0));
|
||||
client2Foo.disconnect();
|
||||
|
||||
|
||||
expect(factory.create(new IPSocket("144.175.1.3", 22), new Credentials("root", "password3"))).andReturn(
|
||||
client3).times(2);
|
||||
expect(factory.create(new IPSocket("144.175.1.4", 22), new Credentials("root", "password4"))).andReturn(
|
||||
|
@ -437,6 +434,12 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
super.testAScriptExecutionAfterBootWithBasicTemplate();
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
@Override
|
||||
public void weCanCancelTasks(NodeMetadata node) throws InterruptedException, ExecutionException {
|
||||
// not sure how to do multithreading in a mock so that tests can work
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = { "testCompareSizes" })
|
||||
public void testCreateAndRunAService() throws Exception {
|
||||
super.testCreateAndRunAService();
|
||||
|
|
|
@ -91,18 +91,18 @@ public class RetryablePredicate<T> implements Predicate<T> {
|
|||
return false;
|
||||
}
|
||||
|
||||
long nextMaxInterval(long attempt, Date end) {
|
||||
protected long nextMaxInterval(long attempt, Date end) {
|
||||
long interval = (period * (long) Math.pow(attempt, 1.5));
|
||||
interval = interval > maxPeriod ? maxPeriod : interval;
|
||||
long max = end.getTime() - System.currentTimeMillis();
|
||||
return (interval > max) ? max : interval;
|
||||
}
|
||||
|
||||
boolean before(Date end) {
|
||||
protected boolean before(Date end) {
|
||||
return new Date().compareTo(end) <= 1;
|
||||
}
|
||||
|
||||
boolean atOrAfter(Date end) {
|
||||
protected boolean atOrAfter(Date end) {
|
||||
return new Date().compareTo(end) >= 0;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue