added runScriptOnNodesWithTag methods. theoretically it is supported for any cloud as a part of compute service. (issue 222)

This commit is contained in:
Alex Yarmula 2010-04-10 19:11:01 -07:00
parent d4d9d76388
commit 882bf5f651
7 changed files with 858 additions and 622 deletions

View File

@ -22,11 +22,13 @@ import static org.testng.Assert.assertEquals;
import org.jclouds.compute.BaseComputeServiceLiveTest; import org.jclouds.compute.BaseComputeServiceLiveTest;
import org.jclouds.compute.domain.*; import org.jclouds.compute.domain.*;
import org.jclouds.domain.Credentials;
import org.jclouds.ssh.jsch.config.JschSshClientModule; import org.jclouds.ssh.jsch.config.JschSshClientModule;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import java.util.Map; import java.util.Map;
/** /**
@ -62,11 +64,15 @@ public class EC2ComputeServiceLiveTest extends BaseComputeServiceLiveTest {
} }
@Test @Test
public void testCredentialsMapping() { public void testScriptExecution() throws Exception {
Template simpleTemplate = client.templateBuilder().smallest().build(); Template simpleTemplate = client.templateBuilder().smallest().build();
// client.runNodesWithTag("ec2", 1, simpleTemplate); client.runNodesWithTag("ec2", 1, simpleTemplate);
Map<String, ? extends NodeMetadata> map = client.getNodesWithTag("ec2"); Map<String, ? extends NodeMetadata> map = client.getNodesWithTag("ec2");
int a = 5; NodeMetadata node = map.values().iterator().next();
Credentials creds = new Credentials("ubuntu", keyPair.get("public"));
client.runScriptOnNodesWithTag("ec2", creds,
"mkdir ~/ahha; sleep 3".getBytes());
client.destroyNodesWithTag("ec2");
} }
@Override @Override

View File

@ -27,9 +27,12 @@ import org.jclouds.compute.domain.Size;
import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.internal.BaseComputeService; import org.jclouds.compute.internal.BaseComputeService;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location; import org.jclouds.domain.Location;
import com.google.inject.ImplementedBy; import com.google.inject.ImplementedBy;
import org.jclouds.ssh.ExecResponse;
/** /**
* Provides portable access to launching compute instances. * Provides portable access to launching compute instances.
@ -152,4 +155,26 @@ public interface ComputeService {
*/ */
Map<String, ? extends NodeMetadata> getNodesWithTag(String tag); Map<String, ? extends NodeMetadata> getNodesWithTag(String tag);
/**
* Runs the script without any additional options
*
* @see #runScriptOnNodesWithTag(String, org.jclouds.domain.Credentials,
* byte[], org.jclouds.compute.options.RunScriptOptions)
*/
Map<String, ExecResponse> runScriptOnNodesWithTag(String tag, Credentials credentials,
byte[] runScript);
/**
* Run the script on all nodes with the specific tag.
*
* @param tag tag to look up the nodes
* @param credentials credentials to use (same for all nodes)
* @param runScript script to run in byte format. If the script is a string, use
* {@link String#getBytes()} to retrieve the bytes
* @param options nullable options to how to run the script
* @return map with node identifiers and corresponding responses
*/
Map<String, ExecResponse> runScriptOnNodesWithTag(String tag, Credentials credentials,
byte[] runScript, RunScriptOptions options);
} }

View File

