You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/10/26 17:20:44 UTC

[2/2] knox git commit: KNOX-1039 - Added admin APIs for managing shared provider configurations and descriptors (Phil Zampino via Sandeep More)

KNOX-1039 - Added admin APIs for managing shared provider configurations and descriptors (Phil Zampino via Sandeep More)


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

Branch: refs/heads/master
Commit: 9ad9bcdbbdb82acdabd05fe1500da9a6f8d22634
Parents: 41952dd
Author: Sandeep More <mo...@apache.org>
Authored: Thu Oct 26 13:20:35 2017 -0400
Committer: Sandeep More <mo...@apache.org>
Committed: Thu Oct 26 13:20:35 2017 -0400

----------------------------------------------------------------------
 .../ambari/AmbariServiceDiscovery.java          |   3 +-
 .../apache/hadoop/gateway/GatewayMessages.java  |  34 +-
 .../gateway/config/impl/GatewayConfigImpl.java  |   3 +-
 .../topology/impl/DefaultTopologyService.java   | 221 +++++--
 .../topology/DefaultTopologyServiceTest.java    | 402 +++++++++++--
 .../topology/file/provider-config-one.xml       |  74 +++
 .../topology/file/simple-descriptor-five.json   |  14 +
 .../topology/file/simple-descriptor-six.json    |  18 +
 .../service/admin/HrefListingMarshaller.java    |  75 +++
 .../service/admin/TopologiesResource.java       | 379 +++++++++++-
 .../hadoop/gateway/i18n/GatewaySpiMessages.java |  10 +-
 .../services/topology/TopologyService.java      |  33 +-
 .../gateway/GatewayAdminTopologyFuncTest.java   | 586 +++++++++++++++++++
 13 files changed, 1736 insertions(+), 116 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-discovery-ambari/src/main/java/org/apache/hadoop/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java
