/**
 * 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.hadoop.hdfs;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.security.auth.login.LoginException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.hdfs.protocol.AvatarConstants.InstanceId;
import org.apache.hadoop.hdfs.protocol.AvatarProtocol;
import org.apache.hadoop.hdfs.protocol.AvatarConstants.Avatar;
import org.apache.hadoop.hdfs.protocol.AvatarConstants.StartupOption;
import org.apache.hadoop.hdfs.protocol.FSConstants.SafeModeAction;
import org.apache.hadoop.hdfs.server.namenode.AvatarNode;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.hdfs.server.namenode.ZookeeperTxId;
import org.apache.hadoop.io.retry.RetryPolicies;
import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.io.retry.RetryProxy;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.UnixUserGroupInformation;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Stat;

/**
 * A {@link AvatarShell} that allows browsing configured avatar policies.
 */
public class AvatarShell extends Configured implements Tool {
  public static final Log LOG = LogFactory
      .getLog("org.apache.hadoop.AvatarShell");

  // AvatarShell deals with hdfs configuration so need to add these
  static {
    Configuration.addDefaultResource("hdfs-default.xml");
    Configuration.addDefaultResource("hdfs-site.xml");
    Configuration.addDefaultResource("avatar-default.xml");
    Configuration.addDefaultResource("avatar-site.xml");
  }

  public AvatarProtocol avatarnode;
  AvatarProtocol rpcAvatarnode;
  private UnixUserGroupInformation ugi;
  volatile boolean clientRunning = true;
  private Configuration conf;
  // We need to keep the default configuration around with 
  // Avatar specific fields unmodified
  private Configuration originalConf;

  /**
   * Start AvatarShell.
   * <p>
   * The AvatarShell connects to the specified AvatarNode and performs basic
   * configuration options.
   * 
   * @throws IOException
   */
  public AvatarShell() throws IOException {
    this(new Configuration());
  }

  /**
   * The AvatarShell connects to the specified AvatarNode and performs basic
   * configuration options.
   * 
   * @param conf
   *          The Hadoop configuration
   * @throws IOException
   */
  public AvatarShell(Configuration conf) {
    super(conf);
    this.conf = this.originalConf = conf;
  }

  public void initAvatarRPC() throws IOException {
    try {
      this.ugi = UnixUserGroupInformation.login(conf, true);
    } catch (LoginException e) {
      throw (IOException) (new IOException().initCause(e));
    }

    this.rpcAvatarnode = createRPCAvatarnode(AvatarNode.getAddress(conf), conf,
        ugi);
    this.avatarnode = createAvatarnode(rpcAvatarnode);
  }

  public static AvatarProtocol createAvatarnode(Configuration conf)
      throws IOException {
    return createAvatarnode(AvatarNode.getAddress(conf), conf);
  }

  public static AvatarProtocol createAvatarnode(
      InetSocketAddress avatarNodeAddr, Configuration conf) throws IOException {
    try {
      return createAvatarnode(createRPCAvatarnode(avatarNodeAddr, conf,
          UnixUserGroupInformation.login(conf, true)));
    } catch (LoginException e) {
      throw (IOException) (new IOException().initCause(e));
    }
  }

  private static AvatarProtocol createRPCAvatarnode(
      InetSocketAddress avatarNodeAddr, Configuration conf,
      UnixUserGroupInformation ugi) throws IOException {
    LOG.info("AvatarShell connecting to " + avatarNodeAddr);
    return (AvatarProtocol) RPC.getProxy(AvatarProtocol.class,
        AvatarProtocol.versionID, avatarNodeAddr, ugi, conf, NetUtils
            .getSocketFactory(conf, AvatarProtocol.class));
  }

