KNOX-1339 - Support for a normalized dispatch used across all services exposed by...
authorSandeep More <more@apache.org>
Mon, 27 Aug 2018 15:14:03 +0000 (11:14 -0400)
committerSandeep More <more@apache.org>
Mon, 27 Aug 2018 15:14:03 +0000 (11:14 -0400)
gateway-server/src/main/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
gateway-server/src/main/resources/conf/topology-v1.xsd
gateway-server/src/test/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributorTest.java
gateway-server/src/test/java/org/apache/knox/gateway/topology/xml/TopologyRulesModuleTest.java
gateway-server/src/test/resources/org/apache/knox/gateway/topology/xml/TopologyRulesModuleTest/topology-with-dispatch.xml [new file with mode: 0644]
gateway-service-definitions/src/main/java/org/apache/knox/gateway/service/definition/CustomDispatch.java
gateway-spi/pom.xml
gateway-spi/src/main/java/org/apache/knox/gateway/topology/Topology.java

index f450e17..1aa54fb 100644 (file)
@@ -183,6 +183,11 @@ public class ServiceDefinitionDeploymentContributor extends ServiceDeploymentCon
 
   private void addDispatchFilter(DeploymentContext context, Service service, ResourceDescriptor resource, Route binding) {
     CustomDispatch customDispatch = binding.getDispatch();
+
+    if(context.getTopology().getDispatch() != null) {
+      customDispatch = context.getTopology().getDispatch();
+    }
+
     if ( customDispatch == null ) {
       customDispatch = serviceDefinition.getDispatch();
     }
index afeade0..1a54f44 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.topology.builder;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.knox.gateway.service.definition.CustomDispatch;
 import org.apache.knox.gateway.topology.Application;
 import org.apache.knox.gateway.topology.Provider;
 import org.apache.knox.gateway.topology.Service;
@@ -32,6 +33,7 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
     private List<Provider> providers;
     private List<Service> services;
     private List<Application> applications;
+    private CustomDispatch dispatch;
 
     public BeanPropertyTopologyBuilder() {
         providers = new ArrayList<Provider>();
@@ -84,6 +86,14 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
         return services;
     }
 
+    public void addDispatch(final CustomDispatch dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public CustomDispatch getDispatch() {
+        return this.dispatch;
+    }
+
     public BeanPropertyTopologyBuilder addApplication( Application application ) {
         applications.add(application);
         return this;
@@ -99,6 +109,8 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
         topology.setDefaultServicePath(defaultService);
         topology.setGenerated(isGenerated);
 
+        topology.addDispatch(dispatch);
+
         for (Provider provider : providers) {
             topology.addProvider(provider);
         }
index a1fcb6d..8cf5b9f 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.topology.xml;
 
 import org.apache.commons.digester3.Rule;
 import org.apache.commons.digester3.binder.AbstractRulesModule;
+import org.apache.knox.gateway.service.definition.CustomDispatch;
 import org.apache.knox.gateway.topology.Application;
 import org.apache.knox.gateway.topology.Param;
 import org.apache.knox.gateway.topology.Provider;
@@ -43,6 +44,15 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
   private static final String PARAM_TAG = "param";
   private static final String VALUE_TAG = "value";
 
+  /* topology dispatch tags */
+  private static final String DISPATCH_TAG = "gateway/dispatch";
+  private static final String CONTRIBUTOR_NAME = "contributor-name";
+  private static final String HA_CONTRIBUTOR_NAME = "ha-contributor-name";
+  private static final String CLASSNAME = "classname";
+  private static final String HA_CLASSNAME = "ha-classname";
+  private static final String HTTP_CLIENT_FACTORY = "http-client-factory";
+  private static final String USE_TWO_WAY_SSL = "use-two-way-ssl";
+
   private static final Rule paramRule = new ParamRule();
 
   @Override
@@ -78,6 +88,15 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
     forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG ).createObject().ofType( Param.class ).then().addRule( paramRule ).then().setNext( "addParam" );
     forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG + "/" + NAME_TAG ).setBeanProperty();
     forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG + "/" + VALUE_TAG ).setBeanProperty();
