You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by su...@apache.org on 2015/01/30 03:03:48 UTC

[10/24] knox git commit: Added versions to service definitions for deployment and topology KNOX-481

Added versions to service definitions for deployment and topology KNOX-481


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

Branch: refs/heads/KNOX-481
Commit: e5319f05ae4fa97ff3c1aaf419112aca7bc82933
Parents: 7c72ff0
Author: Sumit Gupta <su...@apache.org>
Authored: Tue Jan 27 10:56:37 2015 -0500
Committer: Sumit Gupta <su...@apache.org>
Committed: Thu Jan 29 16:43:40 2015 -0500

----------------------------------------------------------------------
 .../apache/hadoop/gateway/GatewayMessages.java  |   3 +
 .../gateway/deploy/DeploymentFactory.java       | 109 ++++++++--------
 .../ServiceDefinitionDeploymentContributor.java |   6 +
 .../interpreter/ServicePropertyInterpreter.java |   3 +-
 .../xml/KnoxFormatXmlTopologyRules.java         |   3 +
 .../topology/xml/TopologyRulesModuleTest.java   |  17 ++-
 .../xml/service-param-topology-knox-format.xml  |   1 +
 .../xml/simple-topology-knox-format.xml         |   1 +
 gateway-spi/pom.xml                             |   6 +-
 .../deploy/ServiceDeploymentContributor.java    |   3 +
 .../ServiceDeploymentContributorBase.java       |   6 +
 .../apache/hadoop/gateway/topology/Service.java |   9 ++
 .../hadoop/gateway/topology/Topology.java       |  36 ++---
 .../apache/hadoop/gateway/topology/Version.java | 130 +++++++++++++++++++
 pom.xml                                         |   6 +-
 15 files changed, 254 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/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 9325c02..fb0e3a4 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
