Author: asavu
Date: Wed Oct 12 19:07:07 2011
New Revision: 1182528
URL: http://svn.apache.org/viewvc?rev=1182528&view=rev
Log:
WHIRR-214. First iteration on refactoring the core to support the addition of
nodes to running clusters (asavu)
Added:
whirr/trunk/core/src/main/java/org/apache/whirr/compute/
whirr/trunk/core/src/main/java/org/apache/whirr/compute/BootstrapTemplate.java
whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarter.java
whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarterFactory.java
whirr/trunk/core/src/main/java/org/apache/whirr/compute/StartupProcess.java
Modified:
whirr/trunk/CHANGES.txt
whirr/trunk/core/src/main/java/org/apache/whirr/Cluster.java
whirr/trunk/core/src/main/java/org/apache/whirr/ClusterController.java
whirr/trunk/core/src/main/java/org/apache/whirr/actions/BootstrapClusterAction.java
whirr/trunk/core/src/test/java/org/apache/whirr/actions/BootstrapClusterActionTest.java
Modified: whirr/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/whirr/trunk/CHANGES.txt?rev=1182528&r1=1182527&r2=1182528&view=diff
==============================================================================
--- whirr/trunk/CHANGES.txt (original)
+++ whirr/trunk/CHANGES.txt Wed Oct 12 19:07:07 2011
@@ -34,6 +34,9 @@ Trunk (unreleased changes)
WHIRR-325. Reduce cloud provider-specific code in scripts (tomwhite and
asavu)
+ WHIRR-214. First iteration on refactoring the core to support the
+ addition of nodes to running clusters (asavu)
+
BUG FIXES
WHIRR-377. Fix broken CLI logging config. (asavu via tomwhite)
Modified: whirr/trunk/core/src/main/java/org/apache/whirr/Cluster.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/Cluster.java?rev=1182528&r1=1182527&r2=1182528&view=diff
==============================================================================
--- whirr/trunk/core/src/main/java/org/apache/whirr/Cluster.java (original)
+++ whirr/trunk/core/src/main/java/org/apache/whirr/Cluster.java Wed Oct 12
19:07:07 2011
@@ -18,24 +18,23 @@
package org.apache.whirr;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.net.InetAddresses;
+import org.apache.whirr.util.DnsUtil;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.domain.Credentials;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Properties;
import java.util.Set;
-import org.apache.whirr.util.DnsUtil;
-import org.jclouds.compute.domain.NodeMetadata;
-import org.jclouds.domain.Credentials;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* This class represents a real cluster of {@link Instance}s.
Modified: whirr/trunk/core/src/main/java/org/apache/whirr/ClusterController.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/ClusterController.java?rev=1182528&r1=1182527&r2=1182528&view=diff
==============================================================================
--- whirr/trunk/core/src/main/java/org/apache/whirr/ClusterController.java
(original)
+++ whirr/trunk/core/src/main/java/org/apache/whirr/ClusterController.java Wed
Oct 12 19:07:07 2011
@@ -21,20 +21,14 @@ package org.apache.whirr;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.apache.whirr.actions.BootstrapClusterAction;
import org.apache.whirr.actions.ConfigureClusterAction;
import org.apache.whirr.actions.DestroyClusterAction;
+import org.apache.whirr.service.ClusterActionHandler;
import org.apache.whirr.service.ClusterStateStore;
import org.apache.whirr.service.ClusterStateStoreFactory;
-import org.apache.whirr.service.ClusterActionHandler;
import org.apache.whirr.service.ComputeCache;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
@@ -48,6 +42,11 @@ import org.jclouds.scriptbuilder.domain.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
import static org.apache.whirr.RolePredicates.withIds;
import static
org.jclouds.compute.options.RunScriptOptions.Builder.overrideCredentialsWith;
Modified:
whirr/trunk/core/src/main/java/org/apache/whirr/actions/BootstrapClusterAction.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/actions/BootstrapClusterAction.java?rev=1182528&r1=1182527&r2=1182528&view=diff
==============================================================================
---
whirr/trunk/core/src/main/java/org/apache/whirr/actions/BootstrapClusterAction.java
(original)
+++
whirr/trunk/core/src/main/java/org/apache/whirr/actions/BootstrapClusterAction.java
Wed Oct 12 19:07:07 2011
@@ -18,55 +18,37 @@
package org.apache.whirr.actions;
-import static org.jclouds.compute.options.TemplateOptions.Builder.runScript;
-import static org.jclouds.scriptbuilder.domain.Statements.appendFile;
-import static org.jclouds.scriptbuilder.domain.Statements.interpret;
-import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
-
import com.google.common.base.Function;
-import com.google.common.base.Splitter;
import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
import org.apache.whirr.Cluster;
import org.apache.whirr.Cluster.Instance;
import org.apache.whirr.ClusterSpec;
import org.apache.whirr.InstanceTemplate;
+import org.apache.whirr.compute.BootstrapTemplate;
+import org.apache.whirr.compute.NodeStarterFactory;
+import org.apache.whirr.compute.StartupProcess;
import org.apache.whirr.service.ClusterActionEvent;
import org.apache.whirr.service.ClusterActionHandler;
import org.apache.whirr.service.jclouds.StatementBuilder;
-import org.apache.whirr.service.jclouds.TemplateBuilderStrategy;
-import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
-import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
-import org.jclouds.compute.domain.TemplateBuilder;
-import org.jclouds.aws.ec2.AWSEC2Client;
-import org.jclouds.scriptbuilder.InitBuilder;
-import org.jclouds.scriptbuilder.domain.OsFamily;
-import org.jclouds.scriptbuilder.domain.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
/**
* A {@link org.apache.whirr.ClusterAction} that starts instances in a cluster
in parallel and
* runs bootstrap scripts on them.
@@ -106,13 +88,16 @@ public class BootstrapClusterAction exte
for (Entry<InstanceTemplate, ClusterActionEvent> entry :
eventMap.entrySet()) {
final InstanceTemplate instanceTemplate = entry.getKey();
final ClusterSpec clusterSpec = entry.getValue().getClusterSpec();
- final int maxNumberOfRetries = clusterSpec.getMaxStartupRetries();
+
+ final int maxNumberOfRetries = clusterSpec.getMaxStartupRetries();
StatementBuilder statementBuilder =
entry.getValue().getStatementBuilder();
+
ComputeServiceContext computeServiceContext =
getCompute().apply(clusterSpec);
final ComputeService computeService =
computeServiceContext.getComputeService();
- final Template template = buildTemplate(clusterSpec, computeService,
- statementBuilder, entry.getValue().getTemplateBuilderStrategy());
+
+ final Template template = BootstrapTemplate.build(clusterSpec,
computeService,
+ statementBuilder, entry.getValue().getTemplateBuilderStrategy());
Future<Set<? extends NodeMetadata>> nodesFuture = executorService.submit(
new StartupProcess(
@@ -146,79 +131,6 @@ public class BootstrapClusterAction exte
}
}
- private Template buildTemplate(ClusterSpec clusterSpec,
- ComputeService computeService, StatementBuilder statementBuilder,
- TemplateBuilderStrategy strategy)
- throws MalformedURLException {
-
- LOG.info("Configuring template");
-
- Statement statement = statementBuilder.build(clusterSpec);
- if (LOG.isDebugEnabled())
- LOG.debug("Running script:\n{}", statement.render(OsFamily.UNIX));
-
- Statement runScript = addUserAndAuthorizeSudo(
- clusterSpec.getClusterUser(),
- clusterSpec.getPublicKey(),
- clusterSpec.getPrivateKey(),
- statement);
-
- TemplateBuilder templateBuilder = computeService.templateBuilder()
- .options(runScript(runScript));
- strategy.configureTemplateBuilder(clusterSpec, templateBuilder);
-
- return setSpotInstancePriceIfSpecified(
- computeService.getContext(), clusterSpec, templateBuilder.build());
- }
-
- /**
- * Set maximum spot instance price based on the configuration
- */
- private Template setSpotInstancePriceIfSpecified(
- ComputeServiceContext context, ClusterSpec spec, Template template) {
-
- if (context != null && context.getProviderSpecificContext().getApi()
instanceof AWSEC2Client) {
- if (spec.getAwsEc2SpotPrice() > 0) {
- template.getOptions().as(AWSEC2TemplateOptions.class)
- .spotPrice(spec.getAwsEc2SpotPrice());
- }
- }
-
- return template;
- }
-
- private static Statement addUserAndAuthorizeSudo(String user,
- String publicKey, String privateKey, Statement statement) {
- return new InitBuilder("setup-" + user,// name of the script
- "/tmp",// working directory
- "/tmp/logs",// location of stdout.log and stderr.log
- ImmutableMap.of("newUser", user, "defaultHome", "/home/users"), //
variables
- ImmutableList.<Statement> of(
- createUserWithPublicAndPrivateKey(user, publicKey, privateKey),
- makeSudoersOnlyPermitting(user),
- statement));
- }
-
- // must be used inside InitBuilder, as this sets the shell variables used in
this statement
- static Statement createUserWithPublicAndPrivateKey(String username,
- String publicKey, String privateKey) {
- // note directory must be created first
- return newStatementList(interpret("mkdir -p $DEFAULT_HOME/$NEW_USER/.ssh",
- "useradd --shell /bin/bash -d $DEFAULT_HOME/$NEW_USER $NEW_USER\n"),
appendFile(
- "$DEFAULT_HOME/$NEW_USER/.ssh/authorized_keys",
Splitter.on('\n').split(publicKey)),
- appendFile(
- "$DEFAULT_HOME/$NEW_USER/.ssh/id_rsa",
Splitter.on('\n').split(privateKey)),
- interpret("chmod 400 $DEFAULT_HOME/$NEW_USER/.ssh/*",
- "chown -R $NEW_USER $DEFAULT_HOME/$NEW_USER\n"));
- }
-
- // must be used inside InitBuilder, as this sets the shell variables used in
this statement
- static Statement makeSudoersOnlyPermitting(String username) {
- return newStatementList(interpret("rm /etc/sudoers", "touch /etc/sudoers",
"chmod 0440 /etc/sudoers",
- "chown root /etc/sudoers\n"), appendFile("/etc/sudoers",
ImmutableSet.of("root ALL = (ALL) ALL",
- "%adm ALL = (ALL) ALL", username + " ALL = (ALL) NOPASSWD: ALL")));
- }
-
private Set<Instance> getInstances(final Set<String> roles,
Set<? extends NodeMetadata> nodes) {
return
Sets.newLinkedHashSet(Collections2.transform(Sets.newLinkedHashSet(nodes),
@@ -232,177 +144,6 @@ public class BootstrapClusterAction exte
}
}));
}
-
- class StartupProcess implements Callable<Set<? extends NodeMetadata>> {
-
- final private String clusterName;
- final private int numberOfNodes;
- final private int minNumberOfNodes;
- final private int maxStartupRetries;
- final private Set<String> roles;
- final private ComputeService computeService;
- final private Template template;
- final private ExecutorService executorService;
- final private NodeStarterFactory starterFactory;
-
- private Set<NodeMetadata> successfulNodes = Sets.newLinkedHashSet();
- private Map<NodeMetadata, Throwable> lostNodes = Maps.newHashMap();
-
- private Future<Set<NodeMetadata>> nodesFuture;
-
- StartupProcess(final String clusterName, final int numberOfNodes,
- final int minNumberOfNodes, final int maxStartupRetries, final
Set<String> roles,
- final ComputeService computeService, final Template template,
- final ExecutorService executorService, final NodeStarterFactory
starterFactory) {
- this.clusterName = clusterName;
- this.numberOfNodes = numberOfNodes;
- this.minNumberOfNodes = minNumberOfNodes;
- this.maxStartupRetries = maxStartupRetries;
- this.roles = roles;
- this.computeService = computeService;
- this.template = template;
- this.executorService = executorService;
- this.starterFactory = starterFactory;
- }
-
- @Override
- public Set<? extends NodeMetadata> call() throws Exception {
- int retryCount = 0;
- boolean retryRequired;
- try {
- do {
- runNodesWithTag();
- waitForOutcomes();
- retryRequired = !isDone();
-
- if (++retryCount > maxStartupRetries) {
- break; // no more retries
- }
- } while (retryRequired);
-
- if (retryRequired) {// if still required, we cannot use the cluster
- // in this case of failed cluster startup, cleaning of the nodes are
postponed
- throw new IOException("Too many instance failed while bootstrapping!
"
- + successfulNodes.size() + " successfully started instances
while " + lostNodes.size() + " instances failed");
- }
- } finally {
- cleanupFailedNodes();
- }
- return successfulNodes;
- }
-
- String getClusterName() {
- return clusterName;
- }
-
- Template getTemplate() {
- return template;
- }
-
- Set<NodeMetadata> getSuccessfulNodes() {
- return successfulNodes;
- }
-
- Map<NodeMetadata, Throwable> getNodeErrors() {
- return lostNodes;
- }
-
- boolean isDone() {
- return successfulNodes.size() >= minNumberOfNodes;
- }
-
- void runNodesWithTag() {
- final int num = numberOfNodes - successfulNodes.size();
- this.nodesFuture = executorService.submit(starterFactory.create(
- computeService, clusterName, roles, num, template));
- }
-
- void waitForOutcomes() throws InterruptedException {
- try {
- Set<? extends NodeMetadata> nodes = nodesFuture.get();
- successfulNodes.addAll(nodes);
- } catch (ExecutionException e) {
- // checking RunNodesException and collect the outcome
- Throwable th = e.getCause();
- if (th instanceof RunNodesException) {
- RunNodesException rnex = (RunNodesException) th;
- successfulNodes.addAll(rnex.getSuccessfulNodes());
- lostNodes.putAll(rnex.getNodeErrors());
- } else {
- LOG.error("Unexpected error while starting " + numberOfNodes + "
nodes, minimum "
- + minNumberOfNodes + " nodes for " + roles + " of cluster " +
clusterName, e);
- }
- }
- }
-
- void cleanupFailedNodes() throws InterruptedException {
- if (lostNodes.size() > 0) {
- // parallel destroy of failed nodes
- Set<Future<NodeMetadata>> deletingNodeFutures =
Sets.newLinkedHashSet();
- Iterator<?> it = lostNodes.keySet().iterator();
- while (it.hasNext()) {
- final NodeMetadata badNode = (NodeMetadata) it.next();
- deletingNodeFutures.add(executorService.submit(
- new Callable<NodeMetadata>() {
- public NodeMetadata call() throws Exception {
- final String nodeId = badNode.getId();
- LOG.info("Deleting failed node node {}", nodeId);
- computeService.destroyNode(nodeId);
- LOG.info("Node deleted: {}", nodeId);
- return badNode;
- }
- }
- ));
- }
- Iterator<Future<NodeMetadata>> results =
deletingNodeFutures.iterator();
- while (results.hasNext()) {
- try {
- results.next().get();
- } catch (ExecutionException e) {
- LOG.warn("Error while destroying failed node:", e);
- }
- }
- }
- }
- }
-}
-
-class NodeStarterFactory {
- NodeStarter create(final ComputeService computeService, final String
clusterName,
- final Set<String> roles, final int num, final Template template) {
- return new NodeStarter(computeService, clusterName, roles, num, template);
- }
-}
-
-class NodeStarter implements Callable<Set<NodeMetadata>> {
-
- private static final Logger LOG =
- LoggerFactory.getLogger(NodeStarter.class);
-
- final private ComputeService computeService;
- final private String clusterName;
- final private Set<String> roles;
- final private int num;
- final private Template template;
-
- public NodeStarter(final ComputeService computeService, final String
clusterName,
- final Set<String> roles, final int num, final Template template) {
- this.computeService = computeService;
- this.clusterName = clusterName;
- this.roles = roles;
- this.num = num;
- this.template = template;
- }
- @SuppressWarnings("unchecked")
- @Override
- public Set<NodeMetadata> call() throws Exception {
- LOG.info("Starting {} node(s) with roles {}", num,
- roles);
- Set<NodeMetadata> nodes = (Set<NodeMetadata>)computeService
- .createNodesInGroup(clusterName, num, template);
- LOG.info("Nodes started: {}", nodes);
- return nodes;
- }
}
Added:
whirr/trunk/core/src/main/java/org/apache/whirr/compute/BootstrapTemplate.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/compute/BootstrapTemplate.java?rev=1182528&view=auto
==============================================================================
---
whirr/trunk/core/src/main/java/org/apache/whirr/compute/BootstrapTemplate.java
(added)
+++
whirr/trunk/core/src/main/java/org/apache/whirr/compute/BootstrapTemplate.java
Wed Oct 12 19:07:07 2011
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.whirr.compute;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.whirr.ClusterSpec;
+import org.apache.whirr.service.jclouds.StatementBuilder;
+import org.apache.whirr.service.jclouds.TemplateBuilderStrategy;
+import org.jclouds.aws.ec2.AWSEC2Client;
+import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.scriptbuilder.InitBuilder;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+
+import static org.jclouds.compute.options.TemplateOptions.Builder.runScript;
+import static org.jclouds.scriptbuilder.domain.Statements.appendFile;
+import static org.jclouds.scriptbuilder.domain.Statements.interpret;
+import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
+
+public class BootstrapTemplate {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(BootstrapTemplate.class);
+
+ public static Template build(
+ ClusterSpec clusterSpec,
+ ComputeService computeService,
+ StatementBuilder statementBuilder,
+ TemplateBuilderStrategy strategy
+ ) throws MalformedURLException {
+
+ LOG.info("Configuring template");
+
+ Statement runScript = addUserAndAuthorizeSudo(
+ clusterSpec.getClusterUser(),
+ clusterSpec.getPublicKey(),
+ clusterSpec.getPrivateKey(),
+ statementBuilder.build(clusterSpec));
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Running script:\n{}", runScript.render(OsFamily.UNIX));
+ }
+
+ TemplateBuilder templateBuilder = computeService.templateBuilder()
+ .options(runScript(runScript));
+ strategy.configureTemplateBuilder(clusterSpec, templateBuilder);
+
+ return setSpotInstancePriceIfSpecified(
+ computeService.getContext(), clusterSpec, templateBuilder.build()
+ );
+ }
+
+ private static Statement addUserAndAuthorizeSudo(
+ String user, String publicKey, String privateKey, Statement statement
+ ) {
+ return new InitBuilder(
+ "setup-" + user,// name of the script
+ "/tmp",// working directory
+ "/tmp/logs",// location of stdout.log and stderr.log
+ ImmutableMap.of("newUser", user, "defaultHome", "/home/users"), //
variables
+ ImmutableList.<Statement> of(
+ createUserWithPublicAndPrivateKey(user, publicKey, privateKey),
+ makeSudoersOnlyPermitting(user),
+ statement)
+ );
+ }
+
+ /**
+ * Set maximum spot instance price based on the configuration
+ */
+ private static Template setSpotInstancePriceIfSpecified(
+ ComputeServiceContext context, ClusterSpec spec, Template template) {
+
+ if (context != null && context.getProviderSpecificContext().getApi()
instanceof AWSEC2Client) {
+ if (spec.getAwsEc2SpotPrice() > 0) {
+ template.getOptions().as(AWSEC2TemplateOptions.class)
+ .spotPrice(spec.getAwsEc2SpotPrice());
+ }
+ }
+
+ return template;
+ }
+
+ // must be used inside InitBuilder, as this sets the shell variables used in
this statement
+ private static Statement createUserWithPublicAndPrivateKey(String username,
+ String publicKey, String privateKey) {
+ // note directory must be created first
+ return newStatementList(
+ interpret(
+ "mkdir -p $DEFAULT_HOME/$NEW_USER/.ssh",
+ "useradd --shell /bin/bash -d $DEFAULT_HOME/$NEW_USER $NEW_USER\n"),
+ appendFile(
+ "$DEFAULT_HOME/$NEW_USER/.ssh/authorized_keys",
+ Splitter.on('\n').split(publicKey)),
+ appendFile(
+ "$DEFAULT_HOME/$NEW_USER/.ssh/id_rsa",
+ Splitter.on('\n').split(privateKey)),
+ interpret(
+ "chmod 400 $DEFAULT_HOME/$NEW_USER/.ssh/*",
+ "chown -R $NEW_USER $DEFAULT_HOME/$NEW_USER\n"));
+ }
+
+ // must be used inside InitBuilder, as this sets the shell variables used in
this statement
+ private static Statement makeSudoersOnlyPermitting(String username) {
+ return newStatementList(
+ interpret(
+ "rm /etc/sudoers",
+ "touch /etc/sudoers",
+ "chmod 0440 /etc/sudoers",
+ "chown root /etc/sudoers\n"),
+ appendFile(
+ "/etc/sudoers",
+ ImmutableSet.of(
+ "root ALL = (ALL) ALL",
+ "%adm ALL = (ALL) ALL",
+ username + " ALL = (ALL) NOPASSWD: ALL")
+ )
+ );
+ }
+
+}
Added: whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarter.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarter.java?rev=1182528&view=auto
==============================================================================
--- whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarter.java
(added)
+++ whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarter.java
Wed Oct 12 19:07:07 2011
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.whirr.compute;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+public class NodeStarter implements Callable<Set<NodeMetadata>> {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(NodeStarter.class);
+
+ final private ComputeService computeService;
+ final private String clusterName;
+ final private Set<String> roles;
+ final private int num;
+ final private Template template;
+
+ public NodeStarter(final ComputeService computeService, final String
clusterName,
+ final Set<String> roles, final int num, final Template template) {
+ this.computeService = computeService;
+ this.clusterName = clusterName;
+ this.roles = roles;
+ this.num = num;
+ this.template = template;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Set<NodeMetadata> call() throws Exception {
+ LOG.info("Starting {} node(s) with roles {}", num,
+ roles);
+ Set<NodeMetadata> nodes = (Set<NodeMetadata>)computeService
+ .createNodesInGroup(clusterName, num, template);
+ LOG.info("Nodes started: {}", nodes);
+ return nodes;
+ }
+}
Added:
whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarterFactory.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarterFactory.java?rev=1182528&view=auto
==============================================================================
---
whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarterFactory.java
(added)
+++
whirr/trunk/core/src/main/java/org/apache/whirr/compute/NodeStarterFactory.java
Wed Oct 12 19:07:07 2011
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.whirr.compute;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.Template;
+
+import java.util.Set;
+
+public class NodeStarterFactory {
+ public NodeStarter create(final ComputeService computeService, final String
clusterName,
+ final Set<String> roles, final int num, final Template template) {
+ return new NodeStarter(computeService, clusterName, roles, num, template);
+ }
+}
Added:
whirr/trunk/core/src/main/java/org/apache/whirr/compute/StartupProcess.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/main/java/org/apache/whirr/compute/StartupProcess.java?rev=1182528&view=auto
==============================================================================
--- whirr/trunk/core/src/main/java/org/apache/whirr/compute/StartupProcess.java
(added)
+++ whirr/trunk/core/src/main/java/org/apache/whirr/compute/StartupProcess.java
Wed Oct 12 19:07:07 2011
@@ -0,0 +1,173 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.whirr.compute;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+public class StartupProcess implements Callable<Set<? extends NodeMetadata>> {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(StartupProcess.class);
+
+ final private String clusterName;
+ final private int numberOfNodes;
+ final private int minNumberOfNodes;
+ final private int maxStartupRetries;
+ final private Set<String> roles;
+ final private ComputeService computeService;
+ final private Template template;
+ final private ExecutorService executorService;
+ final private NodeStarterFactory starterFactory;
+
+ private Set<NodeMetadata> successfulNodes = Sets.newLinkedHashSet();
+ private Map<NodeMetadata, Throwable> lostNodes = Maps.newHashMap();
+
+ private Future<Set<NodeMetadata>> nodesFuture;
+
+ public StartupProcess(final String clusterName, final int numberOfNodes,
+ final int minNumberOfNodes, final int
maxStartupRetries, final Set<String> roles,
+ final ComputeService computeService, final Template
template,
+ final ExecutorService executorService, final
NodeStarterFactory starterFactory) {
+ this.clusterName = clusterName;
+ this.numberOfNodes = numberOfNodes;
+ this.minNumberOfNodes = minNumberOfNodes;
+ this.maxStartupRetries = maxStartupRetries;
+ this.roles = roles;
+ this.computeService = computeService;
+ this.template = template;
+ this.executorService = executorService;
+ this.starterFactory = starterFactory;
+ }
+
+ @Override
+ public Set<? extends NodeMetadata> call() throws Exception {
+ int retryCount = 0;
+ boolean retryRequired;
+ try {
+ do {
+ runNodesWithTag();
+ waitForOutcomes();
+ retryRequired = !isDone();
+
+ if (++retryCount > maxStartupRetries) {
+ break; // no more retries
+ }
+ } while (retryRequired);
+
+ if (retryRequired) {// if still required, we cannot use the cluster
+ // in this case of failed cluster startup, cleaning of the nodes are
postponed
+ throw new IOException("Too many instance failed while bootstrapping! "
+ + successfulNodes.size() + " successfully started instances while
" + lostNodes.size() + " instances failed");
+ }
+ } finally {
+ cleanupFailedNodes();
+ }
+ return successfulNodes;
+ }
+
+ String getClusterName() {
+ return clusterName;
+ }
+
+ Template getTemplate() {
+ return template;
+ }
+
+ Set<NodeMetadata> getSuccessfulNodes() {
+ return successfulNodes;
+ }
+
+ Map<NodeMetadata, Throwable> getNodeErrors() {
+ return lostNodes;
+ }
+
+ boolean isDone() {
+ return successfulNodes.size() >= minNumberOfNodes;
+ }
+
+ void runNodesWithTag() {
+ final int num = numberOfNodes - successfulNodes.size();
+ this.nodesFuture = executorService.submit(starterFactory.create(
+ computeService, clusterName, roles, num, template));
+ }
+
+ void waitForOutcomes() throws InterruptedException {
+ try {
+ Set<? extends NodeMetadata> nodes = nodesFuture.get();
+ successfulNodes.addAll(nodes);
+ } catch (ExecutionException e) {
+ // checking RunNodesException and collect the outcome
+ Throwable th = e.getCause();
+ if (th instanceof RunNodesException) {
+ RunNodesException rnex = (RunNodesException) th;
+ successfulNodes.addAll(rnex.getSuccessfulNodes());
+ lostNodes.putAll(rnex.getNodeErrors());
+ } else {
+ LOG.error("Unexpected error while starting " + numberOfNodes + "
nodes, minimum "
+ + minNumberOfNodes + " nodes for " + roles + " of cluster " +
clusterName, e);
+ }
+ }
+ }
+
+ void cleanupFailedNodes() throws InterruptedException {
+ if (lostNodes.size() > 0) {
+ // parallel destroy of failed nodes
+ Set<Future<NodeMetadata>> deletingNodeFutures = Sets.newLinkedHashSet();
+ Iterator<?> it = lostNodes.keySet().iterator();
+ while (it.hasNext()) {
+ final NodeMetadata badNode = (NodeMetadata) it.next();
+ deletingNodeFutures.add(executorService.submit(
+ new Callable<NodeMetadata>() {
+ public NodeMetadata call() throws Exception {
+ final String nodeId = badNode.getId();
+ LOG.info("Deleting failed node node {}", nodeId);
+ computeService.destroyNode(nodeId);
+ LOG.info("Node deleted: {}", nodeId);
+ return badNode;
+ }
+ }
+ ));
+ }
+ Iterator<Future<NodeMetadata>> results = deletingNodeFutures.iterator();
+ while (results.hasNext()) {
+ try {
+ results.next().get();
+ } catch (ExecutionException e) {
+ LOG.warn("Error while destroying failed node:", e);
+ }
+ }
+ }
+ }
+}
Modified:
whirr/trunk/core/src/test/java/org/apache/whirr/actions/BootstrapClusterActionTest.java
URL:
http://svn.apache.org/viewvc/whirr/trunk/core/src/test/java/org/apache/whirr/actions/BootstrapClusterActionTest.java?rev=1182528&r1=1182527&r2=1182528&view=diff
==============================================================================
---
whirr/trunk/core/src/test/java/org/apache/whirr/actions/BootstrapClusterActionTest.java
(original)
+++
whirr/trunk/core/src/test/java/org/apache/whirr/actions/BootstrapClusterActionTest.java
Wed Oct 12 19:07:07 2011
@@ -18,32 +18,17 @@
package org.apache.whirr.actions;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.Stack;
-import java.util.concurrent.atomic.AtomicInteger;
-
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.whirr.ClusterSpec;
import org.apache.whirr.HandlerMapFactory;
+import org.apache.whirr.compute.NodeStarter;
+import org.apache.whirr.compute.NodeStarterFactory;
import org.apache.whirr.service.ClusterActionHandler;
import org.apache.whirr.service.ClusterActionHandlerFactory;
import org.jclouds.compute.ComputeService;
@@ -71,6 +56,22 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
public class BootstrapClusterActionTest {
private static final Logger LOG =
@@ -132,7 +133,8 @@ public class BootstrapClusterActionTest
reaction.put(dntt, ddttStack);
nodeStarterFactory = new TestNodeStarterFactory(reaction);
- BootstrapClusterAction bootstrapper = new
BootstrapClusterAction(getCompute, handlerMap, nodeStarterFactory);
+ BootstrapClusterAction bootstrapper =
+ new BootstrapClusterAction(getCompute, handlerMap, nodeStarterFactory);
bootstrapper.execute(clusterSpec, null);
if (nodeStarterFactory != null) {
@@ -221,10 +223,10 @@ public class BootstrapClusterActionTest
TestNodeStarterFactory(final Map<Set<String>, Stack<Integer>> plan) {
this.plan = plan;
}
-
+
@Override
- NodeStarter create(ComputeService computeService, String clusterName,
- Set<String> roles, int num, Template template) {
+ public NodeStarter create(final ComputeService computeService, final
String clusterName,
+ final Set<String> roles, final int num, final Template template) {
NodeStarter result = null;
Stack<Integer> stack = plan.get(roles);
if (stack != null) {