KNOX-1322 - Support configuration property to forcibly treat topologies as read-only
authorPhil Zampino <pzampino@apache.org>
Thu, 17 May 2018 22:04:14 +0000 (18:04 -0400)
committerPhil Zampino <pzampino@apache.org>
Thu, 17 May 2018 22:04:14 +0000 (18:04 -0400)
gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/TopologiesResource.java
gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
gateway-test/src/test/java/org/apache/knox/gateway/GatewayAdminTopologyFuncTest.java

index 56440f2..9ad0432 100644 (file)
@@ -163,12 +163,11 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   public static final String GATEWAY_PORT_MAPPING_ENABLED = GATEWAY_PORT_MAPPING_PREFIX + "enabled";
 
   /**
-   * Comma seperated list of MIME Types to be compressed by Knox on the way out.
+   * Comma-separated list of MIME Types to be compressed by Knox on the way out.
    *
    * @since 0.12
    */
-  public static final String MIME_TYPES_TO_COMPRESS = GATEWAY_CONFIG_FILE_PREFIX
-      + ".gzip.compress.mime.types";
+  public static final String MIME_TYPES_TO_COMPRESS = GATEWAY_CONFIG_FILE_PREFIX + ".gzip.compress.mime.types";
 
   public static final String CLUSTER_CONFIG_MONITOR_PREFIX = GATEWAY_CONFIG_FILE_PREFIX + ".cluster.config.monitor.";
   public static final String CLUSTER_CONFIG_MONITOR_INTERVAL_SUFFIX = ".interval";
@@ -195,6 +194,13 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
 
   public static final String REMOTE_ALIAS_SERVICE_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.enabled";
 
+  /**
+   * Comma-separated list of topology names, which should be forcibly treated as read-only.
+   * @since 1.1.0
+   */
+  public static final String READ_ONLY_OVERRIDE_TOPOLOGIES =
+                                                    GATEWAY_CONFIG_FILE_PREFIX + ".read.only.override.topologies";
+
   /* Websocket defaults */
   public static final boolean DEFAULT_WEBSOCKET_FEATURE_ENABLED = false;
   public static final int DEFAULT_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE = Integer.MAX_VALUE;;
@@ -210,13 +216,12 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   public static final boolean DEFAULT_REMOTE_ALIAS_SERVICE_ENABLED = true;
 
 
-
   /**
    * Default list of MIME Type to be compressed.
    * @since 0.12
    */
-  public static final String DEFAULT_MIME_TYPES_TO_COMPRESS = "text/html, text/plain, text/xml, text/css, "
-      + "application/javascript, application/x-javascript, text/javascript";
+  public static final String DEFAULT_MIME_TYPES_TO_COMPRESS =
+        "text/html, text/plain, text/xml, text/css, application/javascript, application/x-javascript, text/javascript";
 
   public static final String COOKIE_SCOPING_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".scope.cookies.feature.enabled";
   public static final boolean DEFAULT_COOKIE_SCOPING_FEATURE_ENABLED = false;
@@ -1025,4 +1030,16 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
     return Boolean.parseBoolean(result);
   }
 
+  @Override
+  public List<String> getReadOnlyOverrideTopologyNames() {
+    List<String> topologyNames = new ArrayList<>();
+
+    String value = get(READ_ONLY_OVERRIDE_TOPOLOGIES);
+    if (value != null && !value.isEmpty()) {
+      topologyNames.addAll(Arrays.asList(value.trim().split("\\s*,\\s*")));
+    }
+
+    return topologyNames;
+  }
+
 }
