You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@whirr.apache.org by to...@apache.org on 2010/08/17 18:53:12 UTC

svn commit: r986393 - in /incubator/whirr/trunk: ./ cli/ cli/src/ cli/src/main/ cli/src/main/java/ cli/src/main/java/org/ cli/src/main/java/org/apache/ cli/src/main/java/org/apache/whirr/ cli/src/main/java/org/apache/whirr/cli/ cli/src/main/java/org/ap...

Author: tomwhite
Date: Tue Aug 17 16:53:11 2010
New Revision: 986393

URL: http://svn.apache.org/viewvc?rev=986393&view=rev
Log:
WHIRR-33. Add a CLI.

Added:
    incubator/whirr/trunk/cli/
    incubator/whirr/trunk/cli/pom.xml   (with props)
    incubator/whirr/trunk/cli/src/
    incubator/whirr/trunk/cli/src/main/
    incubator/whirr/trunk/cli/src/main/java/
    incubator/whirr/trunk/cli/src/main/java/org/
    incubator/whirr/trunk/cli/src/main/java/org/apache/
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java   (with props)
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java   (with props)
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java   (with props)
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java   (with props)
    incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java   (with props)
    incubator/whirr/trunk/cli/src/test/
    incubator/whirr/trunk/cli/src/test/java/
    incubator/whirr/trunk/cli/src/test/java/org/
    incubator/whirr/trunk/cli/src/test/java/org/apache/
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java   (with props)
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java   (with props)
    incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java   (with props)
Modified:
    incubator/whirr/trunk/CHANGES.txt
    incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/Cluster.java
    incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/ClusterSpec.java
    incubator/whirr/trunk/pom.xml

Modified: incubator/whirr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/CHANGES.txt?rev=986393&r1=986392&r2=986393&view=diff
==============================================================================
--- incubator/whirr/trunk/CHANGES.txt (original)
+++ incubator/whirr/trunk/CHANGES.txt Tue Aug 17 16:53:11 2010
@@ -6,6 +6,8 @@ Trunk (unreleased changes)
 
   NEW FEATURES
 
+    WHIRR-33. Add a CLI. (tomwhite)
+
   IMPROVEMENTS
 
     WHIRR-2. Import initial Java source code. (tomwhite)

Added: incubator/whirr/trunk/cli/pom.xml
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/pom.xml?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/pom.xml (added)
+++ incubator/whirr/trunk/cli/pom.xml Tue Aug 17 16:53:11 2010
@@ -0,0 +1,184 @@
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.whirr</groupId>
+    <artifactId>whirr</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <groupId>org.apache.whirr</groupId>
+  <artifactId>whirr-cli</artifactId>
+  <packaging>jar</packaging>
+  <version>0.1.0-SNAPSHOT</version>
+  <name>Whirr CLI</name>
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>whirr-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>whirr-cassandra</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>whirr-hadoop</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>whirr-zookeeper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-aws</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-compute</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-enterprise</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-jsch</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-log4j</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jclouds</groupId>
+      <artifactId>jclouds-rackspace</artifactId>
+      <version>1.0-beta-6</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-all</artifactId>
+      <version>1.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.8.5</version> 
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>zookeeper</artifactId>
+      <version>3.3.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.15</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.mail</groupId>
+          <artifactId>mail</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.jms</groupId>
+          <artifactId>jms</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.jdmk</groupId>
+          <artifactId>jmxtools</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.jmx</groupId>
+          <artifactId>jmxri</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>net.sf.jopt-simple</groupId>
+      <artifactId>jopt-simple</artifactId>
+      <version>3.2</version>
+    </dependency>
+
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>1.4</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <finalName>whirr-cli-${project.version}</finalName>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>org.apache.whirr.cli.Main</mainClass>
+                </transformer>
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>   
+    </plugins>
+  </build>
+  <repositories>
+    <repository>
+      <id>jclouds</id>
+      <url>http://jclouds.googlecode.com/svn/repo</url>
+    </repository>
+  </repositories>
+</project>

