/**
 * 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.server.namenode;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.net.InetSocketAddress;
import javax.security.auth.login.LoginException;

import org.apache.hadoop.ipc.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hdfs.server.protocol.SnapshotProtocol;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.io.retry.RetryPolicies;
import org.apache.hadoop.io.retry.RetryProxy;
import org.apache.hadoop.security.UnixUserGroupInformation;
import org.apache.hadoop.fs.Path;

/**
 * A SnapshotShell that allows creating new snapshots, deleting existing snapshots
 * and listing all snapshots
 */
public class SnapshotShell extends Configured implements Tool {
  public static final Log LOG = LogFactory.getLog(SnapshotShell.class);
  public SnapshotProtocol ssNode;
  final SnapshotProtocol rpcSsNode;
  private UnixUserGroupInformation ugi;
  volatile boolean clientRunning = true;

  /**
   * Start SnapshotShell.
   * 
   * The SnapshotShell connects to the specified SnapshotNode and performs basic
   * options.
   * @throws IOException
   */
  public SnapshotShell() throws IOException {
    this(new Configuration());
  }

  public SnapshotShell(Configuration conf) throws IOException {
    this(conf, SnapshotNode.getAddress(conf));
  }

  public SnapshotShell(Configuration conf, InetSocketAddress address) throws IOException {
    super(conf);
    try {
      this.ugi = UnixUserGroupInformation.login(conf, true);
    } catch (LoginException e) {
      throw (IOException)(new IOException().initCause(e));
    }

    this.rpcSsNode = createRPCSnapshotNode(address, conf, ugi);
    this.ssNode = createSnapshotNode(rpcSsNode);
  }

  public static SnapshotProtocol createSnapshotNode(Configuration conf) throws IOException {
   return createSnapshotNode(SnapshotNode.getAddress(conf), conf);
  }

  public static SnapshotProtocol createSnapshotNode(InetSocketAddress ssNodeAddr,
                                            Configuration conf) throws IOException {
    try {
      return createSnapshotNode(createRPCSnapshotNode(ssNodeAddr, conf,
                                     UnixUserGroupInformation.login(conf, true)));
    } catch (LoginException e) {
      throw (IOException)(new IOException().initCause(e));
    }
  }

  private static SnapshotProtocol createSnapshotNode(SnapshotProtocol rpcSnapshotNode) 
  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 (SnapshotProtocol) RetryProxy.create(SnapshotProtocol.class, 
                                 rpcSnapshotNode, methodNameToPolicyMap);
  }


  private static SnapshotProtocol createRPCSnapshotNode(InetSocketAddress ssNodeAddr,
                    Configuration conf, UnixUserGroupInformation ugi) throws IOException {
    LOG.info("SnapshotShell connecting to " + ssNodeAddr);
    return (SnapshotProtocol)RPC.getProxy(SnapshotProtocol.class, SnapshotProtocol.versionID, 
           ssNodeAddr, ugi, conf, NetUtils.getSocketFactory(conf, SnapshotProtocol.class));
  }


  private void checkOpen() throws IOException {
    if (!clientRunning) {
      IOException result = new IOException("SnapshotNode closed");
      throw result;
    }
  }

  public SnapshotProtocol getSnapshotNode() {
    return ssNode;
  }

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

  /**
   * Displays format of commands.
   */
  private static void printUsage(String cmd) {
    if ("-create".equals(cmd)) {
      System.err.println("Usage: java SnapshotShell [-create snapshot_id [-ignoreleases]]");
    } else if ("-list".equals(cmd)) {
      System.err.println("Usage: java SnapshotShell [-list]");
    } else if ("-delete".equals(cmd)) {
      System.err.println("Usage: java SnapshotShell [-delete snapshot_id]");
    } else {
      System.err.println("Usage: java SnapshotShell "
                         + "[-create snapshot_id [-ignoreleases]] "
                         + "[-list] "
                         + "[-delete snapshot_id] ");
    }
  }

  /**
   * run
   */
  @Override
  public int run(String argv[]) throws Exception {
    if (argv.length < 1) {
      printUsage(null);
      return -1;
    }

    int exitCode = -1;
    int i = 0;
    String cmd = argv[i++];

    // verify that we have enough command line parameters
    if ("-create".equals(cmd)) {
      boolean error = true;
      if (argv.length == 2) {
        error = false;
      } else if (argv.length == 3) {
        if ("-ignoreleases".equals(argv[2])) {
          error = false;
        }
      }

      if (error) { 
        printUsage(cmd);
        return exitCode;
      }
    }
    else if ("-list".equals(cmd)) {
      if (argv.length != 1) {
        printUsage(cmd);
        return exitCode;
      }
    }
    else if ("-delete".equals(cmd)) {
      if (argv.length != 2) {
        printUsage(cmd);
        return exitCode;
      }
    }
    else {
      LOG.error(cmd.substring(1) + ": Unknown command");
      printUsage(null);
      return exitCode;
    }

    try {
      if ("-create".equals(cmd)) {
        exitCode = createSnapshot(argv[1], argv.length == 3);
      } else if ("-list".equals(cmd)) {
        exitCode = listSnapshots();
      } else if ("-delete".equals(cmd)) {
        exitCode = deleteSnapshot(argv[1]);
      }
    } catch (RemoteException e) {
      // This is a error returned by hadoop server. Print
      // out the first line of the error mesage, ignore the stack trace.
      exitCode = -1;
      try {
        String[] content;
        content = e.getLocalizedMessage().split("\n");
        LOG.error(cmd.substring(1) + ": " + content[0]);
      } catch (Exception ex) {
        LOG.error(cmd.substring(1) + ": " + ex.getLocalizedMessage());
      }
    } catch (IOException e) {
      // IO exception encountered locally.
      exitCode = -1;
      LOG.error(cmd.substring(1) + ": " + e.getLocalizedMessage());
    } catch (Exception e) {
      exitCode = -1;
      LOG.error(cmd.substring(1) + ": " + e.getLocalizedMessage());
    } finally {
      // Does the RPC connection need to be closed?
    }

    return exitCode;
  }

  private int createSnapshot(String id, boolean updateLeases) throws IOException {
    checkOpen();

    ssNode.createSnapshot(id, updateLeases);
    System.out.println("Snapshot created with id: " + id);

    return 0;
  }

  private int listSnapshots() throws IOException {
    checkOpen();

    String[] rtn = ssNode.listSnapshots();

    // Print ids
    for (String ss: rtn) {
      System.out.println(ss);
    }

    return 0;
  }

  private int deleteSnapshot(String id) throws IOException {
    checkOpen();

    boolean success = ssNode.deleteSnapshot(id);
    System.out.println("Snapshot with id " + id + " deleted: " + success);

    return 0;
  }

  /**
   * main() has some simple utility methods
   */
  public static void main(String argv[]) throws Exception {
    SnapshotShell shell = null;
    try {
      shell = new SnapshotShell();
    } 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 SnapshotNode... command aborted.");
      System.exit(-1);
    }

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