Skip to content

Commit

Permalink
WHIRR-73. Add a list command to the CLI.
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/incubator/whirr/trunk@1003381 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
tomwhite committed Oct 1, 2010
1 parent d88a31e commit 543bd0c
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -6,6 +6,8 @@ Trunk (unreleased changes)

WHIRR-100. Create a binary distribution of Whirr. (tomwhite)

WHIRR-73. Add a list command to the CLI. (tomwhite)

IMPROVEMENTS

WHIRR-89. Support maven 3 builds. (Adrian Cole via tomwhite)
Expand Down
47 changes: 29 additions & 18 deletions cli/src/main/java/org/apache/whirr/cli/Main.java
Expand Up @@ -18,6 +18,8 @@

package org.apache.whirr.cli;

import com.google.common.collect.Maps;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
Expand All @@ -28,11 +30,10 @@

import org.apache.whirr.cli.command.DestroyClusterCommand;
import org.apache.whirr.cli.command.LaunchClusterCommand;
import org.apache.whirr.cli.command.ListClusterCommand;
import org.apache.whirr.cli.command.VersionCommand;
import org.apache.whirr.service.ServiceFactory;

import com.google.common.collect.Maps;

/**
* The entry point for the Whirr CLI.
*/
Expand All @@ -51,31 +52,41 @@ public class Main {
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());
}
out.println();
out.println("Available services:");
ServiceFactory serviceFactory = new ServiceFactory();
for (String serviceName :
new TreeSet<String>(serviceFactory.availableServices())) {
out.println(" " + serviceName);
}
printUsage(out);
return -1;
}
Command command = commandMap.get(list.get(0));
if (command == null) {
err.printf("Unrecognized command '%s'\n", list.get(0));
err.println();
printUsage(err);
return -1;
}
return command.run(in, out, err, list.subList(1, list.size()));
}


