/*
* Copyright 1999-2010 University of Chicago
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.globus.workspace.remoting.admin.client;
import org.apache.commons.cli.*;
import org.globus.workspace.network.AssociationEntry;
import org.globus.workspace.remoting.admin.NodeReport;
import org.globus.workspace.remoting.admin.VmmNode;
import org.nimbustools.api.services.admin.RemoteNodeManagement;
import java.io.*;
import java.rmi.RemoteException;
import java.util.*;
public class AdminClient extends RMIConfig {
private static final String PROP_RMI_BINDING_NODEMGMT_DIR = "rmi.binding.nodemgmt";
private static final String PROP_DEFAULT_MEMORY = "node.memory.default";
private static final String PROP_DEFAULT_NETWORKS = "node.networks.default";
private static final String PROP_DEFAULT_POOL = "node.pool.default";
private static final String FIELD_HOSTNAME = "hostname";
private static final String FIELD_POOL = "pool";
private static final String FIELD_MEMORY = "memory";
private static final String FIELD_MEM_REMAIN = "memory available";
private static final String FIELD_NETWORKS = "networks";
private static final String FIELD_ACTIVE = "active";
private static final String FIELD_IN_USE = "in_use";
private static final String FIELD_RESULT = "result";
private static final String FIELD_IP = "ip";
private static final String FIELD_MAC = "mac";
private static final String FIELD_BROADCAST = "broadcast";
private static final String FIELD_SUBNET_MASK = "subnet mask";
private static final String FIELD_GATEWAY = "gateway";
private static final String FIELD_EXPLICIT_MAC = "explicit mac";
final static String[] NODE_FIELDS = new String[] {
FIELD_HOSTNAME, FIELD_POOL, FIELD_MEMORY, FIELD_MEM_REMAIN,
FIELD_NETWORKS, FIELD_IN_USE, FIELD_ACTIVE };
final static String[] NODE_REPORT_FIELDS = new String[] {
FIELD_HOSTNAME, FIELD_POOL, FIELD_MEMORY, FIELD_NETWORKS,
FIELD_IN_USE, FIELD_ACTIVE, FIELD_RESULT,
};
final static String[] NODE_REPORT_FIELDS_SHORT = new String[] {
FIELD_HOSTNAME, FIELD_RESULT};
final static String[] NODE_ALLOCATION_FIELDS = new String[] {
FIELD_HOSTNAME, FIELD_IP, FIELD_MAC, FIELD_BROADCAST,
FIELD_SUBNET_MASK, FIELD_GATEWAY, FIELD_IN_USE, FIELD_EXPLICIT_MAC
};
private AdminAction action;
private List<String> hosts;
// node options for adding/updating
private int nodeMemory;
private boolean nodeMemoryConfigured;
private String nodeNetworks;
private String nodePool;
private boolean nodeActive = true;
private boolean nodeActiveConfigured;
private int inUse = 0;
private RemoteNodeManagement remoteNodeManagement;
public static void main(String args[]) {
Throwable anyError = null;
ParameterProblem paramError = null;
ExecutionProblem execError = null;
int ret = EXIT_OK;
try {
final AdminClient adminClient = new AdminClient();
adminClient.setupDebug(args);
adminClient.run(args);
} catch (ParameterProblem e) {
paramError = e;
anyError = e;
ret = EXIT_PARAMETER_PROBLEM;
} catch (ExecutionProblem e) {
execError = e;
anyError = e;
ret = EXIT_EXECUTION_PROBLEM;
} catch (Throwable t) {
anyError = t;
ret = EXIT_UNKNOWN_PROBLEM;
}
if (anyError == null) {
System.exit(ret);
} else {
logger.debug("Got error", anyError);
}
if (paramError != null) {
System.err.println("Parameter Problem:\n\n" + paramError.getMessage());
System.err.println("See --help");
} else if (execError != null) {
System.err.println(execError.getMessage());
} else {
System.err.println("An unexpected error was encountered. Please report this!");
System.err.println(anyError.getMessage());
System.err.println();
System.err.println("Stack trace:");
anyError.printStackTrace(System.err);
}
System.exit(ret);
}
public void run(String[] args)
throws ExecutionProblem, ParameterProblem {
this.loadArgs(args);
if (this.action == AdminAction.Help) {
InputStream is = AdminClient.class.getResourceAsStream("help.txt");
System.out.println(super.getHelpText(is));
return;
}
this.loadAdminClientConfig();
this.remoteNodeManagement = (RemoteNodeManagement) super.setupRemoting();
switch (this.action) {
case AddNodes:
run_addNodes();
break;
case ListNodes:
run_listNodes();
break;
case RemoveNodes:
run_removeNodes();
break;
case UpdateNodes:
run_updateNodes();
break;
case PoolAvailability:
run_poolAvail();
break;
}
}
private void run_addNodes() throws ParameterProblem, ExecutionProblem {
if (!this.nodeMemoryConfigured) {
throw new ParameterProblem(
"Node max memory must be specified as an argument ("+
Opts.MEMORY_LONG +") or config value");
}
if (this.nodeNetworks == null) {
throw new ParameterProblem(
"Node network associations must be specified as an argument ("+
Opts.NETWORKS_LONG +") or config value");
}
if (this.nodePool == null) {
throw new ParameterProblem(
"Node pool name must be specified as an argument ("+
Opts.POOL_LONG +") or config value");
}
final List<VmmNode> nodes = new ArrayList<VmmNode>(this.hosts.size());
for (String hostname : this.hosts) {
nodes.add(new VmmNode(hostname, this.nodeActive, this.nodePool,
this.nodeMemory, this.nodeNetworks, true));
}
final String nodesJson = gson.toJson(nodes);
NodeReport[] reports = null;
try {
final String reportJson = this.remoteNodeManagement.addNodes(nodesJson);
reports = gson.fromJson(reportJson, NodeReport[].class);
} catch (RemoteException e) {
handleRemoteException(e);
}
try {
reporter.report(nodeReportsToMaps(reports), this.outStream);
} catch (IOException e) {
throw new ExecutionProblem("Problem writing output: " + e.getMessage(), e);
}
}
private void run_listNodes() throws ExecutionProblem {
VmmNode[] nodes = null;
try {
final String nodesJson = this.remoteNodeManagement.listNodes();
nodes = this.gson.fromJson(nodesJson, VmmNode[].class);
} catch (RemoteException e) {
handleRemoteException(e);
}
try {
reporter.report(nodesToMaps(nodes), this.outStream);
} catch (IOException e) {
throw new ExecutionProblem("Problem writing output: " + e.getMessage(), e);
}
}
private void run_removeNodes() throws ExecutionProblem {
NodeReport[] reports = null;
try {
final String[] hostnames = this.hosts.toArray(new String[this.hosts.size()]);
final String reportJson = this.remoteNodeManagement.removeNodes(hostnames);
reports = gson.fromJson(reportJson, NodeReport[].class);
} catch (RemoteException e) {
handleRemoteException(e);
}
try {
reporter.report(nodeReportsToMaps(reports), this.outStream);
} catch (IOException e) {
throw new ExecutionProblem("Problem writing output: " + e.getMessage(), e);
}
}
private void run_updateNodes() throws ExecutionProblem {
final String[] hostnames = this.hosts.toArray(new String[this.hosts.size()]);
final Boolean active = this.nodeActiveConfigured ? this.nodeActive : null;
final String resourcepool = this.nodePool;
final Integer memory = this.nodeMemoryConfigured ? this.nodeMemory : null;
final String networks = this.nodeNetworks;
NodeReport[] reports = null;
try {
final String reportJson = this.remoteNodeManagement.updateNodes(
hostnames, active, resourcepool, memory, networks);
reports = gson.fromJson(reportJson, NodeReport[].class);
} catch (RemoteException e) {
super.handleRemoteException(e);
}
try {
reporter.report(nodeReportsToMaps(reports), this.outStream);
} catch (IOException e) {
throw new ExecutionProblem("Problem writing output: " + e.getMessage(), e);
}
}
private void run_poolAvail() throws ExecutionProblem {
AssociationEntry[] entries = null;
try {
if(this.nodePool != null) {
final String entriesJson = this.remoteNodeManagement.getNetworkPool(this.nodePool, this.inUse);
if(entriesJson == null) {
System.err.println("No entries with pool name " + nodePool + " found");
return;
}
entries = gson.fromJson(entriesJson, AssociationEntry[].class);
}
else {
final String entriesJson = this.remoteNodeManagement.getAllNetworkPools(this.inUse);
if(entriesJson == null) {
System.err.println("No pool entries found");
return;
}
entries = gson.fromJson(entriesJson, AssociationEntry[].class);
}
}
catch(RemoteException e) {
System.err.println(e.getMessage());
}
try {
reporter.report(nodeAllocationToMaps(entries), this.outStream);
}
catch (IOException e) {
throw new ExecutionProblem("Problem writing output: " + e.getMessage(), e);
}
}
private void loadAdminClientConfig() throws ParameterProblem, ExecutionProblem {
super.loadConfig(PROP_RMI_BINDING_NODEMGMT_DIR);
// only need node parameter values if doing add-nodes
if (this.action == AdminAction.AddNodes) {
if (!this.nodeMemoryConfigured) {
final String memString = properties.getProperty(PROP_DEFAULT_MEMORY);
if (memString != null) {
this.nodeMemory = parseMemory(memString);
this.nodeMemoryConfigured = true;
}
}
if (this.nodeNetworks == null) {
this.nodeNetworks = properties.getProperty(PROP_DEFAULT_NETWORKS);
}
if (this.nodePool == null) {
// if missing or invalid, error will come later if this value is actually needed
this.nodePool = properties.getProperty(PROP_DEFAULT_POOL);
}
}
}
private void loadArgs(String[] args) throws ParameterProblem {
logger.debug("Parsing command line arguments");
final CommandLineParser parser = new PosixParser();
final Opts opts = new Opts();
final CommandLine line;
try {
line = parser.parse(opts.getOptions(), args);
} catch (ParseException e) {
throw new ParameterProblem(e.getMessage(), e);
}
// figure action first
AdminAction theAction = null;
for (AdminAction a : AdminAction.values()) {
if (line.hasOption(a.option())) {
if (theAction == null) {
theAction = a;
} else {
throw new ParameterProblem("You may only specify a single action");
}
}
}
if (theAction == null) {
throw new ParameterProblem("You must specify an action");
}
this.action = theAction;
logger.debug("Action: " + theAction);
// short circuit for --help arg
if (theAction == AdminAction.Help) {
return;
}
// then action-specific arguments
if (theAction == AdminAction.AddNodes || theAction == AdminAction.UpdateNodes) {
this.hosts = parseHosts(line.getOptionValue(theAction.option()));
if (line.hasOption(Opts.MEMORY)) {
final String memString = line.getOptionValue(Opts.MEMORY);
if (memString == null || memString.trim().length() == 0) {
throw new ParameterProblem("Node memory value is empty");
}
this.nodeMemory = parseMemory(memString);
this.nodeMemoryConfigured = true;
}
if (line.hasOption(Opts.NETWORKS)) {
this.nodeNetworks = line.getOptionValue(Opts.NETWORKS);
}
if (line.hasOption(Opts.POOL)) {
String pool = line.getOptionValue(Opts.POOL);
if (pool == null || pool.trim().length() == 0) {
throw new ParameterProblem("Node pool value is empty");
}
this.nodePool = pool.trim();
}
final boolean active = line.hasOption(Opts.ACTIVE);
final boolean inactive = line.hasOption(Opts.INACTIVE);
if (active && inactive) {
throw new ParameterProblem("You cannot specify both " +
Opts.ACTIVE_LONG + " and " + Opts.INACTIVE_LONG);
}
if (active) {
this.nodeActiveConfigured = true;
}
if (inactive) {
this.nodeActive = false;
this.nodeActiveConfigured = true;
}
} else if (theAction == AdminAction.RemoveNodes) {
this.hosts = parseHosts(line.getOptionValue(theAction.option()));
} else if (theAction == AdminAction.ListNodes) {
final String hostArg = line.getOptionValue(AdminAction.ListNodes.option());
if (hostArg != null) {
this.hosts = parseHosts(hostArg);
}
} else if (theAction == AdminAction.PoolAvailability) {
if(line.hasOption(Opts.POOL)) {
final String pool = line.getOptionValue(Opts.POOL);
if(pool == null || pool.trim().length() == 0) {
throw new ParameterProblem("Pool name value is empty");
}
this.nodePool = pool;
}
if(line.hasOption(Opts.FREE)) {
this.inUse = RemoteNodeManagement.FREE_ENTRIES;
}
if(line.hasOption(Opts.USED)) {
this.inUse = RemoteNodeManagement.USED_ENTRIES;
}
}
//finally everything else
if (!line.hasOption(Opts.CONFIG)) {
throw new ParameterProblem(Opts.CONFIG_LONG + " option is required");
}
String config = line.getOptionValue(Opts.CONFIG);
if (config == null || config.trim().length() == 0) {
throw new ParameterProblem("Config file path is invalid");
}
super.configPath = config.trim();
final boolean batchMode = line.hasOption(Opts.BATCH);
final boolean json = line.hasOption(Opts.JSON);
final Reporter.OutputMode mode;
if (batchMode && json) {
throw new ParameterProblem("You cannot specify both " +
Opts.BATCH_LONG + " and " + Opts.JSON_LONG);
} else if (batchMode) {
mode = Reporter.OutputMode.Batch;
} else if (json) {
mode = Reporter.OutputMode.Json;
} else {
mode = Reporter.OutputMode.Friendly;
}
final String[] fields;
if (line.hasOption(Opts.REPORT)) {
fields = parseFields(line.getOptionValue(Opts.REPORT), theAction);
} else {
fields = theAction.fields();
}
String delimiter = null;
if (line.hasOption(Opts.DELIMITER)) {
delimiter = line.getOptionValue(Opts.DELIMITER);
}
this.reporter = new Reporter(mode, fields, delimiter);
if (line.hasOption(Opts.OUTPUT)) {
final String filename = line.getOptionValue(Opts.OUTPUT);
final File f = new File(filename);
try {
this.outStream = new FileOutputStream(f);
} catch (FileNotFoundException e) {
throw new ParameterProblem(
"Specified output file could not be opened for writing: " +
f.getAbsolutePath(), e);
}
} else {
this.outStream = System.out;
}
final List leftovers = line.getArgList();
if (leftovers != null && !leftovers.isEmpty()) {
throw new ParameterProblem("There are unrecognized arguments, check -h to make " +
"sure you are doing the intended thing: " + leftovers.toString());
}
}
private int parseMemory(String memoryString) throws ParameterProblem {
final int memory;
try {
memory = Integer.valueOf(memoryString.trim());
} catch (NumberFormatException e) {
throw new ParameterProblem("Node memory value must be numeric");
}
if (memory < 0) {
throw new ParameterProblem("Node memory value must be non-negative");
}
return memory;
}
private static List<String> parseHosts(String hostString)
throws ParameterProblem {
if (hostString == null) {
throw new ParameterProblem("Hosts list is invalid");
}
final String[] hostArray = hostString.trim().split("\\s*,\\s*");
if (hostArray.length == 0) {
throw new ParameterProblem("Hosts list is empty");
}
final List<String> hosts = new ArrayList<String>(hostArray.length);
for (final String host : hostArray) {
if (!host.matches("^[\\w-\\.]+$")) {
throw new ParameterProblem("Invalid node hostname specified: " + host);
}
hosts.add(host);
}
return hosts;
}
private static List<Map<String,String>> nodesToMaps(VmmNode[] nodes) {
List<Map<String,String>> maps = new ArrayList<Map<String, String>>(nodes.length);
for (VmmNode node : nodes) {
maps.add(nodeToMap(node));
}
return maps;
}
private static Map<String,String> nodeToMap(VmmNode node) {
final HashMap<String, String> map =
new HashMap<String, String>(7);
map.put(FIELD_HOSTNAME, node.getHostname());
map.put(FIELD_POOL, node.getPoolName());
map.put(FIELD_MEMORY, String.valueOf(node.getMemory()));
map.put(FIELD_MEM_REMAIN, String.valueOf(node.getMemRemain()));
map.put(FIELD_NETWORKS, node.getNetworkAssociations());
map.put(FIELD_IN_USE, String.valueOf(!node.isVacant()));
map.put(FIELD_ACTIVE, String.valueOf(node.isActive()));
return map;
}
private static List<Map<String,String>> nodeReportsToMaps(NodeReport[] nodeReports) {
List<Map<String,String>> maps = new ArrayList<Map<String, String>>(nodeReports.length);
for (NodeReport nodeReport : nodeReports) {
maps.add(nodeReportToMap(nodeReport));
}
return maps;
}
private static Map<String,String> nodeReportToMap(NodeReport nodeReport) {
final HashMap<String, String> map =
new HashMap<String, String>(2);
map.put(FIELD_HOSTNAME, nodeReport.getHostname());
map.put(FIELD_RESULT, nodeReport.getState());
final VmmNode node = nodeReport.getNode();
if (node == null) {
map.put(FIELD_POOL, null);
map.put(FIELD_MEMORY, null);
map.put(FIELD_NETWORKS, null);
map.put(FIELD_IN_USE, null);
map.put(FIELD_ACTIVE, null);
} else {
map.put(FIELD_POOL, node.getPoolName());
map.put(FIELD_MEMORY, String.valueOf(node.getMemory()));
map.put(FIELD_NETWORKS, node.getNetworkAssociations());
map.put(FIELD_IN_USE, String.valueOf(!node.isVacant()));
map.put(FIELD_ACTIVE, String.valueOf(node.isActive()));
}
return map;
}
private static List<Map<String,String>> nodeAllocationToMaps(AssociationEntry[] entries) {
List<Map<String,String>> maps = new ArrayList<Map<String, String>>(entries.length);
for (AssociationEntry entry : entries) {
maps.add(nodeAllocationToMap(entry));
}
return maps;
}
private static Map<String,String> nodeAllocationToMap(AssociationEntry entry) {
final HashMap<String, String> map = new HashMap(8);
map.put(FIELD_HOSTNAME, entry.getHostname());
map.put(FIELD_IP, entry.getIpAddress());
map.put(FIELD_MAC, entry.getMac());
map.put(FIELD_BROADCAST, entry.getBroadcast());
map.put(FIELD_SUBNET_MASK, entry.getSubnetMask());
map.put(FIELD_GATEWAY, entry.getGateway());
map.put(FIELD_IN_USE, Boolean.toString(entry.isInUse()));
map.put(FIELD_EXPLICIT_MAC, Boolean.toString(entry.isExplicitMac()));
return map;
}
}
enum AdminAction implements AdminEnum {
AddNodes(Opts.ADD_NODES, AdminClient.NODE_REPORT_FIELDS),
ListNodes(Opts.LIST_NODES, AdminClient.NODE_FIELDS),
RemoveNodes(Opts.REMOVE_NODES, AdminClient.NODE_REPORT_FIELDS_SHORT),
UpdateNodes(Opts.UPDATE_NODES, AdminClient.NODE_REPORT_FIELDS),
PoolAvailability(Opts.POOL_AVAILABILITY, AdminClient.NODE_ALLOCATION_FIELDS),
Help(Opts.HELP, null);
private final String option;
private final String[] fields;
AdminAction(String option, String[] fields) {
this.option = option;
this.fields = fields;
}
public String option() {
return option;
}
public String[] fields() {
return fields;
}
}