----------------------------------------------------------------------
diff --git a/gateway-discovery-ambari/src/main/java/org/apache/hadoop/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java b/gateway-discovery-ambari/src/main/java/org/apache/hadoop/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java
index 37f68ae..b7f9f53 100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/hadoop/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/hadoop/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java
@@ -247,10 +247,11 @@ class AmbariServiceDiscovery implements ServiceDiscovery {
             }
 
             if (aliasService != null) {
-                // If not password alias is configured, then try the default alias
+                // If no password alias is configured, then try the default alias
                 if (passwordAlias == null) {
                     passwordAlias = DEFAULT_PWD_ALIAS;
                 }
+
                 try {
                     char[] pwd = aliasService.getPasswordFromAliasForGateway(passwordAlias);
                     if (pwd != null) {

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/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 6f73c1e..4cb4c40 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
@@ -514,8 +514,40 @@ public interface GatewayMessages {
   void topologyPortMappingCannotFindTopology(final String topology, final int port);
 
 
+  @Message( level = MessageLevel.INFO, text = "Monitoring simple descriptors in directory: {0}" )
+  void monitoringDescriptorChangesInDirectory(String descriptorsDir);
+
+
+  @Message( level = MessageLevel.INFO, text = "Monitoring shared provider configurations in directory: {0}" )
+  void monitoringProviderConfigChangesInDirectory(String sharedProviderDir);
+
+  @Message( level = MessageLevel.INFO, text = "Prevented deletion of shared provider configuration because there are referencing descriptors: {0}" )
+  void preventedDeletionOfSharedProviderConfiguration(String providerConfigurationPath);
+
+  @Message( level = MessageLevel.INFO, text = "Generated topology {0} because the associated descriptor {1} changed." )
+  void generatedTopologyForDescriptorChange(String topologyName, String descriptorName);
+
   @Message( level = MessageLevel.ERROR, text = "An error occurred while processing {0} : {1}" )
   void simpleDescriptorHandlingError(final String simpleDesc,
-                                     @StackTrace( level = MessageLevel.DEBUG ) Exception e );
+                                     @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+  @Message(level = MessageLevel.DEBUG, text = "Successfully wrote configuration: {0}")
+  void wroteConfigurationFile(final String filePath);
+
+  @Message(level = MessageLevel.ERROR, text = "Failed to write configuration: {0}")
+  void failedToWriteConfigurationFile(final String filePath,
+                                      @StackTrace(level = MessageLevel.DEBUG) Exception e );
+
+  @Message( level = MessageLevel.INFO, text = "Deleting topology {0} because the associated descriptor {1} was deleted." )
+  void deletingTopologyForDescriptorDeletion(String topologyName, String descriptorName);
+
+  @Message( level = MessageLevel.INFO, text = "Deleting descriptor {0} because the associated topology {1} was deleted." )
+  void deletingDescriptorForTopologyDeletion(String descriptorName, String topologyName);
+
+  @Message( level = MessageLevel.DEBUG, text = "Added descriptor {0} reference to provider configuration {1}." )
+  void addedProviderConfigurationReference(String descriptorName, String providerConfigurationName);
+
+  @Message( level = MessageLevel.DEBUG, text = "Removed descriptor {0} reference to provider configuration {1}." )
+  void removedProviderConfigurationReference(String descriptorName, String providerConfigurationName);
 
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
index 0956a4a..4202a18 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.gateway.config.impl;
 
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
@@ -250,7 +251,7 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   @Override
   public String getGatewayConfDir() {
     String value = getVar( GATEWAY_CONF_HOME_VAR, getGatewayHomeDir() + File.separator + "conf"  );
-    return value;
+    return FilenameUtils.normalize(value);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
index 13e1a3d..39e8029 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
@@ -62,6 +62,7 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -91,8 +92,11 @@ public class DefaultTopologyService
   private static DigesterLoader digesterLoader = newLoader(new KnoxFormatXmlTopologyRules(), new AmbariFormatXmlTopologyRules());
   private List<FileAlterationMonitor> monitors = new ArrayList<>();
   private File topologiesDirectory;
+  private File sharedProvidersDirectory;
   private File descriptorsDirectory;
 
+  private DescriptorsMonitor descriptorsMonitor;
+
   private Set<TopologyListener> listeners;
   private volatile Map<File, Topology> topologies;
   private AliasService aliasService;
@@ -211,8 +215,7 @@ public class DefaultTopologyService
   }
 
   private File calculateAbsoluteTopologiesDir(GatewayConfig config) {
-    String normalizedTopologyDir = FilenameUtils.normalize(config.getGatewayTopologyDir());
-    File topoDir = new File(normalizedTopologyDir);
+    File topoDir = new File(config.getGatewayTopologyDir());
     topoDir = topoDir.getAbsoluteFile();
     return topoDir;
   }
@@ -220,15 +223,10 @@ public class DefaultTopologyService
   private File calculateAbsoluteConfigDir(GatewayConfig config) {
     File configDir = null;
 
-    String path = FilenameUtils.normalize(config.getGatewayConfDir());
-    if (path != null) {
-      configDir = new File(config.getGatewayConfDir());
-    } else {
-      configDir = (new File(config.getGatewayTopologyDir())).getParentFile();
-    }
-    configDir = configDir.getAbsoluteFile();
+    String path = config.getGatewayConfDir();
+    configDir = (path != null) ? new File(path) : (new File(config.getGatewayTopologyDir())).getParentFile();
 
-    return configDir;
+    return configDir.getAbsoluteFile();
   }
 
   private void  initListener(FileAlterationMonitor  monitor,
@@ -250,31 +248,34 @@ public class DefaultTopologyService
   private Map<File, Topology> loadTopologies(File directory) {
     Map<File, Topology> map = new HashMap<>();
     if (directory.isDirectory() && directory.canRead()) {
-      for (File file : directory.listFiles(this)) {
-        try {
-          Topology loadTopology = loadTopology(file);
-          if (null != loadTopology) {
-            map.put(file, loadTopology);
-          } else {
+      File[] existingTopologies = directory.listFiles(this);
+      if (existingTopologies != null) {
+        for (File file : existingTopologies) {
+          try {
+            Topology loadTopology = loadTopology(file);
+            if (null != loadTopology) {
+              map.put(file, loadTopology);
+            } else {
+              auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+                      ActionOutcome.FAILURE);
+              log.failedToLoadTopology(file.getAbsolutePath());
+            }
+          } catch (IOException e) {
+            // Maybe it makes sense to throw exception
             auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
-              ActionOutcome.FAILURE);
-            log.failedToLoadTopology(file.getAbsolutePath());
+                    ActionOutcome.FAILURE);
+            log.failedToLoadTopology(file.getAbsolutePath(), e);
+          } catch (SAXException e) {
+            // Maybe it makes sense to throw exception
+            auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+                    ActionOutcome.FAILURE);
+            log.failedToLoadTopology(file.getAbsolutePath(), e);
+          } catch (Exception e) {
+            // Maybe it makes sense to throw exception
+            auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+                    ActionOutcome.FAILURE);
+            log.failedToLoadTopology(file.getAbsolutePath(), e);
           }
-        } catch (IOException e) {
-          // Maybe it makes sense to throw exception
-          auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
-            ActionOutcome.FAILURE);
-          log.failedToLoadTopology(file.getAbsolutePath(), e);
-        } catch (SAXException e) {
-          // Maybe it makes sense to throw exception
-          auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
-            ActionOutcome.FAILURE);
-          log.failedToLoadTopology(file.getAbsolutePath(), e);
-        } catch (Exception e) {
-          // Maybe it makes sense to throw exception
-          auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
-            ActionOutcome.FAILURE);
-          log.failedToLoadTopology(file.getAbsolutePath(), e);
         }
       }
     }
@@ -356,8 +357,7 @@ public class DefaultTopologyService
     File topoDir = topologiesDirectory;
 
     if(topoDir.isDirectory() && topoDir.canRead()) {
-      File[] results = topoDir.listFiles();
-      for (File f : results) {
+      for (File f : listFiles(topoDir)) {
         String fName = FilenameUtils.getBaseName(f.getName());
         if(fName.equals(t.getName())) {
           f.delete();
@@ -381,9 +381,9 @@ public class DefaultTopologyService
   public Map<String, List<String>> getServiceTestURLs(Topology t, GatewayConfig config) {
     File tFile = null;
     Map<String, List<String>> urls = new HashMap<>();
-    if(topologiesDirectory.isDirectory() && topologiesDirectory.canRead()) {
-      for(File f : topologiesDirectory.listFiles()){
-        if(FilenameUtils.removeExtension(f.getName()).equals(t.getName())){
+    if (topologiesDirectory.isDirectory() && topologiesDirectory.canRead()) {
+      for (File f : listFiles(topologiesDirectory)) {
+        if (FilenameUtils.removeExtension(f.getName()).equals(t.getName())) {
           tFile = f;
         }
       }
@@ -405,6 +405,63 @@ public class DefaultTopologyService
   }
 
   @Override
+  public boolean deployProviderConfiguration(String name, String content) {
+    return writeConfig(sharedProvidersDirectory, name, content);
+  }
+
+  @Override
+  public Collection<File> getProviderConfigurations() {
+    List<File> providerConfigs = new ArrayList<>();
+    for (File providerConfig : listFiles(sharedProvidersDirectory)) {
+      if (SharedProviderConfigMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(providerConfig.getName()))) {
+        providerConfigs.add(providerConfig);
+      }
+    }
+    return providerConfigs;
+  }
+
+  @Override
+  public boolean deleteProviderConfiguration(String name) {
+    boolean result = false;
+
+    File providerConfig = getExistingFile(sharedProvidersDirectory, name);
+    if (providerConfig != null) {
+      List<String> references = descriptorsMonitor.getReferencingDescriptors(providerConfig.getAbsolutePath());
+      if (references.isEmpty()) {
+        result = providerConfig.delete();
+      } else {
+        log.preventedDeletionOfSharedProviderConfiguration(providerConfig.getAbsolutePath());
+      }
+    } else {
+      result = true; // If it already does NOT exist, then the delete effectively succeeded
+    }
+
+    return result;
+  }
+
+  @Override
+  public boolean deployDescriptor(String name, String content) {
+    return writeConfig(descriptorsDirectory, name, content);
+  }
+
+  @Override
+  public Collection<File> getDescriptors() {
+    List<File> descriptors = new ArrayList<>();
+    for (File descriptor : listFiles(descriptorsDirectory)) {
+      if (DescriptorsMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(descriptor.getName()))) {
+        descriptors.add(descriptor);
+      }
+    }
+    return descriptors;
+  }
+
+  @Override
+  public boolean deleteDescriptor(String name) {
+    File descriptor = getExistingFile(descriptorsDirectory, name);
+    return (descriptor == null) || descriptor.delete();
+  }
+
+  @Override
   public void addTopologyChangeListener(TopologyListener listener) {
     listeners.add(listener);
   }
@@ -448,6 +505,7 @@ public class DefaultTopologyService
       File simpleDesc =
               new File(descriptorsDirectory, FilenameUtils.getBaseName(file.getName()) + "." + ext);
       if (simpleDesc.exists()) {
+        log.deletingDescriptorForTopologyDeletion(simpleDesc.getName(), file.getName());
         simpleDesc.delete();
       }
     }
@@ -481,20 +539,22 @@ public class DefaultTopologyService
 
       File configDirectory = calculateAbsoluteConfigDir(config);
       descriptorsDirectory = new File(configDirectory, "descriptors");
-      File sharedProvidersDirectory = new File(configDirectory, "shared-providers");
+      sharedProvidersDirectory = new File(configDirectory, "shared-providers");
 
       // Add support for conf/topologies
       initListener(topologiesDirectory, this, this);
 
       // Add support for conf/descriptors
-      DescriptorsMonitor dm = new DescriptorsMonitor(topologiesDirectory, aliasService);
+      descriptorsMonitor = new DescriptorsMonitor(topologiesDirectory, aliasService);
       initListener(descriptorsDirectory,
-                   dm,
-                   dm);
+                   descriptorsMonitor,
+                   descriptorsMonitor);
+      log.monitoringDescriptorChangesInDirectory(descriptorsDirectory.getAbsolutePath());
 
       // Add support for conf/shared-providers
-      SharedProviderConfigMonitor spm = new SharedProviderConfigMonitor(dm, descriptorsDirectory);
+      SharedProviderConfigMonitor spm = new SharedProviderConfigMonitor(descriptorsMonitor, descriptorsDirectory);
       initListener(sharedProvidersDirectory, spm, spm);
+      log.monitoringProviderConfigChangesInDirectory(sharedProvidersDirectory.getAbsolutePath());
 
       // For all the descriptors currently in the descriptors dir at start-up time, trigger topology generation.
       // This happens prior to the start-up loading of the topologies.
@@ -502,7 +562,7 @@ public class DefaultTopologyService
       if (descriptorFilenames != null) {
           for (String descriptorFilename : descriptorFilenames) {
               if (DescriptorsMonitor.isDescriptorFile(descriptorFilename)) {
-                  dm.onFileChange(new File(descriptorsDirectory, descriptorFilename));
+                  descriptorsMonitor.onFileChange(new File(descriptorsDirectory, descriptorFilename));
               }
           }
       }
@@ -514,6 +574,70 @@ public class DefaultTopologyService
 
 
   /**
+   * Utility method for listing the files in the specified directory.
+   * This method is "nicer" than the File#listFiles() because it will not return null.
+   *
+   * @param directory The directory whose files should be returned.
+   *
+   * @return A List of the Files on the directory.
+   */
+  private static List<File> listFiles(File directory) {
+    List<File> result = null;
+    File[] files = directory.listFiles();
+    if (files != null) {
+      result = Arrays.asList(files);
+    } else {
+      result = Collections.emptyList();
+    }
+    return result;
+  }
+
+  /**
+   * Search for a file in the specified directory whose base name (filename without extension) matches the
+   * specified basename.
+   *
+   * @param directory The directory in which to search.
+   * @param basename  The basename of interest.
+   *
+   * @return The matching File
+   */
+  private static File getExistingFile(File directory, String basename) {
+    File match = null;
+    for (File file : listFiles(directory)) {
+      if (FilenameUtils.getBaseName(file.getName()).equals(basename)) {
+        match = file;
+        break;
+      }
+    }
+    return match;
+  }
+
+  /**
+   * Write the specified content to a file.
+   *
+   * @param dest    The destination directory.
+   * @param name    The name of the file.
+   * @param content The contents of the file.
+   *
+   * @return true, if the write succeeds; otherwise, false.
+   */
+  private static boolean writeConfig(File dest, String name, String content) {
+    boolean result = false;
+
+    File destFile = new File(dest, name);
+    try {
+      FileUtils.writeStringToFile(destFile, content);
+      log.wroteConfigurationFile(destFile.getAbsolutePath());
+      result = true;
+    } catch (IOException e) {
+      log.failedToWriteConfigurationFile(destFile.getAbsolutePath(), e);
+    }
+
+    return result;
+  }
+
+
+  /**
    * Change handler for simple descriptors
    */
   public static class DescriptorsMonitor extends FileAlterationListenerAdaptor
@@ -543,7 +667,7 @@ public class DefaultTopologyService
     }
 
     List<String> getReferencingDescriptors(String providerConfigPath) {
-      List<String> result = providerConfigReferences.get(providerConfigPath);
+      List<String> result = providerConfigReferences.get(FilenameUtils.normalize(providerConfigPath));
       if (result == null) {
         result = Collections.emptyList();
       }
@@ -562,6 +686,7 @@ public class DefaultTopologyService
         File topologyFile =
                 new File(topologiesDir, FilenameUtils.getBaseName(file.getName()) + "." + ext);
         if (topologyFile.exists()) {
+          log.deletingTopologyForDescriptorDeletion(topologyFile.getName(), file.getName());
           topologyFile.delete();
         }
       }
@@ -574,8 +699,10 @@ public class DefaultTopologyService
           break;
         }
       }
+
       if (reference != null) {
         providerConfigReferences.get(reference).remove(normalizedFilePath);
+        log.removedProviderConfigurationReference(normalizedFilePath, reference);
       }
     }
 
@@ -584,6 +711,7 @@ public class DefaultTopologyService
       try {
         // When a simple descriptor has been created or modified, generate the new topology descriptor
         Map<String, File> result = SimpleDescriptorHandler.handle(file, topologiesDir, aliasService);
+        log.generatedTopologyForDescriptorChange(result.get("topology").getName(), file.getName());
 
         // Add the provider config reference relationship for handling updates to the provider config
         String providerConfig = FilenameUtils.normalize(result.get("reference").getAbsolutePath());
@@ -602,6 +730,7 @@ public class DefaultTopologyService
 
           // Add the current reference relationship
           refs.add(descriptorName);
+          log.addedProviderConfigurationReference(descriptorName, providerConfig);
         }
       } catch (Exception e) {
         log.simpleDescriptorHandlingError(file.getName(), e);
@@ -662,7 +791,7 @@ public class DefaultTopologyService
     private List<File> getReferencingDescriptors(File sharedProviderConfig) {
       List<File> references = new ArrayList<>();
 
-      for (File descriptor : descriptorsDir.listFiles()) {
+      for (File descriptor : listFiles(descriptorsDir)) {
         if (DescriptorsMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(descriptor.getName()))) {
           for (String reference : descriptorsMonitor.getReferencingDescriptors(FilenameUtils.normalize(sharedProviderConfig.getAbsolutePath()))) {
             references.add(new File(reference));

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
index 498d750..2357ad6 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
@@ -18,16 +18,15 @@
 package org.apache.hadoop.gateway.services.topology;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.monitor.FileAlterationListener;
 import org.apache.commons.io.monitor.FileAlterationMonitor;
 import org.apache.commons.io.monitor.FileAlterationObserver;
 import org.apache.hadoop.gateway.config.GatewayConfig;
 import org.apache.hadoop.gateway.services.security.AliasService;
 import org.apache.hadoop.gateway.services.topology.impl.DefaultTopologyService;
 import org.apache.hadoop.gateway.topology.*;
-import org.apache.hadoop.gateway.topology.discovery.ServiceDiscovery;
-import org.apache.hadoop.gateway.topology.discovery.ServiceDiscoveryConfig;
-import org.apache.hadoop.gateway.topology.discovery.ServiceDiscoveryFactory;
 import org.apache.hadoop.test.TestUtils;
 import org.easymock.EasyMock;
 import org.junit.After;
@@ -38,13 +37,24 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.isA;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -86,12 +96,6 @@ public class DefaultTopologyServiceTest {
     File dir = createDir();
     File topologyDir = new File(dir, "topologies");
 
-    File descriptorsDir = new File(dir, "descriptors");
-    descriptorsDir.mkdirs();
-
-    File sharedProvidersDir = new File(dir, "shared-providers");
-    sharedProvidersDir.mkdirs();
-
     long time = topologyDir.lastModified();
     try {
       createFile(topologyDir, "one.xml", "org/apache/hadoop/gateway/topology/file/topology-one.xml", time);
@@ -104,7 +108,7 @@ public class DefaultTopologyServiceTest {
 
       GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
       EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
-      EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayConfDir()).andReturn(topologyDir.getParentFile().getAbsolutePath()).anyTimes();
       EasyMock.replay(config);
 
       provider.init(config, c);
@@ -167,59 +171,371 @@ public class DefaultTopologyServiceTest {
       assertThat(topology.getName(), is("one"));
       assertThat(topology.getTimestamp(), is(time));
 
+    } finally {
+      FileUtils.deleteQuietly(dir);
+    }
+  }
+
+  /**
+   * KNOX-1014
+   *
+   * Test the lifecycle relationship between simple descriptors and topology files.
+   *
+   * N.B. This test depends on the DummyServiceDiscovery extension being configured:
+   *        org.apache.hadoop.gateway.topology.discovery.test.extension.DummyServiceDiscovery
+   */
+  @Test
+  public void testSimpleDescriptorsTopologyGeneration() throws Exception {
+
+    File dir = createDir();
+    File topologyDir = new File(dir, "topologies");
+    topologyDir.mkdirs();
+
+    File descriptorsDir = new File(dir, "descriptors");
+    descriptorsDir.mkdirs();
+
+    File sharedProvidersDir = new File(dir, "shared-providers");
+    sharedProvidersDir.mkdirs();
+
+    try {
+      TestTopologyListener topoListener = new TestTopologyListener();
+      FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
+
+      TopologyService provider = new DefaultTopologyService();
+      Map<String, String> c = new HashMap<>();
+
+      GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+      EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.replay(config);
+
+      provider.init(config, c);
+      provider.addTopologyChangeListener(topoListener);
+      provider.reloadTopologies();
+
+
       // Add a simple descriptor to the descriptors dir to verify topology generation and loading (KNOX-1006)
-      // N.B. This part of the test depends on the DummyServiceDiscovery extension being configured:
-      //         org.apache.hadoop.gateway.topology.discovery.test.extension.DummyServiceDiscovery
       AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
       EasyMock.expect(aliasService.getPasswordFromAliasForGateway(anyObject(String.class))).andReturn(null).anyTimes();
       EasyMock.replay(aliasService);
       DefaultTopologyService.DescriptorsMonitor dm =
-                                          new DefaultTopologyService.DescriptorsMonitor(topologyDir, aliasService);
+              new DefaultTopologyService.DescriptorsMonitor(topologyDir, aliasService);
+
+      // Listener to simulate the topologies directory monitor, to notice when a topology has been deleted
+      provider.addTopologyChangeListener(new TestTopologyDeleteListener((DefaultTopologyService)provider));
 
       // Write out the referenced provider config first
       File provCfgFile = createFile(sharedProvidersDir,
                                     "ambari-cluster-policy.xml",
                                     "org/apache/hadoop/gateway/topology/file/ambari-cluster-policy.xml",
-                                    1L);
+                                    System.currentTimeMillis());
       try {
         // Create the simple descriptor in the descriptors dir
-        File simpleDesc =
-                createFile(descriptorsDir,
-                           "four.json",
-                           "org/apache/hadoop/gateway/topology/file/simple-topology-four.json",
-                           1L);
+        File simpleDesc = createFile(descriptorsDir,
+                                     "four.json",
+                                     "org/apache/hadoop/gateway/topology/file/simple-topology-four.json",
+                                     System.currentTimeMillis());
 
         // Trigger the topology generation by noticing the simple descriptor
         dm.onFileChange(simpleDesc);
 
         // Load the generated topology
         provider.reloadTopologies();
+        Collection<Topology> topologies = provider.getTopologies();
+        assertThat(topologies.size(), is(1));
+        Iterator<Topology> iterator = topologies.iterator();
+        Topology topology = iterator.next();
+        assertThat("four", is(topology.getName()));
+        int serviceCount = topology.getServices().size();
+        assertEquals("Expected the same number of services as are declared in the simple dscriptor.", 10, serviceCount);
+
+        // Overwrite the simple descriptor with a different set of services, and check that the changes are
+        // propagated to the associated topology
+        simpleDesc = createFile(descriptorsDir,
+                                "four.json",
+                                "org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json",
+                                System.currentTimeMillis());
+        dm.onFileChange(simpleDesc);
+        provider.reloadTopologies();
+        topologies = provider.getTopologies();
+        topology = topologies.iterator().next();
+        assertNotEquals(serviceCount, topology.getServices().size());
+        assertEquals(6, topology.getServices().size());
+
+        // Delete the simple descriptor, and make sure that the associated topology file is deleted
+        simpleDesc.delete();
+        dm.onFileDelete(simpleDesc);
+        provider.reloadTopologies();
+        topologies = provider.getTopologies();
+        assertTrue(topologies.isEmpty());
+
+        // Delete a topology file, and make sure that the associated simple descriptor is deleted
+        // Overwrite the simple descriptor with a different set of services, and check that the changes are
+        // propagated to the associated topology
+        simpleDesc = createFile(descriptorsDir,
+                                "deleteme.json",
+                                "org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json",
+                                System.currentTimeMillis());
+        dm.onFileChange(simpleDesc);
+        provider.reloadTopologies();
         topologies = provider.getTopologies();
-        assertThat(topologies.size(), is(2));
-        names = new HashSet<>(Arrays.asList("one", "four"));
-        iterator = topologies.iterator();
-        topology = iterator.next();
-        assertThat(names, hasItem(topology.getName()));
-        names.remove(topology.getName());
-        topology = iterator.next();
-        assertThat(names, hasItem(topology.getName()));
-        names.remove(topology.getName());
-        assertThat(names.size(), is(0));
+        assertFalse(topologies.isEmpty());
+        topology = topologies.iterator().next();
+        assertEquals("deleteme", topology.getName());
+        File topologyFile = new File(topologyDir, topology.getName() + ".xml");
+        assertTrue(topologyFile.exists());
+        topologyFile.delete();
+        provider.reloadTopologies();
+        assertFalse("Simple descriptor should have been deleted because the associated topology was.",
+                    simpleDesc.exists());
+
       } finally {
         provCfgFile.delete();
-
       }
     } finally {
       FileUtils.deleteQuietly(dir);
     }
   }
 
+  /**
+   * KNOX-1014
+   *
+   * Test the lifecycle relationship between provider configuration files, simple descriptors, and topology files.
+   *
+   * N.B. This test depends on the DummyServiceDiscovery extension being configured:
+   *        org.apache.hadoop.gateway.topology.discovery.test.extension.DummyServiceDiscovery
+   */
+  @Test
+  public void testTopologiesUpdateFromProviderConfigChange() throws Exception {
+    File dir = createDir();
+    File topologyDir = new File(dir, "topologies");
+    topologyDir.mkdirs();
+
+    File descriptorsDir = new File(dir, "descriptors");
+    descriptorsDir.mkdirs();
+
+    File sharedProvidersDir = new File(dir, "shared-providers");
+    sharedProvidersDir.mkdirs();
+
+    try {
+      TestTopologyListener topoListener = new TestTopologyListener();
+      FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
+
+      TopologyService ts = new DefaultTopologyService();
+      Map<String, String> c = new HashMap<>();
+
+      GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+      EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.replay(config);
+
+      ts.init(config, c);
+      ts.addTopologyChangeListener(topoListener);
+      ts.reloadTopologies();
+
+      java.lang.reflect.Field dmField = ts.getClass().getDeclaredField("descriptorsMonitor");
+      dmField.setAccessible(true);
+      DefaultTopologyService.DescriptorsMonitor dm = (DefaultTopologyService.DescriptorsMonitor) dmField.get(ts);
+
+      // Write out the referenced provider configs first
+      createFile(sharedProvidersDir,
+                 "provider-config-one.xml",
+                 "org/apache/hadoop/gateway/topology/file/provider-config-one.xml",
+                 System.currentTimeMillis());
+
+      // Create the simple descriptor, which depends on provider-config-one.xml
+      File simpleDesc = createFile(descriptorsDir,
+                                   "six.json",
+                                   "org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json",
+                                   System.currentTimeMillis());
+
+      // "Notice" the simple descriptor change, and generate a topology based on it
+      dm.onFileChange(simpleDesc);
+
+      // Load the generated topology
+      ts.reloadTopologies();
+      Collection<Topology> topologies = ts.getTopologies();
+      assertThat(topologies.size(), is(1));
+      Iterator<Topology> iterator = topologies.iterator();
+      Topology topology = iterator.next();
+      assertFalse("The Shiro provider is disabled in provider-config-one.xml",
+                  topology.getProvider("authentication", "ShiroProvider").isEnabled());
+
+      // Overwrite the referenced provider configuration with a different ShiroProvider config, and check that the
+      // changes are propagated to the associated topology
+      File providerConfig = createFile(sharedProvidersDir,
+                                       "provider-config-one.xml",
+                                       "org/apache/hadoop/gateway/topology/file/ambari-cluster-policy.xml",
+                                       System.currentTimeMillis());
+
+      // "Notice" the simple descriptor change as a result of the referenced config change
+      dm.onFileChange(simpleDesc);
+
+      // Load the generated topology
+      ts.reloadTopologies();
+      topologies = ts.getTopologies();
+      assertFalse(topologies.isEmpty());
+      topology = topologies.iterator().next();
+      assertTrue("The Shiro provider is enabled in ambari-cluster-policy.xml",
+              topology.getProvider("authentication", "ShiroProvider").isEnabled());
+
+      // Delete the provider configuration, and make sure that the associated topology file is unaffected.
+      // The topology file should not be affected because the simple descriptor handling will fail to resolve the
+      // referenced provider configuration.
+      providerConfig.delete();     // Delete the file
+      dm.onFileChange(simpleDesc); // The provider config deletion will trigger a descriptor change notification
+      ts.reloadTopologies();
+      topologies = ts.getTopologies();
+      assertFalse(topologies.isEmpty());
+      assertTrue("The Shiro provider is enabled in ambari-cluster-policy.xml",
+              topology.getProvider("authentication", "ShiroProvider").isEnabled());
+
+    } finally {
+      FileUtils.deleteQuietly(dir);
+    }
+  }
+
+  /**
+   * KNOX-1039
+   */
+  @Test
+  public void testConfigurationCRUDAPI() throws Exception {
+    File dir = createDir();
+    File topologyDir = new File(dir, "topologies");
+    topologyDir.mkdirs();
+
+    File descriptorsDir = new File(dir, "descriptors");
+    descriptorsDir.mkdirs();
+
+    File sharedProvidersDir = new File(dir, "shared-providers");
+    sharedProvidersDir.mkdirs();
+
+    try {
+      TestTopologyListener topoListener = new TestTopologyListener();
+      FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
+
+      TopologyService ts = new DefaultTopologyService();
+      Map<String, String> c = new HashMap<>();
+
+      GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+      EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.replay(config);
+
+      ts.init(config, c);
+      ts.addTopologyChangeListener(topoListener);
+      ts.reloadTopologies();
+
+      java.lang.reflect.Field dmField = ts.getClass().getDeclaredField("descriptorsMonitor");
+      dmField.setAccessible(true);
+      DefaultTopologyService.DescriptorsMonitor dm = (DefaultTopologyService.DescriptorsMonitor) dmField.get(ts);
+
+      final String simpleDescName  = "six.json";
+      final String provConfOne     = "provider-config-one.xml";
+      final String provConfTwo     = "ambari-cluster-policy.xml";
+
+      // "Deploy" the referenced provider configs first
+      boolean isDeployed =
+        ts.deployProviderConfiguration(provConfOne,
+                FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/provider-config-one.xml").toURI())));
+      assertTrue(isDeployed);
+      File provConfOneFile = new File(sharedProvidersDir, provConfOne);
+      assertTrue(provConfOneFile.exists());
+
+      isDeployed =
+        ts.deployProviderConfiguration(provConfTwo,
+                FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/ambari-cluster-policy.xml").toURI())));
+      assertTrue(isDeployed);
+      File provConfTwoFile = new File(sharedProvidersDir, provConfTwo);
+      assertTrue(provConfTwoFile.exists());
+
+      // Validate the provider configurations known by the topology service
+      Collection<File> providerConfigurations = ts.getProviderConfigurations();
+      assertNotNull(providerConfigurations);
+      assertEquals(2, providerConfigurations.size());
+      assertTrue(providerConfigurations.contains(provConfOneFile));
+      assertTrue(providerConfigurations.contains(provConfTwoFile));
+
+      // "Deploy" the simple descriptor, which depends on provConfOne
+      isDeployed =
+        ts.deployDescriptor(simpleDescName,
+            FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json").toURI())));
+      assertTrue(isDeployed);
+      File simpleDesc = new File(descriptorsDir, simpleDescName);
+      assertTrue(simpleDesc.exists());
+
+      // Validate the simple descriptors known by the topology service
+      Collection<File> descriptors = ts.getDescriptors();
+      assertNotNull(descriptors);
+      assertEquals(1, descriptors.size());
+      assertTrue(descriptors.contains(simpleDesc));
+
+      // "Notice" the simple descriptor, so the provider configuration dependency relationship is recorded
+      dm.onFileChange(simpleDesc);
+
+      // Attempt to delete the referenced provConfOne
+      assertFalse("Should not be able to delete a provider configuration that is referenced by one or more descriptors",
+                  ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfOne)));
+
+      // Overwrite the simple descriptor with content that changes the provider config reference to provConfTwo
+      isDeployed =
+        ts.deployDescriptor(simpleDescName,
+              FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json").toURI())));
+      assertTrue(isDeployed);
+      assertTrue(simpleDesc.exists());
+      ts.getProviderConfigurations();
+
+      // "Notice" the simple descriptor, so the provider configuration dependency relationship is updated
+      dm.onFileChange(simpleDesc);
+
+      // Attempt to delete the referenced provConfOne
+      assertTrue("Should be able to delete the provider configuration, now that it's not referenced by any descriptors",
+                 ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfOne)));
+
+      // Re-validate the provider configurations known by the topology service
+      providerConfigurations = ts.getProviderConfigurations();
+      assertNotNull(providerConfigurations);
+      assertEquals(1, providerConfigurations.size());
+      assertFalse(providerConfigurations.contains(provConfOneFile));
+      assertTrue(providerConfigurations.contains(provConfTwoFile));
+
+      // Attempt to delete the referenced provConfTwo
+      assertFalse("Should not be able to delete a provider configuration that is referenced by one or more descriptors",
+                  ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfTwo)));
+
+      // Delete the referencing simple descriptor
+      assertTrue(ts.deleteDescriptor(FilenameUtils.getBaseName(simpleDescName)));
+      assertFalse(simpleDesc.exists());
+
+      // Re-validate the simple descriptors known by the topology service
+      descriptors = ts.getDescriptors();
+      assertNotNull(descriptors);
+      assertTrue(descriptors.isEmpty());
+
+      // "Notice" the simple descriptor, so the provider configuration dependency relationship is updated
+      dm.onFileDelete(simpleDesc);
+
+      // Attempt to delete the referenced provConfTwo
+      assertTrue("Should be able to delete the provider configuration, now that it's not referenced by any descriptors",
+                 ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfTwo)));
+
+      // Re-validate the provider configurations known by the topology service
+      providerConfigurations = ts.getProviderConfigurations();
+      assertNotNull(providerConfigurations);
+      assertTrue(providerConfigurations.isEmpty());
+
+    } finally {
+      FileUtils.deleteQuietly(dir);
+    }
+  }
+
   private void kickMonitor(FileAlterationMonitor monitor) {
     for (FileAlterationObserver observer : monitor.getObservers()) {
       observer.checkAndNotify();
     }
   }
 
+
   @Test
   public void testProviderParamsOrderIsPreserved() {
 
@@ -252,7 +568,7 @@ public class DefaultTopologyServiceTest {
 
   private class TestTopologyListener implements TopologyListener {
 
-    public ArrayList<List<TopologyEvent>> events = new ArrayList<List<TopologyEvent>>();
+    ArrayList<List<TopologyEvent>> events = new ArrayList<List<TopologyEvent>>();
 
     @Override
     public void handleTopologyEvent(List<TopologyEvent> events) {
@@ -261,4 +577,24 @@ public class DefaultTopologyServiceTest {
 
   }
 
+
+  private class TestTopologyDeleteListener implements TopologyListener {
+
+    FileAlterationListener delegate;
+
+    TestTopologyDeleteListener(FileAlterationListener delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void handleTopologyEvent(List<TopologyEvent> events) {
+      for (TopologyEvent event : events) {
+        if (event.getType().equals(TopologyEvent.Type.DELETED)) {
+          delegate.onFileDelete(new File(event.getTopology().getUri()));
+        }
+      }
+    }
+
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/provider-config-one.xml
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/provider-config-one.xml b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/provider-config-one.xml
new file mode 100644
index 0000000..95465a4
--- /dev/null
+++ b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/provider-config-one.xml
@@ -0,0 +1,74 @@
+<gateway>
+    <provider>
+        <role>authentication</role>
+        <name>ShiroProvider</name>
+        <enabled>false</enabled>
+        <param>
+            <!--
+            session timeout in minutes,  this is really idle timeout,
+            defaults to 30mins, if the property value is not defined,,
+            current client authentication would expire if client idles contiuosly for more than this value
+            -->
+            <name>sessionTimeout</name>
+            <value>30</value>
+        </param>
+        <param>
+            <name>main.ldapRealm</name>
+            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm</value>
+        </param>
+        <param>
+            <name>main.ldapContextFactory</name>
+            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory</value>
+        </param>
+        <param>
+            <name>main.ldapRealm.contextFactory</name>
+            <value>$ldapContextFactory</value>
+        </param>
+        <param>
+            <name>main.ldapRealm.userDnTemplate</name>
+            <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>
+        </param>
+        <param>
+            <name>main.ldapRealm.contextFactory.url</name>
+            <value>ldap://localhost:33389</value>
+        </param>
+        <param>
+            <name>main.ldapRealm.contextFactory.authenticationMechanism</name>
+            <value>simple</value>
+        </param>
+        <param>
+            <name>urls./**</name>
+            <value>authcBasic</value>
+        </param>
+    </provider>
+
+    <provider>
+        <role>identity-assertion</role>
+        <name>Default</name>
+        <enabled>true</enabled>
+    </provider>
+
+    <!--
+    Defines rules for mapping host names internal to a Hadoop cluster to externally accessible host names.
+    For example, a hadoop service running in AWS may return a response that includes URLs containing the
+    some AWS internal host name.  If the client needs to make a subsequent request to the host identified
+    in those URLs they need to be mapped to external host names that the client Knox can use to connect.
+
+    If the external hostname and internal host names are same turn of this provider by setting the value of
+    enabled parameter as false.
+
+    The name parameter specifies the external host names in a comma separated list.
+    The value parameter specifies corresponding internal host names in a comma separated list.
+
+    Note that when you are using Sandbox, the external hostname needs to be localhost, as seen in out
+    of box sandbox.xml.  This is because Sandbox uses port mapping to allow clients to connect to the
+    Hadoop services using localhost.  In real clusters, external host names would almost never be localhost.
+    -->
+    <provider>
+        <role>hostmap</role>
+        <name>static</name>
+        <enabled>true</enabled>
+        <param><name>localhost</name><value>sandbox,sandbox.hortonworks.com</value></param>
+    </provider>
+
+</gateway>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json
new file mode 100644
index 0000000..52cec35
--- /dev/null
+++ b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json
@@ -0,0 +1,14 @@
+{
+  "discovery-type":"DUMMY",
+  "discovery-address":"http://c6401.ambari.apache.org:8080",
+  "provider-config-ref":"../shared-providers/ambari-cluster-policy.xml",
+  "cluster":"dummy",
+  "services":[
+    {"name":"NAMENODE"},
+    {"name":"JOBTRACKER"},
+    {"name":"WEBHDFS"},
+    {"name":"OOZIE"},
+    {"name":"HIVE"},
+    {"name":"RESOURCEMANAGER"}
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json
new file mode 100644
index 0000000..e78f193
--- /dev/null
+++ b/gateway-server/src/test/resources/org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json
@@ -0,0 +1,18 @@
+{
+  "discovery-type":"DUMMY",
+  "discovery-address":"http://c6401.ambari.apache.org:8080",
+  "provider-config-ref":"../shared-providers/provider-config-one.xml",
+  "cluster":"dummy",
+  "services":[
+    {"name":"NAMENODE"},
+    {"name":"JOBTRACKER"},
+    {"name":"WEBHDFS"},
+    {"name":"WEBHCAT"},
+    {"name":"OOZIE"},
+    {"name":"WEBHBASE"},
+    {"name":"HIVE"},
+    {"name":"RESOURCEMANAGER"},
+    {"name":"AMBARI", "urls":["http://c6401.ambari.apache.org:8080"]},
+    {"name":"AMBARIUI", "urls":["http://c6401.ambari.apache.org:8080"]}
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/HrefListingMarshaller.java
----------------------------------------------------------------------
diff --git a/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/HrefListingMarshaller.java b/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/HrefListingMarshaller.java
new file mode 100644
index 0000000..c251213
--- /dev/null
+++ b/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/HrefListingMarshaller.java
@@ -0,0 +1,75 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.service.admin;
+
+import org.eclipse.persistence.jaxb.JAXBContextProperties;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+@Provider
+@Produces({MediaType.APPLICATION_JSON})
+public class HrefListingMarshaller implements MessageBodyWriter<TopologiesResource.HrefListing> {
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return (TopologiesResource.HrefListing.class == type);
+    }
+
+    @Override
+    public long getSize(TopologiesResource.HrefListing instance,
+                        Class<?> type,
+                        Type genericType,
+                        Annotation[] annotations,
+                        MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(TopologiesResource.HrefListing instance,
+                        Class<?> type,
+                        Type genericType,
+                        Annotation[] annotations,
+                        MediaType mediaType,
+                        MultivaluedMap<String, Object> httpHeaders,
+                        OutputStream entityStream) throws IOException, WebApplicationException {
+        try {
+            Map<String, Object> properties = new HashMap<>(1);
+            properties.put( JAXBContextProperties.MEDIA_TYPE, mediaType.toString());
+            JAXBContext context = JAXBContext.newInstance(new Class[]{TopologiesResource.HrefListing.class}, properties);
+            Marshaller m = context.createMarshaller();
+            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            m.marshal(instance, entityStream);
+        } catch (JAXBException e) {
+            throw new IOException(e);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/TopologiesResource.java
----------------------------------------------------------------------
diff --git a/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/TopologiesResource.java b/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/TopologiesResource.java
index 1504eca..28573bf 100644
--- a/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/TopologiesResource.java
+++ b/gateway-service-admin/src/main/java/org/apache/hadoop/gateway/service/admin/TopologiesResource.java
@@ -17,6 +17,11 @@
  */
 package org.apache.hadoop.gateway.service.admin;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.hadoop.gateway.i18n.GatewaySpiMessages;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
 import org.apache.hadoop.gateway.service.admin.beans.BeanConverter;
 import org.apache.hadoop.gateway.service.admin.beans.Topology;
 import org.apache.hadoop.gateway.services.GatewayServices;
@@ -37,25 +42,47 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
+import java.io.File;
+import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+
 import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.notModified;
+import static javax.ws.rs.core.Response.status;
+
 
 @Path("/api/v1")
 public class TopologiesResource {
+
+  private static final String XML_EXT  = ".xml";
+  private static final String JSON_EXT = ".json";
+
+  private static final String TOPOLOGIES_API_PATH    = "topologies";
+  private static final String SINGLE_TOPOLOGY_API_PATH = TOPOLOGIES_API_PATH + "/{id}";
+  private static final String PROVIDERCONFIG_API_PATH = "providerconfig";
+  private static final String SINGLE_PROVIDERCONFIG_API_PATH = PROVIDERCONFIG_API_PATH + "/{name}";
+  private static final String DESCRIPTORS_API_PATH    = "descriptors";
+  private static final String SINGLE_DESCRIPTOR_API_PATH = DESCRIPTORS_API_PATH + "/{name}";
+
+  private static GatewaySpiMessages log = MessagesFactory.get(GatewaySpiMessages.class);
+
   @Context
   private HttpServletRequest request;
 
   @GET
   @Produces({APPLICATION_JSON, APPLICATION_XML})
-  @Path("topologies/{id}")
+  @Path(SINGLE_TOPOLOGY_API_PATH)
   public Topology getTopology(@PathParam("id") String id) {
     GatewayServices services = (GatewayServices) request.getServletContext()
         .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
@@ -78,7 +105,7 @@ public class TopologiesResource {
 
   @GET
   @Produces({APPLICATION_JSON, APPLICATION_XML})
-  @Path("topologies")
+  @Path(TOPOLOGIES_API_PATH)
   public SimpleTopologyWrapper getTopologies() {
     GatewayServices services = (GatewayServices) request.getServletContext()
         .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
@@ -106,7 +133,7 @@ public class TopologiesResource {
 
   @PUT
   @Consumes({APPLICATION_JSON, APPLICATION_XML})
-  @Path("topologies/{id}")
+  @Path(SINGLE_TOPOLOGY_API_PATH)
   public Topology uploadTopology(@PathParam("id") String id, Topology t) {
 
     GatewayServices gs = (GatewayServices) request.getServletContext()
@@ -122,7 +149,7 @@ public class TopologiesResource {
 
   @DELETE
   @Produces(APPLICATION_JSON)
-  @Path("topologies/{id}")
+  @Path(SINGLE_TOPOLOGY_API_PATH)
   public Response deleteTopology(@PathParam("id") String id) {
     boolean deleted = false;
     if(!"admin".equals(id)) {
@@ -143,6 +170,244 @@ public class TopologiesResource {
     return ok().entity("{ \"deleted\" : " + deleted + " }").build();
   }
 
+  @GET
+  @Produces({APPLICATION_JSON})
+  @Path(PROVIDERCONFIG_API_PATH)
+  public HrefListing getProviderConfigurations() {
+    HrefListing listing = new HrefListing();
+    listing.setHref(buildHref(request));
+
+    GatewayServices services =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    List<HrefListItem> configs = new ArrayList<>();
+    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+    // Get all the simple descriptor file names
+    for (File providerConfig : ts.getProviderConfigurations()){
+      String id = FilenameUtils.getBaseName(providerConfig.getName());
+      configs.add(new HrefListItem(buildHref(id, request), providerConfig.getName()));
+    }
+
+    listing.setItems(configs);
+    return listing;
+  }
+
+  @GET
+  @Produces({APPLICATION_XML})
+  @Path(SINGLE_PROVIDERCONFIG_API_PATH)
+  public Response getProviderConfiguration(@PathParam("name") String name) {
+    Response response;
+
+    GatewayServices services =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+
+    File providerConfigFile = null;
+
+    for (File pc : ts.getProviderConfigurations()){
+      // If the file name matches the specified id
+      if (FilenameUtils.getBaseName(pc.getName()).equals(name)) {
+        providerConfigFile = pc;
+        break;
+      }
+    }
+
+    if (providerConfigFile != null) {
+      byte[] content = null;
+      try {
+        content = FileUtils.readFileToByteArray(providerConfigFile);
+        response = ok().entity(content).build();
+      } catch (IOException e) {
+        log.failedToReadConfigurationFile(providerConfigFile.getAbsolutePath(), e);
+        response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+      }
+
+    } else {
+      response = Response.status(Response.Status.NOT_FOUND).build();
+    }
+    return response;
+  }
+
+  @DELETE
+  @Produces(APPLICATION_JSON)
+  @Path(SINGLE_PROVIDERCONFIG_API_PATH)
+  public Response deleteProviderConfiguration(@PathParam("name") String name) {
+    Response response;
+    GatewayServices services =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+    if (ts.deleteProviderConfiguration(name)) {
+      response = ok().entity("{ \"deleted\" : \"provider config " + name + "\" }").build();
+    } else {
+      response = notModified().build();
+    }
+    return response;
+  }
+
+
+  @DELETE
+  @Produces(APPLICATION_JSON)
+  @Path(SINGLE_DESCRIPTOR_API_PATH)
+  public Response deleteSimpleDescriptor(@PathParam("name") String name) {
+    Response response = null;
+    if(!"admin".equals(name)) {
+      GatewayServices services =
+              (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+      TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+      if (ts.deleteDescriptor(name)) {
+        response = ok().entity("{ \"deleted\" : \"descriptor " + name + "\" }").build();
+      }
+    }
+
+    if (response == null) {
+      response = notModified().build();
+    }
+
+    return response;
+  }
+
+
+  @PUT
+  @Consumes({APPLICATION_XML})
+  @Path(SINGLE_PROVIDERCONFIG_API_PATH)
+  public Response uploadProviderConfiguration(@PathParam("name") String name, String content) {
+    Response response = null;
+
+    GatewayServices gs =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);
+
+    boolean isUpdate = configFileExists(ts.getProviderConfigurations(), name);
+
+    String filename = name.endsWith(XML_EXT) ? name : name + XML_EXT;
+    if (ts.deployProviderConfiguration(filename, content)) {
+      try {
+        if (isUpdate) {
+          response = Response.noContent().build();
+        } else{
+          response = created(new URI(buildHref(request))).build();
+        }
+      } catch (URISyntaxException e) {
+        log.invalidResourceURI(e.getInput(), e.getReason(), e);
+        response = status(Response.Status.BAD_REQUEST).entity("{ \"error\" : \"Failed to deploy provider configuration " + name + "\" }").build();
+      }
+    }
+
+    return response;
+  }
+
+
+  private boolean configFileExists(Collection<File> existing, String candidateName) {
+    boolean result = false;
+    for (File exists : existing) {
+      if (FilenameUtils.getBaseName(exists.getName()).equals(candidateName)) {
+        result = true;
+        break;
+      }
+    }
+    return result;
+  }
+
+
+  @PUT
+  @Consumes({APPLICATION_JSON})
+  @Path(SINGLE_DESCRIPTOR_API_PATH)
+  public Response uploadSimpleDescriptor(@PathParam("name") String name, String content) {
+    Response response = null;
+
+    GatewayServices gs =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);
+
+    boolean isUpdate = configFileExists(ts.getDescriptors(), name);
+
+    String filename = name.endsWith(JSON_EXT) ? name : name + JSON_EXT;
+    if (ts.deployDescriptor(filename, content)) {
+      try {
+        if (isUpdate) {
+          response = Response.noContent().build();
+        } else {
+          response = created(new URI(buildHref(request))).build();
+        }
+      } catch (URISyntaxException e) {
+        log.invalidResourceURI(e.getInput(), e.getReason(), e);
+        response = status(Response.Status.BAD_REQUEST).entity("{ \"error\" : \"Failed to deploy descriptor " + name + "\" }").build();
+      }
+    }
+
+    return response;
+  }
+
+
+  @GET
+  @Produces({APPLICATION_JSON})
+  @Path(DESCRIPTORS_API_PATH)
+  public HrefListing getSimpleDescriptors() {
+    HrefListing listing = new HrefListing();
+    listing.setHref(buildHref(request));
+
+    GatewayServices services =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    List<HrefListItem> descriptors = new ArrayList<>();
+    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+    for (File descriptor : ts.getDescriptors()){
+      String id = FilenameUtils.getBaseName(descriptor.getName());
+      descriptors.add(new HrefListItem(buildHref(id, request), descriptor.getName()));
+    }
+
+    listing.setItems(descriptors);
+    return listing;
+  }
+
+
+  @GET
+  @Produces({APPLICATION_JSON, TEXT_PLAIN})
+  @Path(SINGLE_DESCRIPTOR_API_PATH)
+  public Response getSimpleDescriptor(@PathParam("name") String name) {
+    Response response;
+
+    GatewayServices services =
+            (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+
+    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
+
+    File descriptorFile = null;
+
+    for (File sd : ts.getDescriptors()){
+      // If the file name matches the specified id
+      if (FilenameUtils.getBaseName(sd.getName()).equals(name)) {
+        descriptorFile = sd;
+        break;
+      }
+    }
+
+    if (descriptorFile != null) {
+      String mediaType = APPLICATION_JSON;
+
+      byte[] content = null;
+      try {
+        if ("yml".equals(FilenameUtils.getExtension(descriptorFile.getName()))) {
+          mediaType = TEXT_PLAIN;
+        }
+        content = FileUtils.readFileToByteArray(descriptorFile);
+        response = ok().type(mediaType).entity(content).build();
+      } catch (IOException e) {
+        log.failedToReadConfigurationFile(descriptorFile.getAbsolutePath(), e);
+        response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+      }
+    } else {
+      response = Response.status(Response.Status.NOT_FOUND).build();
+    }
+
+    return response;
+  }
+
 
   private static class TopologyComparator implements Comparator<SimpleTopology> {
     @Override
@@ -151,13 +416,14 @@ public class TopologiesResource {
     }
   }
 
-   String buildURI(org.apache.hadoop.gateway.topology.Topology topology, GatewayConfig config, HttpServletRequest req){
+
+  String buildURI(org.apache.hadoop.gateway.topology.Topology topology, GatewayConfig config, HttpServletRequest req){
     String uri = buildXForwardBaseURL(req);
 
-//    Strip extra context
+    // Strip extra context
     uri = uri.replace(req.getContextPath(), "");
 
-//    Add the gateway path
+    // Add the gateway path
     String gatewayPath;
     if(config.getGatewayPath() != null){
       gatewayPath = config.getGatewayPath();
@@ -170,20 +436,31 @@ public class TopologiesResource {
     return uri;
   }
 
-   String buildHref(org.apache.hadoop.gateway.topology.Topology t, HttpServletRequest req) {
+  String buildHref(HttpServletRequest req) {
+    return buildHref((String)null, req);
+  }
+
+  String buildHref(String id, HttpServletRequest req) {
     String href = buildXForwardBaseURL(req);
-//    Make sure that the pathInfo doesn't have any '/' chars at the end.
+    // Make sure that the pathInfo doesn't have any '/' chars at the end.
     String pathInfo = req.getPathInfo();
-    if(pathInfo.endsWith("/")) {
-      while(pathInfo.endsWith("/")) {
-        pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
-      }
+    while(pathInfo.endsWith("/")) {
+      pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
+    }
+
+    href += pathInfo;
+
+    if (id != null) {
+      href += "/" + id;
     }
 
-    href += pathInfo + "/" + t.getName();
     return href;
   }
 
+   String buildHref(org.apache.hadoop.gateway.topology.Topology t, HttpServletRequest req) {
+     return buildHref(t.getName(), req);
+  }
+
   private SimpleTopology getSimpleTopology(org.apache.hadoop.gateway.topology.Topology t, GatewayConfig config) {
     String uri = buildURI(t, config, request);
     String href = buildHref(t, request);
@@ -200,34 +477,34 @@ public class TopologiesResource {
 
     String baseURL = "";
 
-//    Get Protocol
+    // Get Protocol
     if(req.getHeader(X_Forwarded_Proto) != null){
       baseURL += req.getHeader(X_Forwarded_Proto) + "://";
     } else {
       baseURL += req.getProtocol() + "://";
     }
 
-//    Handle Server/Host and Port Here
+    // Handle Server/Host and Port Here
     if (req.getHeader(X_Forwarded_Host) != null && req.getHeader(X_Forwarded_Port) != null){
-//        Double check to see if host has port
+      // Double check to see if host has port
       if(req.getHeader(X_Forwarded_Host).contains(req.getHeader(X_Forwarded_Port))){
         baseURL += req.getHeader(X_Forwarded_Host);
       } else {
-//        If there's no port, add the host and port together;
+        // If there's no port, add the host and port together;
         baseURL += req.getHeader(X_Forwarded_Host) + ":" + req.getHeader(X_Forwarded_Port);
       }
     } else if(req.getHeader(X_Forwarded_Server) != null && req.getHeader(X_Forwarded_Port) != null){
-//      Tack on the server and port if they're available. Try host if server not available
+      // Tack on the server and port if they're available. Try host if server not available
       baseURL += req.getHeader(X_Forwarded_Server) + ":" + req.getHeader(X_Forwarded_Port);
     } else if(req.getHeader(X_Forwarded_Port) != null) {
-//      if we at least have a port, we can use it.
+      // if we at least have a port, we can use it.
       baseURL += req.getServerName() + ":" + req.getHeader(X_Forwarded_Port);
     } else {
-//      Resort to request members
+      // Resort to request members
       baseURL += req.getServerName() + ":" + req.getLocalPort();
     }
 
-//    Handle Server context
+    // Handle Server context
     if( req.getHeader(X_Forwarded_Context) != null ) {
       baseURL += req.getHeader( X_Forwarded_Context );
     } else {
@@ -237,6 +514,64 @@ public class TopologiesResource {
     return baseURL;
   }
 
+
+  static class HrefListing {
+    @JsonProperty
+    String href;
+
+    @JsonProperty
+    List<HrefListItem> items;
+
+    HrefListing() {}
+
+    public void setHref(String href) {
+      this.href = href;
+    }
+
+    public String getHref() {
+      return href;
+    }
+
+    public void setItems(List<HrefListItem> items) {
+      this.items = items;
+    }
+
+    public List<HrefListItem> getItems() {
+      return items;
+    }
+  }
+
+  static class HrefListItem {
+    @JsonProperty
+    String href;
+
+    @JsonProperty
+    String name;
+
+    HrefListItem() {}
+
+    HrefListItem(String href, String name) {
+      this.href = href;
+      this.name = name;
+    }
+
+    public void setHref(String href) {
+      this.href = href;
+    }
+
+    public String getHref() {
+      return href;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+    public String getName() {
+      return name;
+    }
+  }
+
+
   @XmlAccessorType(XmlAccessType.NONE)
   public static class SimpleTopology {
 

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-spi/src/main/java/org/apache/hadoop/gateway/i18n/GatewaySpiMessages.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/i18n/GatewaySpiMessages.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/i18n/GatewaySpiMessages.java
index 45fcb54..aad4d8a 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/i18n/GatewaySpiMessages.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/i18n/GatewaySpiMessages.java
@@ -79,7 +79,13 @@ public interface GatewaySpiMessages {
   @Message( level = MessageLevel.ERROR, text = "Gateway has failed to start. Unable to prompt user for master secret setup. Please consider using knoxcli.sh create-master" )
   void unableToPromptForMasterUseKnoxCLI();
 
- @Message( level = MessageLevel.ERROR, text = "Error in generating certificate: {0}" )
- void failedToGenerateCertificate( @StackTrace( level = MessageLevel.ERROR ) Exception e );
+  @Message( level = MessageLevel.ERROR, text = "Error in generating certificate: {0}" )
+  void failedToGenerateCertificate( @StackTrace( level = MessageLevel.ERROR ) Exception e );
+
+  @Message(level = MessageLevel.ERROR, text = "Failed to read configuration: {0}")
+  void failedToReadConfigurationFile(final String filePath, @StackTrace(level = MessageLevel.DEBUG) Exception e );
+
+  @Message(level = MessageLevel.ERROR, text = "Invalid resource URI {0} : {1}")
+  void invalidResourceURI(final String uri, final String reason, @StackTrace(level = MessageLevel.DEBUG) Exception e );
 
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/9ad9bcdb/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/topology/TopologyService.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/topology/TopologyService.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/topology/TopologyService.java
index a964f38..017b3ec 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/topology/TopologyService.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/topology/TopologyService.java
@@ -22,6 +22,7 @@ import org.apache.hadoop.gateway.services.Service;
 import org.apache.hadoop.gateway.topology.Topology;
 import org.apache.hadoop.gateway.topology.TopologyListener;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -29,22 +30,34 @@ import java.util.Map;
 
 public interface TopologyService extends Service {
 
-  public void reloadTopologies();
+  void reloadTopologies();
 
-  public void deployTopology(Topology t);
+  void deployTopology(Topology t);
 
-  public void redeployTopologies(String topologyName);
+  void redeployTopologies(String topologyName);
 
-  public void addTopologyChangeListener(TopologyListener listener);
+  void addTopologyChangeListener(TopologyListener listener);
 
-  public void startMonitor() throws Exception;
+  void startMonitor() throws Exception;
 
-  public void stopMonitor() throws Exception;
+  void stopMonitor() throws Exception;
 
-  public Collection<Topology> getTopologies();
+  Collection<Topology> getTopologies();
 
-  public void deleteTopology(Topology t);
+  boolean deployProviderConfiguration(String name, String content);
 
-  public Map<String, List<String>> getServiceTestURLs(Topology t, GatewayConfig config);
+  Collection<File> getProviderConfigurations();
 
-  }
+  boolean deployDescriptor(String name, String content);
+
+  Collection<File> getDescriptors();
+
+  void deleteTopology(Topology t);
+
+  boolean deleteDescriptor(String name);
+
+  boolean deleteProviderConfiguration(String name);
+
+  Map<String, List<String>> getServiceTestURLs(Topology t, GatewayConfig config);
+
+}