You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by km...@apache.org on 2014/03/06 07:17:40 UTC

git commit: KNOX-294: Add version support to knoxcli. KNOX-296: Add redeploy support to knoxcli.

Repository: knox
Updated Branches:
  refs/heads/master 9423ded73 -> a5362cad5


KNOX-294: Add version support to knoxcli.
KNOX-296: Add redeploy support to knoxcli.


Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/a5362cad
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/a5362cad
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/a5362cad

Branch: refs/heads/master
Commit: a5362cad587f47a33998561534b160e08216cbd5
Parents: 9423ded
Author: Kevin Minder <ke...@hortonworks.com>
Authored: Thu Mar 6 01:17:30 2014 -0500
Committer: Kevin Minder <ke...@hortonworks.com>
Committed: Thu Mar 6 01:17:30 2014 -0500

----------------------------------------------------------------------
 gateway-release/home/bin/gateway.sh             |   4 +-
 gateway-release/home/bin/knoxcli.sh             |   2 +-
 .../hadoop/gateway/GatewayCommandLine.java      |  11 +-
 .../apache/hadoop/gateway/GatewayMessages.java  |  10 +
 .../apache/hadoop/gateway/GatewayResources.java |   5 +-
 .../apache/hadoop/gateway/GatewayServer.java    |  68 +++++-
 .../topology/file/FileTopologyProvider.java     |   4 +-
 .../org/apache/hadoop/gateway/util/KnoxCLI.java | 232 ++++++++++++-------
 .../hadoop/gateway/topology/Topology.java       |  10 +
 .../hadoop/gateway/GatewayDeployFuncTest.java   | 147 ++++++------
 10 files changed, 324 insertions(+), 169 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-release/home/bin/gateway.sh
----------------------------------------------------------------------
diff --git a/gateway-release/home/bin/gateway.sh b/gateway-release/home/bin/gateway.sh
index 4de979b..a449c30 100755
--- a/gateway-release/home/bin/gateway.sh
+++ b/gateway-release/home/bin/gateway.sh
@@ -84,7 +84,7 @@ function main {
       stop)   
          appStop
          ;;
-      status) 
+      status)
          appStatus
          ;;
       clean) 
@@ -94,7 +94,7 @@ function main {
          printHelp
          ;;
       *)