@ -27,6 +27,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.concurrent.ConcurrentUtils.awaitCompletion; import static org.jclouds.concurrent.ConcurrentUtils.awaitCompletion;
import static org.jclouds.concurrent.ConcurrentUtils.makeListenable; import static org.jclouds.concurrent.ConcurrentUtils.makeListenable;
import static org.jclouds.util.Utils.checkNotEmpty;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -34,6 +35,7 @@ import java.util.Map.Entry;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -51,6 +53,7 @@ import org.jclouds.compute.domain.NodeState;
import org.jclouds.compute.domain.Size; import org.jclouds.compute.domain.Size;
import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.compute.strategy.DestroyNodeStrategy; import org.jclouds.compute.strategy.DestroyNodeStrategy;
import org.jclouds.compute.strategy.GetNodeMetadataStrategy; import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
@ -58,6 +61,7 @@ import org.jclouds.compute.strategy.ListNodesStrategy;
import org.jclouds.compute.strategy.RebootNodeStrategy; import org.jclouds.compute.strategy.RebootNodeStrategy;
import org.jclouds.compute.strategy.RunNodesAndAddToSetStrategy; import org.jclouds.compute.strategy.RunNodesAndAddToSetStrategy;
import org.jclouds.compute.util.ComputeUtils; import org.jclouds.compute.util.ComputeUtils;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location; import org.jclouds.domain.Location;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
@ -68,6 +72,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.jclouds.ssh.ExecResponse;
/** /**
* *
@ -316,4 +321,58 @@ public class BaseComputeService implements ComputeService {
awaitCompletion(responses, executor, null, logger, "rebooting nodes"); awaitCompletion(responses, executor, null, logger, "rebooting nodes");
logger.debug("<< rebooted"); logger.debug("<< rebooted");
} }
/**
* @see #runScriptOnNodesWithTag(String, org.jclouds.domain.Credentials, byte[],
* org.jclouds.compute.options.RunScriptOptions)
*/
public Map<String, ExecResponse> runScriptOnNodesWithTag(String tag, Credentials credentials,
byte[] runScript) {
return runScriptOnNodesWithTag(tag, credentials, runScript, RunScriptOptions.NONE);
}
/**
* Run the script on all nodes with the specific tag.
*
* @param tag tag to look up the nodes
* @param credentials nullable credentials to use (same for all nodes).
* @param runScript script to run in byte format. If the script is a string, use
* {@link String#getBytes()} to retrieve the bytes
* @param options nullable options to how to run the script
* @return map with node identifiers and corresponding responses
*/
public Map<String, ExecResponse> runScriptOnNodesWithTag(String tag, @Nullable Credentials credentials,
byte[] runScript, @Nullable RunScriptOptions options) {
checkNotEmpty(tag, "Tag must be provided");
checkNotNull(runScript,
"The script (represented by bytes array - use \"script\".getBytes() must be provided");
if(options == null) options = RunScriptOptions.NONE;
Map<String, ? extends NodeMetadata> nodes = getNodesWithTag(tag);
Map<String, ExecResponse> responses = Maps.newHashMap();
for(NodeMetadata node : nodes.values()) {
if(NodeState.RUNNING != node.getState()) continue; //make sure the node is active
if(options.isOverrideCredentials()) {
//override the credentials with provided to this method
checkNotNull(credentials, "If the credentials need to be overridden, they can't be null");
node = ComputeUtils.installNewCredentials(node, credentials);
} else {
//don't override
checkNotNull(node.getCredentials(), "If the default credentials need to be used, they can't be null");
}
//todo: execute script as root if required
ComputeUtils.SshCallable<?> callable = utils.runScriptOnNode(node, "computeserv.sh", runScript);
Map<ComputeUtils.SshCallable<?>, ?> scriptRunResults = utils.runCallablesOnNode(node,
Sets.newHashSet(callable),
null);
responses.put(node.getId(),
(ExecResponse) scriptRunResults.get(callable));
}
return responses;
}
} }

View File