  private static AvatarProtocol createAvatarnode(AvatarProtocol rpcAvatarnode)
      throws IOException {
    RetryPolicy createPolicy = RetryPolicies
        .retryUpToMaximumCountWithFixedSleep(5, 5000, TimeUnit.MILLISECONDS);

    Map<Class<? extends Exception>, RetryPolicy> remoteExceptionToPolicyMap = new HashMap<Class<? extends Exception>, RetryPolicy>();

    Map<Class<? extends Exception>, RetryPolicy> exceptionToPolicyMap = new HashMap<Class<? extends Exception>, RetryPolicy>();
    exceptionToPolicyMap.put(RemoteException.class, RetryPolicies
        .retryByRemoteException(RetryPolicies.TRY_ONCE_THEN_FAIL,
            remoteExceptionToPolicyMap));
    RetryPolicy methodPolicy = RetryPolicies.retryByException(
        RetryPolicies.TRY_ONCE_THEN_FAIL, exceptionToPolicyMap);
    Map<String, RetryPolicy> methodNameToPolicyMap = new HashMap<String, RetryPolicy>();

    methodNameToPolicyMap.put("create", methodPolicy);

    return (AvatarProtocol) RetryProxy.create(AvatarProtocol.class,
        rpcAvatarnode, methodNameToPolicyMap);
  }

    /**
   * Close the connection to the avatarNode.
   */
  public synchronized void close() throws IOException {
    if (clientRunning) {
      clientRunning = false;
      RPC.stopProxy(rpcAvatarnode);
    }
  }

  /**
   * Displays format of commands.
   */
  private static void printUsage(String cmd) {
    if ("-showAvatar".equals(cmd)) {
      System.err.println("Usage: java AvatarShell"
          + " [-{zero|one} -showAvatar] [-service serviceName]");
    } else if ("-setAvatar".equals(cmd)) {
      System.err.println("Usage: java AvatarShell"
              + " [-{zero|one} -setAvatar {primary|standby}] [-force] [-service serviceName]");
    } else if ("-shutdownAvatar".equals(cmd)) {
      System.err.println("Usage: java AvatarShell" +
          " [-{zero|one} -shutdownAvatar] [-service serviceName]");
    } else if ("-failover".equals(cmd)) {
      System.err.println("Usage: java AvatarShell" +
          " [-failover] [-service serviceName]");
    }  else if ("-isInitialized".equals(cmd)) {
        System.err.println("Usage: java AvatarShell" +
            " [-{zero|one} -isInitialized] [-service serviceName]");
    } else if ("-waittxid".equals(cmd)) {
      System.err.println("Usage: java AvatarShell"
          + " [-waittxid] [-service serviceName]");
    } else {
      System.err.println("Usage: java AvatarShell");
      System.err.println("           [-{zero|one} -showAvatar] [-service serviceName]");
      System.err.println("           [-{zero|one} -setAvatar {primary|standby}] [-force] [-service serviceName]");
      System.err.println("           [-{zero|one} -shutdownAvatar] [-service serviceName]");
      System.err.println("           [-{zero|one} -leaveSafeMode] [-service serviceName]");
      System.err.println("           [-failover] [-service serviceName]");
      System.err.println("           [-{zero|one} -isInitialized] [-service serviceName]");
      System.err.println("           [-waittxid] [-service serviceName]");
      System.err.println();
      ToolRunner.printGenericCommandUsage(System.err);
    }
  }

  private boolean isPrimary(Configuration conf, String zkRegistration) {
    InetSocketAddress actualAddr = NameNode.getClientProtocolAddress(conf);
    String actualName = actualAddr.getHostName() + ":" + actualAddr.getPort();
    return actualName.equals(zkRegistration);
  }

  protected long getMaxWaitTimeForWaitTxid() {
    return 1000 * 60 * 10; // 10 minutes.
  }