-         printf "Usage: $0 {start|stop|status|clean|setup}\n"
+         printf "Usage: $0 {start|stop|status|clean}\n"
          ;;
    esac
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-release/home/bin/knoxcli.sh
----------------------------------------------------------------------
diff --git a/gateway-release/home/bin/knoxcli.sh b/gateway-release/home/bin/knoxcli.sh
index 93f55ca..da6e6e2 100755
--- a/gateway-release/home/bin/knoxcli.sh
+++ b/gateway-release/home/bin/knoxcli.sh
@@ -55,7 +55,7 @@ APP_ERR_FILE="$APP_LOG_DIR/$APP_NAME.err"
 . $APP_BIN_DIR/knox-env.sh
 
 function main {
-   printf "Starting $APP_LABEL \n"
+   #printf "Starting $APP_LABEL \n"
    #printf "$@"
    
    exec $JAVA $APP_MEM_OPTS $APP_DBG_OPTS $APP_LOG_OPTS -jar $APP_JAR "$@" || exit 1

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayCommandLine.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayCommandLine.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayCommandLine.java
index 54e03f8..a7e2ebc 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayCommandLine.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayCommandLine.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.gateway;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.cli.PosixParser;
@@ -64,13 +65,19 @@ public class GatewayCommandLine {
   public static final String PERSIST_SHORT = "pm";
 
   public static final String NOSTART_LONG = "nostart";
-  public static final String NOSTART_SHORT = "n";
+  public static final String NOSTART_SHORT = "ns";
+
+  public static final String REDEPLOY_LONG = "redeploy";
+  public static final String REDEPLOY_SHORT = "rd";
 
   private static Options createCommandLine() {
     Options options = new Options();
     options.addOption( HELP_SHORT, HELP_LONG, false, res.helpMessage() );
     options.addOption( VERSION_SHORT, VERSION_LONG, false, res.versionHelpMessage() );
-    options.addOption( PERSIST_SHORT, PERSIST_LONG, false, res.persistmasterHelpMessage() );
+    Option redeploy = new Option( REDEPLOY_SHORT, REDEPLOY_LONG, true, res.redeployHelpMessage() );
+    redeploy.setOptionalArg( true );
+    options.addOption( redeploy );
+    options.addOption( PERSIST_SHORT, PERSIST_LONG, false, res.persistMasterHelpMessage() );
     options.addOption( NOSTART_SHORT, NOSTART_LONG, false, res.nostartHelpMessage() );
     return options;
   }

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
index de87aa9..bc25f83 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
@@ -90,6 +90,15 @@ public interface GatewayMessages {
   @Message( level = MessageLevel.ERROR, text = "Failed to deploy topology {0}: {1}" )
   void failedToDeployTopology( String name, @StackTrace(level=MessageLevel.DEBUG) Throwable e );
 
+  @Message( level = MessageLevel.ERROR, text = "Failed to redeploy topology {0}" )
+  void failedToRedeployTopology( String name );
+
+  @Message( level = MessageLevel.ERROR, text = "Failed to redeploy topology {0}: {1}" )
+  void failedToRedeployTopology( String name, @StackTrace(level=MessageLevel.DEBUG) Throwable e );
+
+  @Message( level = MessageLevel.ERROR, text = "Failed to redeploy topologies: {0}" )
+  void failedToRedeployTopologies( @StackTrace(level=MessageLevel.DEBUG) Throwable e );
+
   @Message( level = MessageLevel.ERROR, text = "Failed to undeploy topology {0}: {1}" )
   void failedToUndeployTopology( String name, @StackTrace(level=MessageLevel.DEBUG) Exception e );
 
@@ -303,4 +312,5 @@ public interface GatewayMessages {
   
   @Message( level = MessageLevel.WARN, text = "Value not found for cluster:{0}, alias: {1}" )
   void aliasValueNotFound( String cluster, String alias );
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayResources.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayResources.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayResources.java
index ce4b6c7..0f19b2d 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayResources.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayResources.java
@@ -42,7 +42,10 @@ public interface GatewayResources {
   String nostartHelpMessage();
 
   @Resource( text="This parameter causes the provider master secret to be persisted. This prevents the server from prompting for a master secret on subsequent starts." )
-  String persistmasterHelpMessage();
+  String persistMasterHelpMessage();
+
+  @Resource( text="This parameter causes the existing topologies to be redeployed. A single topology may be specified via an optional parameter.  The server will not be started." )
+  String redeployHelpMessage();
 
   @Resource( text="Display server version information." )
   String versionHelpMessage();

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayServer.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayServer.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayServer.java
index b49322d..abfce3a 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayServer.java
@@ -47,9 +47,9 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.jboss.shrinkwrap.api.exporter.ExplodedExporter;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.xml.sax.SAXException;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
@@ -90,10 +90,10 @@ public class GatewayServer {
       if( cmd.hasOption( GatewayCommandLine.HELP_LONG ) ) {
         GatewayCommandLine.printHelp();
       } else if( cmd.hasOption( GatewayCommandLine.VERSION_LONG ) ) {
-        buildProperties = loadBuildProperties();
-        System.out.println( res.gatewayVersionMessage( // I18N not required.
-            buildProperties.getProperty( "build.version", "unknown" ),
-            buildProperties.getProperty( "build.hash", "unknown" ) ) );
+        printVersion();
+      } else if( cmd.hasOption( GatewayCommandLine.REDEPLOY_LONG  ) ) {
+        GatewayConfig config = new GatewayConfigImpl();
+        redeployTopologies( config, cmd.getOptionValue( GatewayCommandLine.REDEPLOY_LONG ) );
       } else {
         services = instantiateGatewayServices();
         if (services == null) {
@@ -110,13 +110,21 @@ public class GatewayServer {
           startGateway( config, services );
         }
       }
-    } catch( ParseException e ) {
+    } catch ( ParseException e ) {
       log.failedToParseCommandLine( e );
-    } catch (ServiceLifecycleException e) {
+      GatewayCommandLine.printHelp();
+    } catch ( ServiceLifecycleException e ) {
       log.failedToStartGateway( e );
     }
   }
 
+  private static void printVersion() {
+    buildProperties = loadBuildProperties();
+    System.out.println( res.gatewayVersionMessage( // I18N not required.
+        buildProperties.getProperty( "build.version", "unknown" ),
+        buildProperties.getProperty( "build.hash", "unknown" ) ) );
+  }
+
   private static GatewayServices instantiateGatewayServices() {
     ServiceLoader<GatewayServices> loader = ServiceLoader.load( GatewayServices.class );
     Iterator<GatewayServices> services = loader.iterator();
@@ -179,6 +187,52 @@ public class GatewayServer {
     input.close();
   }
 
+  private static void redeployTopology( Topology topology ) {
+    File topologyFile = new File( topology.getUri() );
+    long start = System.currentTimeMillis();
+    long limit = 1000L; // One second.
+    long elapsed = 1;
+    while( elapsed <= limit ) {
+      try {
+        long origTimestamp = topologyFile.lastModified();
+        long setTimestamp = Math.max( System.currentTimeMillis(), topologyFile.lastModified() + elapsed );
+        if( topologyFile.setLastModified( setTimestamp ) ) {
+          long newTimstamp = topologyFile.lastModified();
+          if( newTimstamp > origTimestamp ) {
+            break;
+          } else {
+            Thread.sleep( 10 );
+            elapsed = System.currentTimeMillis() - start;
+            continue;
+          }
+        } else {
+          log.failedToRedeployTopology( topology.getName() );
+          break;
+        }
+      } catch( InterruptedException e ) {
+        log.failedToRedeployTopology( topology.getName(), e );
+        e.printStackTrace();
+      }
+    }
+  }
+
+  public static void redeployTopologies( GatewayConfig config, String topologyName ) {
+    try {
+      File topologiesDir = calculateAbsoluteTopologiesDir( config );
+      FileTopologyProvider provider = new FileTopologyProvider( topologiesDir );
+      provider.reloadTopologies();
+      for( Topology topology : provider.getTopologies() ) {
+        if( topologyName == null || topologyName.equals( topology.getName() ) )  {
+          redeployTopology( topology );
+        }
+      }
+    } catch( SAXException e ) {
+      log.failedToRedeployTopologies( e );
+    } catch( IOException e ) {
+      log.failedToRedeployTopologies( e );
+    }
+  }
+
   public static GatewayServer startGateway( GatewayConfig config, GatewayServices svcs ) {
     try {
       log.startingGateway();

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/file/FileTopologyProvider.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/file/FileTopologyProvider.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/file/FileTopologyProvider.java
index 15bb75e..8e20621 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/file/FileTopologyProvider.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/file/FileTopologyProvider.java
@@ -42,6 +42,7 @@ import org.xml.sax.SAXException;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -84,12 +85,13 @@ public class FileTopologyProvider implements TopologyProvider, TopologyMonitor,
     this( null, VFS.getManager().toFileObject( directory ) );
   }
 
-  private static Topology loadTopology( FileObject file ) throws IOException, SAXException {
+  private static Topology loadTopology( FileObject file ) throws IOException, SAXException, URISyntaxException {
     log.loadingTopologyFile( file.getName().getFriendlyURI() );
     Digester digester = digesterLoader.newDigester();
     FileContent content = file.getContent();
     TopologyBuilder topologyBuilder = digester.parse( content.getInputStream() );
     Topology topology = topologyBuilder.build();
+    topology.setUri( file.getURL().toURI() );
     topology.setName( FilenameUtils.removeExtension( file.getName().getBaseName() ) );
     topology.setTimestamp( content.getLastModifiedTime() );
     return topology;

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java
index 7694ec6..040dfea 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java
@@ -17,15 +17,9 @@
  */
 package org.apache.hadoop.gateway.util;
 
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
 import org.apache.hadoop.conf.Configured;
 import org.apache.hadoop.gateway.GatewayCommandLine;
+import org.apache.hadoop.gateway.GatewayServer;
 import org.apache.hadoop.gateway.config.GatewayConfig;
 import org.apache.hadoop.gateway.config.impl.GatewayConfigImpl;
 import org.apache.hadoop.gateway.services.CLIGatewayServices;
@@ -39,6 +33,16 @@ import org.apache.hadoop.util.Tool;
 import org.apache.hadoop.util.ToolRunner;
 import org.apache.log4j.PropertyConfigurator;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
 /**
  *
  */
@@ -47,11 +51,13 @@ public class KnoxCLI extends Configured implements Tool {
   private static final String USAGE_PREFIX = "KnoxCLI {cmd} [options]";
   final static private String COMMANDS =
       "   [--help]\n" +
-      "   [" + CertCreateCommand.USAGE + "]\n" +
+      "   [" + VersionCommand.USAGE + "]\n" +
       "   [" + MasterCreateCommand.USAGE + "]\n" +
+      "   [" + CertCreateCommand.USAGE + "]\n" +
       "   [" + AliasCreateCommand.USAGE + "]\n" +
       "   [" + AliasDeleteCommand.USAGE + "]\n" +
-      "   [" + AliasListCommand.USAGE + "]\n";
+      "   [" + AliasListCommand.USAGE + "]\n" +
+      "   [" + RedeployCommand.USAGE + "]\n";
 
   /** allows stdout to be captured if necessary */
   public PrintStream out = System.out;
@@ -109,11 +115,13 @@ public class KnoxCLI extends Configured implements Tool {
   /**
    * Parse the command line arguments and initialize the data
    * <pre>
-   * % knox master-create keyName [--size size] [--generate]
-   * % knox create-alias alias [--cluster] [--generate] [--value v]
-   * % knox list-alias [--cluster]
-   * % knox delete=alias alias [--cluster]
-   * % knox create-cert alias [--hostname]
+   * % knoxcli version
+   * % knoxcli master-create keyName [--size size] [--generate]
+   * % knoxcli create-alias alias [--cluster c] [--generate] [--value v]
+   * % knoxcli list-alias [--cluster c]
+   * % knoxcli delete=alias alias [--cluster c]
+   * % knoxcli create-cert alias [--hostname h]
+   * % knoxcli redeploy [--cluster c]
    * </pre>
    * @param args
    * @return
@@ -154,8 +162,23 @@ public class KnoxCLI extends Configured implements Tool {
       } else if (args[i].equals("list-alias")) {
         command = new AliasListCommand();
       } else if (args[i].equals("--value")) {
+        if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
+          printKnoxShellUsage();
+          return -1;
+        }
         this.value = args[++i];
-      } else if (args[i].equals("--cluster")) {
+        if ( command != null && command instanceof MasterCreateCommand ) {
+          this.master = this.value;
+        }
+      } else if ( args[i].equals("version") ) {
+        command = new VersionCommand();
+      } else if ( args[i].equals("redeploy") ) {
+        command = new RedeployCommand();
+      } else if ( args[i].equals("--cluster") || args[i].equals("--topology") ) {
+        if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
+          printKnoxShellUsage();
+          return -1;
+        }
         this.cluster = args[++i];
       } else if (args[i].equals("--generate")) {
         if ( command != null && command instanceof MasterCreateCommand ) {
@@ -164,16 +187,24 @@ public class KnoxCLI extends Configured implements Tool {
           this.generate = "true";
         }
       } else if (args[i].equals("--hostname")) {
+        if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
+          printKnoxShellUsage();
+          return -1;
+        }
         this.hostname = args[++i];
       } else if (args[i].equals("--master")) {
-        // testing only
+        // For testing only
+        if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
+          printKnoxShellUsage();
+          return -1;
+        }
         this.master = args[++i];
       } else if (args[i].equals("--help")) {
         printKnoxShellUsage();
         return -1;
       } else {
         printKnoxShellUsage();
-        ToolRunner.printGenericCommandUsage(System.err);
+        //ToolRunner.printGenericCommandUsage(System.err);
         return -1;
       }
     }
@@ -181,26 +212,34 @@ public class KnoxCLI extends Configured implements Tool {
   }
 
   private void printKnoxShellUsage() {
-    out.println(USAGE_PREFIX + COMMANDS);
-    if (command != null) {
+    out.println( USAGE_PREFIX + "\n" + COMMANDS );
+    if ( command != null ) {
       out.println(command.getUsage());
-    }
-    else {
-      out.println("=========================================================" +
-          "======");
-      out.println(MasterCreateCommand.USAGE + ":\n\n" + MasterCreateCommand.DESC);
-      out.println("=========================================================" +
-          "======");
-      out.println(CertCreateCommand.USAGE + ":\n\n" + CertCreateCommand.DESC);
-      out.println("=========================================================" +
-          "======");
-      out.println(AliasCreateCommand.USAGE + ":\n\n" + AliasCreateCommand.DESC);
-      out.println("=========================================================" +
-          "======");
-      out.println(AliasDeleteCommand.USAGE + ":\n\n" + AliasDeleteCommand.DESC);
-      out.println("=========================================================" +
-          "======");
-      out.println(AliasListCommand.USAGE + ":\n\n" + AliasListCommand.DESC);
+    } else {
+      char[] chars = new char[79];
+      Arrays.fill( chars, '=' );
+      String div = new String( chars );
+
+      out.println( div );
+      out.println( VersionCommand.USAGE + "\n\n" + VersionCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( MasterCreateCommand.USAGE + "\n\n" + MasterCreateCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( CertCreateCommand.USAGE + "\n\n" + CertCreateCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( AliasCreateCommand.USAGE + "\n\n" + AliasCreateCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( AliasDeleteCommand.USAGE + "\n\n" + AliasDeleteCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( AliasListCommand.USAGE + "\n\n" + AliasListCommand.DESC );
+      out.println();
+      out.println( div );
+      out.println( RedeployCommand.USAGE + "\n\n" + RedeployCommand.DESC );
     }
   }
 
@@ -222,29 +261,24 @@ public class KnoxCLI extends Configured implements Tool {
     public abstract String getUsage();
 
     protected AliasService getAliasService() {
-      AliasService as = (AliasService) 
-           services.getService(GatewayServices.ALIAS_SERVICE);
+      AliasService as = services.getService(GatewayServices.ALIAS_SERVICE);
       return as;
     }
 
     protected KeystoreService getKeystoreService() {
-      KeystoreService ks = (KeystoreService) 
-           services.getService(GatewayServices.KEYSTORE_SERVICE);
+      KeystoreService ks = services.getService(GatewayServices.KEYSTORE_SERVICE);
       return ks;
     }
   }
   
-  /**
-  *
-  */
  private class AliasListCommand extends Command {
 
   public static final String USAGE = "list-alias [--cluster c]";
   public static final String DESC = "The list-alias command lists all of the aliases\n" +
-  		                               "for the given hadoop --cluster. The default\n" +
-  		                               "--cluster being the gateway itself.";
+                                    "for the given hadoop --cluster. The default\n" +
+                                    "--cluster being the gateway itself.";
 
-  /* (non-Javadoc)
+   /* (non-Javadoc)
     * @see org.apache.hadoop.gateway.util.KnoxCLI.Command#execute()
     */
    @Override
@@ -271,25 +305,18 @@ public class KnoxCLI extends Configured implements Tool {
    }
  }
 
- /**
-  *
-  */
  public class CertCreateCommand extends Command {
 
   public static final String USAGE = "create-cert [--hostname h]";
   public static final String DESC = "The create-cert command creates and populates\n" +
-  		                               "a gateway.jks keystore with a self-signed certificate\n" +
-  		                               "to be used as the gateway identity. It also adds an alias\n" +
-  		                               "to the __gateway-credentials.jceks credential store for the\n" +
-  		                               "key passphrase.";
+                                    "a gateway.jks keystore with a self-signed certificate\n" +
+                                    "to be used as the gateway identity. It also adds an alias\n" +
+                                    "to the __gateway-credentials.jceks credential store for the\n" +
+                                    "key passphrase.";
   private static final String GATEWAY_CREDENTIAL_STORE_NAME = "__gateway";
   private static final String GATEWAY_IDENTITY_PASSPHRASE = "gateway-identity-passphrase";
 
-  /**
-    * 
-    */
    public CertCreateCommand() {
-     // TODO Auto-generated constructor stub
    }
 
    /* (non-Javadoc)
@@ -326,14 +353,14 @@ public class KnoxCLI extends Configured implements Tool {
          char[] passphrase = as.getPasswordFromAliasForCluster(GATEWAY_CREDENTIAL_STORE_NAME, GATEWAY_IDENTITY_PASSPHRASE);
          ks.addSelfSignedCertForGateway("gateway-identity", passphrase, hostname);
 //         logAndValidateCertificate();
-         out.println("gateway-identity has been successfully created.");
+         out.println("Certificate gateway-identity has been successfully created.");
        } catch (KeystoreServiceException e) {
          throw new ServiceLifecycleException("Keystore was not loaded properly - the provided (or persisted) master secret may not match the password for the keystore.", e);
        }
      }
    }
 
-  /* (non-Javadoc)
+   /* (non-Javadoc)
     * @see org.apache.hadoop.gateway.util.KnoxCLI.Command#getUsage()
     */
    @Override
@@ -343,19 +370,16 @@ public class KnoxCLI extends Configured implements Tool {
 
  }
 
- /**
-  *
-  */
  public class AliasCreateCommand extends Command {
 
-  public static final String USAGE = "create-alias aliasname [--value value]" +
-  		                              " [--cluster c] [--generate]";
+  public static final String USAGE = "create-alias aliasname [--cluster c] " +
+                                     "[ (--value v) | (--generate) ]";
   public static final String DESC = "The create-alias command will create an alias\n" +
-  		                               "and secret pair within the credential store for the\n" +
-  		                               "indicated --cluster otherwise within the gateway\n" +
-  		                               "credential store. The actual secret may be specified via\n" +
-  		                               "the --value option or --generate will create a random secret\n" +
-  		                               "for you.";
+                                    "and secret pair within the credential store for the\n" +
+                                    "indicated --cluster otherwise within the gateway\n" +
+                                    "credential store. The actual secret may be specified via\n" +
+                                    "the --value option or --generate will create a random secret\n" +
+                                    "for you.";
   
   private String name = null; 
 
@@ -407,8 +431,8 @@ public class KnoxCLI extends Configured implements Tool {
  public class AliasDeleteCommand extends Command {
   public static final String USAGE = "delete-alias aliasname [--cluster c]";
   public static final String DESC = "The delete-alias command removes the\n" +
-  		                               "indicated alias from the --cluster specific\n" +
-  		                               "credential store or the gateway credential store.";
+                                    "indicated alias from the --cluster specific\n" +
+                                    "credential store or the gateway credential store.";
   
   private String name = null;
 
@@ -450,13 +474,10 @@ public class KnoxCLI extends Configured implements Tool {
  public class MasterCreateCommand extends Command {
   public static final String USAGE = "create-master";
   public static final String DESC = "The create-master command persists the\n" +
-  		                               "master secret in a file located at:\n" +
-  		                               "{GATEWAY_HOME}/data/security/master. It\n" +
-  		                               "will prompt the user for the secret to persist.";
+                                    "master secret in a file located at:\n" +
+                                    "{GATEWAY_HOME}/data/security/master. It\n" +
+                                    "will prompt the user for the secret to persist.";
 
-  /**
-    * @param keyName
-    */
    public MasterCreateCommand() {
    }
 
@@ -477,7 +498,62 @@ public class KnoxCLI extends Configured implements Tool {
    }
  }
 
- /**
+  private class VersionCommand extends Command {
+
+    public static final String USAGE = "version";
+    public static final String DESC = "Displays Knox version information.";
+
+    @Override
+    public void execute() throws Exception {
+      Properties buildProperties = loadBuildProperties();
+      System.out.println(
+          String.format(
+              "Apache Knox: %s (%s)",
+              buildProperties.getProperty( "build.version", "unknown" ),
+              buildProperties.getProperty( "build.hash", "unknown" ) ) );
+    }
+
+    @Override
+    public String getUsage() {
+      return USAGE + ":\n\n" + DESC;
+    }
+
+  }
+
+  private class RedeployCommand extends Command {
+
+    public static final String USAGE = "redeploy [--cluster c]";
+    public static final String DESC =
+        "Redeploys one or all of the gateway's clusters (a.k.a topologies).";
+
+    @Override
+    public void execute() throws Exception {
+      GatewayConfig config = new GatewayConfigImpl();
+      GatewayServer.redeployTopologies( config, cluster );
+    }
+
+    @Override
+    public String getUsage() {
+      return USAGE + ":\n\n" + DESC;
+    }
+
+  }
+
+  private static Properties loadBuildProperties() {
+    Properties properties = new Properties();
+    InputStream inputStream = KnoxCLI.class.getClassLoader().getResourceAsStream( "build.properties" );
+    if( inputStream != null ) {
+      try {
+        properties.load( inputStream );
+        inputStream.close();
+      } catch( IOException e ) {
+        // Ignore.
+      }
+    }
+    return properties;
+  }
+
+  /**
   * @param args
   * @throws Exception 
   */

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Topology.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Topology.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Topology.java
index 9dd3514..c849ec4 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Topology.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Topology.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.gateway.topology;
 
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -25,6 +26,7 @@ import java.util.Map;
 
 public class Topology {
 
+  private URI uri;
   private String name;
   private long timestamp;
   private List<Provider> providerList = new ArrayList<Provider>();
@@ -32,6 +34,14 @@ public class Topology {
   private List<Service> services = new ArrayList<Service>();
   private Map<String, Map<String, Service>> serviceMap = new HashMap<String, Map<String, Service>>();
 
+  public URI getUri() {
+    return uri;
+  }
+
+  public void setUri( URI uri ) {
+    this.uri = uri;
+  }
+
   public String getName() {
     return name;
   }

http://git-wip-us.apache.org/repos/asf/knox/blob/a5362cad/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayDeployFuncTest.java
----------------------------------------------------------------------
diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayDeployFuncTest.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayDeployFuncTest.java
index 7b71707..6cfac03 100644
--- a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayDeployFuncTest.java
+++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayDeployFuncTest.java
@@ -55,9 +55,8 @@ import static com.jayway.restassured.RestAssured.given;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.greaterThan;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
 
 public class GatewayDeployFuncTest {
 
@@ -202,13 +201,13 @@ public class GatewayDeployFuncTest {
     System.in.read();
   }
 
-  @Test
-  public void testDeployUndeploy() throws Exception {
-    long timeout = 10 * 1000; // Ten seconds.
+  @Test( timeout = 20*1000 )
+  public void testDeployRedeployUndeploy() throws InterruptedException, IOException {
     long sleep = 200;
     String username = "guest";
     String password = "guest-password";
     String serviceUrl =  clusterUrl + "/test-service-path/test-service-resource";
+    long topoTimestampBefore, topoTimestampAfter;
 
     File topoDir = new File( config.getGatewayTopologyDir() );
     File deployDir = new File( config.getGatewayDeploymentDir() );
@@ -218,49 +217,85 @@ public class GatewayDeployFuncTest {
     assertThat( topoDir.listFiles().length, is( 0 ) );
     assertThat( deployDir.listFiles().length, is( 0 ) );
 
+    File descriptor = writeTestTopology( "test-cluster", createTopology() );
+
+    warDir = waitForFiles( deployDir, "test-cluster.war\\.[0-9A-Fa-f]+", 1, 0, sleep );
+    for( File webInfDir : warDir.listFiles() ) {
+      waitForFiles( webInfDir, ".*", 4, 0, sleep );
+    }
+    waitForAccess( serviceUrl, username, password, sleep );
+
+    // Redeploy and make sure the timestamp is updated.
+    topoTimestampBefore = descriptor.lastModified();
+    GatewayServer.redeployTopologies( config, null );
+    topoTimestampAfter = descriptor.lastModified();
+    assertThat( topoTimestampAfter, greaterThan( topoTimestampBefore ) );
+
+    // Check to make sure there are two war directories with the same root.
+    warDir = waitForFiles( deployDir, "test-cluster.war\\.[0-9A-Fa-f]+", 2, 1, sleep );
+    for( File webInfDir : warDir.listFiles() ) {
+      waitForFiles( webInfDir, ".*", 4, 0, sleep );
+    }
+    waitForAccess( serviceUrl, username, password, sleep );
+
+    // Redeploy and make sure the timestamp is updated.
+    topoTimestampBefore = descriptor.lastModified();
+    GatewayServer.redeployTopologies( config, "test-cluster" );
+    topoTimestampAfter = descriptor.lastModified();
+    assertThat( topoTimestampAfter, greaterThan( topoTimestampBefore ) );
+
+    // Check to make sure there are two war directories with the same root.
+    warDir = waitForFiles( deployDir, "test-cluster.war\\.[0-9A-Fa-f]+", 3, 2, sleep );
+    for( File webInfDir : warDir.listFiles() ) {
+      waitForFiles( webInfDir, ".*", 4, 0, sleep );
+    }
+    waitForAccess( serviceUrl, username, password, sleep );
+
+    // Delete the test topology.
+    assertThat( "Failed to delete the topology file.", descriptor.delete(), is( true ) );
+
+    waitForFiles( deployDir, ".*", 0, -1, sleep );
+
+    // Wait a bit more to make sure undeployment finished.
+    Thread.sleep( sleep );
+
+    // Make sure the test topology is not accessible.
+    given().auth().preemptive().basic( username, password )
+        .expect().statusCode( HttpStatus.SC_NOT_FOUND )
+        .when().get( serviceUrl );
+
+    // Make sure deployment directory is empty.
+    assertThat( topoDir.listFiles().length, is( 0 ) );
+    assertThat( deployDir.listFiles().length, is( 0 ) );
+  }
+
+  private File writeTestTopology( String name, XMLTag xml ) throws IOException {
     // Create the test topology.
-    File tempFile = new File( config.getGatewayTopologyDir(), "test-cluster.xml." + UUID.randomUUID() );
+    File tempFile = new File( config.getGatewayTopologyDir(), name + ".xml." + UUID.randomUUID() );
     FileOutputStream stream = new FileOutputStream( tempFile );
-    createTopology().toStream( stream );
+    xml.toStream( stream );
     stream.close();
-    File descriptor = new File( config.getGatewayTopologyDir(), "test-cluster.xml" );
+    File descriptor = new File( config.getGatewayTopologyDir(), name + ".xml" );
     tempFile.renameTo( descriptor );
+    return descriptor;
+  }
 
-    // Make sure deployment directory has one WAR with the correct name.
-    long before = System.currentTimeMillis();
-    long elapsed = 0;
+  private File waitForFiles( File dir, String pattern, int count, int index, long sleep ) throws InterruptedException {
+    RegexDirFilter filter = new RegexDirFilter( pattern );
     while( true ) {
-      elapsed = System.currentTimeMillis() - before;
-      assertThat( "Waited too long for topology deployment dir creation.", elapsed, lessThan( timeout ) );
-      File[] files = deployDir.listFiles( new RegexDirFilter( "test-cluster.war\\.[0-9A-Fa-f]+" ) );
-      if( files.length == 1 ) {
-        warDir = files[0];
-        break;
-      }
-      Thread.sleep( sleep );
-    }
-    while( true ) {
-      elapsed = System.currentTimeMillis() - before;
-      assertThat( "Waited too long for topology deployment file creation.", elapsed, lessThan( timeout ) );
-      File webInfDir = new File( warDir, "WEB-INF" );
-      File[] files = webInfDir.listFiles();
-      //System.out.println( "DEPLOYMENT FILES: " + files.length );
-      //for( File file : files ) {
-      //  System.out.println( "  " + file.getAbsolutePath() );
-      //}
-      if( files.length >= 4 ) {
-        break;
+      File[] files = dir.listFiles( filter );
+      if( files.length == count ) {
+        return ( index < 0 ) ? null : files[ index ];
       }
       Thread.sleep( sleep );
     }
+  }
 
-    // Make sure the test topology is accessible.
+  private void waitForAccess( String url, String username, String password, long sleep ) throws InterruptedException {
     while( true ) {
-      elapsed = System.currentTimeMillis() - before;
-      assertThat( "Waited too long for topology to be accessible.", elapsed, lessThan( timeout ) );
       Response response = given()
           .auth().preemptive().basic( username, password )
-          .when().get( serviceUrl ).andReturn();
+          .when().get( url ).andReturn();
       if( response.getStatusCode() == HttpStatus.SC_NOT_FOUND ) {
         Thread.sleep( sleep );
         continue;
@@ -269,50 +304,8 @@ public class GatewayDeployFuncTest {
       assertThat( response.getBody().asString(), is( "test-service-response" ) );
       break;
     }
-
-    // Delete the test topology.
-    assertThat( "Failed to delete the topology file.", descriptor.delete(), is( true ) );
-
-    // Make sure the deployment directory is empty.
-    before = System.currentTimeMillis();
-    while( true ) {
-      File[] deploymentFiles = deployDir.listFiles( new RegexDirFilter( "test-cluster.war\\.[0-9A-Fa-f]+" ) );
-      if( deploymentFiles.length == 0 ) {
-        break;
-      } else if( ( System.currentTimeMillis() - before ) > timeout ) {
-        System.out.println( "TOPOLOGY FILES   " + topoDir );
-        File[] topologyFiles = topoDir.listFiles( new RegexDirFilter( "test-cluster.*" ) );
-        for( File file : topologyFiles ) {
-          System.out.println( "  " + file.getAbsolutePath() );
-        }
-        System.out.println( "DEPLOYMENT FILES " + deployDir );
-        for( File file : deploymentFiles ) {
-          System.out.println( "  " + file.getAbsolutePath() );
-        }
-        fail( "Waited too long for topology undeployment: " + ( System.currentTimeMillis() - before ) + "ms" );
-      } else {
-        Thread.sleep( sleep );
-      }
-    }
-
-    // Wait a bit more to make sure undeployment finished.
-    Thread.sleep( sleep );
-
-    // Make sure the test topology is not accessible.
-    given()
-        //.log().all()
-        .auth().preemptive().basic( username, password )
-        .expect()
-            //.log().all()
-        .statusCode( HttpStatus.SC_NOT_FOUND )
-        .when().get( serviceUrl );
-
-    // Make sure deployment directory is empty.
-    assertThat( topoDir.listFiles().length, is( 0 ) );
-    assertThat( deployDir.listFiles().length, is( 0 ) );
   }
 
-
   private class RegexDirFilter implements FilenameFilter {
 
     Pattern pattern;