@ -0,0 +1,88 @@
/**
*
* 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.options;
/**
* Enables additional options for running a script.
*
* @author Oleksiy Yarmula
*/
public class RunScriptOptions {
/**
* Default options. The default settings are:
* <ul>
* <li>override the credentials with ones supplied in
* call to {@link org.jclouds.compute.ComputeService#runScriptOnNodesWithTag}</li>
* <li>do not run the script as root (run with current privileges)</li>
* </ul>
*/
public static final RunScriptOptions NONE = new RunScriptOptions();
private boolean overrideCredentials = true;
private boolean runAsRoot = false;
private void overrideCredentials(boolean overrideCredentials) {
this.overrideCredentials = overrideCredentials;
}
private void runAsRoot(boolean runAsRoot) {
this.runAsRoot = runAsRoot;
}
/**
* Whether to override the credentials with ones supplied in
* call to {@link org.jclouds.compute.ComputeService#runScriptOnNodesWithTag}.
* By default, true.
* @return value
*/
public boolean isOverrideCredentials() {
return overrideCredentials;
}
/**
* Whether to run the script as root (run with current privileges).
* By default, false.
* @return value
*/
public boolean isRunAsRoot() {
return runAsRoot;
}
public static class Builder {
private RunScriptOptions options;
public Builder overrideCredentials(boolean value) {
if(options == null) options = new RunScriptOptions();
options.overrideCredentials(value);
return this;
}
public Builder runAsRoot(boolean value) {
if(options == null) options = new RunScriptOptions();
options.runAsRoot(value);
return this;
}
public RunScriptOptions build() {
return options;
}
}
}

View File

@ -30,6 +30,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -39,9 +40,11 @@ import javax.inject.Named;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.compute.domain.ComputeMetadata; import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.internal.NodeMetadataImpl;
import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.concurrent.ConcurrentUtils; import org.jclouds.concurrent.ConcurrentUtils;
import org.jclouds.domain.Credentials;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.InitBuilder; import org.jclouds.scriptbuilder.InitBuilder;
import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.OsFamily;
@ -127,7 +130,7 @@ public class ComputeUtils {
return new RunScriptOnNode(runScriptNotRunning, node, scriptName, script); return new RunScriptOnNode(runScriptNotRunning, node, scriptName, script);
} }
public void runCallablesOnNode(NodeMetadata node, Iterable<? extends SshCallable<?>> parallel, public Map<SshCallable<?>, ?> runCallablesOnNode(NodeMetadata node, Iterable<? extends SshCallable<?>> parallel,
@Nullable SshCallable<?> last) { @Nullable SshCallable<?> last) {
checkState(this.sshFactory != null, "runScript requested, but no SshModule configured"); checkState(this.sshFactory != null, "runScript requested, but no SshModule configured");
@ -161,7 +164,7 @@ public class ComputeUtils {
Throwables.propagate(e); Throwables.propagate(e);
} }
} }
break; return transform(responses);
} catch (RuntimeException from) { } catch (RuntimeException from) {
if (Iterables.size(Iterables.filter(Throwables.getCausalChain(from), if (Iterables.size(Iterables.filter(Throwables.getCausalChain(from),
ConnectException.class)) >= 1// auth fail sometimes happens in EC2 ConnectException.class)) >= 1// auth fail sometimes happens in EC2
@ -170,16 +173,31 @@ public class ComputeUtils {
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Throwables.propagate(e); throw Throwables.propagate(e);
} }
continue; continue;
} }
Throwables.propagate(from); throw Throwables.propagate(from);
} finally { } finally {
if (ssh != null) if (ssh != null)
ssh.disconnect(); ssh.disconnect();
} }
} }
throw new RuntimeException(String.format("Couldn't connect to node %s and run the script", node.getId()));
}
public <T> Map<SshCallable<?>, T> transform(Map<SshCallable<?>, ListenableFuture<?>> responses) {
Map<SshCallable<?>, T> actualResponses = Maps.newHashMap();
for(Map.Entry<SshCallable<?>, ListenableFuture<?>> entry : responses.entrySet()) {
try {
actualResponses.put(entry.getKey(), (T) entry.getValue().get());
} catch(InterruptedException e) {
throw Throwables.propagate(e);
} catch(ExecutionException e) {
throw Throwables.propagate(e);
}
}
return actualResponses;
} }
public static interface SshCallable<T> extends Callable<T> { public static interface SshCallable<T> extends Callable<T> {
@ -302,4 +320,15 @@ public class ComputeUtils {
return createdNode.getCredentials().key != null return createdNode.getCredentials().key != null
&& createdNode.getCredentials().key.startsWith("-----BEGIN RSA PRIVATE KEY-----"); && createdNode.getCredentials().key.startsWith("-----BEGIN RSA PRIVATE KEY-----");
} }
/**
* Given the instances of {@link NodeMetadata} (immutable)
* and {@link Credentials} (immutable), returns a new instance of {@link NodeMetadata}
* that has new credentials
*/
public static NodeMetadata installNewCredentials(NodeMetadata node, Credentials newCredentials) {
return new NodeMetadataImpl(node.getId(), node.getName(), node.getLocationId(), node.getUri(),
node.getUserMetadata(), node.getTag(), node.getState(), node. getPublicAddresses(),
node.getPrivateAddresses(), node.getExtra(), newCredentials);
}
} }