+
+    /* topology dispatch */
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG ).createObject().ofType( CustomDispatch.class ).then().setNext( "addDispatch" );
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + CONTRIBUTOR_NAME ).callMethod( "setContributorName" ).usingElementBodyAsArgument();
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + HA_CONTRIBUTOR_NAME ).callMethod( "setHaContributorName" ).usingElementBodyAsArgument();
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + CLASSNAME ).callMethod( "setClassName" ).usingElementBodyAsArgument();
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + HA_CLASSNAME ).callMethod( "setHaClassName" ).usingElementBodyAsArgument();
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + HTTP_CLIENT_FACTORY ).callMethod( "setHttpClientFactory" ).usingElementBodyAsArgument();
+    forPattern( ROOT_TAG + "/" + DISPATCH_TAG + "/" + USE_TWO_WAY_SSL ).callMethod( "setUseTwoWaySsl" ).usingElementBodyAsArgument();
   }
 
   private static class ParamRule extends Rule {
index 722a365..04f673b 100644 (file)
@@ -26,7 +26,7 @@ limitations under the License.
 
                 <h:element name="gateway" minOccurs="0" maxOccurs="1">
                     <h:complexType>
-                        <h:choice minOccurs="1" maxOccurs="unbounded">
+                        <h:sequence minOccurs="1" maxOccurs="unbounded">
 
                             <h:element name="provider" maxOccurs="unbounded">
                                 <h:complexType>
@@ -49,7 +49,21 @@ limitations under the License.
                                 </h:complexType>
                             </h:element>
 
-                        </h:choice>
+                            <h:element name="dispatch" minOccurs="0" maxOccurs="1">
+                                <h:complexType>
+                                    <h:all>
+                                        <h:element name="contributor-name" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                        <h:element name="ha-contributor-name" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                        <h:element name="classname" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                        <h:element name="ha-classname" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                        <h:element name="http-client-factory" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                        <h:element name="use-two-way-ssl" type="h:string" minOccurs="0" maxOccurs="1"/>
+                                    </h:all>
+                                </h:complexType>
+                            </h:element>
+
+                        </h:sequence>
+
                     </h:complexType>
                 </h:element>
 
index 4045789..9a69625 100644 (file)
@@ -163,6 +163,120 @@ public class ServiceDefinitionDeploymentContributorTest {
                  "true", useTwoWaySslFilterParamValues.get(0));
   }
 
