浏览代码

AMBARI-8247. Expose stack and service kerberos descriptors via REST API
* Note, this patch doesn't expose new resources for these descriptors
but returns them as new properties in the existing stack and service
resources. This is temporary and these properties will be removed
shortly, before the 2.0 release, and replaced with proper sub-resources

Robert Levas 10 年之前
父节点
当前提交
b62846d25f
共有 16 个文件被更改,包括 689 次插入4 次删除
  1. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
  2. 29 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java
  3. 62 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/StackVersionResponse.java
  4. 34 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java
  5. 63 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackVersionResourceProvider.java
  6. 19 0
      ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
  7. 4 0
      ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
  8. 20 0
      ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java
  9. 1 0
      ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java
  10. 18 1
      ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
  11. 43 1
      ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
  12. 2 0
      ambari-server/src/main/resources/properties.json
  13. 58 0
      ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
  14. 38 0
      ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java
  15. 147 0
      ambari-server/src/test/resources/stacks/HDP/2.0.8/services/HDFS/kerberos.json
  16. 146 0
      ambari-server/src/test/resources/stacks/HDP/2.1.1/services/HDFS/metainfo.xml

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java

@@ -90,6 +90,11 @@ public class AmbariMetaInfo {
   public static final String SERVICE_METRIC_FILE_NAME = "metrics.json";
   public static final String SERVICE_ALERT_FILE_NAME = "alerts.json";
 
+  /**
+   * The filename name for a Kerberos descriptor file at either the stack or service level
+   */
+  public static final String KERBEROS_DESCRIPTOR_FILE_NAME = "kerberos.json";
+
   /**
    * This string is used in placeholder in places that are common for
    * all operating systems or in situations where os type is not important.

+ 29 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.controller;
 import org.apache.ambari.server.state.CustomCommandDefinition;
 import org.apache.ambari.server.state.ServiceInfo;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -41,6 +42,13 @@ public class StackServiceResponse {
   private Map<String, Map<String, Map<String, String>>> configTypes;
   private List<String> requiredServices;
 
+  /**
+   * A File pointing to the service-level Kerberos descriptor file
+   *
+   * This may be null if a relevant file is not available.
+   */
+  private File kerberosDescriptorFile;
+
   /**
    * Constructor.
    *
@@ -67,6 +75,8 @@ public class StackServiceResponse {
         customCommands.add(command.getName());
       }
     }
+
+    kerberosDescriptorFile = service.getKerberosDescriptorFile();
   }
 
   public String getStackName() {
@@ -137,6 +147,25 @@ public class StackServiceResponse {
     this.requiredServices = requiredServices;
   }
 
+  /**
+   * Gets a File pointing to the service-level Kerberos descriptor
+   *
+   * @return a File pointing to the service-level Kerberos descriptor, or null if no relevant file is
+   * available
+   */
+  public File getKerberosDescriptorFile() {
+    return kerberosDescriptorFile;
+  }
+
+  /**
+   * Sets the service-level Kerberos descriptor File
+   *
+   * @param kerberosDescriptorFile a File pointing to the service-level Kerberos descriptor
+   */
+  public void setKerberosDescriptorFile(File kerberosDescriptorFile) {
+    this.kerberosDescriptorFile = kerberosDescriptorFile;
+  }
+
   /**
    * Gets whether the service represented by this response supports running
    * "Service Checks". A service check is possible where there is a custom

+ 62 - 2
ambari-server/src/main/java/org/apache/ambari/server/controller/StackVersionResponse.java

@@ -18,6 +18,8 @@
 
 package org.apache.ambari.server.controller;
 
+import java.io.File;
+import java.util.Collection;
 import java.util.Map;
 
 
@@ -30,14 +32,32 @@ public class StackVersionResponse {
   private String parentVersion;
   private Map<String, Map<String, Map<String, String>>> configTypes;
 
+  /**
+   * A File pointing to the stack-level Kerberos descriptor file
+   *
+   * This may be null if a relevant file is not available.
+   */
+  private File stackKerberosDescriptorFile;
+
+  /**
+   * A Collection of Files pointing to the service-level Kerberos descriptor files
+   *
+   * This may be null or empty if no relevant files are available.
+   */
+  private Collection<File> serviceKerberosDescriptorFiles;
+
   public StackVersionResponse(String stackVersion, String minUpgradeVersion,
-                              boolean active, String parentVersion, 
-                              Map<String, Map<String, Map<String, String>>> configTypes) {
+                              boolean active, String parentVersion,
+                              Map<String, Map<String, Map<String, String>>> configTypes,
+                              File stackKerberosDescriptorFile,
+                              Collection<File> serviceKerberosDescriptorFiles) {
     setStackVersion(stackVersion);
     setMinUpgradeVersion(minUpgradeVersion);
     setActive(active);
     setParentVersion(parentVersion);
     setConfigTypes(configTypes);
+    setKerberosDescriptorFile(stackKerberosDescriptorFile);
+    setServiceKerberosDescriptorFiles(serviceKerberosDescriptorFiles);
   }
 
   public String getStackName() {
@@ -86,4 +106,44 @@ public class StackVersionResponse {
       Map<String, Map<String, Map<String, String>>> configTypes) {
     this.configTypes = configTypes;
   }
+
+  /**
+   * Gets a File pointing to the stack-level Kerberos descriptor
+   *
+   * @return a File pointing to the stack-level Kerberos descriptor, or null if no relevant file is
+   * available
+   */
+  public File getStackKerberosDescriptorFile() {
+    return stackKerberosDescriptorFile;
+  }
+
+  /**
+   * Sets the stack-level Kerberos descriptor File
+   *
+   * @param stackKerberosDescriptorFile a File pointing to the stack-level Kerberos descriptor
+   */
+  public void setKerberosDescriptorFile(File stackKerberosDescriptorFile) {
+    this.stackKerberosDescriptorFile = stackKerberosDescriptorFile;
+  }
+
+  /**
+   * Gets the Collection of Files pointing to the stack-specific service-level Kerberos descriptor
+   * files
+   *
+   * @return a Collection of Files pointing to the stack-specific service-level Kerberos descriptor
+   * files, or null if no relevant files are available
+   */
+  public Collection<File> getServiceKerberosDescriptorFiles() {
+    return serviceKerberosDescriptorFiles;
+  }
+
+  /**
+   * Sets the Collection of stack-specific service-level Kerberos descriptor Files
+   *
+   * @param serviceKerberosDescriptorFiles a Collection of stack-specific service-level Kerberos
+   *                                       descriptor Files
+   */
+  public void setServiceKerberosDescriptorFiles(Collection<File> serviceKerberosDescriptorFiles) {
+    this.serviceKerberosDescriptorFiles = serviceKerberosDescriptorFiles;
+  }
 }

+ 34 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java

@@ -19,6 +19,7 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import com.google.gson.Gson;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.StackServiceRequest;
@@ -26,7 +27,12 @@ import org.apache.ambari.server.controller.StackServiceResponse;
 import org.apache.ambari.server.controller.spi.*;
 import org.apache.ambari.server.controller.spi.Resource.Type;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
 import java.util.*;
 
 public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
@@ -64,6 +70,9 @@ public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
   private static final String CUSTOM_COMMANDS_PROPERTY_ID = PropertyHelper.getPropertyId(
       "StackServices", "custom_commands");
 
+  private static final String KERBEROS_DESCRIPTOR_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "StackServices", "kerberos_descriptor");
+
   private static Set<String> pkPropertyIds = new HashSet<String>(
       Arrays.asList(new String[] { STACK_NAME_PROPERTY_ID,
           STACK_VERSION_PROPERTY_ID, SERVICE_NAME_PROPERTY_ID }));