  /**
   * Waits till the last txid node appears in Zookeeper, such that it matches
   * the ssid node.
   */
  private void waitForLastTxIdNode(AvatarZooKeeperClient zk, Configuration conf)
      throws Exception {
    // Gather session id and transaction id data.
    String address = AvatarNode.getClusterAddress(conf);
    long maxWaitTime = this.getMaxWaitTimeForWaitTxid();
    long start = System.currentTimeMillis();
    while (true) {
      if (System.currentTimeMillis() - start > maxWaitTime) {
        throw new IOException("No valid last txid znode found");
      }
      try {
        long sessionId = zk.getPrimarySsId(address);
        ZookeeperTxId zkTxId = zk.getPrimaryLastTxId(address);
        if (sessionId != zkTxId.getSessionId()) {
          LOG.warn("Session Id in the ssid node : " + sessionId
              + " does not match the session Id in the txid node : "
              + zkTxId.getSessionId() + " retrying...");
          Thread.sleep(10000);
          continue;
        }
      } catch (Throwable e) {
        LOG.warn("Caught exception : " + e + " retrying ...");
        Thread.sleep(10000);
        continue;
      }
      break;
    }
  }

  private String[] getAvatarCommand(String serviceName, String... args) {
    List<String> cmdlist = new ArrayList<String>();
    for (String arg : args) {
      cmdlist.add(arg);
    }
    if (serviceName != null) {
      cmdlist.add("-service");
      cmdlist.add(serviceName);
    }
    return cmdlist.toArray(new String[cmdlist.size()]);
  }

  private int failover(String serviceName) throws Exception {
    AvatarZooKeeperClient zk = new AvatarZooKeeperClient(conf, null);
    try {
      InetSocketAddress defaultAddr = NameNode.getClientProtocolAddress(conf);
      String defaultName = defaultAddr.getHostName() + ":"
          + defaultAddr.getPort();
      String registration = zk.getPrimaryAvatarAddress(defaultName, new Stat(),
          false);

      if (registration == null) {
        throw new IOException("No node found in zookeeper");
      }

      Configuration zeroConf = AvatarNode.updateAddressConf(conf,
          InstanceId.NODEZERO);
      Configuration oneConf = AvatarNode.updateAddressConf(conf,
          InstanceId.NODEONE);

      boolean onePrimary = isPrimary(oneConf, registration);
      boolean zeroPrimary = isPrimary(zeroConf, registration);
      if (!onePrimary && !zeroPrimary) {
        throw new IOException(
            "None of the -zero or -one instances are the primary in zk, zk registration : "
                + registration);
      }

      AvatarShell shell = new AvatarShell(originalConf);

      String[] cmd = null;
      if (zeroPrimary) {
        cmd = getAvatarCommand(serviceName, "-one", "-isInitialized");
        if (shell.run(cmd) != 0) {
          throw new IOException("-one is not initialized");
        }
        cmd = getAvatarCommand(serviceName, "-zero", "-shutdownAvatar");
        if (shell.run(cmd) != 0) {
          throw new IOException("-zero shutdownAvatar failed");
        }
        waitForLastTxIdNode(zk, originalConf);
        cmd = getAvatarCommand(serviceName, "-one", "-setAvatar", "primary");
        return shell.run(cmd);
      } else {
        cmd = getAvatarCommand(serviceName, "-zero", "-isInitialized");
        if (shell.run(cmd) != 0) {
          throw new IOException("-zero is not initialized");
        }
        cmd = getAvatarCommand(serviceName, "-one", "-shutdownAvatar");
        if (shell.run(cmd) != 0) {
          throw new IOException("-one shutdownAvatar failed");
        }
        waitForLastTxIdNode(zk, originalConf);
        cmd = getAvatarCommand(serviceName, "-zero", "-setAvatar", "primary");
        return shell.run(cmd);
      }
    } finally {
      zk.shutdown();
    }
  }

  private boolean processServiceName(String serviceName) {
    // validate service name
    if (serviceName != null) {
      if (!AvatarNode.validateServiceName(conf, serviceName)) {
        return false;
      }

      // remove the service name suffix
      AvatarNode.initializeGenericKeys(conf, serviceName);
    }
    return true;
  }