@@ -124,6 +124,9 @@ public interface GatewayMessages {
   @Message( level = MessageLevel.WARN, text = "Ignoring service deployment contributor with invalid null role: {0}" )
   void ignoringServiceContributorWithMissingRole( String className );
 
+  @Message( level = MessageLevel.WARN, text = "Ignoring service deployment contributor with invalid null version: {0}" )
+  void ignoringServiceContributorWithMissingVersion( String className );
+
   @Message( level = MessageLevel.WARN, text = "Ignoring provider deployment contributor with invalid null name: {0}" )
   void ignoringProviderContributorWithMissingName( String className );
 

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/DeploymentFactory.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/DeploymentFactory.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/DeploymentFactory.java
index 9dfb808..48fd8f0 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/DeploymentFactory.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/DeploymentFactory.java
@@ -19,18 +19,17 @@ package org.apache.hadoop.gateway.deploy;
 
 import org.apache.hadoop.gateway.GatewayMessages;
 import org.apache.hadoop.gateway.GatewayForwardingServlet;
-import org.apache.hadoop.gateway.GatewayResources;
 import org.apache.hadoop.gateway.GatewayServlet;
 import org.apache.hadoop.gateway.config.GatewayConfig;
 import org.apache.hadoop.gateway.descriptor.GatewayDescriptor;
 import org.apache.hadoop.gateway.descriptor.GatewayDescriptorFactory;
 import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
-import org.apache.hadoop.gateway.i18n.resources.ResourcesFactory;
 import org.apache.hadoop.gateway.services.GatewayServices;
 import org.apache.hadoop.gateway.services.registry.ServiceRegistry;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
 import org.apache.hadoop.gateway.topology.Topology;
+import org.apache.hadoop.gateway.topology.Version;
 import org.apache.hadoop.gateway.util.ServiceDefinitionsLoader;
 import org.jboss.shrinkwrap.api.ShrinkWrap;
 import org.jboss.shrinkwrap.api.asset.Asset;
@@ -44,26 +43,16 @@ import java.beans.Statement;
 import java.io.File;
 import java.io.IOException;
 import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.LinkedHashMap;
+import java.util.*;
 
 public abstract class DeploymentFactory {
 
   private static final String DEFAULT_APP_REDIRECT_CONTEXT_PATH = "redirectTo";
   private static final String STACKS_SERVICES_DIRECTORY = "services";
-  private static GatewayResources res = ResourcesFactory.get( GatewayResources.class );
   private static GatewayMessages log = MessagesFactory.get( GatewayMessages.class );
   private static GatewayServices gatewayServices = null;
 
-  //private static Set<ServiceDeploymentContributor> SERVICE_CONTRIBUTORS;
-  private static Map<String,Map<String,ServiceDeploymentContributor>> SERVICE_CONTRIBUTOR_MAP;
+  private static Map<String,Map<String,Map<Version, ServiceDeploymentContributor>>> SERVICE_CONTRIBUTOR_MAP;
   static {
     loadServiceContributors();
   }
@@ -73,7 +62,7 @@ public abstract class DeploymentFactory {
   static {
     loadProviderContributors();
   }
-  
+
   public static void setGatewayServices(GatewayServices services) {
     DeploymentFactory.gatewayServices = services;
   }
@@ -84,7 +73,7 @@ public abstract class DeploymentFactory {
     String stacks = config.getGatewayStacksDir();
     File stacksDir = new File(stacks, STACKS_SERVICES_DIRECTORY);
     Set<ServiceDeploymentContributor> deploymentContributors = ServiceDefinitionsLoader.loadServiceDefinitions(stacksDir);
-    addServiceDeploymentContributors(SERVICE_CONTRIBUTOR_MAP, deploymentContributors.iterator());
+    addServiceDeploymentContributors(deploymentContributors.iterator());
 
     Map<String,List<ProviderDeploymentContributor>> providers = selectContextProviders( topology );
     Map<String,List<ServiceDeploymentContributor>> services = selectContextServices( topology );
@@ -196,7 +185,7 @@ public abstract class DeploymentFactory {
         = new HashMap<String,List<ServiceDeploymentContributor>>();
     for( Service service : topology.getServices() ) {
       String role = service.getRole();
-      ServiceDeploymentContributor contributor = getServiceContributor( role, service.getName() );
+      ServiceDeploymentContributor contributor = getServiceContributor( role, service.getName(), service.getVersion() );
       if( contributor != null ) {
         List<ServiceDeploymentContributor> list = defaults.get( role );
         if( list == null ) {
@@ -252,12 +241,12 @@ public abstract class DeploymentFactory {
       }
     }
   }
-  
+
   private static void injectServices(Object contributor) {
     if (gatewayServices != null) {
       Statement stmt = null;
       for(String serviceName : gatewayServices.getServiceNames()) {
-        
+
         try {
           // TODO: this is just a temporary injection solution
           // TODO: test for the existence of the setter before attempting it
@@ -293,12 +282,12 @@ public abstract class DeploymentFactory {
       }
     }
     for( Service service : topology.getServices() ) {
-      ServiceDeploymentContributor contributor = getServiceContributor( service.getRole(), null );
+      ServiceDeploymentContributor contributor = getServiceContributor( service.getRole(), service.getName(), service.getVersion() );
       if( contributor != null ) {
         try {
           contributor.contributeService( context, service );
           if (gatewayServices != null) {
-            ServiceRegistry sr = (ServiceRegistry) gatewayServices.getService(GatewayServices.SERVICE_REGISTRY_SERVICE);
+            ServiceRegistry sr = gatewayServices.getService(GatewayServices.SERVICE_REGISTRY_SERVICE);
             if (sr != null) {
               String regCode = sr.getRegistrationCode(topology.getName());
               sr.registerService(regCode, topology.getName(), service.getRole(), service.getUrls() );
@@ -326,14 +315,22 @@ public abstract class DeploymentFactory {
     return contributor;
   }
 
-  public static ServiceDeploymentContributor getServiceContributor( String role, String name ) {
+  public static ServiceDeploymentContributor getServiceContributor( String role, String name, Version version ) {
     ServiceDeploymentContributor contributor = null;
-    Map<String,ServiceDeploymentContributor> nameMap = SERVICE_CONTRIBUTOR_MAP.get( role );
-    if( nameMap != null ) {
-      if( name == null ) {
-        contributor = nameMap.values().iterator().next();
-      } else if ( !nameMap.isEmpty() ) {
-        contributor = nameMap.get( name );
+    Map<String,Map<Version, ServiceDeploymentContributor>> nameMap = SERVICE_CONTRIBUTOR_MAP.get( role );
+    if( nameMap != null && !nameMap.isEmpty()) {
+      Map<Version, ServiceDeploymentContributor> versionMap = null;
+      if ( name == null ) {
+        versionMap = nameMap.values().iterator().next();
+      } else {
+        versionMap = nameMap.get( name );
+      }
+      if ( versionMap != null && !versionMap.isEmpty()) {
+        if( version == null ) {
+          contributor = ((TreeMap<Version, ServiceDeploymentContributor>) versionMap).firstEntry().getValue();
+        } else {
+          contributor = versionMap.get( version );
+        }
       }
     }
     return contributor;
@@ -404,19 +401,16 @@ public abstract class DeploymentFactory {
       }
     }
     return null;
-  }  
-  
-  private static void loadServiceContributors() {
-    Map<String,Map<String,ServiceDeploymentContributor>> roleMap
-        = new HashMap<String,Map<String,ServiceDeploymentContributor>>();
+  }
 
+  private static void loadServiceContributors() {
+    SERVICE_CONTRIBUTOR_MAP = new HashMap<String, Map<String, Map<Version, ServiceDeploymentContributor>>>();
     ServiceLoader<ServiceDeploymentContributor> loader = ServiceLoader.load( ServiceDeploymentContributor.class );
     Iterator<ServiceDeploymentContributor> contributors = loader.iterator();
-     addServiceDeploymentContributors(roleMap, contributors);
-     SERVICE_CONTRIBUTOR_MAP = roleMap;
+    addServiceDeploymentContributors(contributors);
   }
 
-   private static void addServiceDeploymentContributors(Map<String, Map<String, ServiceDeploymentContributor>> roleMap, Iterator<ServiceDeploymentContributor> contributors) {
+   private static void addServiceDeploymentContributors(Iterator<ServiceDeploymentContributor> contributors) {
       while( contributors.hasNext() ) {
         ServiceDeploymentContributor contributor = contributors.next();
         if( contributor.getName() == null ) {
@@ -427,12 +421,21 @@ public abstract class DeploymentFactory {
           log.ignoringServiceContributorWithMissingRole( contributor.getClass().getName() );
           continue;
         }
-        Map nameMap = roleMap.get( contributor.getRole() );
+        if( contributor.getVersion() == null ) {
+          log.ignoringServiceContributorWithMissingVersion(contributor.getClass().getName());
+          continue;
+        }
+        Map<String,Map<Version, ServiceDeploymentContributor>> nameMap = SERVICE_CONTRIBUTOR_MAP.get( contributor.getRole() );
         if( nameMap == null ) {
-          nameMap = new HashMap<String,ServiceDeploymentContributor>();
-          roleMap.put( contributor.getRole(), nameMap );
+          nameMap = new HashMap<String,Map<Version, ServiceDeploymentContributor>>();
+          SERVICE_CONTRIBUTOR_MAP.put( contributor.getRole(), nameMap );
+        }
+        Map<Version, ServiceDeploymentContributor> versionMap = nameMap.get(contributor.getName());
+        if (versionMap == null) {
+          versionMap = new TreeMap<Version, ServiceDeploymentContributor>();
+          nameMap.put(contributor.getName(), versionMap);
         }
-        nameMap.put( contributor.getName(), contributor );
+        versionMap.put( contributor.getVersion(), contributor );
       }
    }
 
@@ -479,18 +482,18 @@ public abstract class DeploymentFactory {
     return contributor;
   }
 
-  static ServiceDeploymentContributor getServiceContributor(
-      Map<String,List<ServiceDeploymentContributor>> services, String role, String name ) {
-    ServiceDeploymentContributor contributor = null;
-    if( name == null ) {
-      List<ServiceDeploymentContributor> list = services.get( role );
-      if( !list.isEmpty() ) {
-        contributor = list.get( 0 );
-      }
-    } else {
-      contributor = getServiceContributor( role, name );
-    }
-    return contributor;
-  }
+//  static ServiceDeploymentContributor getServiceContributor(
+//      Map<String,List<ServiceDeploymentContributor>> services, String role, String name ) {
+//    ServiceDeploymentContributor contributor = null;
+//    if( name == null ) {
+//      List<ServiceDeploymentContributor> list = services.get( role );
+//      if( !list.isEmpty() ) {
+//        contributor = list.get( 0 );
+//      }
+//    } else {
+//      contributor = getServiceContributor( role, name );
+//    }
+//    return contributor;
+//  }
 
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
index 7220cdd..789a221 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
@@ -27,6 +27,7 @@ import org.apache.hadoop.gateway.filter.rewrite.api.UrlRewriteRulesDescriptor;
 import org.apache.hadoop.gateway.service.definition.*;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
+import org.apache.hadoop.gateway.topology.Version;
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -60,6 +61,11 @@ public class ServiceDefinitionDeploymentContributor extends ServiceDeploymentCon
   }
 
   @Override
+  public Version getVersion() {
+    return new Version(serviceDefinition.getVersion());
+  }
+
+  @Override
   public void contributeService(DeploymentContext context, Service service) throws Exception {
     contributeRewriteRules(context, service);
     contributeResources(context, service);

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/builder/property/interpreter/ServicePropertyInterpreter.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/builder/property/interpreter/ServicePropertyInterpreter.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/builder/property/interpreter/ServicePropertyInterpreter.java
index 985fd6b..d297178 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/builder/property/interpreter/ServicePropertyInterpreter.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/builder/property/interpreter/ServicePropertyInterpreter.java
@@ -59,7 +59,8 @@ public class ServicePropertyInterpreter extends AbstractInterpreter {
         }
         nextToken = nextToken.substring(dotPosition + 1);
 
-        Service service = topology.getService(serviceRole, serviceName);
+      //TODO: sumit - version needs to be passed parsed and passed in if we want to continue to support the 'ambari' format
+        Service service = topology.getService(serviceRole, serviceName, null);
         if (service == null) {
             service = new Service();
             service.setName(serviceName);

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/xml/KnoxFormatXmlTopologyRules.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
index 745c661..b32f0c9 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
@@ -22,6 +22,7 @@ import org.apache.commons.digester3.binder.AbstractRulesModule;
 import org.apache.hadoop.gateway.topology.Param;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
+import org.apache.hadoop.gateway.topology.Version;
 import org.apache.hadoop.gateway.topology.builder.BeanPropertyTopologyBuilder;
 import org.xml.sax.Attributes;
 
@@ -39,6 +40,7 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
   private static final String VALUE_TAG = "value";
 
   private static final Rule paramRule = new ParamRule();
+
   @Override
   protected void configure() {
     forPattern( ROOT_TAG ).createObject().ofType( BeanPropertyTopologyBuilder.class );
@@ -48,6 +50,7 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
     forPattern( ROOT_TAG + "/" + SERVICE_TAG ).createObject().ofType( Service.class ).then().setNext( "addService" );
     forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + ROLE_TAG ).setBeanProperty();
     forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + NAME_TAG ).setBeanProperty();
+    forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + VERSION_TAG ).createObject().ofType(Version.class).then().setBeanProperty().then().setNext("setVersion");
     forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + URL_TAG ).callMethod( "addUrl" ).usingElementBodyAsArgument();
     forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + PARAM_TAG ).createObject().ofType( Param.class ).then().addRule( paramRule ).then().setNext( "addParam" );
     forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + PARAM_TAG + "/" + NAME_TAG ).setBeanProperty();

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/xml/TopologyRulesModuleTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/xml/TopologyRulesModuleTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/xml/TopologyRulesModuleTest.java
index 06b5b2a..85836fc 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/xml/TopologyRulesModuleTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/xml/TopologyRulesModuleTest.java
@@ -17,11 +17,15 @@
  */
 package org.apache.hadoop.gateway.topology.xml;
 
+import org.apache.commons.collections.keyvalue.MultiKey;
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.commons.collections.map.MultiKeyMap;
 import org.apache.commons.digester3.Digester;
 import org.apache.commons.digester3.binder.DigesterLoader;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
 import org.apache.hadoop.gateway.topology.Topology;
+import org.apache.hadoop.gateway.topology.Version;
 import org.apache.hadoop.gateway.topology.builder.TopologyBuilder;
 import org.junit.After;
 import org.junit.Before;
@@ -72,7 +76,8 @@ public class TopologyRulesModuleTest {
 
     Service comp = topology.getServices().iterator().next();
     assertThat( comp, notNullValue() );
-    assertThat( comp.getRole(), is( "WEBHDFS" ) );
+    assertThat( comp.getRole(), is("WEBHDFS") );
+    assertThat( comp.getVersion().toString(), is( "2.4.0" ));
     assertThat( comp.getUrls().size(), is( 2 ) );
     assertThat( comp.getUrls(), hasItem( "http://host1:80/webhdfs" ) );
     assertThat( comp.getUrls(), hasItem( "http://host2:80/webhdfs" ) );
@@ -116,6 +121,7 @@ public class TopologyRulesModuleTest {
     assertThat( service.getName(), is( "test-service-name" ) );
     assertThat( service.getParams(), hasEntry( is( "test-service-param-name-1" ), is( "test-service-param-value-1" ) ) );
     assertThat( service.getParams(), hasEntry( is( "test-service-param-name-2" ), is( "test-service-param-value-2" ) ) );
+    assertThat( service.getVersion(), is( new Version(1,0,0)));
   }
 
 
@@ -136,7 +142,7 @@ public class TopologyRulesModuleTest {
     assertThat( topology.getServices().size(), is( 4 ) );
     assertThat( topology.getProviders().size(), is( 2 ) );
 
-    Service webhdfsService = topology.getService( "WEBHDFS", null );
+    Service webhdfsService = topology.getService( "WEBHDFS", null, null);
     assertThat( webhdfsService, notNullValue() );
     assertThat( webhdfsService.getRole(), is( "WEBHDFS" ) );
     assertThat( webhdfsService.getName(), nullValue() );
@@ -144,21 +150,21 @@ public class TopologyRulesModuleTest {
     assertThat( webhdfsService.getUrls(), hasItem( "http://host1:50070/webhdfs" ) );
     assertThat( webhdfsService.getUrls(), hasItem( "http://host2:50070/webhdfs" ) );
 
-    Service webhcatService = topology.getService( "WEBHCAT", null );
+    Service webhcatService = topology.getService( "WEBHCAT", null, null);
     assertThat( webhcatService, notNullValue() );
     assertThat( webhcatService.getRole(), is( "WEBHCAT" ) );
     assertThat( webhcatService.getName(), nullValue() );
     assertThat( webhcatService.getUrls().size(), is( 1 ) );
     assertThat( webhcatService.getUrls(), hasItem( "http://host:50111/templeton" ) );
 
-    Service oozieService = topology.getService( "OOZIE", null );
+    Service oozieService = topology.getService( "OOZIE", null, null);
     assertThat( oozieService, notNullValue() );
     assertThat( oozieService.getRole(), is( "OOZIE" ) );
     assertThat( oozieService.getName(), nullValue() );
     assertThat( webhcatService.getUrls().size(), is( 1 ) );
     assertThat( oozieService.getUrls(), hasItem( "http://host:11000/oozie" ) );
 
-    Service hiveService = topology.getService( "HIVE", null );
+    Service hiveService = topology.getService( "HIVE", null, null);
     assertThat( hiveService, notNullValue() );
     assertThat( hiveService.getRole(), is( "HIVE" ) );
     assertThat( hiveService.getName(), nullValue() );
@@ -217,5 +223,4 @@ public class TopologyRulesModuleTest {
     assertThat( service.getParams(), hasEntry( is( "test-service-param-name-1" ), is( "test-service-param-value-1" ) ) );
     assertThat( service.getParams(), hasEntry( is( "test-service-param-name-2" ), is( "test-service-param-value-2" ) ) );
   }
-
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/service-param-topology-knox-format.xml
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/service-param-topology-knox-format.xml b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/service-param-topology-knox-format.xml
index a7c476e..870015a 100644
--- a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/service-param-topology-knox-format.xml
+++ b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/service-param-topology-knox-format.xml
@@ -31,6 +31,7 @@
     <service>
         <role>test-service-role</role>
         <name>test-service-name</name>
+        <version>1.0.0</version>
         <url>test-service-scheme://test-service-host1:42/test-service-path</url>
         <url>test-service-scheme://test-service-host2:42/test-service-path</url>
         <param>

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/simple-topology-knox-format.xml
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/simple-topology-knox-format.xml b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/simple-topology-knox-format.xml
index 46175d1..1a181f1 100644
--- a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/simple-topology-knox-format.xml
+++ b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/xml/simple-topology-knox-format.xml
@@ -63,6 +63,7 @@
 
     <service>
         <role>WEBHDFS</role>
+        <version>2.4.0</version>
         <url>http://host1:80/webhdfs</url>
         <url>http://host2:80/webhdfs</url>
     </service>

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-spi/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-spi/pom.xml b/gateway-spi/pom.xml
index 506f4a0..6e7eeea 100644
--- a/gateway-spi/pom.xml
+++ b/gateway-spi/pom.xml
@@ -91,7 +91,11 @@
             <groupId>com.jayway.jsonpath</groupId>
             <artifactId>json-path</artifactId>
         </dependency>
-        
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributor.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributor.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributor.java
index ba21928..19bc1d6 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributor.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributor.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.gateway.deploy;
 
 import org.apache.hadoop.gateway.topology.Service;
+import org.apache.hadoop.gateway.topology.Version;
 
 public interface ServiceDeploymentContributor {
 
@@ -27,6 +28,8 @@ public interface ServiceDeploymentContributor {
   // The name of this service deployment contributor.  Not used yet.
   String getName();
 
+  Version getVersion();
+
   // Called after provider initializeContribution methods and in arbitrary order relative to other service contributors.
   void initializeContribution( DeploymentContext context );
 

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributorBase.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributorBase.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributorBase.java
index 4d33fa8..f82bad5 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributorBase.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/deploy/ServiceDeploymentContributorBase.java
@@ -21,6 +21,7 @@ import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
 import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
+import org.apache.hadoop.gateway.topology.Version;
 
 import java.net.URISyntaxException;
 import java.util.Collection;
@@ -28,6 +29,11 @@ import java.util.List;
 
 public abstract class ServiceDeploymentContributorBase extends DeploymentContributorBase implements ServiceDeploymentContributor {
 
+  @Override
+  public Version getVersion() {
+    return new Version();
+  }
+
   public void initializeContribution( DeploymentContext context ) {
     // Noop.
   }

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Service.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Service.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Service.java
index 9763174..41270a9 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Service.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Service.java
@@ -27,6 +27,7 @@ public class Service {
 
   private String role;
   private String name;
+  private Version version;
   private Map<String, String> params = new LinkedHashMap<String, String>();
   private List<String> urls;
 
@@ -46,6 +47,14 @@ public class Service {
     this.name = name;
   }
 
+  public Version getVersion() {
+    return version;
+  }
+
+  public void setVersion(Version version) {
+    this.version = version;
+  }
+
   public List<String> getUrls() {
     if ( urls == null ) {
       urls = new ArrayList<String>();

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/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 c8d611d..2f5d671 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,12 +17,11 @@
  */
 package org.apache.hadoop.gateway.topology;
 
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.commons.collections.map.MultiKeyMap;
+
 import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class Topology {
 
@@ -32,7 +31,12 @@ public class Topology {
   public List<Provider> providerList = new ArrayList<Provider>();
   private Map<String,Map<String,Provider>> providerMap = new HashMap<String,Map<String,Provider>>();
   public List<Service> services = new ArrayList<Service>();
-  private Map<String, Map<String, Service>> serviceMap = new HashMap<String, Map<String, Service>>();
+
+  private MultiKeyMap serviceMap;
+
+  public Topology() {
+    serviceMap = MultiKeyMap.decorate(new HashedMap());
+  }
 
   public URI getUri() {
     return uri;
@@ -62,27 +66,13 @@ public class Topology {
     return services;
   }
 
-  public Service getService( String role, String name ) {
-    Service service = null;
-    Map<String, Service> nameMap = serviceMap.get( role );
-    if( nameMap != null) {
-      service = nameMap.get( name );
-      if ( service == null && !nameMap.values().isEmpty() ) {
-        service = (Service) nameMap.values().toArray()[0];
-      }
-    }
-    return service;
+  public Service getService(String role, String name, Version version) {
+    return (Service)serviceMap.get(role, name, version);
   }
 
   public void addService( Service service ) {
     services.add( service );
-    String role = service.getRole();
-    Map<String, Service> nameMap = serviceMap.get( role );
-    if( nameMap == null ) {
-      nameMap = new HashMap<String, Service>();
-      serviceMap.put( role, nameMap );
-    }
-    nameMap.put( service.getName(), service );
+    serviceMap.put(service.getRole(), service.getName(), service.getVersion(), service);
   }
 
   public Collection<Provider> getProviders() {

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Version.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Version.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Version.java
new file mode 100644
index 0000000..08aca08
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/Version.java
@@ -0,0 +1,130 @@
+/**
+ * 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.gateway.topology;
+
+public class Version implements Comparable<Version> {
+
+  private int major;
+
+  private int minor;
+
+  private int patch;
+
+  public Version() {
+  }
+
+  public Version(String version) {
+    setVersion(version);
+  }
+
+  public Version(int major, int minor, int patch) {
+    this.major = major;
+    this.minor = minor;
+    this.patch = patch;
+  }
+
+  public int getMajor() {
+    return major;
+  }
+
+  public void setMajor(int major) {
+    this.major = major;
+  }
+
+  public int getMinor() {
+    return minor;
+  }
+
+  public void setMinor(int minor) {
+    this.minor = minor;
+  }
+
+  public int getPatch() {
+    return patch;
+  }
+
+  public void setPatch(int patch) {
+    this.patch = patch;
+  }
+
+  public void setVersion(String version) {
+    if (version != null) {
+      parseVersion(version);
+    }
+  }
+
+  private void parseVersion(String version) {
+    String parts[] = version.split("\\.");
+    int length = parts.length;
+    if (length >= 1) {
+      major = Integer.parseInt(parts[0]);
+    }
+    if (length >= 2) {
+      minor = Integer.parseInt(parts[1]);
+    }
+    if (length >= 3) {
+      patch = Integer.parseInt(parts[2]);
+    }
+  }
+
+  @Override
+  public int compareTo(Version version) {
+    if (major > version.getMajor()) {
+      return 1;
+    }
+    if (major < version.getMajor()) {
+      return -1;
+    }
+    if (minor > version.getMinor()) {
+      return 1;
+    }
+    if (minor < version.getMinor()) {
+      return -1;
+    }
+    if (patch > version.getPatch()) {
+      return 1;
+    }
+    if (patch < version.getPatch()) {
+      return -1;
+    }
+    return 0;
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.append(major);
+    buffer.append(".");
+    buffer.append(minor);
+    buffer.append(".");
+    buffer.append(patch);
+    return buffer.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof Version)) {
+      return false;
+    }
+    Version that = (Version) o;
+    if (major == that.getMajor() && minor == that.getMinor() && patch == that.getPatch()) {
+      return true;
+    }
+    return false;
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/e5319f05/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d787901..284e621 100644
--- a/pom.xml
+++ b/pom.xml
@@ -824,7 +824,11 @@
                 <artifactId>commons-net</artifactId>
                 <version>1.4.1</version>
             </dependency>
-
+            <dependency>
+                <groupId>commons-collections</groupId>
+                <artifactId>commons-collections</artifactId>
+                <version>3.2.1</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-digester3</artifactId>