@@ -136,6 +145,31 @@ public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
       setResourceProperty(resource, CUSTOM_COMMANDS_PROPERTY_ID,
           response.getCustomCommands(), requestedIds);
 
+      // TODO (rlevas): Convert this to an official resource
+      File kerberosDescriptorFile = response.getKerberosDescriptorFile();
+      if (kerberosDescriptorFile != null) {
+        KerberosServiceDescriptor[] descriptors;
+        try {
+          descriptors = KerberosServiceDescriptor.fromFile(kerberosDescriptorFile);
+        } catch (IOException e) {
+          throw new SystemException("Failed to parse the service's Kerberos descriptor", e);
+        }
+
+        if (descriptors != null) {
+          String serviceName = response.getServiceName();
+
+          // Iterate over the KerberosServiceDescriptors to find the one for this service since
+          // Kerberos descriptor files can contain details about more than one service
+          for(KerberosServiceDescriptor descriptor:descriptors) {
+            if(serviceName.equals(descriptor.getName())) {
+              setResourceProperty(resource, KERBEROS_DESCRIPTOR_PROPERTY_ID,
+                  descriptor.toMap(), requestedIds);
+              break; // Stop looping, this was the service we are looking for.
+            }
+          }
+        }
+      }
+
       resources.add(resource);
     }
 