  /**
   * run
   */
  public int run(String argv[]) throws Exception {

    if (argv.length < 1) {
      printUsage("");
      return -1;
    }
    
    int exitCode = 0;
    
    if ("-waittxid".equals(argv[0])) {
      AvatarZooKeeperClient zk = new AvatarZooKeeperClient(conf, null);
      try {
        String serviceName = null;
        if (argv.length == 3 && "-service".equals(argv[1])) {
          serviceName = argv[2];
        }
        if (!processServiceName(serviceName)) {
          return -1;
        }
        waitForLastTxIdNode(zk, originalConf);
      } catch (Exception e) {
        exitCode = -1;
        System.err.println(argv[0].substring(1) + ": "
            + e.getLocalizedMessage());
      } finally {
        zk.shutdown();
      }
      return exitCode;
    }

    if ("-failover".equals(argv[0])) {
      try {
        String serviceName = null;
        if (argv.length == 3 && "-service".equals(argv[1])) {
          serviceName = argv[2];
        }
        if (!processServiceName(serviceName)) {
          return -1;
        }
        exitCode = failover(serviceName);
      } catch (Exception e) {
        exitCode = -1;
        System.err.println(argv[0].substring(1) + ": "
            + e.getLocalizedMessage());
      }
      return exitCode;
    }
    
    int i = 0;
    String instance = argv[i++];
    String cmd = argv[i++];

    // Get the role
    String role = null;
    boolean forceSetAvatar = false;
    if ("-setAvatar".equals(cmd)) {
      if (argv.length < 3) {
        printUsage(cmd);
        return -1;
      }
      role = argv[i++];
      if (i != argv.length && "-force".equals(argv[i])) {
        forceSetAvatar = true;
        i++;
      }
    }

    String serviceName = null;
    if (i != argv.length) {
      if (i+2 != argv.length || !"-service".equals(argv[i])) {
        printUsage(cmd);
        return -1;
      }
      serviceName = argv[i+1];
    }
    
    if (!processServiceName(serviceName)) {
      return -1;
    }

    // remove 0/1 suffix
    if ((conf = AvatarZKShell.updateConf(instance, originalConf)) == null) {
      printUsage(cmd);
      return -1;
    }

    initAvatarRPC();

    try {
      if ("-showAvatar".equals(cmd)) {
        exitCode = showAvatar();
      } else if ("-setAvatar".equals(cmd)) {
        exitCode = setAvatar(role, forceSetAvatar);
      } else if ("-isInitialized".equals(cmd)) {
        exitCode = isInitialized();
      } else if ("-shutdownAvatar".equals(cmd)) {
        shutdownAvatar();
      } else if ("-leaveSafeMode".equals(cmd)) {
        leaveSafeMode();
      } else {
        exitCode = -1;
        System.err.println(cmd.substring(1) + ": Unknown command");
        printUsage("");
      }
    } catch (IllegalArgumentException arge) {
      exitCode = -1;
      arge.printStackTrace();
      System.err.println(cmd.substring(1) + ": " + arge.getLocalizedMessage());
      printUsage(cmd);
    } catch (RemoteException e) {
      //
      // This is a error returned by avatarnode server. Print
      // out the first line of the error mesage, ignore the stack trace.
      exitCode = -1;
      try {
        String[] content;
        content = e.getLocalizedMessage().split("\n");
        System.err.println(cmd.substring(1) + ": " + content[0]);
      } catch (Exception ex) {
        System.err.println(cmd.substring(1) + ": " + ex.getLocalizedMessage());
      }
    } catch (IOException e) {
      //
      // IO exception encountered locally.
      // 
      exitCode = -1;
      System.err.println(cmd.substring(1) + ": " + e.getLocalizedMessage());
    } catch (Throwable re) {
      exitCode = -1;
      System.err.println(cmd.substring(1) + ": " + re.getLocalizedMessage());
    } finally {
    }
    return exitCode;
  }

  /**
   * Apply operation specified by 'cmd' on all parameters starting from
   * argv[startindex].
   */
  private int showAvatar()
      throws IOException {
    int exitCode = 0;
    Avatar avatar = avatarnode.reportAvatar();
    System.out.println("The current avatar of " + AvatarNode.getAddress(conf)
        + " is " + avatar);
    return exitCode;
  }
  