View File

@ -18,6 +18,7 @@
*/ */
package org.jclouds.util; package org.jclouds.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.util.Patterns.CHAR_TO_PATTERN; import static org.jclouds.util.Patterns.CHAR_TO_PATTERN;
import static org.jclouds.util.Patterns.TOKEN_TO_PATTERN; import static org.jclouds.util.Patterns.TOKEN_TO_PATTERN;
@ -201,4 +202,25 @@ public class Utils {
return builder.toString(); return builder.toString();
} }
/**
* Will throw an exception if the argument is null or empty.
* @param nullableString
* string to verify. Can be null or empty.
*/
public static void checkNotEmpty(String nullableString) {
checkNotEmpty(nullableString, "Argument can't be null or empty");
}
/**
* Will throw an exception if the argument is null or empty. Accepts
* a custom error message.
* @param nullableString
* string to verify. Can be null or empty.
* @param message
* message to show in case of exception
*/
public static void checkNotEmpty(String nullableString, String message) {
checkArgument(nullableString != null && nullableString.length() > 0,
message);
}
} }

View File

@ -32,6 +32,8 @@ import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState; import org.jclouds.compute.domain.NodeState;
import org.jclouds.compute.domain.OsFamily; import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.domain.Credentials;
import org.jclouds.rest.RestContext; import org.jclouds.rest.RestContext;
import org.jclouds.ssh.jsch.config.JschSshClientModule; import org.jclouds.ssh.jsch.config.JschSshClientModule;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
@ -78,7 +80,7 @@ public class GoGridComputeServiceLiveTest extends BaseComputeServiceLiveTest {
Template t = service.templateBuilder().minRam(1024).imageId("1532").build(); Template t = service.templateBuilder().minRam(1024).imageId("1532").build();
assertEquals(t.getImage().getId(), "1532"); assertEquals(t.getImage().getId(), "1532");
service.runNodesWithTag(this.service, 1, t); service.runNodesWithTag(this.service, 3, t);
Map<String, ? extends ComputeMetadata> nodes = service.getNodes(); Map<String, ? extends ComputeMetadata> nodes = service.getNodes();
@ -96,6 +98,11 @@ public class GoGridComputeServiceLiveTest extends BaseComputeServiceLiveTest {
service.rebootNode(nodeMetadata); // blocks until finished service.rebootNode(nodeMetadata); // blocks until finished
assertEquals(service.getNodeMetadata(nodeMetadata).getState(), NodeState.RUNNING); assertEquals(service.getNodeMetadata(nodeMetadata).getState(), NodeState.RUNNING);
service.destroyNode(nodeMetadata);
client.runScriptOnNodesWithTag("gogrid", null/*no credentials*/,
"mkdir ~/ahha; sleep 3".getBytes(),
new RunScriptOptions.Builder().overrideCredentials(false).build());
service.destroyNodesWithTag("gogrid");
} }
} }