index 4187214..d1089c0 100644 (file)
@@ -257,7 +257,40 @@ public class GatewayConfigImplTest {
 
     assertEquals(TimeUnit.SECONDS.toMillis(20), config.getHttpClientConnectionTimeout());
     assertEquals(TimeUnit.SECONDS.toMillis(20), config.getHttpClientSocketTimeout());
+  }
+
+
+  // KNOX-1322
+  @Test
+  public void testGetReadOnlyOverrideTopologyNames() {
+    GatewayConfigImpl config = new GatewayConfigImpl();
 
+    List<String> names = config.getReadOnlyOverrideTopologyNames();
+    assertNotNull(names);
+    assertTrue(names.isEmpty());
+
+    config.set(GatewayConfigImpl.READ_ONLY_OVERRIDE_TOPOLOGIES, "");
+    names = config.getReadOnlyOverrideTopologyNames();
+    assertNotNull(names);
+    assertTrue(names.isEmpty());
+
+    config.set(GatewayConfigImpl.READ_ONLY_OVERRIDE_TOPOLOGIES, "admin");
+    names = config.getReadOnlyOverrideTopologyNames();
+    assertNotNull(names);
+    assertFalse(names.isEmpty());
+    assertEquals(1, names.size());
+    assertEquals("admin", names.get(0));
+
+    config.set(GatewayConfigImpl.READ_ONLY_OVERRIDE_TOPOLOGIES, "admin, sandbox, test ,default");
+    names = config.getReadOnlyOverrideTopologyNames();
+    assertNotNull(names);
+    assertFalse(names.isEmpty());
+    assertEquals(4, names.size());
+    assertTrue(names.contains("admin"));
+    assertTrue(names.contains("sandbox"));
+    assertTrue(names.contains("test"));
+    assertTrue(names.contains("default"));
   }
 
+
 }
