/**
* 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.command;
import static org.apache.whirr.ClusterSpec.Property.CLUSTER_NAME;
import static org.apache.whirr.ClusterSpec.Property.CREDENTIAL;
import static org.apache.whirr.ClusterSpec.Property.IDENTITY;
import static org.apache.whirr.ClusterSpec.Property.INSTANCE_TEMPLATES;
import static org.apache.whirr.ClusterSpec.Property.PRIVATE_KEY_FILE;
import static org.apache.whirr.ClusterSpec.Property.PROVIDER;
import java.io.IOException;
import java.io.PrintStream;
import java.util.EnumSet;
import java.util.Map;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.whirr.Cluster;
import org.apache.whirr.ClusterController;
import org.apache.whirr.ClusterControllerFactory;
import org.apache.whirr.ClusterSpec;
import org.apache.whirr.ClusterSpec.Property;
import org.apache.whirr.state.ClusterStateStore;
import org.apache.whirr.state.ClusterStateStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
/**
* An abstract command for interacting with clusters.
*/
public abstract class AbstractClusterCommand extends Command {
private static final Logger LOG =
LoggerFactory.getLogger(AbstractClusterCommand.class);
protected ClusterControllerFactory factory;
protected ClusterStateStoreFactory stateStoreFactory;
protected OptionParser parser = new OptionParser();
private Map<Property, OptionSpec<?>> optionSpecs;
private OptionSpec<String> configOption;
public AbstractClusterCommand(String name, String description, ClusterControllerFactory factory) {
this(name, description, factory, new ClusterStateStoreFactory());
}
public AbstractClusterCommand(String name, String description, ClusterControllerFactory factory,
ClusterStateStoreFactory stateStoreFactory) {
super(name, description);
configOption = parser.accepts("config", "Note that Whirr properties specified in " +
"this file should all have a whirr. prefix.")
.withRequiredArg()
.describedAs("config.properties")
.ofType(String.class);
this.factory = factory;
this.stateStoreFactory = stateStoreFactory;
optionSpecs = Maps.newHashMap();
for (Property property : EnumSet.allOf(Property.class)) {
ArgumentAcceptingOptionSpec<?> spec = null;
if (property.getType().equals(Boolean.class)) {
spec = parser.accepts(property.getSimpleName(), property.getDescription()).withOptionalArg()
.ofType(property.getType());
} else {
spec = parser.accepts(property.getSimpleName(), property.getDescription()).withRequiredArg()
.ofType(property.getType());
}
if (property.hasMultipleArguments()) {
spec.withValuesSeparatedBy(',');
}
optionSpecs.put(property, spec);
}
}
/**
* Load the cluster spec by parsing the command line option set
*/
protected ClusterSpec getClusterSpec(OptionSet optionSet) throws ConfigurationException {
Configuration optionsConfig = new PropertiesConfiguration();
for (Map.Entry<Property, OptionSpec<?>> entry : optionSpecs.entrySet()) {
Property property = entry.getKey();
OptionSpec<?> option = entry.getValue();
Object value;
if (property.hasMultipleArguments()) {
value = optionSet.valuesOf(option);
} else {
value = optionSet.valueOf(option);
}
if (value == null && property.getType().equals(Boolean.class) && optionSet.has(property.getSimpleName())) {
value = Boolean.TRUE.toString();
}
if (value != null) {
optionsConfig.setProperty(property.getConfigName(), value);
}
}
CompositeConfiguration config = new CompositeConfiguration();
config.addConfiguration(optionsConfig);
if (optionSet.has(configOption)) {
Configuration defaults = new PropertiesConfiguration(optionSet.valueOf(configOption));
config.addConfiguration(defaults);
}
ClusterSpec clusterSpec = new ClusterSpec(config);
for (Property required : EnumSet.of(CLUSTER_NAME, PROVIDER, IDENTITY, CREDENTIAL,
INSTANCE_TEMPLATES, PRIVATE_KEY_FILE)) {
if (clusterSpec.getConfiguration().getString(required.getConfigName()) == null) {
throw new IllegalArgumentException(String.format("Option '%s' not set.",
required.getSimpleName()));
}
}
return clusterSpec;
}
/**
* Get the cluster instance together with NodeMetadata (through API calls)
*/
protected Cluster getCluster(ClusterSpec clusterSpec, ClusterController controller)
throws IOException, InterruptedException {
return new Cluster(controller.getInstances(
clusterSpec, createClusterStateStore(clusterSpec)));
}
/**
* Create the specified service
*/
protected ClusterController createClusterController(String serviceName) {
ClusterController controller = factory.create(serviceName);
if (controller == null) {
LOG.warn("Unable to find service {}, using default.", serviceName);
controller = factory.create(null);
}
return controller;
}
/**
* Create the cluster state store object
*/
protected ClusterStateStore createClusterStateStore(ClusterSpec spec) {
return stateStoreFactory.create(spec);
}
protected void printProviderInfo(PrintStream out, PrintStream err,
ClusterSpec clusterSpec, OptionSet optionSet) {
if (!clusterSpec.isQuiet()) {
out.println(String.format("Running on provider %s using identity %s", clusterSpec.getProvider(), clusterSpec.getIdentity()));
}
}
/**
* Print command execution error and a hint to help the user get more help
*/
protected void printErrorAndHelpHint(PrintStream stream, Throwable e) {
stream.println(e.getMessage());
stream.println("Help: whirr help " + getName());
}
/**
* Print a generic usage indication for commands
*/
@Override
public void printUsage(PrintStream stream) throws IOException {
stream.println("Usage: whirr " + getName() + " [OPTIONS]");
stream.println();
parser.printHelpOn(stream);
}
}