+  /**
+   * Test for a feature that sets dispatch for all the services defined in a
+   * topology. The dispatch is defined just once in the topology inside the
+   * <gateway> </gateway> tag.
+   * @since 1.2.0
+   */
+  @Test
+  public void testTopologyDispatch() throws Exception {
+
+    final String TEST_SERVICE_ROLE     = "Test";
+    final String DISPATCH = "dispatch-impl";
+    final String EXPECTED_DISPATCH_CLASS = "org.apache.knox.gateway.hdfs.dispatch.HdfsHttpClientDispatch";
+    final String EXPECTED_HA_DISPATCH_CLASS = "org.apache.knox.gateway.hdfs.dispatch.HdfsUIHaDispatch";
+
+    UrlRewriteRulesDescriptor clusterRules = EasyMock.createNiceMock(UrlRewriteRulesDescriptor.class);
+    EasyMock.replay(clusterRules);
+
+    UrlRewriteRulesDescriptor svcRules = EasyMock.createNiceMock(UrlRewriteRulesDescriptor.class);
+    EasyMock.replay(svcRules);
+
+    ServiceDefinition svcDef = EasyMock.createNiceMock(ServiceDefinition.class);
+    EasyMock.expect(svcDef.getRole()).andReturn(TEST_SERVICE_ROLE).anyTimes();
+    List<Route> svcRoutes = new ArrayList<>();
+    Route route = EasyMock.createNiceMock(Route.class);
+    List<Rewrite> filters = new ArrayList<>();
+    EasyMock.expect(route.getRewrites()).andReturn(filters).anyTimes();
+    svcRoutes.add(route);
+    EasyMock.replay(route);
+    EasyMock.expect(svcDef.getRoutes()).andReturn(svcRoutes).anyTimes();
+
+    CustomDispatch serviceDispatch = EasyMock.createNiceMock(CustomDispatch.class);
+    EasyMock.expect(serviceDispatch.getClassName()).andReturn("TestDispatch").anyTimes();
+    EasyMock.expect(serviceDispatch.getHaClassName()).andReturn("TestHADispatch").anyTimes();
+    EasyMock.expect(serviceDispatch.getHaContributorName()).andReturn(null).anyTimes();
+    EasyMock.expect(serviceDispatch.getUseTwoWaySsl()).andReturn(false).anyTimes();
+    EasyMock.replay(serviceDispatch);
+
+    EasyMock.expect(svcDef.getDispatch()).andReturn(serviceDispatch).anyTimes();
+    EasyMock.replay(svcDef);
+
+    ServiceDefinitionDeploymentContributor sddc = new ServiceDefinitionDeploymentContributor(svcDef, svcRules);
+
+    DeploymentContext context = EasyMock.createNiceMock(DeploymentContext.class);
+    EasyMock.expect(context.getDescriptor("rewrite")).andReturn(clusterRules).anyTimes();
+    GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gc.isXForwardedEnabled()).andReturn(false).anyTimes();
+    EasyMock.expect(gc.isCookieScopingToPathEnabled()).andReturn(false).anyTimes();
+    EasyMock.replay(gc);
+    EasyMock.expect(context.getGatewayConfig()).andReturn(gc).anyTimes();
+
+    /* dispatch configured in topology */
+    CustomDispatch topologyDispatch = EasyMock.createNiceMock(CustomDispatch.class);
+    EasyMock.expect(topologyDispatch.getClassName()).andReturn(EXPECTED_DISPATCH_CLASS).anyTimes();
+    EasyMock.expect(topologyDispatch.getHaClassName()).andReturn(EXPECTED_HA_DISPATCH_CLASS).anyTimes();
+    EasyMock.expect(topologyDispatch.getHaContributorName()).andReturn(null).anyTimes();
+    EasyMock.expect(topologyDispatch.getUseTwoWaySsl()).andReturn(false).anyTimes();
+    EasyMock.replay(topologyDispatch);
+
+    // Configure the topology
+    Topology topology = EasyMock.createNiceMock(Topology.class);
+    List<Provider> providers = new ArrayList<>();
+    Provider haProvider = EasyMock.createNiceMock(Provider.class);
+    EasyMock.expect(haProvider.getRole()).andReturn("ha").anyTimes();
+    EasyMock.expect(haProvider.isEnabled()).andReturn(true).anyTimes();
+    Map<String, String> providerParams = new HashMap<>();
+    providerParams.put(TEST_SERVICE_ROLE, "whatever");
+    EasyMock.expect(haProvider.getParams()).andReturn(providerParams).anyTimes();
+
+    EasyMock.replay(haProvider);
+    providers.add(haProvider);
+    EasyMock.expect(topology.getProviders()).andReturn(providers).anyTimes();
+    /* mock topology dispatch */
+    EasyMock.expect(topology.getDispatch()).andReturn(topologyDispatch).anyTimes();
+    EasyMock.replay(topology);
+    EasyMock.expect(context.getTopology()).andReturn(topology).anyTimes();
+
+    TestGatewayDescriptor gd = new TestGatewayDescriptor();
+    EasyMock.expect(context.getGatewayDescriptor()).andReturn(gd).anyTimes();
+    EasyMock.replay(context);
+
+    // Configure the service with the useTwoWaySsl param to OVERRIDE the value in the service definition
+    Service service = EasyMock.createNiceMock(Service.class);
+    Map<String, String> svcParams = new HashMap<>();
+    EasyMock.expect(service.getParams()).andReturn(svcParams).anyTimes();
+    EasyMock.expect(service.getRole()).andReturn(TEST_SERVICE_ROLE).anyTimes();
+    EasyMock.replay(service);
+
+    sddc.contributeService(context, service);
+
+    List<ResourceDescriptor> resources = gd.resources();
+    assertEquals(1, gd.resources().size());
+    ResourceDescriptor res = gd.resources().get(0);
+    assertNotNull(res);
+    List<FilterDescriptor> filterList = res.filters();
+    assertEquals(1, filterList.size());
+    FilterDescriptor f = filterList.get(0);
+    assertNotNull(f);
+    assertEquals("dispatch", f.role());
+    List<FilterParamDescriptor> fParams = f.params();
+    assertNotNull(fParams);
+
+    // Collect the values of filter params named useTwoWaySsl
+    List<String> dispatchImpl = new ArrayList<>();
+    for (FilterParamDescriptor param : fParams) {
+      if (param.name().equals(DISPATCH)) {
+        dispatchImpl.add(param.value());
+      }
+    }
+
+    assertEquals("Expected the topology dispatch to override the service definition value for dispatch ",
+        EXPECTED_HA_DISPATCH_CLASS, dispatchImpl.get(0));
+
+  }
+
 
   private static class TestGatewayDescriptor extends GatewayDescriptorImpl {
   }
index d75dcfb..cbcd302 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.topology.xml;
 
 import org.apache.commons.digester3.Digester;
 import org.apache.commons.digester3.binder.DigesterLoader;
+import org.apache.knox.gateway.service.definition.CustomDispatch;
 import org.apache.knox.gateway.topology.Application;
 import org.apache.knox.gateway.topology.Provider;
 import org.apache.knox.gateway.topology.Service;