  private int isInitialized()
      throws IOException {
    int exitCode = avatarnode.isInitialized() ? 0 : -1;
    if (exitCode == 0) {
      LOG.info("Standby has been successfully initialized");
    } else {
      LOG.error("Standby has not been initialized yet");
    }
    return exitCode;
  }

  /**
   * Sets the avatar to the specified value
   */
  public int setAvatar(String role, boolean forceSetAvatar)
      throws IOException {
    Avatar dest;
    if (Avatar.ACTIVE.toString().equalsIgnoreCase(role)) {
      dest = Avatar.ACTIVE;
    } else if (Avatar.STANDBY.toString().equalsIgnoreCase(role)) {
      throw new IOException("setAvatar Command only works to switch avatar" +
      		" from Standby to Primary");
    } else {
      throw new IOException("Unknown avatar type " + role);
    }
    Avatar current = avatarnode.getAvatar();
    if (current == dest) {
      System.out.println("This instance is already in " + current + " avatar.");
    } else {
      avatarnode.setAvatar(dest, forceSetAvatar);
      updateZooKeeper();
    }
    return 0;
  }
  
  public void shutdownAvatar() throws IOException {
    clearZooKeeper();
    avatarnode.shutdownAvatar();
  }
  
  public void leaveSafeMode() throws IOException {
    avatarnode.setSafeMode(SafeModeAction.SAFEMODE_LEAVE);
  }

  public void clearZooKeeper() throws IOException {
    Avatar avatar = avatarnode.getAvatar();
    if (avatar != Avatar.ACTIVE) {
      throw new IOException("Cannot clear zookeeper because the node " +
      		" provided is not Primary");
    }
    String connection = conf.get("fs.ha.zookeeper.quorum");
    if (connection == null)
      return;
    AvatarZooKeeperClient zk = new AvatarZooKeeperClient(conf, null);

    // Clear NameNode address in ZK
    System.out.println("Clear Client Address");
    LOG.info("Clear Client Address information in ZooKeeper");
    InetSocketAddress defaultAddr;
    String[] aliases;

    defaultAddr = NameNode.getClientProtocolAddress(originalConf);

    String defaultName = defaultAddr.getHostName() + ":"
        + defaultAddr.getPort();

    zk.clearPrimary(defaultName);
    
    aliases = conf.getStrings("fs.default.name.aliases");
    if (aliases != null) {
      for (String alias : aliases) {
        zk.clearPrimary(alias);
      }
    }
    System.out.println("Clear Service Address");
    LOG.info("Clear Service Address information in ZooKeeper");
    // Clear service address in ZK

    defaultAddr = NameNode.getDNProtocolAddress(originalConf);
    if (defaultAddr != null) {
      String defaultServiceName = defaultAddr.getHostName() + ":"
          + defaultAddr.getPort();
      zk.clearPrimary(defaultServiceName);
    }
    aliases = conf.getStrings("dfs.namenode.dn-address.aliases");
    if (aliases != null) {
      for (String alias : aliases) {
        zk.clearPrimary(alias);
      }
    }
    System.out.println("Clear Http Address");
    LOG.info("Clear Http Address information in ZooKeeper");
    // Clear http address in ZK
    // Stolen from NameNode so we have the same code in both places
    defaultAddr = 
      NetUtils.createSocketAddr(originalConf.get("dfs.http.address"));

    String defaultHttpAddress = defaultAddr.getHostName() + ":"
        + defaultAddr.getPort();

    zk.clearPrimary(defaultHttpAddress);

    aliases = conf.getStrings("dfs.http.address.aliases");
    if (aliases != null) {
      for (String alias : aliases) {
        zk.clearPrimary(alias);
      }
    }
  }