+ 63 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackVersionResourceProvider.java

@@ -19,7 +19,10 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
@@ -38,6 +41,8 @@ import org.apache.ambari.server.controller.spi.Resource.Type;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
+import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
 
 public class StackVersionResourceProvider extends ReadOnlyResourceProvider {
 
@@ -47,6 +52,7 @@ public class StackVersionResourceProvider extends ReadOnlyResourceProvider {
   public static final String STACK_ACTIVE_PROPERTY_ID      = PropertyHelper.getPropertyId("Versions", "active");
   public static final String STACK_CONFIG_TYPES            = PropertyHelper.getPropertyId("Versions", "config_types");
   public static final String STACK_PARENT_PROPERTY_ID      = PropertyHelper.getPropertyId("Versions", "parent_stack_version");
+  public static final String KERBEROS_DESCRIPTOR_PROPERTY_ID = PropertyHelper.getPropertyId("Versions", "kerberos_descriptor");
 
   private static Set<String> pkPropertyIds = new HashSet<String>(
       Arrays.asList(new String[] { STACK_NAME_PROPERTY_ID, STACK_VERSION_PROPERTY_ID }));
@@ -104,12 +110,69 @@ public class StackVersionResourceProvider extends ReadOnlyResourceProvider {
       setResourceProperty(resource, STACK_CONFIG_TYPES,
           response.getConfigTypes(), requestedIds);
 
+      // TODO (rlevas): Convert this to an official resource
+      KerberosDescriptor kerberosDescriptor;
+      try {
+        kerberosDescriptor = buildKerberosDescriptor(response);
+      } catch (IOException e) {
+        throw new SystemException("Failed to build composite Kerberos descriptor data", e);
+      }
+      if (kerberosDescriptor != null) {
+        setResourceProperty(resource, KERBEROS_DESCRIPTOR_PROPERTY_ID,
+            kerberosDescriptor.toMap(), requestedIds);
+      }
+
       resources.add(resource);
     }
 
     return resources;
   }
 
+  /**
+   * Given data from a StackVersionResponse build a complete Kerberos descriptor hierarchy.
+   *
+   * @param stackVersionResponse the StackVersionResponse instance containing the details of the
+   *                             stack and the relevant Kerberos descriptor files
+   * @return a KerberosDescriptor containing the complete hierarchy for the stack
+   * @throws IOException     if the specified File is not found or not a readable
+   * @throws AmbariException if the specified File does not contain valid JSON-encoded Kerberos
+   *                         descriptor
+   */
+  private KerberosDescriptor buildKerberosDescriptor(StackVersionResponse stackVersionResponse)
+      throws IOException {
+    KerberosDescriptor kerberosDescriptor = null;
+
+    // Process the stack-level Kerberos descriptor file
+    File stackKerberosDescriptorFile = stackVersionResponse.getStackKerberosDescriptorFile();
+    if (stackKerberosDescriptorFile != null) {
+      kerberosDescriptor = KerberosDescriptor.fromFile(stackKerberosDescriptorFile);
+    }
+
+    // Process the service-level Kerberos descriptor files
+    Collection<File> serviceDescriptorFiles = stackVersionResponse.getServiceKerberosDescriptorFiles();
+    if ((serviceDescriptorFiles != null) && !serviceDescriptorFiles.isEmpty()) {
+      // Make sure kerberosDescriptor is not null. This will be the case if there is no stack-level
+      // Kerberos descriptor file.
+      if (kerberosDescriptor == null) {
+        kerberosDescriptor = new KerberosDescriptor();
+      }
+
+      // For each service-level Kerberos descriptor file, parse into an array of KerberosServiceDescriptors
+      // and then append each to the KerberosDescriptor hierarchy.
+      for (File file : serviceDescriptorFiles) {
+        KerberosServiceDescriptor[] serviceDescriptors = KerberosServiceDescriptor.fromFile(file);
+
+        if (serviceDescriptors != null) {
+          for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors) {
+            kerberosDescriptor.putService(serviceDescriptor);
+          }
+        }
+      }
+    }
+
+    return kerberosDescriptor;
+  }
+
   private StackVersionRequest getRequest(Map<String, Object> properties) {
     return new StackVersionRequest(
         (String) properties.get(STACK_NAME_PROPERTY_ID),

+ 19 - 0
ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java

@@ -31,6 +31,7 @@ import java.io.File;
  * Encapsulates IO operations on a stack definition service directory.
  */
 public class ServiceDirectory extends StackDefinitionDirectory {
+
   /**
    * metrics file
    */
@@ -41,6 +42,11 @@ public class ServiceDirectory extends StackDefinitionDirectory {
    */
   private File alertsFile;
 
+  /**
+   * kerberos descriptor file
+   */
+  private File kerberosDescriptorFile;
+
   /**
    * package directory path
    */
@@ -94,6 +100,10 @@ public class ServiceDirectory extends StackDefinitionDirectory {
     File af = new File(directory.getAbsolutePath()
         + File.separator + AmbariMetaInfo.SERVICE_ALERT_FILE_NAME);
     alertsFile = af.exists() ? af : null;
+
+    File kdf = new File(directory.getAbsolutePath()
+        + File.separator + AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME);
+    kerberosDescriptorFile = kdf.exists() ? kdf : null;
   }
 
   /**
@@ -123,6 +133,15 @@ public class ServiceDirectory extends StackDefinitionDirectory {
     return alertsFile;
   }
 
+  /**
+   * Obtain the Kerberos Descriptor file.
+   *
+   * @return Kerberos Descriptor file
+   */
+  public File getKerberosDescriptorFile() {
+    return kerberosDescriptorFile;
+  }
+
   /**
    * Obtain the service metainfo file object representation.
    *

+ 4 - 0
ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java

@@ -96,6 +96,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> {
 
     serviceInfo.setMetricsFile(serviceDirectory.getMetricsFile());
     serviceInfo.setAlertsFile(serviceDirectory.getAlertsFile());
+    serviceInfo.setKerberosDescriptorFile(serviceDirectory.getKerberosDescriptorFile());
     serviceInfo.setSchemaVersion(AmbariMetaInfo.SCHEMA_VERSION_2);
     serviceInfo.setServicePackageFolder(serviceDirectory.getPackageDir());
 
@@ -152,6 +153,9 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> {
     if (serviceInfo.getAlertsFile() == null) {
       serviceInfo.setAlertsFile(parent.getAlertsFile());
     }
+    if (serviceInfo.getKerberosDescriptorFile() == null) {
+      serviceInfo.setKerberosDescriptorFile(parent.getKerberosDescriptorFile());
+    }
 
     mergeCustomCommands(parent.getCustomCommands(), serviceInfo.getCustomCommands());
     mergeConfigDependencies(parent);

+ 20 - 0
ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java

@@ -62,6 +62,11 @@ public class StackDirectory extends StackDefinitionDirectory {
    */
   private String rcoFilePath;
 
+  /**
+   * kerberos descriptor file path
+   */
+  private String kerberosDescriptorFilePath;
+
   /**
    * repository file
    */
@@ -181,6 +186,15 @@ public class StackDirectory extends StackDefinitionDirectory {
     return rcoFilePath;
   }
 
+  /**
+   * Obtain the path to the (stack-level) Kerberos descriptor file
+   *
+   * @return the path to the (stack-level) Kerberos descriptor file
+   */
+  public String getKerberosDescriptorFilePath() {
+    return kerberosDescriptorFilePath;
+  }
+
   /**
    * Obtain the repository directory path.
    *
@@ -257,6 +271,12 @@ public class StackDirectory extends StackDefinitionDirectory {
       rcoFilePath = getAbsolutePath() + File.separator + AmbariMetaInfo.RCO_FILE_NAME;
     }
 
+
+    if (subDirs.contains(AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME)) {
+      // kerberosDescriptorFilePath is expected to be absolute
+      kerberosDescriptorFilePath = getAbsolutePath() + File.separator + AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME;
+    }
+
     parseUpgradePacks(subDirs);
     parseServiceDirectories(subDirs);
     parseRepoFile(subDirs);

+ 1 - 0
ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java

@@ -378,6 +378,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> {
       stackInfo.setParentStackVersion(smx.getExtends());
       stackInfo.setStackHooksFolder(stackDirectory.getHooksDir());
       stackInfo.setRcoFileLocation(stackDirectory.getRcoFilePath());
+      stackInfo.setKerberosDescriptorFileLocation(stackDirectory.getKerberosDescriptorFilePath());
       stackInfo.setUpgradesFolder(stackDirectory.getUpgradesDir());
       stackInfo.setUpgradePacks(stackDirectory.getUpgradePacks());
       stackInfo.setRoleCommandOrder(stackDirectory.getRoleCommandOrder());

+ 18 - 1
ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java

@@ -108,6 +108,9 @@ public class ServiceInfo {
   @XmlTransient
   private File alertsFile = null;
 
+  @XmlTransient
+  private File kerberosDescriptorFile = null;
+
   /**
    * Internal list of os-specific details (loaded from xml). Added at schema ver 2
    */
@@ -533,7 +536,7 @@ public class ServiceInfo {
   public void setAlertsFile(File file) {
     alertsFile = file;
   }
-  
+
   /**
    * @return the alerts file, or <code>null</code> if none exists
    */
@@ -541,6 +544,20 @@ public class ServiceInfo {
     return alertsFile;
   }
 
+  /**
+   * @param file the file containing the alert definitions
+   */
+  public void setKerberosDescriptorFile(File file) {
+    kerberosDescriptorFile = file;
+  }
+
+  /**
+   * @return the kerberos descriptor file, or <code>null</code> if none exists
+   */
+  public File getKerberosDescriptorFile() {
+    return kerberosDescriptorFile;
+  }
+
   /**
    * @return config types this service contains configuration for, but which are primarily related to another service
    */

+ 43 - 1
ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java

@@ -18,10 +18,12 @@
 
 package org.apache.ambari.server.state;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -35,6 +37,7 @@ public class StackInfo implements Comparable<StackInfo>{
   private String minUpgradeVersion;
   private boolean active;
   private String rcoFileLocation;
+  private String kerberosDescriptorFileLocation;
   private List<RepositoryInfo> repositories;
   private Collection<ServiceInfo> services;
   private String parentStackVersion;
@@ -188,8 +191,28 @@ public class StackInfo implements Comparable<StackInfo>{
 
   public StackVersionResponse convertToResponse() {
 
+    // Get the stack-level Kerberos descriptor file path
+    String stackDescriptorFileFilePath = getKerberosDescriptorFileLocation();
+
+    // Collect the services' Kerberos descriptor files
+    Collection<ServiceInfo> serviceInfos = getServices();
+    // The collection of service descriptor files. A Set is being used because some Kerberos descriptor
+    // files contain multiple services, therefore the same File may be encountered more than once.
+    // For example the YARN directory may contain YARN and MAPREDUCE2 services.
+    Collection<File> serviceDescriptorFiles = new HashSet<File>();
+    if (serviceInfos != null) {
+      for (ServiceInfo serviceInfo : serviceInfos) {
+        File file = serviceInfo.getKerberosDescriptorFile();
+        if (file != null) {
+          serviceDescriptorFiles.add(file);
+        }
+      }
+    }
+
     return new StackVersionResponse(getVersion(), getMinUpgradeVersion(),
-      isActive(), getParentStackVersion(), getConfigTypeAttributes());
+        isActive(), getParentStackVersion(), getConfigTypeAttributes(),
+        (stackDescriptorFileFilePath == null) ? null : new File(stackDescriptorFileFilePath),
+        serviceDescriptorFiles);
   }
 
   public String getMinUpgradeVersion() {
@@ -232,6 +255,25 @@ public class StackInfo implements Comparable<StackInfo>{
     this.rcoFileLocation = rcoFileLocation;
   }
 
+  /**
+   * Gets the path to the stack-level Kerberos descriptor file
+   *
+   * @return a String containing the path to the stack-level Kerberos descriptor file
+   */
+  public String getKerberosDescriptorFileLocation() {
+    return kerberosDescriptorFileLocation;
+  }
+
+  /**
+   * Sets the path to the stack-level Kerberos descriptor file
+   *
+   * @param kerberosDescriptorFileLocation a String containing the path to the stack-level Kerberos
+   *                                       descriptor file
+   */
+  public void setKerberosDescriptorFileLocation(String kerberosDescriptorFileLocation) {
+    this.kerberosDescriptorFileLocation = kerberosDescriptorFileLocation;
+  }
+
   public String getStackHooksFolder() {
     return stackHooksFolder;
   }

+ 2 - 0
ambari-server/src/main/resources/properties.json

@@ -203,6 +203,7 @@
         "Versions/active",
         "Versions/parent_stack_version",
         "Versions/config_types",
+        "Versions/kerberos_descriptor",
         "_"
     ],
     "StackService":[
@@ -217,6 +218,7 @@
         "StackServices/service_check_supported",
         "StackServices/custom_commands",
         "StackServices/required_services",
+        "StackServices/kerberos_descriptor",
         "_"
     ],
     "StackConfiguration":[

+ 58 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java

@@ -30,6 +30,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.File;
+import java.io.FileReader;
 import java.lang.reflect.Field;
 import java.util.Collection;
 import java.util.HashSet;
@@ -41,6 +42,7 @@ import java.util.Set;
 import javax.persistence.EntityManager;
 import javax.xml.bind.JAXBException;
 
+import com.google.gson.Gson;
 import junit.framework.Assert;
 
 import org.apache.ambari.server.AmbariException;
@@ -818,6 +820,33 @@ public class AmbariMetaInfoTest {
     Assert.assertNull(list);
   }
 
+  @Test
+  public void testKerberosJson() throws Exception {
+    ServiceInfo svc;
+
+    svc = metaInfo.getService(STACK_NAME_HDP, "2.0.8", "HDFS");
+    Assert.assertNotNull(svc);
+
+    File kerberosDescriptorFile1 = svc.getKerberosDescriptorFile();
+    Assert.assertNotNull(kerberosDescriptorFile1);
+    Assert.assertTrue(kerberosDescriptorFile1.exists());
+
+    svc = metaInfo.getService(STACK_NAME_HDP, "2.1.1", "HDFS");
+    Assert.assertNotNull(svc);
+
+    File kerberosDescriptorFile2 = svc.getKerberosDescriptorFile();
+    Assert.assertNotNull(kerberosDescriptorFile1);
+    Assert.assertTrue(kerberosDescriptorFile1.exists());
+
+    Assert.assertEquals(kerberosDescriptorFile1, kerberosDescriptorFile2);
+
+    svc = metaInfo.getService(STACK_NAME_HDP, "2.0.7", "HDFS");
+    Assert.assertNotNull(svc);
+
+    File kerberosDescriptorFile3 = svc.getKerberosDescriptorFile();
+    Assert.assertNull(kerberosDescriptorFile3);
+  }
+
   @Test
   public void testGanglia134Dependencies() throws Exception {
     ServiceInfo service = metaInfo.getService(STACK_NAME_HDP, "1.3.4", "GANGLIA");
@@ -1679,6 +1708,35 @@ public class AmbariMetaInfoTest {
     }
   }
 
+  @Test
+  public void testKerberosDescriptor() throws Exception {
+    ServiceInfo service;
+
+    // Test that kerberos descriptor file is not available when not supplied in service definition
+    service = metaInfo.getService(STACK_NAME_HDP, "2.1.1", "PIG");
+    Assert.assertNotNull(service);
+    Assert.assertNull(service.getKerberosDescriptorFile());
+
+    // Test that kerberos descriptor file is available when supplied in service definition
+    service = metaInfo.getService(STACK_NAME_HDP, "2.0.8", "HDFS");
+    Assert.assertNotNull(service);
+    Assert.assertNotNull(service.getKerberosDescriptorFile());
+
+    // Test that kerberos descriptor file is available from inherited stack version
+    service = metaInfo.getService(STACK_NAME_HDP, "2.1.1", "HDFS");
+    Assert.assertNotNull(service);
+    Assert.assertNotNull(service.getKerberosDescriptorFile());
+
+
+    // Test that kerberos.json file can be parsed into mapped data
+    Map<?,?> kerberosDescriptorData = new Gson()
+        .fromJson(new FileReader(service.getKerberosDescriptorFile()), Map.class);
+
+    Assert.assertNotNull(kerberosDescriptorData);
+    Assert.assertEquals(2, kerberosDescriptorData.size());
+  }
+
+
   private TestAmbariMetaInfo setupTempAmbariMetaInfo(String buildDir, boolean replayMocks) throws Exception {
     File stackRootTmp = new File(buildDir + "/ambari-metaInfo");
     File stackRoot = new File("src/test/resources/stacks");

+ 38 - 0
ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java

@@ -390,6 +390,43 @@ public class ServiceModuleTest {
     assertEquals(alertsFile, child.getModuleInfo().getAlertsFile());
   }
 
+  @Test
+  public void testResolve_KerberosDescriptorFile() throws Exception {
+    File kerberosDescriptorFile = new File("testKerberosDescriptorFile");
+
+    // specified in child only
+    ServiceInfo info = new ServiceInfo();
+    ServiceInfo parentInfo = new ServiceInfo();
+
+    ServiceModule child = createServiceModule(info);
+    ServiceModule parent = createServiceModule(parentInfo);
+
+    // set in the module constructor from a value obtained from service directory which is mocked
+    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
+    parent.getModuleInfo().setKerberosDescriptorFile(null);
+
+    resolveService(child, parent);
+    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
+
+    // specified in parent only
+    child = createServiceModule(info);
+    parent = createServiceModule(parentInfo);
+    parent.getModuleInfo().setKerberosDescriptorFile(kerberosDescriptorFile);
+    child.getModuleInfo().setKerberosDescriptorFile(null);
+
+    resolveService(child, parent);
+    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
+
+    // specified in both
+    child = createServiceModule(info);
+    parent = createServiceModule(parentInfo);
+    parent.getModuleInfo().setKerberosDescriptorFile(new File("someOtherDir"));
+    child.getModuleInfo().setKerberosDescriptorFile(kerberosDescriptorFile);
+
+    resolveService(child, parent);
+    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
+  }
+
   @Test
   public void testResolve_CustomCommands() throws Exception {
     List<CustomCommandDefinition> customCommands = new ArrayList<CustomCommandDefinition>();
@@ -918,6 +955,7 @@ public class ServiceModuleTest {
     expect(serviceDirectory.getConfigurationDirectory(dir)).andReturn(configDir).anyTimes();
     expect(serviceDirectory.getMetricsFile()).andReturn(new File("testMetricsFile")).anyTimes();
     expect(serviceDirectory.getAlertsFile()).andReturn(new File("testAlertsFile")).anyTimes();
+    expect(serviceDirectory.getKerberosDescriptorFile()).andReturn(new File("testKerberosDescriptorFile")).anyTimes();
     expect(serviceDirectory.getPackageDir()).andReturn("packageDir").anyTimes();
     replay(serviceDirectory);
 

+ 147 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.8/services/HDFS/kerberos.json

@@ -0,0 +1,147 @@
+{
+  "name": "HDFS",
+  "components": [
+    {
+      "name": "NAMENODE",
+      "identities": [
+        {
+          "name": "namenode_nn",
+          "principal": {
+            "value": "nn/_HOST@${realm}",
+            "configuration": "hdfs-site/dfs.namenode.kerberos.principal"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/nn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            },
+            "configuration": "hdfs-site/dfs.namenode.keytab.file"
+          }
+        },
+        {
+          "name": "namenode_host",
+          "principal": {
+            "value": "host/_HOST@${realm}"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/nn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            }
+          }
+        },
+        {
+          "name": "/spnego",
+          "principal": {
+            "configuration": "hdfs-site/dfs.namenode.kerberos.internal.spnego.principal"
+          }
+        }
+      ],
+      "configurations": [
+        {
+          "hdfs-site": {
+            "dfs.block.access.token.enable": "true"
+          }
+        }
+      ]
+    },
+    {
+      "name": "DATANODE",
+      "identities": [
+        {
+          "name": "datanode_dn",
+          "principal": {
+            "value": "dn/_HOST@${realm}",
+            "configuration": "hdfs-site/dfs.datanode.kerberos.principal"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/dn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            },
+            "configuration": "hdfs-site/dfs.datanode.keytab.file"
+          }
+        },
+        {
+          "name": "datanode_host",
+          "principal": {
+            "value": "host/_HOST@${realm}"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/dn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "SECONDARY_NAMENODE",
+      "identities": [
+        {
+          "name": "secondary_namenode_nn",
+          "principal": {
+            "value": "nn/_HOST@${realm}",
+            "configuration": "hdfs-site/dfs.secondary.namenode.kerberos.principal"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/snn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            },
+            "configuration": "hdfs-site/dfs.secondary.namenode.kerberos.principal"
+          }
+        },
+        {
+          "name": "secondary_namenode_host",
+          "principal": {
+            "value": "host/_HOST@${realm}"
+          },
+          "keytab": {
+            "file": "${keytab_dir}/snn.service.keytab",
+            "owner": {
+              "name": "${hadoop-env/hdfs_user}",
+              "access": "r"
+            },
+            "group": {
+              "name": "${cluster-env/user_group}",
+              "access": ""
+            }
+          }
+        },
+        {
+          "name": "/spnego",
+          "principal": {
+            "configuration": "hdfs-site/dfs.secondary.namenode.kerberos.internal.spnego.principal"
+          }
+        }
+      ]
+    }
+  ]
+}

+ 146 - 0
ambari-server/src/test/resources/stacks/HDP/2.1.1/services/HDFS/metainfo.xml

@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+<!--
+   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.
+-->
+<metainfo>
+  <schemaVersion>2.0</schemaVersion>
+  <services>
+    <service>
+      <name>HDFS</name>
+      <comment>Apache Hadoop Distributed File System</comment>
+      <version>2.1.0.2.0.6.0</version>
+
+      <components>
+        <component>
+          <name>NAMENODE</name>
+          <category>MASTER</category>
+          <commandScript>
+            <script>scripts/namenode.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+          <customCommands>
+            <customCommand>
+              <name>DECOMMISSION</name>
+              <commandScript>
+                <script>scripts/namenode_dec_overr.py</script>
+                <scriptType>PYTHON</scriptType>
+                <timeout>600</timeout>
+              </commandScript>
+            </customCommand>
+            <customCommand>
+              <name>YET_ANOTHER_CHILD_COMMAND</name>
+              <commandScript>
+                <script>scripts/yet_another_child_command.py</script>
+                <scriptType>PYTHON</scriptType>
+                <timeout>600</timeout>
+              </commandScript>
+            </customCommand>
+          </customCommands>
+        </component>
+
+        <component>
+          <name>DATANODE</name>
+          <category>SLAVE</category>
+          <commandScript>
+            <script>scripts/datanode.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </component>
+
+        <component>
+          <name>SECONDARY_NAMENODE</name>
+          <category>MASTER</category>
+          <commandScript>
+            <script>scripts/snamenode.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </component>
+
+        <component>
+          <name>HDFS_CLIENT</name>
+          <category>CLIENT</category>
+          <commandScript>
+            <script>scripts/hdfs_client_overridden.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </component>
+
+        <component>
+          <name>JOURNALNODE</name>
+          <category>MASTER</category>
+          <commandScript>
+            <script>scripts/journalnode.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </component>
+
+        <component>
+          <name>ZKFC</name>
+          <category>SLAVE</category>
+          <commandScript>
+            <script>scripts/zkfc_slave.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </component>
+      </components>
+
+      <osSpecifics>
+        <osSpecific>
+          <osFamily>any</osFamily>
+          <packages>
+            <package>
+              <name>child-package-def</name>
+            </package>
+          </packages>
+        </osSpecific>
+      </osSpecifics>
+
+      <commandScript>
+        <script>scripts/service_check_2.py</script>
+        <scriptType>PYTHON</scriptType>
+        <timeout>300</timeout>
+      </commandScript>
+
+      <customCommands>
+        <customCommand>
+          <name>RESTART</name>
+          <commandScript>
+            <script>scripts/restart_child.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </customCommand>
+        <customCommand>
+          <name>YET_ANOTHER_CHILD_SRV_COMMAND</name>
+          <commandScript>
+            <script>scripts/yet_another_child_srv_command.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
+        </customCommand>
+      </customCommands>
+
+      <configuration-dependencies>
+      </configuration-dependencies>
+    </service>
+  </services>
+</metainfo>