Propchange: incubator/whirr/trunk/cli/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java (added)
+++ incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,49 @@
+/**
+ * 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.cli;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * A CLI command.
+ */
+public abstract class Command {
+  
+  private String name;
+  private String description;
+  
+  public Command(String name, String description) {
+    this.name = name;
+    this.description = description;
+  }
+  
+  public String getName() {
+    return name;
+  }
+  
+  public String getDescription() {
+    return description;
+  }
+  
+  public abstract int run(InputStream in, PrintStream out, PrintStream err,
+      List<String> args) throws Exception;
+
+}

Propchange: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Command.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java (added)
+++ incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,72 @@
+/**
+ * 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.cli;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.whirr.cli.command.DestroyClusterCommand;
+import org.apache.whirr.cli.command.LaunchClusterCommand;
+
+import com.google.common.collect.Maps;
+
+/**
+ * The entry point for the Whirr CLI.
+ */
+public class Main {
+  
+  private Map<String, Command> commandMap = Maps.newLinkedHashMap();
+  private int maxLen = 0;
+  
+  Main(Command... commands) throws IOException {
+    for (Command command : commands) {
+      commandMap.put(command.getName(), command);
+      maxLen = Math.max(maxLen, command.getName().length());
+    }
+  }
+  
+  int run(InputStream in, PrintStream out, PrintStream err,
+      List<String> list) throws Exception {
+    if (list.isEmpty()) {
+      out.println("Usage: whirr COMMAND [ARGS]");
+      out.println("where COMMAND may be one of:");
+      out.println();
+      for (Command command : commandMap.values()) {
+        out.printf("%" + maxLen + "s  %s\n", command.getName(),
+            command.getDescription());
+      }
+      return -1;
+    }
+    Command command = commandMap.get(list.get(0));
+    return command.run(in, out, err, list.subList(1, list.size()));
+  }
+
+  public static void main(String... args) throws Exception {
+    Main main = new Main(
+        new LaunchClusterCommand(),
+        new DestroyClusterCommand()
+    );
+    int rc = main.run(System.in, System.out, System.err, Arrays.asList(args));
+    System.exit(rc);
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/Main.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java (added)
+++ incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,75 @@
+/**
+ * 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.cli.command;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+import org.apache.whirr.cli.Command;
+import org.apache.whirr.service.ClusterSpec;
+import org.apache.whirr.service.ServiceFactory;
+import org.apache.whirr.service.ClusterSpec.InstanceTemplate;
+
+/**
+ * An abstract command for interacting with clusters.
+ */
+public abstract class ClusterSpecCommand extends Command {
+
+  protected ServiceFactory factory;
+  
+  protected OptionParser parser = new OptionParser();
+  protected OptionSpec<String> cloudProvider = parser.accepts("cloud-provider")
+      .withRequiredArg().defaultsTo("ec2").ofType(String.class);
+  protected OptionSpec<String> cloudIdentity = parser.accepts("cloud-identity")
+      .withRequiredArg().ofType(String.class);
+  protected OptionSpec<String> cloudCredential = parser.accepts("cloud-credential")
+      .withRequiredArg().ofType(String.class);
+  protected OptionSpec<String> secretKeyFile = parser.accepts("secret-key-file")
+      .withRequiredArg()
+      .defaultsTo(System.getProperty("user.home") + "/.ssh/id_rsa")
+      .ofType(String.class);
+  
+  public ClusterSpecCommand(String name, String description, ServiceFactory factory) {
+    super(name, description);
+    this.factory = factory;
+  }
+  
+  protected ClusterSpec getClusterSpec(OptionSet optionSet,
+        InstanceTemplate... instanceTemplates) {
+    return getClusterSpec(optionSet, Arrays.asList(instanceTemplates));
+  }
+  
+  protected ClusterSpec getClusterSpec(OptionSet optionSet,
+        List<InstanceTemplate> instanceTemplates) {
+    ClusterSpec clusterSpec = new ClusterSpec(instanceTemplates);
+    clusterSpec.setProvider(optionSet.valueOf(cloudProvider));
+    clusterSpec.setIdentity(checkNotNull(optionSet.valueOf(cloudIdentity),
+        "cloud-identity"));
+    clusterSpec.setCredential(optionSet.valueOf(cloudCredential));
+    clusterSpec.setSecretKeyFile(optionSet.valueOf(secretKeyFile));
+    return clusterSpec;
+  }
+
+}

Propchange: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/ClusterSpecCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java (added)
+++ incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,71 @@
+/**
+ * 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.cli.command;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+
+import org.apache.whirr.service.ClusterSpec;
+import org.apache.whirr.service.Service;
+import org.apache.whirr.service.ServiceFactory;
+
+/**
+ * A command to destroy a running cluster (terminate and cleanup).
+ */
+public class DestroyClusterCommand extends ClusterSpecCommand {
+
+  public DestroyClusterCommand() throws IOException {
+    this(new ServiceFactory());
+  }
+
+  public DestroyClusterCommand(ServiceFactory factory) {
+    super("destroy-cluster", "Terminate and cleanup resources for a running cluster.", factory);
+  }
+  
+  @Override
+  public int run(InputStream in, PrintStream out, PrintStream err,
+      List<String> args) throws Exception {
+    
+    OptionSet optionSet = parser.parse(args.toArray(new String[0]));
+
+    List<String> nonOptionArguments = optionSet.nonOptionArguments();
+    if (nonOptionArguments.size() < 2) {
+      printUsage(parser, err);
+      return -1;
+    }
+    String serviceName = nonOptionArguments.get(0);
+    ClusterSpec clusterSpec = getClusterSpec(optionSet);
+    clusterSpec.setClusterName(nonOptionArguments.get(1));
+
+    Service service = factory.create(serviceName);
+    service.destroyCluster(clusterSpec);
+    return 0;
+  }
+
+  private void printUsage(OptionParser parser, PrintStream stream) throws IOException {
+    stream.println("Usage: whirr destroy-cluster [OPTIONS] <service-name> <cluster-name>");
+    stream.println();
+    parser.printHelpOn(stream);
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/DestroyClusterCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java (added)
+++ incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,97 @@
+/**
+ * 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.cli.command;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Set;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+
+import org.apache.whirr.service.Cluster;
+import org.apache.whirr.service.ClusterSpec;
+import org.apache.whirr.service.Service;
+import org.apache.whirr.service.ServiceFactory;
+import org.apache.whirr.service.ClusterSpec.InstanceTemplate;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Sets;
+import com.google.inject.internal.Lists;
+
+/**
+ * A command to launch a new cluster.
+ */
+public class LaunchClusterCommand extends ClusterSpecCommand {
+
+  public LaunchClusterCommand() throws IOException {
+    this(new ServiceFactory());
+  }
+
+  public LaunchClusterCommand(ServiceFactory factory) {
+    super("launch-cluster", "Launch a new cluster running a service.", factory);
+  }
+
+  @Override
+  public int run(InputStream in, PrintStream out, PrintStream err,
+      List<String> args) throws Exception {
+    
+    OptionSet optionSet = parser.parse(args.toArray(new String[0]));
+
+    List<String> nonOptionArguments = optionSet.nonOptionArguments();
+    if (nonOptionArguments.size() < 4) {
+      printUsage(parser, err);
+      return -1;
+    }
+    
+    final int nonTemplateArgumentsSize = 2;
+
+    if ((nonOptionArguments.size() - nonTemplateArgumentsSize) % 2 == 1) {
+      printUsage(parser, err);
+      return -1;
+    }
+
+    List<InstanceTemplate> templates = Lists.newArrayList();
+    for (int i = 0; i < (nonOptionArguments.size() - 1) / 2; i++) {
+      int number = Integer.parseInt(nonOptionArguments.get(2 * i + nonTemplateArgumentsSize));
+      String rolesString = nonOptionArguments.get(2 * i + nonTemplateArgumentsSize + 1);
+      Set<String> roles = Sets.newHashSet(Splitter.on("+").split(rolesString));
+      templates.add(new InstanceTemplate(number, roles));
+    }
+    ClusterSpec clusterSpec = getClusterSpec(optionSet, templates);
+    String serviceName = nonOptionArguments.get(0);
+    clusterSpec.setClusterName(nonOptionArguments.get(1));
+    
+    Service service = factory.create(serviceName);
+    Cluster cluster = service.launchCluster(clusterSpec);
+    out.printf("Started cluster of %s instances\n",
+        cluster.getInstances().size());
+    out.println(cluster);
+    return 0;
+  }
+
+  private void printUsage(OptionParser parser, PrintStream stream) throws IOException {
+    stream.println("Usage: whirr launch-cluster [OPTIONS] <service-name> " +
+        "<cluster-name> <num> <roles> [<num> <roles>]*");
+    stream.println();
+    parser.printHelpOn(stream);
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/main/java/org/apache/whirr/cli/command/LaunchClusterCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java (added)
+++ incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,71 @@
+/**
+ * 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.cli;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.inject.internal.Lists;
+
+public class MainTest {
+  
+  static class TestCommand extends Command {
+    public TestCommand() {
+      super("test-command", "test description");
+    }
+
+    @Override
+    public int run(InputStream in, PrintStream out, PrintStream err,
+        List<String> args) throws Exception {
+      return 0;
+    }
+  }
+  
+  @Test
+  public void testNoArgs() throws Exception {
+    Main main = new Main(new TestCommand());
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    PrintStream out = new PrintStream(bytes);
+    int rc = main.run(null, out, null, Collections.<String>emptyList());
+    assertThat(rc, is(-1));
+    assertThat(bytes.toString(), containsString("Usage: whirr COMMAND"));
+    assertThat(bytes.toString(), containsString("test-command  test description"));
+  }
+  
+  @Test
+  public void testCommand() throws Exception {
+    Command command = mock(Command.class);
+    when(command.getName()).thenReturn("mock-command");
+    Main main = new Main(command);
+    main.run(null, null, null, Lists.newArrayList("mock-command", "arg1", "arg2"));
+    verify(command).run(null, null, null, Lists.newArrayList("arg1", "arg2"));
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/MainTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java (added)
+++ incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,99 @@
+/**
+ * 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.cli.command;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+
+import org.apache.whirr.service.ClusterSpec;
+import org.apache.whirr.service.Service;
+import org.apache.whirr.service.ServiceFactory;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.internal.matchers.StringContains;
+
+import com.google.inject.internal.Lists;
+
+public class DestroyClusterCommandTest {
+
+  private ByteArrayOutputStream outBytes;
+  private PrintStream out;
+  private ByteArrayOutputStream errBytes;
+  private PrintStream err;
+
+  @Before
+  public void setUp() {
+    outBytes = new ByteArrayOutputStream();
+    out = new PrintStream(outBytes);
+    errBytes = new ByteArrayOutputStream();
+    err = new PrintStream(errBytes);
+  }
+  
+  @Test
+  public void testNoArgs() throws Exception {
+    DestroyClusterCommand command = new DestroyClusterCommand();
+    int rc = command.run(null, null, err, Collections.<String>emptyList());
+    assertThat(rc, is(-1));
+    assertThat(errBytes.toString(), containsUsageString());
+  }
+  
+  private Matcher<String> containsUsageString() {
+    return StringContains.containsString("Usage: whirr destroy-cluster " +
+        "[OPTIONS] <service-name> <cluster-name>");
+  }
+  
+  @Test
+  public void testAllOptions() throws Exception {
+    
+    ServiceFactory factory = mock(ServiceFactory.class);
+    Service service = mock(Service.class);
+    when(factory.create((String) any())).thenReturn(service);
+    
+    DestroyClusterCommand command = new DestroyClusterCommand(factory);
+    
+    int rc = command.run(null, out, null, Lists.newArrayList(
+        "--cloud-provider", "rackspace",
+        "--cloud-identity", "myusername", "--cloud-credential", "mypassword",
+        "--secret-key-file", "secret-key",
+        "test-service", "test-cluster"));
+    
+    assertThat(rc, is(0));
+
+    ClusterSpec expectedClusterSpec = new ClusterSpec();
+    expectedClusterSpec.setProvider("rackspace");
+    expectedClusterSpec.setIdentity("myusername");
+    expectedClusterSpec.setCredential("mypassword");
+    expectedClusterSpec.setClusterName("test-cluster");
+    expectedClusterSpec.setSecretKeyFile("secret-key");
+    
+    verify(factory).create("test-service");
+    
+    verify(service).destroyCluster(expectedClusterSpec);
+    
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/DestroyClusterCommandTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java?rev=986393&view=auto
==============================================================================
--- incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java (added)
+++ incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java Tue Aug 17 16:53:11 2010
@@ -0,0 +1,127 @@
+/**
+ * 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.cli.command;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+
+import org.apache.whirr.cli.command.LaunchClusterCommand;
+import org.apache.whirr.service.Cluster;
+import org.apache.whirr.service.ClusterSpec;
+import org.apache.whirr.service.Service;
+import org.apache.whirr.service.ServiceFactory;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.internal.matchers.StringContains;
+
+import com.google.common.collect.Sets;
+import com.google.inject.internal.Lists;
+
+public class LaunchClusterCommandTest {
+
+  private ByteArrayOutputStream outBytes;
+  private PrintStream out;
+  private ByteArrayOutputStream errBytes;
+  private PrintStream err;
+
+  @Before
+  public void setUp() {
+    outBytes = new ByteArrayOutputStream();
+    out = new PrintStream(outBytes);
+    errBytes = new ByteArrayOutputStream();
+    err = new PrintStream(errBytes);
+  }
+  @Test
+  public void testNoArgs() throws Exception {
+    LaunchClusterCommand command = new LaunchClusterCommand();
+    int rc = command.run(null, null, err, Collections.<String>emptyList());
+    assertThat(rc, is(-1));
+    assertThat(errBytes.toString(), containsUsageString());
+  }
+  
+  @Test
+  public void testMissingNumberAndRoles() throws Exception {
+    LaunchClusterCommand command = new LaunchClusterCommand();
+    int rc = command.run(null, null, err, Lists.newArrayList("test-service",
+        "test-cluster"));
+    assertThat(rc, is(-1));
+    assertThat(errBytes.toString(), containsUsageString());
+  }
+  
+  @Test
+  public void testMissingRoles() throws Exception {
+    LaunchClusterCommand command = new LaunchClusterCommand();
+    int rc = command.run(null, null, err, Lists.newArrayList("test-service",
+        "test-cluster", "1", "role1+role2", "2"));
+    assertThat(rc, is(-1));
+    assertThat(errBytes.toString(), containsUsageString());
+  }
+  
+  private Matcher<String> containsUsageString() {
+    return StringContains.containsString("Usage: whirr launch-cluster " +
+        "[OPTIONS] <service-name> <cluster-name> <num> <roles> [<num> <roles>]*");
+  }
+  
+  @Test
+  public void testAllOptions() throws Exception {
+    
+    ServiceFactory factory = mock(ServiceFactory.class);
+    Service service = mock(Service.class);
+    Cluster cluster = mock(Cluster.class);
+    when(factory.create((String) any())).thenReturn(service);
+    when(service.launchCluster((ClusterSpec) any())).thenReturn(cluster);
+    
+    LaunchClusterCommand command = new LaunchClusterCommand(factory);
+    
+    int rc = command.run(null, out, null, Lists.newArrayList(
+        "--cloud-provider", "rackspace",
+        "--cloud-identity", "myusername", "--cloud-credential", "mypassword",
+        "--secret-key-file", "secret-key",
+        "test-service", "test-cluster", "1", "role1+role2", "2", "role3"));
+    
+    assertThat(rc, is(0));
+
+    ClusterSpec expectedClusterSpec = new ClusterSpec(
+        new ClusterSpec.InstanceTemplate(1, Sets.newHashSet("role1", "role2")),
+        new ClusterSpec.InstanceTemplate(2, Sets.newHashSet("role3"))
+    );
+    expectedClusterSpec.setProvider("rackspace");
+    expectedClusterSpec.setIdentity("myusername");
+    expectedClusterSpec.setCredential("mypassword");
+    expectedClusterSpec.setClusterName("test-cluster");
+    expectedClusterSpec.setSecretKeyFile("secret-key");
+    
+    verify(factory).create("test-service");
+    
+    verify(service).launchCluster(expectedClusterSpec);
+    
+    assertThat(outBytes.toString(), containsString("Started cluster of 0 instances"));
+    
+  }
+}

Propchange: incubator/whirr/trunk/cli/src/test/java/org/apache/whirr/cli/command/LaunchClusterCommandTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/Cluster.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/Cluster.java?rev=986393&r1=986392&r2=986393&view=diff
==============================================================================
--- incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/Cluster.java (original)
+++ incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/Cluster.java Tue Aug 17 16:53:11 2010
@@ -25,6 +25,8 @@ import java.util.Set;
 
 import org.jclouds.domain.Credentials;
 
+import com.google.common.base.Objects;
+
 /**
  * This class represents a real cluster of {@link Instances}.
  *
@@ -66,6 +68,14 @@ public class Cluster {
       return privateAddress;
     }
     
+    public String toString() {
+      return Objects.toStringHelper(this)
+        .add("roles", roles)
+        .add("publicAddress", publicAddress)
+        .add("privateAddress", privateAddress)
+        .toString();
+    }
+    
   }
   
   private Set<Instance> instances;
@@ -86,5 +96,12 @@ public class Cluster {
   public Properties getConfiguration() {
     return configuration;
   }
+  
+  public String toString() {
+    return Objects.toStringHelper(this)
+      .add("instances", instances)
+      .add("configuration", configuration)
+      .toString();
+  }
 
 }

Modified: incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/ClusterSpec.java
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/ClusterSpec.java?rev=986393&r1=986392&r2=986393&view=diff
==============================================================================
--- incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/ClusterSpec.java (original)
+++ incubator/whirr/trunk/core/src/main/java/org/apache/whirr/service/ClusterSpec.java Tue Aug 17 16:53:11 2010
@@ -19,6 +19,7 @@
 package org.apache.whirr.service;
 
 import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
@@ -62,6 +63,26 @@ public class ClusterSpec {
       return numberOfInstances;
     }
     
+    public boolean equals(Object o) {
+      if (o instanceof InstanceTemplate) {
+        InstanceTemplate that = (InstanceTemplate) o;
+        return Objects.equal(numberOfInstances, that.numberOfInstances)
+          && Objects.equal(roles, that.roles);
+      }
+      return false;
+    }
+    
+    public int hashCode() {
+      return Objects.hashCode(numberOfInstances, roles);
+    }
+    
+    public String toString() {
+      return Objects.toStringHelper(this)
+        .add("numberOfInstances", numberOfInstances)
+        .add("roles", roles)
+        .toString();
+    }
+    
   }
   
   private Properties configuration;
@@ -158,4 +179,37 @@ public class ClusterSpec {
     return Files.toString(new File(getSecretKeyFile() + ".pub"), Charsets.UTF_8);
   }
   
+  public boolean equals(Object o) {
+    if (o instanceof ClusterSpec) {
+      ClusterSpec that = (ClusterSpec) o;
+      return Objects.equal(configuration, that.configuration)
+        && Objects.equal(instanceTemplates, that.instanceTemplates)
+        && Objects.equal(serviceName, that.serviceName)
+        && Objects.equal(provider, that.provider)
+        && Objects.equal(identity, that.identity)
+        && Objects.equal(credential, that.credential)
+        && Objects.equal(clusterName, that.clusterName)
+        && Objects.equal(secretKeyFile, that.secretKeyFile);
+    }
+    return false;
+  }
+  
+  public int hashCode() {
+    return Objects.hashCode(configuration, instanceTemplates, serviceName,
+        provider, identity, credential, clusterName, secretKeyFile);
+  }
+  
+  public String toString() {
+    return Objects.toStringHelper(this)
+      .add("configuration", configuration)
+      .add("instanceTemplates", instanceTemplates)
+      .add("serviceName", serviceName)
+      .add("provider", provider)
+      .add("identity", identity)
+      .add("credential", credential)
+      .add("clusterName", clusterName)
+      .add("secretKeyFile", secretKeyFile)
+      .toString();
+  }
+  
 }

Modified: incubator/whirr/trunk/pom.xml
URL: http://svn.apache.org/viewvc/incubator/whirr/trunk/pom.xml?rev=986393&r1=986392&r2=986393&view=diff
==============================================================================
--- incubator/whirr/trunk/pom.xml (original)
+++ incubator/whirr/trunk/pom.xml Tue Aug 17 16:53:11 2010
@@ -25,6 +25,7 @@
 
   <modules>
     <module>build-tools</module>
+    <module>cli</module>
     <module>core</module>
     <module>services/cassandra</module>
     <module>services/hadoop</module>