  /*
   * This method tries to update the information in ZooKeeper
   * For every address of the NameNode it is being run for
   * (fs.default.name, dfs.namenode.dn-address, dfs.namenode.http.address)
   * if they are present.
   * It also creates information for aliases in ZooKeeper for lists of strings
   * in fs.default.name.aliases, dfs.namenode.dn-address.aliases and 
   * dfs.namenode.http.address.aliases
   * 
   * Each address it transformed to the address of the zNode to be created by
   * substituting all . and : characters to /. The slash is also added in the 
   * front to make it a valid zNode address.
   * So dfs.domain.com:9000 will be /dfs/domain/com/9000
   * 
   * If any part of the path does not exist it is created automatically
   * 
   */
  public void updateZooKeeper() throws IOException {
    Avatar avatar = avatarnode.getAvatar();
    if (avatar != Avatar.ACTIVE) {
      throw new IOException("Cannot update ZooKeeper information to point to " +
      		"the AvatarNode in Standby mode");
    }
    String connection = conf.get("fs.ha.zookeeper.quorum");
    if (connection == null)
      return;
    AvatarZooKeeperClient zk = new AvatarZooKeeperClient(conf, null);

    // Update NameNode address in ZK
    System.out.println("Update Client Address");
    LOG.info("Update Client Address information in ZooKeeper");
    InetSocketAddress defaultAddr;
    String[] aliases;
    InetSocketAddress addr = NameNode.getClientProtocolAddress(conf);

    String primaryAddress = addr.getHostName() + ":" + addr.getPort();
    defaultAddr = NameNode.getClientProtocolAddress(originalConf);

    String defaultName = defaultAddr.getHostName() + ":"
        + defaultAddr.getPort();
    zk.registerPrimary(defaultName, primaryAddress);
    aliases = conf.getStrings("fs.default.name.aliases");
    if (aliases != null) {
      for (String alias : aliases) {
        zk.registerPrimary(alias, primaryAddress);
      }
    }
    System.out.println("Update Service Address");
    LOG.info("Update Service Address information in ZooKeeper");
    // Update service address in ZK
    addr = NameNode.getDNProtocolAddress(conf);

    defaultAddr = NameNode.getDNProtocolAddress(originalConf);
    if (defaultAddr != null) {
      String primaryServiceAddress = addr.getHostName() + ":" + addr.getPort();
      String defaultServiceName = defaultAddr.getHostName() + ":"
          + defaultAddr.getPort();
      zk.registerPrimary(defaultServiceName, primaryServiceAddress);
    }
    aliases = conf.getStrings("dfs.namenode.dn-address.aliases");
    if (aliases != null) {
      String primaryServiceAddress = addr.getHostName() + ":" + addr.getPort();
      for (String alias : aliases) {
        zk.registerPrimary(alias, primaryServiceAddress);
      }
    }
    System.out.println("Update Http Address");
    LOG.info("Update Http Address information in ZooKeeper");
    // Update http address in ZK
    // Stolen from NameNode so we have the same code in both places
    addr = NetUtils.createSocketAddr(conf.get("dfs.http.address"));
    String primaryHttpAddress = addr.getHostName() + ":" + addr.getPort();
    defaultAddr = 
      NetUtils.createSocketAddr(originalConf.get("dfs.http.address"));

    String defaultHttpAddress = defaultAddr.getHostName() + ":"
        + defaultAddr.getPort();
    zk.registerPrimary(defaultHttpAddress, primaryHttpAddress);

    aliases = conf.getStrings("dfs.http.address.aliases");
    if (aliases != null) {
      for (String alias : aliases) {
        zk.registerPrimary(alias, primaryHttpAddress);
      }
    }
  }

  

  public static class DummyWatcher implements Watcher {

    @Override
    public void process(WatchedEvent event) {
      // This is a dummy watcher since we are only doing creates and deletes
    }
  }
  /**
   * main() has some simple utility methods
   */
  public static void main(String argv[]) throws Exception {
    AvatarShell shell = null;
    try {
      shell = new AvatarShell();
    } catch (RPC.VersionMismatch v) {
      System.err.println("Version Mismatch between client and server"
          + "... command aborted.");
      System.exit(-1);
    } catch (IOException e) {
      System.err.println("Bad connection to AvatarNode. command aborted.");
      System.exit(-1);
    }

    int res;
    try {
      res = ToolRunner.run(shell, argv);
    } finally {
      shell.close();
    }
    System.exit(res);
  }
}