@@ -259,4 +260,26 @@ public class TopologyRulesModuleTest {
     assertThat( app.getParams().get( "test-param-name" ), is( "test-param-value" ) );
   }
 
+  @Test
+  public void testParseTopologyWithDispatch() throws IOException, SAXException {
+    Digester digester = loader.newDigester();
+    String name = "topology-with-dispatch.xml";
+    URL url = TestUtils.getResourceUrl( TopologyRulesModuleTest.class, name );
+    assertThat( "Failed to find URL for resource " + name, url, notNullValue() );
+    File file = new File( url.getFile() );
+    TopologyBuilder topologyBuilder = digester.parse( url );
+    Topology topology = topologyBuilder.build();
+    assertThat( "Failed to parse resource " + name, topology, notNullValue() );
+    topology.setTimestamp( file.lastModified() );
+
+    CustomDispatch dispatch = topology.getDispatch();
+    assertThat( "Failed to find dispatch", dispatch, notNullValue() );
+    assertThat( dispatch.getContributorName(), is("testContributor") );
+    assertThat( dispatch.getHaContributorName(), is("testHAContributor") );
+    assertThat( dispatch.getClassName(), is("org.apache.hadoop.gateway.hbase.HBaseDispatch") );
+    assertThat( dispatch.getHaClassName(), is("testHAClassName") );
+    assertThat( dispatch.getHttpClientFactory(), is("testHttpClientFactory") );
+    assertThat( dispatch.getUseTwoWaySsl(), is(true) );
+  }
+
 }
diff --git a/gateway-server/src/test/resources/org/apache/knox/gateway/topology/xml/TopologyRulesModuleTest/topology-with-dispatch.xml b/gateway-server/src/test/resources/org/apache/knox/gateway/topology/xml/TopologyRulesModuleTest/topology-with-dispatch.xml
new file mode 100644 (file)
index 0000000..5224726
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  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.
+-->
+<topology>
+    <name>test-topology-name</name>
+    <gateway>
+        <dispatch>
+            <contributor-name>testContributor</contributor-name>
+            <ha-contributor-name>testHAContributor</ha-contributor-name>
+            <classname>org.apache.hadoop.gateway.hbase.HBaseDispatch</classname>
+            <ha-classname>testHAClassName</ha-classname>
+            <http-client-factory>testHttpClientFactory</http-client-factory>
+            <use-two-way-ssl>true</use-two-way-ssl>
+        </dispatch>
+    </gateway>
+</topology>
\ No newline at end of file
index ac82b39..5bd85f5 100644 (file)
@@ -88,4 +88,9 @@ public class CustomDispatch {
   public void setUseTwoWaySsl(boolean useTwoWaySsl) {
     this.useTwoWaySsl = useTwoWaySsl;
   }
+
+  /* this is used when we use Apache Commons Digestor bindings, see KnoxFormatXmlTopologyRules.configure() */
+  public void setUseTwoWaySsl(String useTwoWaySsl) {
+    this.useTwoWaySsl = Boolean.parseBoolean(useTwoWaySsl);
+  }
 }
index 4b4344e..534b10a 100644 (file)
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
         </dependency>
-
-
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-service-definitions</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
index e46197d..bbbd129 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.topology;
 
 import org.apache.commons.collections.map.HashedMap;
 import org.apache.commons.collections.map.MultiKeyMap;
+import org.apache.knox.gateway.service.definition.CustomDispatch;
 
 import java.net.URI;
 import java.util.ArrayList;
@@ -40,6 +41,7 @@ public class Topology {
   private MultiKeyMap serviceMap;
   private List<Application> applications = new ArrayList<Application>();
   private Map<String,Application> applicationMap = new HashMap<>();
+  private CustomDispatch dispatch = null;
 
   public Topology() {
     serviceMap = MultiKeyMap.decorate(new HashedMap());
@@ -112,6 +114,25 @@ public class Topology {
     return url;
   }
 
+  /**
+   * Add dispatch to be used by all the services
+   * deployed in this topology
+   * @since 1.2.0
+   * @param dispatch
+   */
+  public void addDispatch(final CustomDispatch dispatch) {
+    this.dispatch = dispatch;
+  }
+
+  /**
+   * Get the dispatch to be used by all the services
+   * deployed in this topology
+   * @return dispatch
+   */
+  public CustomDispatch getDispatch() {
+    return dispatch;
+  }
+
   public Application getApplication(String url) {
     return applicationMap.get( fixApplicationUrl( url ) );
   }