private void printUsage(PrintStream stream) {
stream.println("Usage: whirr COMMAND [ARGS]");
stream.println("where COMMAND may be one of:");
stream.println();
for (Command command : commandMap.values()) {
stream.printf("%" + maxLen + "s %s\n", command.getName(),
command.getDescription());
}
stream.println();
stream.println("Available services:");
ServiceFactory serviceFactory = new ServiceFactory();
for (String serviceName : new TreeSet<String>(
serviceFactory.availableServices())) {
stream.println(" " + serviceName);
}
}
public static void main(String... args) throws Exception {
Main main = new Main(
new VersionCommand(),
new LaunchClusterCommand(),
new DestroyClusterCommand()
new DestroyClusterCommand(),
new ListClusterCommand()
);
int rc = main.run(System.in, System.out, System.err, Arrays.asList(args));
System.exit(rc);
Expand Down
@@ -0,0 +1,89 @@
/**
* 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 com.google.common.base.Joiner;
import com.google.common.collect.Iterables;

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.ClusterSpec;
import org.apache.whirr.service.Service;
import org.apache.whirr.service.ServiceFactory;
import org.jclouds.compute.domain.NodeMetadata;

/**
* A command to list the nodes in a cluster.
*/
public class ListClusterCommand extends AbstractClusterSpecCommand {

public ListClusterCommand() throws IOException {
this(new ServiceFactory());
}

public ListClusterCommand(ServiceFactory factory) {
super("list-cluster", "List the nodes in a 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]));

if (!optionSet.nonOptionArguments().isEmpty()) {
printUsage(parser, err);
return -1;
}
try {
ClusterSpec clusterSpec = getClusterSpec(optionSet);

Service service = factory.create(clusterSpec.getServiceName());
Set<? extends NodeMetadata> nodes = service.getNodes(clusterSpec);
for (NodeMetadata node : nodes) {
out.println(Joiner.on('\t').join(node.getId(), node.getImageId(),
getFirstAddress(node.getPublicAddresses()),
getFirstAddress(node.getPrivateAddresses()),
node.getState(), node.getLocation().getId()));
}
return 0;
} catch (IllegalArgumentException e) {
err.println(e.getMessage());
printUsage(parser, err);
return -1;
}
}

private String getFirstAddress(Set<String> addresses) {
return addresses.isEmpty() ? "" : Iterables.get(addresses, 0);
}

private void printUsage(OptionParser parser, PrintStream stream) throws IOException {
stream.println("Usage: whirr list-cluster [OPTIONS]");
stream.println();
parser.printHelpOn(stream);
}
}
10 changes: 10 additions & 0 deletions cli/src/test/java/org/apache/whirr/cli/MainTest.java
Expand Up @@ -60,6 +60,16 @@ public void testNoArgs() throws Exception {
assertThat(bytes.toString(), containsString("test-command test description"));
}

@Test
public void testUnrecognizedCommand() throws Exception {
Main main = new Main(new TestCommand());
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream err = new PrintStream(bytes);
int rc = main.run(null, null, err, Lists.newArrayList("bogus-command"));
assertThat(rc, is(-1));
assertThat(bytes.toString(), containsString("Unrecognized command 'bogus-command'"));
}

@Test
public void testCommand() throws Exception {
Command command = mock(Command.class);
Expand Down
@@ -0,0 +1,115 @@
/**
* 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 com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Collections;
import java.util.Set;

import org.apache.whirr.service.ClusterSpec;
import org.apache.whirr.service.Service;
import org.apache.whirr.service.ServiceFactory;
import org.hamcrest.Matcher;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;
import org.jclouds.compute.domain.internal.NodeMetadataImpl;
import org.jclouds.domain.LocationScope;
import org.jclouds.domain.internal.LocationImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.internal.matchers.StringContains;

public class ListClusterCommandTest {

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 testInsufficientOptions() throws Exception {
ListClusterCommand command = new ListClusterCommand();
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 list-cluster [OPTIONS]");
}

@Test
public void testAllOptions() throws Exception {

ServiceFactory factory = mock(ServiceFactory.class);
Service service = mock(Service.class);
when(factory.create((String) any())).thenReturn(service);
NodeMetadata node1 = new NodeMetadataImpl(null, "name1", "id1",
new LocationImpl(LocationScope.PROVIDER, "location-id1",
"location-desc1", null),
null, Collections.<String,String>emptyMap(), null, null, "image-id",
null, NodeState.RUNNING,
Lists.newArrayList("100.0.0.1"),
Lists.newArrayList("10.0.0.1"), null);
NodeMetadata node2 = new NodeMetadataImpl(null, "name2", "id2",
new LocationImpl(LocationScope.PROVIDER, "location-id2",
"location-desc2", null),
null, Collections.<String,String>emptyMap(), null, null, "image-id",
null, NodeState.RUNNING,
Lists.newArrayList("100.0.0.2"),
Lists.newArrayList("10.0.0.2"), null);
when(service.getNodes((ClusterSpec) any())).thenReturn(
(Set) Sets.newLinkedHashSet(Lists.newArrayList(node1, node2)));

ListClusterCommand command = new ListClusterCommand(factory);

int rc = command.run(null, out, null, Lists.newArrayList(
"--service-name", "test-service",
"--cluster-name", "test-cluster",
"--identity", "myusername"));

assertThat(rc, is(0));

assertThat(outBytes.toString(), is(
"id1\timage-id\t100.0.0.1\t10.0.0.1\tRUNNING\tlocation-id1\n" +
"id2\timage-id\t100.0.0.2\t10.0.0.2\tRUNNING\tlocation-id2\n"));

verify(factory).create("test-service");

}
}
33 changes: 33 additions & 0 deletions core/src/main/java/org/apache/whirr/service/Service.java
Expand Up @@ -20,9 +20,15 @@

import static org.jclouds.compute.predicates.NodePredicates.withTag;

import com.google.common.base.Predicate;

import java.io.IOException;
import java.util.Set;

import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;

/**
* This class represents a service that a client wants to use. This class is
Expand Down Expand Up @@ -58,5 +64,32 @@ public void destroyCluster(ClusterSpec clusterSpec) throws IOException {
ComputeServiceContextBuilder.build(clusterSpec).getComputeService();
computeService.destroyNodesMatching(withTag(clusterSpec.getClusterName()));
}

public Set<? extends NodeMetadata> getNodes(ClusterSpec clusterSpec)
throws IOException {
ComputeService computeService =
ComputeServiceContextBuilder.build(clusterSpec).getComputeService();
return computeService.listNodesDetailsMatching(
runningWithTag(clusterSpec.getClusterName()));
}

public static Predicate<ComputeMetadata> runningWithTag(final String tag) {
return new Predicate<ComputeMetadata>() {
@Override
public boolean apply(ComputeMetadata computeMetadata) {
// Not all list calls return NodeMetadata (e.g. VCloud)
if (computeMetadata instanceof NodeMetadata) {
NodeMetadata nodeMetadata = (NodeMetadata) computeMetadata;
return tag.equals(nodeMetadata.getTag())
&& nodeMetadata.getState() == NodeState.RUNNING;
}
return false;
}
@Override
public String toString() {
return "runningWithTag(" + tag + ")";
}
};
}

}
Expand Up @@ -21,7 +21,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.whirr.service.RunUrlBuilder.runUrls;
import static org.jclouds.compute.options.TemplateOptions.Builder.runScript;
import static org.jclouds.compute.predicates.NodePredicates.runningWithTag;
import static org.jclouds.io.Payloads.newStringPayload;

import com.google.common.base.Function;
Expand Down Expand Up @@ -55,6 +54,7 @@
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.predicates.NodePredicates;
import org.jclouds.io.Payload;
import org.jclouds.ssh.ExecResponse;

Expand Down Expand Up @@ -115,7 +115,8 @@ public Cluster launchCluster(ClusterSpec clusterSpec) throws IOException {
try {
Map<? extends NodeMetadata, ExecResponse> responses = computeService
.runScriptOnNodesMatching(
runningWithTag(clusterSpec.getClusterName()), configureScript);
NodePredicates.runningWithTag(clusterSpec.getClusterName()),
configureScript);
assert responses.size() > 0 : "no nodes matched "
+ clusterSpec.getClusterName();
} catch (RunScriptOnNodesException e) {
Expand Down

0 comments on commit 543bd0c

Please sign in to comment.