index 3b8f2fe..b29fcbc 100644 (file)
@@ -102,19 +102,27 @@ public class TopologiesResource {
   @Produces({APPLICATION_JSON, APPLICATION_XML})
   @Path(SINGLE_TOPOLOGY_API_PATH)
   public Topology getTopology(@PathParam("id") String id) {
-    GatewayServices services = (GatewayServices) request.getServletContext()
-        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
-    GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+    GatewayServices services =
+              (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+    GatewayConfig config =
+                    (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
 
     TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
 
     for (org.apache.knox.gateway.topology.Topology t : ts.getTopologies()) {
-      if(t.getName().equals(id)) {
+      if (t.getName().equals(id)) {
         try {
           t.setUri(new URI( buildURI(t, config, request) ));
         } catch (URISyntaxException se) {
           t.setUri(null);
         }
+
+        // For any read-only override topology, mark it as generated to discourage modification.
+        List<String> ambariManagedTopos = config.getReadOnlyOverrideTopologyNames();
+        if (ambariManagedTopos.contains(t.getName())) {
+          t.setGenerated(true);
+        }
+
         return BeanConverter.getTopology(t);
       }
     }
@@ -125,25 +133,21 @@ public class TopologiesResource {
   @Produces({APPLICATION_JSON, APPLICATION_XML})
   @Path(TOPOLOGIES_API_PATH)
   public SimpleTopologyWrapper getTopologies() {
-    GatewayServices services = (GatewayServices) request.getServletContext()
-        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
-
+    GatewayServices services =
+              (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+    GatewayConfig config =
+        (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
 
     TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
 
     ArrayList<SimpleTopology> st = new ArrayList<SimpleTopology>();
-    GatewayConfig conf = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
-
     for (org.apache.knox.gateway.topology.Topology t : ts.getTopologies()) {
-      st.add(getSimpleTopology(t, conf));
+      st.add(getSimpleTopology(t, config));
     }
+    st.sort(new TopologyComparator());
 
-    Collections.sort(st, new TopologyComparator());
     SimpleTopologyWrapper stw = new SimpleTopologyWrapper();
-
-    for(SimpleTopology t : st){
-      stw.topologies.add(t);
-    }
+    stw.topologies.addAll(st);
 
     return stw;
 
index 980f602..ab6a473 100644 (file)
@@ -373,11 +373,20 @@ public interface GatewayConfig {
 
   /**
    * Returns whether the Remote Alias Service is enabled or not.
-   * This value also depends on whether remote registry is enabled or not.
-   * if it is enabled then this option takes effect else this option has no
-   * effect.
-   * @return
+   *
+   * This value also depends on whether the remote configuration registry is enabled or not.
+   * If it is enabled, then this option takes effect, else this option has no effect.
+   *
+   * @return true, if the remote alias service is enabled; otherwise, false;
    */
   boolean isRemoteAliasServiceEnabled();
 
+  /**
+   * Get the list of those topology names which should be treated as read-only, regardless of their actual read-write
+   * status.
+   *
+   * @return A list of the names of those topologies which should be treated as read-only.
+   */
+  List<String> getReadOnlyOverrideTopologyNames();
+
 }
index 1b198c9..cb2de7f 100644 (file)
@@ -20,11 +20,13 @@ package org.apache.knox.gateway;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
 
 import java.io.File;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -679,4 +681,16 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
     return false;
   }
 
+  @Override
+  public List<String> getReadOnlyOverrideTopologyNames() {
+    List<String> readOnly = new ArrayList<>();
+
+    String value = get(GatewayConfigImpl.READ_ONLY_OVERRIDE_TOPOLOGIES);
+    if (value != null && !value.isEmpty()) {
+      readOnly.addAll(Arrays.asList(value.trim().split("\\s*,\\s*")));
+    }
+
+    return readOnly;
+  }
+
 }
index 0bb0b23..3bd0605 100644 (file)
@@ -36,6 +36,7 @@ import io.restassured.http.ContentType;
 import com.mycila.xmltool.XMLDoc;
 import com.mycila.xmltool.XMLTag;
 import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
 import org.apache.knox.gateway.services.DefaultGatewayServices;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
@@ -454,6 +455,63 @@ public class GatewayAdminTopologyFuncTest {
     LOG_EXIT();
   }
 
+
+  /**
+   * KNOX-1322
+   */
+  @Test( timeout = TestUtils.LONG_TIMEOUT )
+  public void testTopologyObjectForcedReadOnly() throws Exception {
+    LOG_ENTER();
+
+    final String testTopologyName = "test-cluster";
+
+    // First, verify that without any special config, the topology is NOT marked as generated (i.e., read-only)
+    validateGeneratedElement(testTopologyName, "false");
+
+    try {
+      gateway.stop();
+
+      // Update gateway config, such that the test topology should be marked as generated (i.e., read-only)
+      GatewayTestConfig conf = new GatewayTestConfig();
+      conf.set(GatewayConfigImpl.READ_ONLY_OVERRIDE_TOPOLOGIES, testTopologyName);
+      setupGateway(conf);
+
+      // Verify that the generate element reflects the configuration change
+      validateGeneratedElement(testTopologyName, "true");
+
+      // Verify that another topology is unaffected by the configuration
+      validateGeneratedElement("admin", "false");
+
+    } finally {
+      // Restart the gateway with old settings.
+      gateway.stop();
+      setupGateway(new GatewayTestConfig());
+    }
+
+    LOG_EXIT();
+  }
+
+
+  /**
+   * Access the specified topology, and validate the value of the generated element therein.
+   *
+   * @param topologyName  The name of the topology to validate
+   * @param expectedValue The expected value of the generated element.
+   */
+  private void validateGeneratedElement(String topologyName, String expectedValue) throws Exception {
+    String testClusterTopology = given().auth().preemptive().basic("admin", "admin-password")
+                                        .header("Accept", MediaType.APPLICATION_XML)
+                                        .then()
+                                        .statusCode(HttpStatus.SC_OK)
+                                        .when().get(clusterUrl + "/api/v1/topologies/" + topologyName)
+                                               .thenReturn().getBody().asString();
+    assertNotNull(testClusterTopology);
+    Document doc = XmlUtils.readXml(new InputSource(new StringReader(testClusterTopology)));
+    assertNotNull(doc);
+    assertThat(doc, hasXPath("/topology/generated", is(expectedValue)));
+  }
+
+
   @Test( timeout = TestUtils.LONG_TIMEOUT )
   public void testPositiveAuthorization() throws ClassNotFoundException{
     LOG_ENTER();