Pārlūkot izejas kodu

AMBARI-8542. Provide a way to parse and handle Kerberos descriptors

Robert Levas 10 gadi atpakaļ
vecāks
revīzija
e7b8383b5a
17 mainītis faili ar 4358 papildinājumiem un 0 dzēšanām
  1. 331 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
  2. 626 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
  3. 115 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
  4. 205 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosConfigurationDescriptor.java
  5. 351 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
  6. 54 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptorType.java
  7. 278 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
  8. 445 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosKeytabDescriptor.java
  9. 212 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosPrincipalDescriptor.java
  10. 318 0
      ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
  11. 206 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptorTest.java
  12. 242 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosConfigurationDescriptorTest.java
  13. 328 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosDescriptorTest.java
  14. 154 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptorTest.java
  15. 146 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosKeytabDescriptorTest.java
  16. 109 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosPrincipalDescriptorTest.java
  17. 238 0
      ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptorTest.java

+ 331 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java

@@ -0,0 +1,331 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import org.apache.ambari.server.AmbariException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * AbstractKerberosDescriptor is the base class for all Kerberos*Descriptor and associated classes.
+ * <p/>
+ * It provides storage and management for the parent and name values on behalf of implementing classes.
+ * It also provides utility and helper methods.
+ */
+public abstract class AbstractKerberosDescriptor {
+
+  /**
+   * a regular expression Pattern used to find "variable" placeholders in strings
+   */
+  private static final Pattern PATTERN_VARIABLE = Pattern.compile("\\$\\{(?:(.+?)/)?(.+?)\\}");
+
+  /**
+   * An AbstractKerberosDescriptor serving as the parent (or container) for this
+   * AbstractKerberosDescriptor.
+   * <p/>
+   * This value may be null in the event no parent has been identified.
+   */
+  private AbstractKerberosDescriptor parent = null;
+
+  /**
+   * A String declaring the name of this AbstractKerberosDescriptor.
+   * <p/>
+   * This value may be null in the event a name (or identifier) is not relevant.
+   */
+  private String name = null;
+
+  /**
+   * Performs variable replacement on the supplied String value using values from the replacementsMap.
+   * <p/>
+   * The value is a String containing one or more "variables" in the form of ${variable_name}, such
+   * that "variable_name" may indicate a group identifier; else "" is used as the group.
+   * For example:
+   * <p/>
+   * variable_name:  group: ""; property: "variable_name"
+   * group1/variable_name:  group: "group1"; property: "variable_name"
+   * root/group1/variable_name:  Not Supported
+   * <p/>
+   * The replacementsMap is a Map of Maps creating a (small) hierarchy of data to traverse in order
+   * to resolve the variable.
+   * <p/>
+   * If a variable resolves to one or more variables, that new variable(s) will be processed and replaced.
+   * If variable exists after a set number of iterations it is assumed that a cycle has been created
+   * and the process will abort returning a String in a possibly unexpected state.
+   *
+   * @param value           a String containing zero or more variables to be replaced
+   * @param replacementsMap a Map of data used to perform the variable replacements
+   * @return a new String
+   */
+  public static String replaceVariables(String value, Map<String, Map<String, String>> replacementsMap) throws AmbariException {
+    if ((value != null) && (replacementsMap != null) && !replacementsMap.isEmpty()) {
+      int count = 0; // Used to help prevent an infinite loop...
+      boolean replacementPerformed;
+
+      do {
+        if (++count > 1000) {
+          throw new AmbariException(String.format("Circular reference found while replacing variables in %s", value));
+        }
+
+        Matcher matcher = PATTERN_VARIABLE.matcher(value);
+        StringBuffer sb = new StringBuffer();
+
+        replacementPerformed = false;
+
+        while (matcher.find()) {
+          String type = matcher.group(1);
+          String name = matcher.group(2);
+          Map<String, String> replacements;
+
+          if ((name != null) && !name.isEmpty()) {
+            if (type == null) {
+              replacements = replacementsMap.get("");
+            } else {
+              replacements = replacementsMap.get(type);
+            }
+
+            if (replacements != null) {
+              String replacement = replacements.get(name);
+
+              if (replacement != null) {
+                // Escape '$' and '\' so they don't cause any issues.
+                matcher.appendReplacement(sb, replacement.replace("\\", "\\\\").replace("$", "\\$"));
+                replacementPerformed = true;
+              }
+            }
+          }
+        }
+
+        matcher.appendTail(sb);
+        value = sb.toString();
+      }
+      while (replacementPerformed); // Process the string again to make sure new variables were not introduced
+    }
+
+    return value;
+  }
+
+  /**
+   * Generates a Map of data that represents this AbstractKerberosDescriptor implementation.
+   * <p/>
+   * This method should be overwritten by AbstractKerberosDescriptor implementations to generate a
+   * Map of data specific to it.
+   * <p/>
+   * It is not necessary to call this method from the overriding method.
+   * <p/>
+   * The map of data generated should be setup so that it can be fed back into the implementation
+   * class to build a copy of it. For example:
+   * <p/>
+   * <pre>
+   *  descriptor1 = AbstractKerberosDescriptorImpl(...)
+   *  map = descriptor1.toMap()
+   *  descriptor2 = AbstractKerberosDescriptor(map)
+   *  descriptor1 should have the same data as descriptor2
+   * </pre>
+   *
+   * @return a Map of date representing this AbstractKerberosDescriptor implementation
+   */
+  public Map<String, Object> toMap() {
+    HashMap<String, Object> dataMap = new HashMap<String, Object>();
+    String name = getName();
+
+    if (name != null) {
+      dataMap.put("name", name);
+    }
+
+    return dataMap;
+  }
+
+  /**
+   * Returns the name of this descriptor
+   *
+   * @return a String indicating the name of this descriptor
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Sets the name of this descriptor
+   *
+   * @param name a String indicating the name of this descriptor
+   */
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  /**
+   * Returns the parent (or container) of this descriptor
+   *
+   * @return an AbstractKerberosDescriptor representing the parent (or container) of this descriptor
+   * or null if no parent was set
+   */
+  public AbstractKerberosDescriptor getParent() {
+    return parent;
+  }
+
+  /**
+   * Sets the parent (or container) of this descriptor
+   *
+   * @param parent an AbstractKerberosDescriptor representing the parent (or container) of this
+   *               descriptor or null to clear the value
+   */
+  public void setParent(AbstractKerberosDescriptor parent) {
+    this.parent = parent;
+  }
+
+  /**
+   * Test this AbstractKerberosDescriptor to see if it is a container.
+   * <p/>
+   * The default implementation always returns false.  Implementing classes should override this
+   * method to return something different, like true.
+   *
+   * @return true if this AbstractKerberosDescriptor is a container, false otherwise
+   */
+  public boolean isContainer() {
+    return false;
+  }
+
+  /**
+   * Parses a file containing JSON-formatted text into a (generic) Map.
+   *
+   * @param file a File containing the JSON-formatted text to parse
+   * @return a Map of the data
+   * @throws FileNotFoundException if the specified File does not point to a valid file
+   * @throws IOException           if the specified File is not a readable file
+   */
+  protected static Map<String, Object> parseFile(File file) throws IOException {
+    if (file == null) {
+      return Collections.emptyMap();
+    } else if (!file.isFile() || !file.canRead()) {
+      throw new IOException(String.format("%s is not a readable file", file.getAbsolutePath()));
+    } else {
+      return new Gson().fromJson(new FileReader(file),
+          new TypeToken<Map<String, Object>>() {
+          }.getType());
+    }
+  }
+
+  /**
+   * Parses a JSON-formatted String into a (generic) Map.
+   *
+   * @param json a String containing the JSON-formatted text to parse
+   * @return a Map of the data
+   */
+  protected static Map<String, Object> parseJSON(String json) {
+    if ((json == null) || json.isEmpty()) {
+      return Collections.emptyMap();
+    } else {
+      return new Gson().fromJson(json,
+          new TypeToken<Map<String, Object>>() {
+          }.getType());
+    }
+  }
+
+  /**
+   * Safely retrieves the requested value from the supplied Map
+   *
+   * @param map a Map containing the relevant data
+   * @param key a String declaring the item to retrieve
+   * @return an Object representing the requested data; or null if not found
+   */
+  protected static Object getValue(Map<?, ?> map, String key) {
+    return ((map == null) || key == null) ? null : map.get(key);
+  }
+
+  /**
+   * Safely retrieves the requested value (converted to a String) from the supplied Map
+   * <p/>
+   * The found value will be converted to a String using the {@link Object#toString()} method.
+   *
+   * @param map a Map containing the relevant data
+   * @param key a String declaring the item to retrieve
+   * @return a String representing the requested data; or null if not found
+   */
+  protected static String getStringValue(Map<?, ?> map, String key) {
+    Object value = getValue(map, key);
+    return (value == null) ? null : value.toString();
+  }
+
+  /**
+   * Gets the requested AbstractKerberosDescriptor implementation using a type name and a relevant
+   * descriptor name.
+   * <p/>
+   * Implementation classes should override this method to handle relevant descriptor types.
+   *
+   * @param type a String indicating the type of the requested descriptor
+   * @param name a String indicating the name of the requested descriptor
+   * @return a AbstractKerberosDescriptor representing the requested descriptor or null if not found
+   */
+  protected AbstractKerberosDescriptor getDescriptor(KerberosDescriptorType type, String name) {
+    return null;
+  }
+
+  /**
+   * Traverses up the hierarchy to find the "root" or "parent" container.
+   * <p/>
+   * The root AbstractKerberosDescriptor is the first descriptor in the hierarchy with a null parent.
+   *
+   * @return the AbstractKerberosDescriptor implementation that is found to be the root of the hierarchy.
+   */
+  protected AbstractKerberosDescriptor getRoot() {
+    AbstractKerberosDescriptor root = this;
+
+    while (root.getParent() != null) {
+      root = root.getParent();
+    }
+
+    return root;
+  }
+
+  @Override
+  public int hashCode() {
+    return 37 *
+        ((getName() == null)
+            ? 0
+            : getName().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object instanceof AbstractKerberosDescriptor) {
+      AbstractKerberosDescriptor descriptor = (AbstractKerberosDescriptor) object;
+      return (
+          (getName() == null)
+              ? (descriptor.getName() == null)
+              : getName().equals(descriptor.getName())
+      );
+    } else {
+      return false;
+    }
+  }
+}

+ 626 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java

@@ -0,0 +1,626 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.state.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AbstractKerberosDescriptorContainer is an abstract class implementing AbstractKerberosDescriptor
+ * and providing facility to handle common descriptor container functionality.
+ * <p/>
+ * Each AbstractKerberosDescriptorContainer contains identities and configurations as well as a
+ * name and a parent (which is inherited from AbstractKerberosDescriptor).
+ * <p/>
+ * An AbstractKerberosDescriptorContainer has the following properties:
+ * <ul>
+ * <li>identities</li>
+ * <li>configurations</li>
+ * </ul>
+ * <p/>
+ * The following (pseudo) JSON Schema will yield a valid AbstractKerberosDescriptorContainer
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "AbstractKerberosDescriptorContainer",
+ *      "description": "Describes an AbstractKerberosDescriptorContainer",
+ *      "type": "object",
+ *      "properties": {
+ *        "identities": {
+ *          "description": "A list of Kerberos identity descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosIdentityDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}"
+ *          }
+ *        },
+ *        "configurations": {
+ *          "description": "A list of relevant configuration blocks",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosConfigurationDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}"
+ *          }
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * This implementation does not set the
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} value, it is
+ * left up to the implementing class to do so.
+ */
+public abstract class AbstractKerberosDescriptorContainer extends AbstractKerberosDescriptor {
+
+  /**
+   * A List of KerberosIdentityDescriptors contained in this AbstractKerberosDescriptorContainer
+   */
+  private List<KerberosIdentityDescriptor> identities = null;
+
+  /**
+   * A Map of KerberosConfigurationDescriptors contained in this AbstractKerberosDescriptorContainer
+   */
+  private Map<String, KerberosConfigurationDescriptor> configurations = null;
+
+  /**
+   * Constructs a new AbstractKerberosDescriptorContainer
+   * <p/>
+   * This constructor must be called from the constructor(s) of the implementing classes
+   *
+   * @param data a Map of data used for collecting groups of common descriptors
+   */
+  protected AbstractKerberosDescriptorContainer(Map<?, ?> data) {
+    if (data != null) {
+      Object list;
+
+      // (Safely) Get the set of KerberosIdentityDescriptors
+      list = data.get(KerberosDescriptorType.IDENTITY.getDescriptorPluralName());
+      if (list instanceof Collection) {
+        for (Object item : (Collection) list) {
+          if (item instanceof Map) {
+            putIdentity(new KerberosIdentityDescriptor((Map<?, ?>) item));
+          }
+        }
+      }
+
+      // (Safely) Get the set of KerberosConfigurationDescriptors
+      list = data.get(KerberosDescriptorType.CONFIGURATION.getDescriptorPluralName());
+      if (list instanceof Collection) {
+        for (Object item : (Collection) list) {
+          if (item instanceof Map) {
+            putConfiguration(new KerberosConfigurationDescriptor((Map<?, ?>) item));
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the raw List of KerberosIdentityDescriptors contained within this
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * The returned KerberosIdentityDescriptors are not merged with data from referenced
+   * KerberosConfigurationDescriptors. This is the same calling
+   * {@link AbstractKerberosDescriptorContainer#getIdentities(boolean)} and setting the argument to
+   * 'false'
+   *
+   * @return the relevant List of KerberosIdentityDescriptors
+   */
+  public List<KerberosIdentityDescriptor> getIdentities() {
+    try {
+      return getIdentities(false);
+    } catch (AmbariException e) {
+      // AmbariException will not be thrown unless an error occurs while trying to dereference
+      // identities.  This method does not attempt to dereference identities.
+      return null;
+    }
+  }
+
+  /**
+   * Returns a List of KerberosIdentityDescriptors contained within this
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * If resolveReferences is true, a "detached" set of KerberosIdentityDescriptors are returned.
+   * Any KerberosIdentityDescriptor that implies it references some other KerberosIdentityDescriptor
+   * in the hierarchy will be resolved. Meaning, if the name of the KerberosIdentityDescriptor
+   * indicates a path to some other KerberosIdentityDescriptor (i.e, /spnego, /HDFS/NAMENODE/nn, etc...)
+   * the referenced KerberosIdentityDescriptor is found, detached (or copied), and updated with
+   * the information from the initial KerberosIdentityDescriptor.  Because of this, all of the
+   * KerberosIdentityDescriptors to be included are copied into the resulting list, and dissociating
+   * them with the rest of the hierarchy such that changes to them will not be reflected within the
+   * entire KerberosDescriptor tree.
+   * <p/>
+   * If resolveReferences is false, the raw List of KerberosIdentityDescriptors are returned. This
+   * data is not manipulated by resolving references and therefore it may be missing data, however
+   * this List is of "attached" descriptors, so changes will be reflected within the KerberosDescriptor
+   * hierarchy.
+   *
+   * @param resolveReferences a Boolean value indicating whether to resolve references (true) or not
+   *                          (false)
+   * @return a List of the requested KerberosIdentityDescriptors
+   */
+  public List<KerberosIdentityDescriptor> getIdentities(boolean resolveReferences) throws AmbariException {
+    if (resolveReferences) {
+      if (identities == null) {
+        return Collections.emptyList();
+      } else {
+        List<KerberosIdentityDescriptor> list = new ArrayList<KerberosIdentityDescriptor>();
+
+        // For each KerberosIdentityDescriptor, copy it and then attempt to find the referenced
+        // KerberosIdentityDescriptor.
+        // * If a reference is found, copy that, update it with the initial KerberosIdentityDescriptor
+        //   and then add it to the list.
+        // * If a reference is not found, simply add the initial KerberosIdentityDescriptor to the list
+        for (KerberosIdentityDescriptor identity : identities) {
+          KerberosIdentityDescriptor referencedIdentity;
+          try {
+            referencedIdentity = getReferencedIdentityDescriptor(identity.getName());
+          } catch (AmbariException e) {
+            throw new AmbariException(String.format("Invalid Kerberos identity reference: %s", identity.getName()), e);
+          }
+
+          // Detach this identity from the tree...
+          identity = new KerberosIdentityDescriptor(identity.toMap());
+
+          if (referencedIdentity != null) {
+            KerberosIdentityDescriptor detachedIdentity = new KerberosIdentityDescriptor(referencedIdentity.toMap());
+            detachedIdentity.update(identity);
+            list.add(detachedIdentity);
+          } else {
+            list.add(identity);
+          }
+        }
+
+        return list;
+      }
+    } else {
+      return identities;
+    }
+  }
+
+  /**
+   * Return a KerberosIdentityDescriptor with the specified name.
+   *
+   * @param name a String declaring the name of the descriptor to retrieve
+   * @return the requested KerberosIdentityDescriptor
+   */
+  public KerberosIdentityDescriptor getIdentity(String name) {
+    KerberosIdentityDescriptor identity = null;
+
+    if ((name != null) && (identities != null)) {
+      // Iterate over the List of KerberosIdentityDescriptors to find one with the requested name
+      // If one is found, break out of the loop.
+      for (KerberosIdentityDescriptor descriptor : identities) {
+        if (name.equals(descriptor.getName())) {
+          identity = descriptor;
+          break;
+        }
+      }
+    }
+
+    return identity;
+  }
+
+  /**
+   * Adds the specified KerberosIdentityDescriptor to the list of KerberosIdentityDescriptor.
+   * <p/>
+   * This method attempts to ensure that the names or KerberosIdentityDescriptors are unique within
+   * the List.
+   *
+   * @param identity the KerberosIdentityDescriptor to add
+   */
+  public void putIdentity(KerberosIdentityDescriptor identity) {
+    if (identity != null) {
+      String name = identity.getName();
+
+      if (identities == null) {
+        identities = new ArrayList<KerberosIdentityDescriptor>();
+      }
+
+      // If the identity has a name, ensure that one one with that name is in the List
+      // Note: this cannot be enforced since any AbstractKerberosDescriptor+'s name property can be
+      // changed
+      if ((name != null) && !name.isEmpty()) {
+        removeIdentity(identity.getName());
+      }
+
+      identities.add(identity);
+
+      // Set the identity's parent to this AbstractKerberosDescriptorContainer
+      identity.setParent(this);
+    }
+  }
+
+  /**
+   * Remove all KerberosIdentityDescriptors have have the specified name
+   * <p/>
+   * One or more KerberosIdentityDescriptors may be removed if multiple KerberosIdentityDescriptors
+   * have the same name.
+   *
+   * @param name a String declaring the name of the descriptors to remove
+   */
+  public void removeIdentity(String name) {
+    if ((name != null) && (identities != null)) {
+      Iterator<KerberosIdentityDescriptor> iterator = identities.iterator();
+
+      while (iterator.hasNext()) {
+        KerberosIdentityDescriptor identity = iterator.next();
+        if (name.equals(identity.getName())) {
+          identity.setParent(null);
+          iterator.remove();
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a Map of raw KerberosConfigurationDescriptors contained within this
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * The returned KerberosConfigurationDescriptors are not merged with data from KerberosDescriptor
+   * hierarchy. This is the same calling
+   * {@link AbstractKerberosDescriptorContainer#getConfigurations(boolean)} and setting the argument
+   * to 'false'
+   *
+   * @return a List of KerberosConfigurationDescriptors
+   */
+  public Map<String, KerberosConfigurationDescriptor> getConfigurations() {
+    return getConfigurations(false);
+  }
+
+  /**
+   * Returns a Map of KerberosConfigurationDescriptors contained within this
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * If includeInherited is true, the Map will contain "detached" KerberosConfigurationDescriptor
+   * instances, but the data will be merged with all relevant KerberosConfigurationDescriptors within
+   * the hierarchy.  Data higher in the hierarchy (the service level is higher in the hierarchy than
+   * the component level) will be overwritten by data lower in the hierarchy.
+   * <p/>
+   * If includeInherited is false, the Map will consist of attached (non-merged)
+   * KerberosConfigurationDescriptors. This data is not manipulated by merging with data within the
+   * KerberosDescriptor hierarchy and therefore it may be missing data, however this Map is of
+   * "attached" descriptors, so changes will be reflected within the KerberosDescriptor
+   * hierarchy.
+   *
+   * @param includeInherited a Boolean value indicating whether to include configuration within the
+   *                         KerberosDescriptor hierarchy (true) or not (false)
+   * @return a Map of Strings to KerberosConfigurationDescriptors, where the key is the type
+   * (core-site, etc...)
+   */
+  public Map<String, KerberosConfigurationDescriptor> getConfigurations(boolean includeInherited) {
+    if (includeInherited) {
+      Map<String, KerberosConfigurationDescriptor> mergedConfigurations = new HashMap<String, KerberosConfigurationDescriptor>();
+      List<Map<String, KerberosConfigurationDescriptor>> configurationSets = new ArrayList<Map<String, KerberosConfigurationDescriptor>>();
+      AbstractKerberosDescriptor currentDescriptor = this;
+
+      // Walk up the hierarchy and collect the configuration sets.
+      while (currentDescriptor != null) {
+        if (currentDescriptor.isContainer()) {
+          Map<String, KerberosConfigurationDescriptor> configurations = ((AbstractKerberosDescriptorContainer) currentDescriptor).getConfigurations();
+
+          if (configurations != null) {
+            configurationSets.add(configurations);
+          }
+        }
+
+        currentDescriptor = currentDescriptor.getParent();
+      }
+
+      // Reverse the collection so that we can merge from top to bottom
+      Collections.reverse(configurationSets);
+
+      for (Map<String, KerberosConfigurationDescriptor> map : configurationSets) {
+        for (Map.Entry<String, KerberosConfigurationDescriptor> entry : map.entrySet()) {
+          // For each configuration type, copy it and determine if an entry exists or not.
+          // ** If one exists, merge the current data into the existing data (potentially
+          //    overwriting values).
+          // ** If one does not exist, simply add a copy of the current one to the Map
+          String currentType = entry.getKey();
+          KerberosConfigurationDescriptor currentConfiguration = entry.getValue();
+
+          if (currentConfiguration != null) {
+            KerberosConfigurationDescriptor detachedConfiguration = new KerberosConfigurationDescriptor(currentConfiguration.toMap());
+            KerberosConfigurationDescriptor mergedConfiguration = mergedConfigurations.get(entry.getKey());
+
+            if (mergedConfiguration == null) {
+              mergedConfigurations.put(currentType, detachedConfiguration);
+            } else {
+              mergedConfiguration.update(detachedConfiguration);
+            }
+          }
+        }
+      }
+
+      return mergedConfigurations;
+    } else {
+      return configurations;
+    }
+  }
+
+  /**
+   * Adds the specified KerberosConfigurationDescriptor to the list of KerberosConfigurationDescriptors.
+   * <p/>
+   * If an entry exists of the same configuration type, it will be overwritten.
+   *
+   * @param configuration the KerberosConfigurationDescriptor to add
+   */
+  public void putConfiguration(KerberosConfigurationDescriptor configuration) {
+    if (configuration != null) {
+      String type = configuration.getType();
+
+      if (type == null) {
+        throw new IllegalArgumentException("The configuration type must not be null");
+      }
+
+      if (configurations == null) {
+        configurations = new HashMap<String, KerberosConfigurationDescriptor>();
+      }
+
+      configurations.put(type, configuration);
+
+      // Set the configuration's parent to this AbstractKerberosDescriptorContainer
+      configuration.setParent(this);
+    }
+  }
+
+  /**
+   * Returns the requested KerberosConfigurationDescriptor
+   *
+   * @param name a String declaring the name of the descriptor to retrieve
+   * @return the requested KerberosConfigurationDescriptor or null if not found
+   */
+  public KerberosConfigurationDescriptor getConfiguration(String name) {
+    return ((name == null) || (configurations == null)) ? null : configurations.get(name);
+  }
+
+  /**
+   * Test this AbstractKerberosDescriptor to see if it is a container.
+   * <p/>
+   * This implementation always returns true since it implements a descriptor container.
+   *
+   * @return true if this AbstractKerberosDescriptor is a container, false otherwise
+   */
+  public boolean isContainer() {
+    return true;
+  }
+
+  /**
+   * Updates this AbstractKerberosDescriptorContainer using information from the supplied
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * Information from updates will overwrite information in this AbstractKerberosDescriptorContainer.
+   * More specifically, the name of this AbstractKerberosDescriptorContainer may be updated as well
+   * as each individual KerberosIdentityDescriptor and KerberosConfigurationDescriptor contained
+   * within it.  Any new KerberosIdentityDescriptors and KerberosConfigurationDescriptors will be
+   * appended to there appropriate lists.
+   *
+   * @param updates an AbstractKerberosDescriptorContainer containing the updates to this
+   *                AbstractKerberosDescriptorContainer
+   */
+  public void update(AbstractKerberosDescriptorContainer updates) {
+    if (updates != null) {
+      String updatedName = updates.getName();
+      if (updatedName != null) {
+        setName(updatedName);
+      }
+
+      Map<String, KerberosConfigurationDescriptor> updatedConfigurations = updates.getConfigurations();
+      if (updatedConfigurations != null) {
+        for (Map.Entry<String, KerberosConfigurationDescriptor> entry : updatedConfigurations.entrySet()) {
+          KerberosConfigurationDescriptor existingConfiguration = getConfiguration(entry.getKey());
+
+          // Copy this descriptor so we don't alter the hierarchy of existing data we don't intend to change
+          KerberosConfigurationDescriptor clone = new KerberosConfigurationDescriptor(entry.getValue().toMap());
+
+          if (existingConfiguration == null) {
+            putConfiguration(clone);
+          } else {
+            existingConfiguration.update(clone);
+          }
+        }
+      }
+
+      List<KerberosIdentityDescriptor> updatedIdentities = updates.getIdentities();
+      if (updatedIdentities != null) {
+        for (KerberosIdentityDescriptor updatedIdentity : updatedIdentities) {
+          KerberosIdentityDescriptor existing = getIdentity(updatedIdentity.getName());
+
+          // Copy this descriptor so we don't alter the hierarchy of existing data we don't intend to change
+          KerberosIdentityDescriptor clone = new KerberosIdentityDescriptor(updatedIdentity.toMap());
+
+          if (existing == null) {
+            putIdentity(clone);
+          } else {
+            existing.update(clone);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Attempts to find the KerberosIdentityDescriptor at the specified path.
+   * <p/>
+   * The path value is expected to be an "absolute" path through the Kerberos Descriptor hierarchy
+   * to some specific KerberosIdentityDescriptor.  The path must be in one of the following forms:
+   * <p/>
+   * /identity
+   * /service/identity
+   * /service/component/identity
+   *
+   * @param path a String declaring the path to a KerberosIdentityDescriptor
+   * @return a KerberosIdentityDescriptor identified by the path or null if not found
+   */
+  protected KerberosIdentityDescriptor getReferencedIdentityDescriptor(String path)
+      throws AmbariException {
+    KerberosIdentityDescriptor identityDescriptor = null;
+
+    if ((path != null) && path.startsWith("/")) {
+      // The name indicates it is referencing an identity somewhere in the hierarchy... try to find it.
+      // /[<service name>/[<component name>/]]<identity name>
+      String[] pathParts = path.split("/");
+
+      String serviceName = null;
+      String componentName = null;
+      String identityName;
+
+      switch (pathParts.length) {
+        case 3:
+          serviceName = pathParts[0];
+          componentName = pathParts[1];
+          identityName = pathParts[2];
+          break;
+        case 2:
+          serviceName = pathParts[0];
+          identityName = pathParts[1];
+          break;
+        case 1:
+          identityName = pathParts[0];
+          break;
+        default:
+          throw new AmbariException(String.format("Unexpected path length in %s", path));
+      }
+
+      if (identityName != null) {
+        // Start at the top of the hierarchy
+        AbstractKerberosDescriptor descriptor = getRoot();
+
+        if (descriptor != null) {
+          if ((serviceName != null) && !serviceName.isEmpty()) {
+            descriptor = descriptor.getDescriptor(KerberosDescriptorType.SERVICE, serviceName);
+
+            if ((descriptor != null) && (componentName != null) && !componentName.isEmpty()) {
+              descriptor = descriptor.getDescriptor(KerberosDescriptorType.COMPONENT, componentName);
+            }
+          }
+
+          if (descriptor != null) {
+            descriptor = descriptor.getDescriptor(KerberosDescriptorType.IDENTITY, identityName);
+
+            if (descriptor instanceof KerberosIdentityDescriptor) {
+              identityDescriptor = (KerberosIdentityDescriptor) descriptor;
+            }
+          }
+        }
+      }
+    }
+
+    return identityDescriptor;
+
+  }
+
+  /**
+   * Gets the requested AbstractKerberosDescriptor implementation using a type name and a relevant
+   * descriptor name.
+   * <p/>
+   * This implementation handles identity and configuration descriptors within this
+   * AbstractKerberosDescriptorContainer.
+   * <p/>
+   * Implementing classes should override this to handle other types, but call this method to
+   * ensure no data is missed.
+   *
+   * @param type a String indicating the type of the requested descriptor
+   * @param name a String indicating the name of the requested descriptor
+   * @return a AbstractKerberosDescriptor representing the requested descriptor or null if not found
+   */
+  @Override
+  protected AbstractKerberosDescriptor getDescriptor(KerberosDescriptorType type, String name) {
+    if (KerberosDescriptorType.IDENTITY == type) {
+      return getIdentity(name);
+    } else if (KerberosDescriptorType.CONFIGURATION == type) {
+      return getConfiguration(name);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this AbstractKerberosDescriptorContainer
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer}
+   *
+   * @return a Map of values for this AbstractKerberosDescriptorContainer
+   * @see org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = super.toMap();
+
+    if (identities != null) {
+      List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
+      for (KerberosIdentityDescriptor identity : identities) {
+        list.add(identity.toMap());
+      }
+      map.put(KerberosDescriptorType.IDENTITY.getDescriptorPluralName(), list);
+    }
+
+    if (configurations != null) {
+      List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
+      for (KerberosConfigurationDescriptor configuration : configurations.values()) {
+        list.add(configuration.toMap());
+      }
+      map.put(KerberosDescriptorType.CONFIGURATION.getDescriptorPluralName(), list);
+    }
+
+    return map;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getIdentities() == null)
+            ? 0
+            : getIdentities().hashCode()) +
+        ((getConfigurations() == null)
+            ? 0
+            : getConfigurations().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object instanceof AbstractKerberosDescriptorContainer) {
+      AbstractKerberosDescriptorContainer descriptor = (AbstractKerberosDescriptorContainer) object;
+      return super.equals(object) &&
+          (
+              (getIdentities() == null)
+                  ? (descriptor.getIdentities() == null)
+                  : getIdentities().equals(descriptor.getIdentities())
+          ) &&
+          (
+              (getConfigurations() == null)
+                  ? (descriptor.getConfigurations() == null)
+                  : getConfigurations().equals(descriptor.getConfigurations())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 115 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java

@@ -0,0 +1,115 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.util.Map;
+
+/**
+ * KerberosComponentDescriptor implements AbstractKerberosDescriptorContainer. It contains the data
+ * related to a component which include the following properties:
+ * <ul>
+ * <li>name</li>
+ * <li>identities</li>
+ * <li>configurations</li>
+ * </ul>
+ * Example:
+ * <pre>
+ *  {
+ *    "name" : "COMPONENT_NAME",
+ *    "identities" : { ... },
+ *    "configurations" : { ... }
+ *  }
+ *  </pre>
+ */
+
+/**
+ * KerberosComponentDescriptor is an implementation of an AbstractKerberosDescriptorContainer that
+ * encapsulates the details about an Ambari component.
+ * <p/>
+ * A KerberosComponentDescriptor has the following properties:
+ * <ul>
+ * <li>identities ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * <li>configurations ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * </ul>
+ * <p/>
+ * The following (pseudo) JSON Schema will yield a valid KerberosComponentDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosComponentDescriptor",
+ *      "description": "Describes an Ambari component",
+ *      "type": "object",
+ *      "properties": {
+ *        "identities": {
+ *          "description": "A list of Kerberos identity descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosIdentityDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}"
+ *          }
+ *        },
+ *        "configurations": {
+ *          "description": "A list of relevant configuration blocks",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosConfigurationDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}"
+ *          }
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosComponentDescriptor#name value
+ */
+public class KerberosComponentDescriptor extends AbstractKerberosDescriptorContainer {
+
+  /**
+   * Creates a new KerberosComponentDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor} for an
+   * example JSON structure that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor
+   */
+  public KerberosComponentDescriptor(Map<?, ?> data) {
+    super(data);
+
+    // The name for this KerberosComponentDescriptor is stored in the "name" entry in the map
+    // This is not automatically set by the super classes.
+    setName(getStringValue(data, "name"));
+  }
+
+  @Override
+  public int hashCode() {
+    return 35 * super.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    return (object == this) ||
+        (
+            (object != null) &&
+                (object.getClass() == KerberosComponentDescriptor.class) &&
+                super.equals(object)
+        );
+  }
+}

+ 205 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosConfigurationDescriptor.java

@@ -0,0 +1,205 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * KerberosConfigurationDescriptor is an implementation of an AbstractKerberosDescriptor that
+ * encapsulates data related to an Ambari configuration block.
+ * <p/>
+ * A KerberosConfigurationDescriptor has the following properties:
+ * <ul>
+ * <li>type</li>
+ * <li>properties</li>
+ * </ul>
+ * <p/>
+ * The following is an example of a JSON structure that will yield a valid KerberosConfigurationDescriptor
+ * <pre>
+ *   {
+ *    "core-site": {
+ *      "hadoop.security.authentication": "kerberos",
+ *      "hadoop.rpc.protection": "authentication; integrity; privacy",
+ *      "hadoop.security.authorization": "true"
+ *    }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosConfigurationDescriptor#type value
+ */
+public class KerberosConfigurationDescriptor extends AbstractKerberosDescriptor {
+  /**
+   * A Map of the properties in this KerberosConfigurationDescriptor
+   */
+  private Map<String, String> properties = null;
+
+  /**
+   * Creates a new KerberosConfigurationDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor} for an
+   * example JSON structure that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor
+   */
+  public KerberosConfigurationDescriptor(Map<?, ?> data) {
+
+    if (data != null) {
+      Set<?> keySet = data.keySet();
+
+      // Only a single entry is expected...
+      Object key = keySet.iterator().next();
+      if (key != null) {
+        Object object = data.get(key);
+
+        setType(key.toString());
+
+        if (object instanceof Map) {
+          for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
+            Object value = entry.getValue();
+            putProperty(entry.getKey().toString(), (value == null) ? null : value.toString());
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the type of the configuration data represented by this KerberosConfigurationDescriptor
+   *
+   * @return a String declaring the configuration type, i.e., core-site
+   */
+  public String getType() {
+    return getName();
+  }
+
+  /**
+   * Sets the type of the configuration data represented by this KerberosConfigurationDescriptor
+   *
+   * @param type a String declaring the configuration group type, i.e., core-site
+   */
+  public void setType(String type) {
+    setName(type);
+  }
+
+  /**
+   * Gets the properties of the configuration data represented by this KerberosConfigurationDescriptor
+   *
+   * @return a Map of properties
+   */
+  public Map<String, String> getProperties() {
+    return properties;
+  }
+
+  /**
+   * Gets the value of the configuration property with the specified name
+   *
+   * @param name a String declaring the name of the property to retrieve
+   * @return a String or null if the property value is not found
+   */
+  public String getProperty(String name) {
+    return ((name == null) || (properties == null)) ? null : properties.get(name);
+  }
+
+  /**
+   * Adds or updates the value of the configuration property with the specified name
+   * <p/>
+   * If the property exists, it will be overwritten; else a new entry will be created.
+   *
+   * @param name  a String declaring the name of the property to set
+   * @param value a String containing the value of the property
+   */
+  public void putProperty(String name, String value) {
+    if (name == null) {
+      throw new IllegalArgumentException("The property name must not be null");
+    }
+
+    if (properties == null) {
+      properties = new HashMap<String, String>();
+    }
+
+    properties.put(name, value);
+  }
+
+  /**
+   * Updates this KerberosConfigurationDescriptor with data from another KerberosConfigurationDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosConfigurationDescriptor containing the updated values
+   */
+  public void update(KerberosConfigurationDescriptor updates) {
+    if (updates != null) {
+      setType(updates.getType());
+
+      Map<String, String> updatedProperties = updates.getProperties();
+      if (updatedProperties != null) {
+        for (Map.Entry<String, String> entry : updatedProperties.entrySet()) {
+          putProperty(entry.getKey(), entry.getValue());
+        }
+      }
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosConfigurationDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}
+   *
+   * @return a Map of values for this KerberosConfigurationDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    map.put(getName(), (properties == null) ? null : new HashMap<String, Object>(properties));
+    return map;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getProperties() == null)
+            ? 0
+            : getProperties().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosConfigurationDescriptor.class) {
+      KerberosConfigurationDescriptor descriptor = (KerberosConfigurationDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getProperties() == null)
+                  ? (descriptor.getProperties() == null)
+                  : getProperties().equals(descriptor.getProperties())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 351 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java

@@ -0,0 +1,351 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * KerberosDescriptor is an implementation of an AbstractKerberosDescriptorContainer that
+ * encapsulates an entire Kerberos descriptor hierarchy.
+ * <p/>
+ * A KerberosDescriptor has the following properties:
+ * <ul>
+ * <li>services</li>
+ * <li>properties</li>
+ * <li>identities ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * <li>configurations ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * </ul>
+ * <p/>
+ * The following (pseudo) JSON Schema will yield a valid KerberosDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosDescriptor",
+ *      "description": "Describes a Kerberos descriptor",
+ *      "type": "object",
+ *      "properties": {
+ *        "services": {
+ *          "description": "A list of service descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosServiceDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor}"
+ *          }
+ *        },
+ *        "properties": {
+ *          "description": "Global properties that can be used in variable replacements",
+ *          "type": "object",
+ *          "patternProperties": {
+ *            "^[\w\.\_]?$": {}"
+ *          }
+ *        }
+ *        "identities": {
+ *          "description": "A list of Kerberos identity descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosIdentityDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}"
+ *          }
+ *        },
+ *        "configurations": {
+ *          "description": "A list of relevant configuration blocks",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosConfigurationDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}"
+ *          }
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will be null
+ */
+public class KerberosDescriptor extends AbstractKerberosDescriptorContainer {
+
+  /**
+   * A Map of the "global" properties contained within this KerberosDescriptor
+   */
+  private Map<String, String> properties = null;
+
+  /**
+   * A Map of the services contained within this KerberosDescriptor
+   */
+  private Map<String, KerberosServiceDescriptor> services = null;
+
+
+  /**
+   * Given a file containing JSON-formatted text, attempts to create a KerberosDescriptor
+   *
+   * @param file a File pointing to the file containing JSON-formatted text
+   * @return a newly created KerberosDescriptor
+   * @throws FileNotFoundException if the file does not point to a readable file
+   */
+  public static KerberosDescriptor fromFile(File file) throws IOException {
+    return new KerberosDescriptor(parseFile(file));
+  }
+
+  /**
+   * Given a String containing JSON-formatted text, attempts to create a KerberosDescriptor
+   *
+   * @param json a File pointing to the file containing JSON-formatted text
+   * @return a newly created KerberosDescriptor
+   */
+  public static KerberosDescriptor fromJSON(String json) {
+    return new KerberosDescriptor(parseJSON(json));
+  }
+
+  /**
+   * Creates an empty KerberosDescriptor
+   */
+  public KerberosDescriptor() {
+    this(null);
+  }
+
+  /**
+   * Creates a new KerberosDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosDescriptor
+   */
+  public KerberosDescriptor(Map<?, ?> data) {
+    super(data);
+
+    if (data != null) {
+      Object list = data.get(KerberosDescriptorType.SERVICE.getDescriptorPluralName());
+      if (list instanceof Collection) {
+        for (Object item : (Collection) list) {
+          if (item instanceof Map) {
+            putService(new KerberosServiceDescriptor((Map<?, ?>) item));
+          }
+        }
+      }
+
+      Object map = data.get("properties");
+      if (map instanceof Map) {
+        for (Map.Entry<?, ?> entry : ((Map<?, ?>) map).entrySet()) {
+          Object value = entry.getValue();
+          putProperty(entry.getKey().toString(), (value == null) ? null : value.toString());
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a Map of the KerberosServiceDescriptors in this KerberosDescriptor
+   *
+   * @return a Map of String to KerberosServiceDescriptor
+   */
+  public Map<String, KerberosServiceDescriptor> getServices() {
+    return services;
+  }
+
+  /**
+   * Gets the KerberosServiceDescriptor with the specified name
+   *
+   * @param name a String declaring the name of the KerberosServiceDescriptor to retrieve
+   * @return the requested KerberosServiceDescriptor or null if not found
+   */
+  public KerberosServiceDescriptor getService(String name) {
+    return ((name == null) || (services == null)) ? null : services.get(name);
+  }
+
+  /**
+   * Adds or replaces a KerberosServiceDescriptor
+   * <p/>
+   * If a KerberosServiceDescriptor with the same name already exists in the services Map, it will
+   * be replaced; else a new entry will be made.
+   *
+   * @param service the KerberosServiceDescriptor to put
+   */
+  public void putService(KerberosServiceDescriptor service) {
+    if (service != null) {
+      String name = service.getName();
+
+      if (name == null) {
+        throw new IllegalArgumentException("The service name must not be null");
+      }
+
+      if (services == null) {
+        services = new HashMap<String, KerberosServiceDescriptor>();
+      }
+
+      services.put(name, service);
+
+      // Set the service's parent to this KerberosDescriptor
+      service.setParent(this);
+    }
+  }
+
+  /**
+   * Gets the Map of properties for this KerberosDescriptor
+   *
+   * @return a Map of String to String values
+   */
+  public Map<String, String> getProperties() {
+    return properties;
+  }
+
+  /**
+   * Gets the value of the property with the specified name
+   *
+   * @param name a String declaring the name of the property to retrieve
+   * @return a String or null if the property was not found
+   */
+  public String getProperty(String name) {
+    return ((name == null) || (properties == null)) ? null : properties.get(name);
+  }
+
+  /**
+   * Adds or updates a property value
+   * <p/>
+   * If a property exists with the specified name, replaces its value; else adds a new entry.
+   *
+   * @param name  a String declaring the name of the property to put
+   * @param value a String containing the value of the property to put
+   */
+  public void putProperty(String name, String value) {
+    if (name == null) {
+      throw new IllegalArgumentException("The property name must not be null");
+    }
+
+    if (properties == null) {
+      properties = new HashMap<String, String>();
+    }
+
+    properties.put(name, value);
+  }
+
+  /**
+   * Updates this KerberosDescriptor with data from another KerberosDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosDescriptor containing the updated values
+   */
+  public void update(KerberosDescriptor updates) {
+    if (updates != null) {
+      Map<String, KerberosServiceDescriptor> updatedServiceDescriptors = updates.getServices();
+      if (updatedServiceDescriptors != null) {
+        for (Map.Entry<String, KerberosServiceDescriptor> entry : updatedServiceDescriptors.entrySet()) {
+          KerberosServiceDescriptor existing = getService(entry.getKey());
+          if (existing == null) {
+            putService(entry.getValue());
+          } else {
+            existing.update(entry.getValue());
+          }
+        }
+      }
+
+      Map<String, String> updatedProperties = updates.getProperties();
+      if (updatedProperties != null) {
+        for (Map.Entry<String, String> entry : updatedProperties.entrySet()) {
+          putProperty(entry.getKey(), entry.getValue());
+        }
+      }
+    }
+
+    super.update(updates);
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosDescriptor}
+   *
+   * @return a Map of values for this KerberosDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = super.toMap();
+
+    if (services != null) {
+      List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
+      for (KerberosServiceDescriptor service : services.values()) {
+        list.add(service.toMap());
+      }
+      map.put(KerberosDescriptorType.SERVICE.getDescriptorPluralName(), list);
+    }
+
+    if (properties != null) {
+      map.put("properties", new HashMap<String, String>(properties));
+    }
+
+    return map;
+  }
+
+  /**
+   * Sets the parent (or container) of this descriptor
+   * <p/>
+   * This implementation prevents the parent from being externally set by always throwing an
+   * UnsupportedOperationException.
+   *
+   * @param parent an AbstractKerberosDescriptor representing the parent (or container) of this
+   */
+  @Override
+  public void setParent(AbstractKerberosDescriptor parent) {
+    throw new UnsupportedOperationException("This KerberosDescriptor may not have a parent assigned to it.");
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getProperties() == null)
+            ? 0
+            : getProperties().hashCode()) +
+        ((getServices() == null)
+            ? 0
+            : getServices().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosDescriptor.class) {
+      KerberosDescriptor descriptor = (KerberosDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getProperties() == null)
+                  ? (descriptor.getProperties() == null)
+                  : getProperties().equals(descriptor.getProperties())
+          ) &&
+          (
+              (getServices() == null)
+                  ? (descriptor.getServices() == null)
+                  : getServices().equals(descriptor.getServices())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 54 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptorType.java

@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.state.kerberos;
+
+public enum KerberosDescriptorType {
+  SERVICE("service", "services"),
+  COMPONENT("component", "components"),
+  IDENTITY("identity", "identities"),
+  PRINCIPAL("principal", "principals"),
+  KEYTAB("keytab", "keytabs"),
+  CONFIGURATION("configuration", "configurations");
+
+  private final String descriptorName;
+  private final String descriptorPluralName;
+
+  private KerberosDescriptorType(String descriptorName, String descriptorPluralName) {
+    this.descriptorName = descriptorName;
+    this.descriptorPluralName = descriptorPluralName;
+  }
+
+  /**
+   * Gets the identifying name for this KerberosDescriptorType
+   *
+   * @return a String declaring the identifying name for this KerberosDescriptorType
+   */
+  public String getDescriptorName() {
+    return descriptorName;
+  }
+
+  /**
+   * Gets the identifying name for a group of this KerberosDescriptorType
+   *
+   * @return a String declaring the identifying name for a group of this KerberosDescriptorType
+   */
+  public String getDescriptorPluralName() {
+    return descriptorPluralName;
+  }
+}

+ 278 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java

@@ -0,0 +1,278 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.util.Map;
+
+/**
+ * KerberosIdentityDescriptor is an implementation of an AbstractKerberosDescriptor that
+ * encapsulates data related to a Kerberos identity - including its principal and keytab file details.
+ * <p/>
+ * A KerberosIdentityDescriptor has the following properties:
+ * <ul>
+ * <li>name</li>
+ * <li>principal</li>
+ * <li>keytab</li>
+ * <li>password</li>
+ * </ul>
+ * <p/>
+ * The following (pseudo) JSON Schema will yield a valid KerberosIdentityDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosIdentityDescriptor",
+ *      "description": "Describes a Kerberos identity",
+ *      "type": "object",
+ *      "properties": {
+ *        "name": {
+ *          "description": "An identifying name for this identity. The name may reference another
+ *                          KerberosIdentityDescriptor by declaring the path to it",
+ *          "type": "string"
+ *        },
+ *        "principal": {
+ *          "description": "Details about this identity's principal",
+ *          "type": "{@link org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor}",
+ *        }
+ *        "keytab": {
+ *          "description": "Details about this identity's keytab",
+ *          "type": "{@link org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor}",
+ *          }
+ *        }
+ *        "password": {
+ *          "description": "The password to use for this identity. If not set a secure random
+ *                          password will automatically be generated",
+ *          "type": "string"
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosIdentityDescriptor#name value
+ */
+public class KerberosIdentityDescriptor extends AbstractKerberosDescriptor {
+
+  /**
+   * The KerberosPrincipalDescriptor containing the principal details for this Kerberos identity
+   */
+  private KerberosPrincipalDescriptor principal = null;
+
+  /**
+   * The KerberosKeytabDescriptor containing the keytab details for this Kerberos identity
+   */
+  private KerberosKeytabDescriptor keytab = null;
+
+  /**
+   * A String containing the password for this Kerberos identity
+   * <p/>
+   * If this value is null or empty, a random password will be generated as necessary.
+   */
+  private String password = null;
+
+  /**
+   * Creates a new KerberosIdentityDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor
+   */
+  public KerberosIdentityDescriptor(Map<?, ?> data) {
+    // The name for this KerberosIdentityDescriptor is stored in the "name" entry in the map
+    // This is not automatically set by the super classes.
+    setName(getStringValue(data, "name"));
+
+    if (data != null) {
+      Object item;
+
+      setPassword(getStringValue(data, "password"));
+
+      item = data.get(KerberosDescriptorType.PRINCIPAL.getDescriptorName());
+      if (item instanceof Map) {
+        setPrincipalDescriptor(new KerberosPrincipalDescriptor((Map<?, ?>) item));
+      }
+
+      item = data.get(KerberosDescriptorType.KEYTAB.getDescriptorName());
+      if (item instanceof Map) {
+        setKeytabDescriptor(new KerberosKeytabDescriptor((Map<?, ?>) item));
+      }
+    }
+  }
+
+  /**
+   * Gets the KerberosPrincipalDescriptor related to this KerberosIdentityDescriptor
+   *
+   * @return the KerberosPrincipalDescriptor related to this KerberosIdentityDescriptor
+   */
+  public KerberosPrincipalDescriptor getPrincipalDescriptor() {
+    return principal;
+  }
+
+  /**
+   * Sets the KerberosPrincipalDescriptor related to this KerberosIdentityDescriptor
+   *
+   * @param principal the KerberosPrincipalDescriptor related to this KerberosIdentityDescriptor
+   */
+  public void setPrincipalDescriptor(KerberosPrincipalDescriptor principal) {
+    this.principal = principal;
+
+    if (this.principal != null) {
+      this.principal.setParent(this);
+    }
+  }
+
+  /**
+   * Gets the KerberosKeytabDescriptor related to this KerberosIdentityDescriptor
+   *
+   * @return the KerberosKeytabDescriptor related to this KerberosIdentityDescriptor
+   */
+  public KerberosKeytabDescriptor getKeytabDescriptor() {
+    return keytab;
+  }
+
+  /**
+   * Sets the KerberosKeytabDescriptor related to this KerberosIdentityDescriptor
+   *
+   * @param keytab the KerberosKeytabDescriptor related to this KerberosIdentityDescriptor
+   */
+  public void setKeytabDescriptor(KerberosKeytabDescriptor keytab) {
+    this.keytab = keytab;
+
+    if (this.keytab != null) {
+      this.keytab.setParent(this);
+    }
+  }
+
+  /**
+   * Gets the password for this this KerberosIdentityDescriptor
+   *
+   * @return A String containing the password for this this KerberosIdentityDescriptor
+   * @see #password
+   */
+  public String getPassword() {
+    return password;
+  }
+
+  /**
+   * Sets the password for this this KerberosIdentityDescriptor
+   *
+   * @param password A String containing the password for this this KerberosIdentityDescriptor
+   * @see #password
+   */
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  /**
+   * Updates this KerberosIdentityDescriptor with data from another KerberosIdentityDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosIdentityDescriptor containing the updated values
+   */
+  public void update(KerberosIdentityDescriptor updates) {
+    if (updates != null) {
+      setName(updates.getName());
+
+      setPassword(updates.getPassword());
+
+      KerberosPrincipalDescriptor existingPrincipal = getPrincipalDescriptor();
+      if (existingPrincipal == null) {
+        setPrincipalDescriptor(updates.getPrincipalDescriptor());
+      } else {
+        existingPrincipal.update(updates.getPrincipalDescriptor());
+      }
+
+      KerberosKeytabDescriptor existingKeytabDescriptor = getKeytabDescriptor();
+      if (existingKeytabDescriptor == null) {
+        setKeytabDescriptor(updates.getKeytabDescriptor());
+      } else {
+        existingKeytabDescriptor.update(updates.getKeytabDescriptor());
+      }
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosIdentityDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}
+   *
+   * @return a Map of values for this KerberosIdentityDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> dataMap = super.toMap();
+
+    if (principal != null) {
+      dataMap.put(KerberosDescriptorType.PRINCIPAL.getDescriptorName(), principal.toMap());
+    }
+
+    if (keytab != null) {
+      dataMap.put(KerberosDescriptorType.KEYTAB.getDescriptorName(), keytab.toMap());
+    }
+
+    if (password != null) {
+      dataMap.put("password", password);
+    }
+
+    return dataMap;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getPrincipalDescriptor() == null)
+            ? 0
+            : getPrincipalDescriptor().hashCode()) +
+        ((getKeytabDescriptor() == null)
+            ? 0
+            : getKeytabDescriptor().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosIdentityDescriptor.class) {
+      KerberosIdentityDescriptor descriptor = (KerberosIdentityDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getPrincipalDescriptor() == null)
+                  ? (descriptor.getPrincipalDescriptor() == null)
+                  : getPrincipalDescriptor().equals(descriptor.getPrincipalDescriptor())
+          ) &&
+          (
+              (getKeytabDescriptor() == null)
+                  ? (descriptor.getKeytabDescriptor() == null)
+                  : getKeytabDescriptor().equals(descriptor.getKeytabDescriptor())
+          ) &&
+          (
+              (getPassword() == null)
+                  ? (descriptor.getPassword() == null)
+                  : getPassword().equals(descriptor.getPassword())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 445 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosKeytabDescriptor.java

@@ -0,0 +1,445 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * KerberosKeytabDescriptor is an implementation of an AbstractKerberosDescriptor that
+ * encapsulates data related to a Kerberos keytab file.  This class is typically associated with a
+ * KerberosPrincipalDescriptor via a KerberosIdentityDescriptor.
+ * <p/>
+ * A KerberosKeytabDescriptor has the following properties:
+ * <ul>
+ * <li>file</li>
+ * <li>owner {name, access}</li>
+ * <li>group {name, access}</li>
+ * <li>configuration</li>
+ * </ul>
+ * <p/>
+ * The following JSON Schema will yield a valid KerberosPrincipalDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosKeytabDescriptor",
+ *      "description": "Describes a Kerberos keytab file and associated details",
+ *      "type": "object",
+ *      "properties": {
+ *        "file": {
+ *          "description": "The absolute path for the keytab file",
+ *          "type": "string"
+ *        },
+ *        "owner": {
+ *          "description": "Details about the file's user ownership",
+ *          "type": "object",
+ *          "properties": {
+ *            "name": {
+ *              "description": "The local username that should be set as the owner of this file",
+ *              "type": "string"
+ *            },
+ *            "access": {
+ *              "description": "The relevant access permissions that should be set for the owner of
+ *                              this file. Expected values are 'rw', 'r', ''"
+ *              "type": "string"
+ *            }
+ *          }
+ *        }
+ *        "group": {
+ *          "description": "Details about the file's group ownership",
+ *          "type": "object",
+ *          "properties": {
+ *            "name": {
+ *              "description": "The local group name that should be set as the group owner of this file",
+ *              "type": "string"
+ *            },
+ *            "access": {
+ *              "description": "The relevant access permissions that should be set for the group
+ *                              owner of this file. Expected values are 'rw', 'r', ''"
+ *              "type": "string"
+ *            }
+ *          }
+ *        }
+ *        "configuration": {
+ *          "description": "The configuration type and property name indicating the property to be
+ *                          updated with the generated absolute path to the keytab file
+ *                          - format: config-type/property.name",
+ *          "type": "string"
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosKeytabDescriptor#file value
+ */
+public class KerberosKeytabDescriptor extends AbstractKerberosDescriptor {
+
+  /**
+   * A String declaring the local username that should be set as the owner of the keytab file
+   */
+  private String ownerName = null;
+
+  /**
+   * A String declaring the access permissions that should be set on the keytab file related to the
+   * owner.
+   * <p/>
+   * Expected values are:
+   * <ul>
+   * <li>"rw" - read/write</li>
+   * <li>"r" - read-only</li>
+   * <li>"" - no access</li>
+   * </ul>
+   */
+  private String ownerAccess = null;
+
+  /**
+   * A String declaring the local groip name that should be set as the group owner of the keytab file
+   */
+  private String groupName = null;
+
+  /**
+   * A String declaring the access permissions that should be set on the keytab file related to the
+   * group.
+   * <p/>
+   * Expected values are:
+   * <ul>
+   * <li>"rw" - read/write</li>
+   * <li>"r" - read-only</li>
+   * <li>"" - no access</li>
+   * </ul>
+   */
+  private String groupAccess = null;
+
+  /**
+   * A string declaring configuration type and property name indicating the property to be updated
+   * with the absolute path to the keytab file
+   * <p/>
+   * This String is expected to be in the following format: configuration-type/property.name, where
+   * <ul>
+   * <li>configuration-type is the configuration file type where the property exists</li>
+   * <li>property.value is the name of the relevant property within the configuration</li>
+   * </ul>
+   * <p/>
+   * Example: hdfs-site/dfs.namenode.keytab.file
+   */
+  private String configuration = null;
+
+
+  /**
+   * Creates a new KerberosKeytabDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor
+   */
+  public KerberosKeytabDescriptor(Map<?, ?> data) {
+    // The name for this KerberosKeytabDescriptor is stored in the "file" entry in the map
+    // This is not automatically set by the super classes.
+    setName(getStringValue(data, "file"));
+
+    if (data != null) {
+      Object object;
+
+      object = data.get("owner");
+      if (object instanceof Map) {
+        Map<?, ?> map = (Map<?, ?>) object;
+        setOwnerName(getStringValue(map, "name"));
+        setOwnerAccess(getStringValue(map, "access"));
+      }
+
+      object = data.get("group");
+      if (object instanceof Map) {
+        Map<?, ?> map = (Map<?, ?>) object;
+        setGroupName(getStringValue(map, "name"));
+        setGroupAccess(getStringValue(map, "access"));
+      }
+
+      setConfiguration(getStringValue(data, "configuration"));
+    }
+  }
+
+  /**
+   * Gets the path to the keytab file
+   * <p/>
+   * The value may include variable placeholders to be replaced as needed
+   * <ul>
+   * <li>
+   * ${variable} placeholders are replaced on the server - see
+   * {@link org.apache.ambari.server.state.kerberos.KerberosDescriptor#replaceVariables(String, java.util.Map)}
+   * </li>
+   * </ul>
+   *
+   * @return a String declaring the keytab file's absolute path
+   * @see org.apache.ambari.server.state.kerberos.KerberosDescriptor#replaceVariables(String, java.util.Map)
+   */
+  public String getFile() {
+    return getName();
+  }
+
+  /**
+   * Sets the path to the keytab file
+   *
+   * @param file a String declaring this keytab's file path
+   * @see #getFile()
+   */
+  public void setFile(String file) {
+    setName(file);
+  }
+
+  /**
+   * Gets the local username to set as the owner of the keytab file
+   *
+   * @return a String declaring the name of the user to own the keytab file
+   */
+  public String getOwnerName() {
+    return ownerName;
+  }
+
+  /**
+   * Sets the local username to set as the owner of the keytab file
+   *
+   * @param name a String declaring the name of the user to own the keytab file
+   */
+  public void setOwnerName(String name) {
+    this.ownerName = name;
+  }
+
+  /**
+   * Gets the access permissions that should be set on the keytab file related to the file's owner
+   *
+   * @return a String declaring the access permissions that should be set on the keytab file related
+   * to the file's owner
+   * @see #ownerAccess
+   */
+  public String getOwnerAccess() {
+    return ownerAccess;
+  }
+
+  /**
+   * Sets the access permissions that should be set on the keytab file related to the file's owner
+   *
+   * @param access a String declaring the access permissions that should be set on the keytab file
+   *               related to the file's owner
+   * @see #ownerAccess
+   */
+  public void setOwnerAccess(String access) {
+    this.ownerAccess = access;
+  }
+
+  /**
+   * Gets the local group name to set as the group owner of the keytab file
+   *
+   * @return a String declaring the name of the group to own the keytab file
+   */
+  public String getGroupName() {
+    return groupName;
+  }
+
+  /**
+   * Sets the local group name to set as the group owner of the keytab file
+   *
+   * @param name a String declaring the name of the group to own the keytab file
+   */
+  public void setGroupName(String name) {
+    this.groupName = name;
+  }
+
+  /**
+   * Gets the access permissions that should be set on the keytab file related to the file's group
+   *
+   * @return a String declaring the access permissions that should be set on the keytab file related
+   * to the file's group
+   * @see #groupAccess
+   */
+  public String getGroupAccess() {
+    return groupAccess;
+  }
+
+  /**
+   * Sets the access permissions that should be set on the keytab file related to the file's group
+   *
+   * @param access a String declaring the access permissions that should be set on the keytab file
+   *               related to the file's group
+   * @see #groupAccess
+   */
+  public void setGroupAccess(String access) {
+    this.groupAccess = access;
+  }
+
+  /**
+   * Gets the configuration type and property name indicating the property to be updated with the
+   * keytab's file path
+   *
+   * @return a String declaring the configuration type and property name indicating the property to
+   * be updated with the keytab's file path
+   * #see #configuration
+   */
+  public String getConfiguration() {
+    return configuration;
+  }
+
+  /**
+   * Sets the configuration type and property name indicating the property to be updated with the
+   * keytab's file path
+   *
+   * @param configuration a String declaring the configuration type and property name indicating the
+   *                      property to be updated with the keytab's file path
+   * @see #configuration
+   */
+  public void setConfiguration(String configuration) {
+    this.configuration = configuration;
+  }
+
+  /**
+   * Updates this KerberosKeytabDescriptor with data from another KerberosKeytabDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosKeytabDescriptor containing the updated values
+   */
+  public void update(KerberosKeytabDescriptor updates) {
+    if (updates != null) {
+      String updatedValue;
+
+      updatedValue = updates.getFile();
+      if (updatedValue != null) {
+        setFile(updatedValue);
+      }
+
+      updatedValue = updates.getConfiguration();
+      if (updatedValue != null) {
+        setConfiguration(updatedValue);
+      }
+
+      updatedValue = updates.getOwnerName();
+      if (updatedValue != null) {
+        setOwnerName(updatedValue);
+      }
+
+      updatedValue = updates.getOwnerAccess();
+      if (updatedValue != null) {
+        setOwnerAccess(updatedValue);
+      }
+
+      updatedValue = updates.getGroupName();
+      if (updatedValue != null) {
+        setGroupName(updatedValue);
+      }
+
+      updatedValue = updates.getGroupAccess();
+      if (updatedValue != null) {
+        setGroupAccess(updatedValue);
+      }
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosKeytabDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor}
+   *
+   * @return a Map of values for this KerberosKeytabDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = new HashMap<String, Object>();
+
+    map.put("file", getFile());
+
+    map.put("owner", new HashMap<String, Object>() {{
+      put("name", getOwnerName());
+      put("access", getOwnerAccess());
+    }});
+
+    map.put("group", new HashMap<String, Object>() {{
+      put("name", getGroupName());
+      put("access", getGroupAccess());
+    }});
+
+    map.put("configuration", getConfiguration());
+
+    return map;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getConfiguration() == null)
+            ? 0
+            : getConfiguration().hashCode()) +
+        ((getOwnerName() == null)
+            ? 0
+            : getOwnerName().hashCode()) +
+        ((getOwnerAccess() == null)
+            ? 0
+            : getOwnerAccess().hashCode()) +
+        ((getGroupName() == null)
+            ? 0
+            : getGroupName().hashCode()) +
+        ((getGroupAccess() == null)
+            ? 0
+            : getGroupAccess().hashCode()) +
+        ((getConfiguration() == null)
+            ? 0
+            : getConfiguration().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosKeytabDescriptor.class) {
+      KerberosKeytabDescriptor descriptor = (KerberosKeytabDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getConfiguration() == null)
+                  ? (descriptor.getConfiguration() == null)
+                  : getConfiguration().equals(descriptor.getConfiguration())
+          ) &&
+          (
+              (getOwnerName() == null)
+                  ? (descriptor.getOwnerName() == null)
+                  : getOwnerName().equals(descriptor.getOwnerName())
+          ) &&
+          (
+              (getOwnerAccess() == null)
+                  ? (descriptor.getOwnerAccess() == null)
+                  : getOwnerAccess().equals(descriptor.getOwnerAccess())
+          ) &&
+          (
+              (getGroupName() == null)
+                  ? (descriptor.getGroupName() == null)
+                  : getGroupName().equals(descriptor.getGroupName())
+          ) &&
+          (
+              (getGroupAccess() == null)
+                  ? (descriptor.getGroupAccess() == null)
+                  : getGroupAccess().equals(descriptor.getGroupAccess())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 212 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosPrincipalDescriptor.java

@@ -0,0 +1,212 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * KerberosPrincipalDescriptor is an implementation of an AbstractKerberosDescriptor that
+ * encapsulates data related to a Kerberos principal.  This class is typically associated with the
+ * KerberosKeytabDescriptor via a KerberosIdentityDescriptor.
+ * <p/>
+ * A KerberosPrincipalDescriptor has the following properties:
+ * <ul>
+ * <li>value</li>
+ * <li>configuration</li>
+ * </ul>
+ * <p/>
+ * The following JSON Schema will yield a valid KerberosPrincipalDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosIdentityDescriptor",
+ *      "description": "Describes a Kerberos principal and associated details",
+ *      "type": "object",
+ *      "properties": {
+ *        "value": {
+ *          "description": "The pattern to use to generate the principal",
+ *          "type": "string"
+ *        },
+ *        "configuration": {
+ *          "description": "The configuration type and property name indicating the property to be
+ *                          updated with the generated principal - format: config-type/property.name",
+ *          "type": "string"
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosPrincipalDescriptor#value value
+ */
+public class KerberosPrincipalDescriptor extends AbstractKerberosDescriptor {
+
+  /**
+   * A string declaring configuration type and property name indicating the property to be updated
+   * with the generated principal
+   * <p/>
+   * This String is expected to be in the following format: configuration-type/property.name, where
+   * <ul>
+   * <li>configuration-type is the configuration file type where the property exists</li>
+   * <li>property.value is the name of the relevant property within the configuration</li>
+   * </ul>
+   * <p/>
+   * Example: hdfs-site/dfs.namenode.kerberos.principal
+   */
+  private String configuration;
+
+  /**
+   * Creates a new KerberosPrincipalDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor
+   */
+  public KerberosPrincipalDescriptor(Map<?, ?> data) {
+    // The name for this KerberosPrincipalDescriptor is stored in the "value" entry in the map
+    // This is not automatically set by the super classes.
+    setName(getStringValue(data, "value"));
+
+    setConfiguration(getStringValue(data, "configuration"));
+  }
+
+  /**
+   * Gets the value (or principal name pattern) for this KerberosPrincipalDescriptor
+   * <p/>
+   * The value may include variable placeholders to be replaced as needed
+   * <ul>
+   * <li>
+   * ${variable} placeholders are replaced on the server - see
+   * {@link org.apache.ambari.server.state.kerberos.KerberosDescriptor#replaceVariables(String, java.util.Map)}
+   * </li>
+   * <li>the _HOST placeholder is replaced on the hosts to dynamically populate the relevant hostname</li>
+   * </ul>
+   *
+   * @return a String declaring this principal's value
+   * @see org.apache.ambari.server.state.kerberos.KerberosDescriptor#replaceVariables(String, java.util.Map)
+   */
+  public String getValue() {
+    return getName();
+  }
+
+  /**
+   * Sets the value (or principal name pattern) for this KerberosPrincipalDescriptor
+   *
+   * @param value a String declaring this principal's value
+   * @see #getValue()
+   */
+  public void setValue(String value) {
+    setName(value);
+  }
+
+  /**
+   * Gets the configuration type and property name indicating the property to be updated with the
+   * generated principal
+   *
+   * @return a String declaring the configuration type and property name indicating the property to
+   * be updated with the generated principal
+   * #see #configuration
+   */
+  public String getConfiguration() {
+    return configuration;
+  }
+
+  /**
+   * Sets the configuration type and property name indicating the property to be updated with the
+   * generated principal
+   *
+   * @param configuration a String declaring the configuration type and property name indicating the
+   *                      property to be updated with the generated principal
+   * @see #configuration
+   */
+  public void setConfiguration(String configuration) {
+    this.configuration = configuration;
+  }
+
+  /**
+   * Updates this KerberosPrincipalDescriptor with data from another KerberosPrincipalDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosPrincipalDescriptor containing the updated values
+   */
+  public void update(KerberosPrincipalDescriptor updates) {
+    if (updates != null) {
+      String updatedValue;
+
+      updatedValue = updates.getValue();
+      if (updatedValue != null) {
+        setValue(updatedValue);
+      }
+
+      updatedValue = updates.getConfiguration();
+      if (updatedValue != null) {
+        setConfiguration(updatedValue);
+      }
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosPrincipalDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor}
+   *
+   * @return a Map of values for this KerberosPrincipalDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = new HashMap<String, Object>();
+
+    map.put("value", getValue());
+    map.put("configuration", getConfiguration());
+
+    return map;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getConfiguration() == null)
+            ? 0
+            : getConfiguration().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosPrincipalDescriptor.class) {
+      KerberosPrincipalDescriptor descriptor = (KerberosPrincipalDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getConfiguration() == null)
+                  ? (descriptor.getConfiguration() == null)
+                  : getConfiguration().equals(descriptor.getConfiguration())
+          );
+    } else {
+      return false;
+    }
+  }
+}

+ 318 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java

@@ -0,0 +1,318 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents information required to configure Kerberos for a particular service.
+ * <p/>
+ * The data map is expected to have the following properties:
+ * <ul>
+ * <li>name</li>
+ * <li>components</li>
+ * <li>identities</li>
+ * <li>configurations</li>
+ * </ul>
+ * Example:
+ * <pre>
+ *  "name" => "SERVICE",
+ *  "identities" => Collection&lt;Map&lt;String, Object&gt;&gt;
+ *  "components" => Collection&lt;Map&lt;String, Object&gt;&gt;
+ *  "configurations" => Collection&lt;Map&lt;String, Object&gt;&gt;
+ * </pre>
+ */
+
+/**
+ * KerberosServiceDescriptor is an implementation of an AbstractKerberosDescriptorContainer that
+ * encapsulates an Ambari service and it components.
+ * <p/>
+ * A KerberosServiceDescriptor has the following properties:
+ * <ul>
+ * <li>name</li>
+ * <li>components</li>
+ * <li>identities ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * <li>configurations ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
+ * </ul>
+ * <p/>
+ * The following (pseudo) JSON Schema will yield a valid KerberosServiceDescriptor
+ * <pre>
+ *   {
+ *      "$schema": "http://json-schema.org/draft-04/schema#",
+ *      "title": "KerberosServiceDescriptor",
+ *      "description": "Describes an Ambari service",
+ *      "type": "object",
+ *      "properties": {
+ *        "name": {
+ *          "description": "An identifying name for this service descriptor.",
+ *          "type": "string"
+ *        },
+ *        "components": {
+ *          "description": "A list of Ambari component descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosComponentDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor}"
+ *          }
+ *        },
+ *        "identities": {
+ *          "description": "A list of Kerberos identity descriptors",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosIdentityDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}"
+ *          }
+ *        },
+ *        "configurations": {
+ *          "description": "A list of relevant configuration blocks",
+ *          "type": "array",
+ *          "items": {
+ *            "title": "KerberosConfigurationDescriptor"
+ *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}"
+ *          }
+ *        }
+ *      }
+ *   }
+ * </pre>
+ * <p/>
+ * In this implementation,
+ * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
+ * KerberosServiceDescriptor#name value.
+ */
+public class KerberosServiceDescriptor extends AbstractKerberosDescriptorContainer {
+
+  /**
+   * A Map of the components contained within this KerberosServiceDescriptor
+   */
+  private Map<String, KerberosComponentDescriptor> components;
+
+  /**
+   * Creates a new KerberosServiceDescriptor
+   *
+   * @param name a String declaring this service's name
+   * @param file a JSON-formatted file containing this service's descriptor data
+   * @throws FileNotFoundException if the descriptor file is not found
+   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
+   */
+  public static KerberosServiceDescriptor fromFile(String name, File file) throws IOException {
+    return new KerberosServiceDescriptor(name, parseFile(file));
+  }
+
+  /**
+   * Creates a new KerberosServiceDescriptor
+   *
+   * @param name a String declaring this service's name
+   * @param json a JSON-formatted String containing this service's descriptor data
+   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
+   */
+  public static KerberosServiceDescriptor fromJSON(String name, String json) {
+    return new KerberosServiceDescriptor(name, parseJSON(json));
+  }
+
+  /**
+   * Creates a new KerberosServiceDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
+   */
+  public KerberosServiceDescriptor(Map<?, ?> data) {
+    // The name for this KerberosServiceDescriptor is stored in the "name" entry in the map
+    // This is not automatically set by the super classes.
+    this(getStringValue(data, "name"), data);
+  }
+
+  /**
+   * Creates a new KerberosServiceDescriptor
+   * <p/>
+   * See {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor} for the JSON
+   * Schema that may be used to generate this map.
+   *
+   * @param name a String declaring this service's name
+   * @param data a Map of values use to populate the data for the new instance
+   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
+   */
+  public KerberosServiceDescriptor(String name, Map<?, ?> data) {
+    super(data);
+
+    // This is not automatically set by the super classes.
+    setName(name);
+
+    if (data != null) {
+      Object list = data.get(KerberosDescriptorType.COMPONENT.getDescriptorPluralName());
+      if (list instanceof Collection) {
+        // Assume list is Collection<Map<String, Object>>
+        for (Object item : (Collection) list) {
+          if (item instanceof Map) {
+            putComponent(new KerberosComponentDescriptor((Map<?, ?>) item));
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a Map of the KerberosComponentDescriptors related to this KerberosServiceDescriptor
+   *
+   * @return a Map of String to KerberosComponentDescriptor
+   */
+  public Map<String, KerberosComponentDescriptor> getComponents() {
+    return components;
+  }
+
+  /**
+   * Returns the KerberosComponentDescriptors with the specified name
+   *
+   * @param name the name of the component for which to retrieve a descriptor
+   * @return the KerberosComponentDescriptor for the requested component or null if not found
+   */
+  public KerberosComponentDescriptor getComponent(String name) {
+    return ((name == null) || (components == null)) ? null : components.get(name);
+  }
+
+  /**
+   * Adds or replaces a KerberosComponentDescriptor
+   * <p/>
+   * If a KerberosComponentDescriptor with the same name already exists in the components Map, it
+   * will be replaced; else a new entry will be made.
+   *
+   * @param component the KerberosComponentDescriptor to put
+   */
+  public void putComponent(KerberosComponentDescriptor component) {
+    if (component != null) {
+      String name = component.getName();
+
+      if (name == null) {
+        throw new IllegalArgumentException("The component name must not be null");
+      }
+
+      if (components == null) {
+        components = new HashMap<String, KerberosComponentDescriptor>();
+      }
+
+      components.put(name, component);
+      component.setParent(this);
+    }
+  }
+
+  /**
+   * Updates this KerberosServiceDescriptor with data from another KerberosServiceDescriptor
+   * <p/>
+   * Properties will be updated if the relevant updated values are not null.
+   *
+   * @param updates the KerberosServiceDescriptor containing the updated values
+   */
+  public void update(KerberosServiceDescriptor updates) {
+    if (updates != null) {
+      Map<String, KerberosComponentDescriptor> updatedComponents = updates.getComponents();
+      if (updatedComponents != null) {
+        for (Map.Entry<String, KerberosComponentDescriptor> entry : updatedComponents.entrySet()) {
+          KerberosComponentDescriptor existing = getComponent(entry.getKey());
+          if (existing == null) {
+            putComponent(entry.getValue());
+          } else {
+            existing.update(entry.getValue());
+          }
+        }
+      }
+    }
+
+    super.update(updates);
+  }
+
+  /**
+   * Gets the requested AbstractKerberosDescriptor implementation using a type name and a relevant
+   * descriptor name.
+   * <p/>
+   * This implementation handles component descriptors and relies on the
+   * AbstractKerberosDescriptorContainer implementation to handle other types.
+   *
+   * @param type a String indicating the type of the requested descriptor
+   * @param name a String indicating the name of the requested descriptor
+   * @return a AbstractKerberosDescriptor representing the requested descriptor or null if not found
+   */
+  @Override
+  protected AbstractKerberosDescriptor getDescriptor(KerberosDescriptorType type, String name) {
+    if (KerberosDescriptorType.COMPONENT == type) {
+      return getComponent(name);
+    } else {
+      return super.getDescriptor(type, name);
+    }
+  }
+
+  /**
+   * Creates a Map of values that can be used to create a copy of this KerberosServiceDescriptor
+   * or generate the JSON structure described in
+   * {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor}
+   *
+   * @return a Map of values for this KerberosServiceDescriptor
+   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
+   */
+  @Override
+  public Map<String, Object> toMap() {
+    Map<String, Object> map = super.toMap();
+
+    if (components != null) {
+      List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
+      for (KerberosComponentDescriptor component : components.values()) {
+        list.add(component.toMap());
+      }
+      map.put(KerberosDescriptorType.COMPONENT.getDescriptorPluralName(), list);
+    }
+
+    return map;
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() +
+        ((getComponents() == null)
+            ? 0
+            : getComponents().hashCode());
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null) {
+      return false;
+    } else if (object == this) {
+      return true;
+    } else if (object.getClass() == KerberosServiceDescriptor.class) {
+      KerberosServiceDescriptor descriptor = (KerberosServiceDescriptor) object;
+      return super.equals(object) &&
+          (
+              (getComponents() == null)
+                  ? (descriptor.getComponents() == null)
+                  : getComponents().equals(descriptor.getComponents())
+          );
+    } else {
+      return false;
+    }
+  }
+
+}
+

+ 206 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptorTest.java

@@ -0,0 +1,206 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KerberosComponentDescriptorTest {
+  public static final String JSON_VALUE =
+      " {" +
+          "  \"name\": \"COMPONENT_NAME\"," +
+          "  \"identities\": [" +
+          KerberosIdentityDescriptorTest.JSON_VALUE +
+          "]," +
+          "  \"configurations\": [" +
+          "    {" +
+          "      \"service-site\": {" +
+          "        \"service.component.property1\": \"value1\"," +
+          "        \"service.component.property2\": \"value2\"" +
+          "      }" +
+          "    }" +
+          "  ]" +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("name", "A_DIFFERENT_COMPONENT_NAME");
+          put(KerberosDescriptorType.IDENTITY.getDescriptorPluralName(), new ArrayList<Object>() {{
+            add(KerberosIdentityDescriptorTest.MAP_VALUE);
+            add(KerberosIdentityDescriptorTest.MAP_VALUE_ALT);
+            add(KerberosIdentityDescriptorTest.MAP_VALUE_REFERENCE);
+          }});
+          put(KerberosDescriptorType.CONFIGURATION.getDescriptorPluralName(), new ArrayList<Map<String, Object>>() {{
+            add(new HashMap<String, Object>() {
+              {
+                put("service-site", new HashMap<String, String>() {
+                  {
+                    put("service.component.property1", "red");
+                    put("service.component.property", "green");
+                  }
+                });
+              }
+            });
+          }});
+        }
+      };
+
+  public static void validateFromJSON(KerberosComponentDescriptor componentDescriptor) {
+    Assert.assertNotNull(componentDescriptor);
+    Assert.assertTrue(componentDescriptor.isContainer());
+
+    Assert.assertEquals("COMPONENT_NAME", componentDescriptor.getName());
+
+    List<KerberosIdentityDescriptor> identities = componentDescriptor.getIdentities();
+
+    Assert.assertNotNull(identities);
+    Assert.assertEquals(1, identities.size());
+
+    Map<String, KerberosConfigurationDescriptor> configurations = componentDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("value1", properties.get("service.component.property1"));
+    Assert.assertEquals("value2", properties.get("service.component.property2"));
+  }
+
+  public static void validateFromMap(KerberosComponentDescriptor componentDescriptor) {
+    Assert.assertNotNull(componentDescriptor);
+    Assert.assertTrue(componentDescriptor.isContainer());
+
+    Assert.assertEquals("A_DIFFERENT_COMPONENT_NAME", componentDescriptor.getName());
+
+    List<KerberosIdentityDescriptor> identities = componentDescriptor.getIdentities();
+
+    Assert.assertNotNull(identities);
+    Assert.assertEquals(3, identities.size());
+
+    Map<String, KerberosConfigurationDescriptor> configurations = componentDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("red", properties.get("service.component.property1"));
+    Assert.assertEquals("green", properties.get("service.component.property"));
+  }
+
+  public static void validateUpdatedData(KerberosComponentDescriptor componentDescriptor) {
+    Assert.assertNotNull(componentDescriptor);
+
+    Assert.assertEquals("A_DIFFERENT_COMPONENT_NAME", componentDescriptor.getName());
+
+    List<KerberosIdentityDescriptor> identities = componentDescriptor.getIdentities();
+
+    Assert.assertNotNull(identities);
+    Assert.assertEquals(3, identities.size());
+
+    Map<String, KerberosConfigurationDescriptor> configurations = componentDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(3, properties.size());
+    Assert.assertEquals("red", properties.get("service.component.property1"));
+    Assert.assertEquals("value2", properties.get("service.component.property2"));
+    Assert.assertEquals("green", properties.get("service.component.property"));
+  }
+
+  private static KerberosComponentDescriptor createFromJSON() {
+    Map<Object, Object> map = new Gson()
+        .fromJson(JSON_VALUE, new TypeToken<Map<Object, Object>>() {
+        }.getType());
+    return new KerberosComponentDescriptor(map);
+  }
+
+  private static KerberosComponentDescriptor createFromMap() throws AmbariException {
+    return new KerberosComponentDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+  @Test
+  public void testMapDeserialize() throws AmbariException {
+    validateFromMap(createFromMap());
+  }
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosComponentDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+
+  @Test
+  public void testUpdate() throws AmbariException {
+    KerberosComponentDescriptor componentDescriptor = createFromJSON();
+    KerberosComponentDescriptor updatedComponentDescriptor = createFromMap();
+
+    Assert.assertNotNull(componentDescriptor);
+    Assert.assertNotNull(updatedComponentDescriptor);
+
+    componentDescriptor.update(updatedComponentDescriptor);
+
+    validateUpdatedData(componentDescriptor);
+  }
+}

+ 242 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosConfigurationDescriptorTest.java

@@ -0,0 +1,242 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.*;
+
+public class KerberosConfigurationDescriptorTest {
+  private static final String JSON_SINGLE_VALUE =
+      "{ \"configuration-type\": {" +
+          "     \"property1\": \"${property-value1}\"," +
+          "     \"property2\": \"${property.value2}\"" +
+          "}}";
+
+  private static final String JSON_MULTIPLE_VALUE =
+      "[" +
+          "{ \"configuration-type\": {" +
+          "     \"property1\": \"value1\"," +
+          "     \"property2\": \"value2\"" +
+          "}}," +
+          "{ \"configuration-type2\": {" +
+          "     \"property1\": \"value1\"," +
+          "     \"property3\": \"value3\"," +
+          "     \"property2\": \"value2\"" +
+          "}}" +
+          "]";
+
+  private static final Map<String, Map<String, Object>> MAP_SINGLE_VALUE =
+      new HashMap<String, Map<String, Object>>() {
+        {
+          put("configuration-type", new HashMap<String, Object>() {
+            {
+              put("property1", "black");
+              put("property2", "white");
+            }
+          });
+        }
+      };
+
+  private static final Collection<Map<String, Map<String, Object>>> MAP_MULTIPLE_VALUES =
+      new ArrayList<Map<String, Map<String, Object>>>() {
+        {
+          add(MAP_SINGLE_VALUE);
+          add(new HashMap<String, Map<String, Object>>() {
+            {
+              put("configuration-type2", new HashMap<String, Object>() {
+                {
+                  put("property1", "red");
+                  put("property2", "yellow");
+                  put("property3", "green");
+                }
+              });
+            }
+          });
+        }
+      };
+
+
+  @Test
+  public void testJSONDeserialize() {
+    Map<String, Map<String, Object>> jsonData = new Gson().fromJson(JSON_SINGLE_VALUE,
+        new TypeToken<Map<String, Map<String, Object>>>() {
+        }.getType());
+
+    KerberosConfigurationDescriptor configuration = new KerberosConfigurationDescriptor(jsonData);
+
+    Assert.assertNotNull(configuration);
+    Assert.assertFalse(configuration.isContainer());
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("configuration-type", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("${property-value1}", properties.get("property1"));
+    Assert.assertEquals("${property.value2}", properties.get("property2"));
+  }
+
+  @Test
+  public void testJSONDeserializeMultiple() {
+    List<Map<String, Object>> jsonData = new Gson().fromJson(JSON_MULTIPLE_VALUE,
+        new TypeToken<List<Map<String, Object>>>() {
+        }.getType());
+
+
+    List<KerberosConfigurationDescriptor> configurations = new ArrayList<KerberosConfigurationDescriptor>();
+
+    for (Map<String, Object> item : jsonData) {
+      configurations.add(new KerberosConfigurationDescriptor(item));
+    }
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(2, configurations.size());
+
+    for (KerberosConfigurationDescriptor configuration : configurations) {
+      Assert.assertFalse(configuration.isContainer());
+      String type = configuration.getType();
+      Assert.assertEquals(2, configurations.size());
+
+      Map<String, String> properties = configuration.getProperties();
+
+      if ("configuration-type".equals(type)) {
+        Assert.assertNotNull(properties);
+        Assert.assertEquals(2, properties.size());
+        Assert.assertEquals("value1", properties.get("property1"));
+        Assert.assertEquals("value2", properties.get("property2"));
+
+      } else if ("configuration-type2".equals(type)) {
+        Assert.assertNotNull(properties);
+        Assert.assertEquals(3, properties.size());
+        Assert.assertEquals("value1", properties.get("property1"));
+        Assert.assertEquals("value2", properties.get("property2"));
+        Assert.assertEquals("value3", properties.get("property3"));
+        Assert.assertEquals("value1", configuration.getProperty("property1"));
+        Assert.assertEquals("value2", configuration.getProperty("property2"));
+        Assert.assertEquals("value3", configuration.getProperty("property3"));
+      } else {
+        Assert.fail("Missing expected configuration type");
+      }
+    }
+  }
+
+  @Test
+  public void testMapDeserialize() {
+    KerberosConfigurationDescriptor configuration = new KerberosConfigurationDescriptor(MAP_SINGLE_VALUE);
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertNotNull(configuration);
+    Assert.assertFalse(configuration.isContainer());
+    Assert.assertEquals("configuration-type", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("black", properties.get("property1"));
+    Assert.assertEquals("white", properties.get("property2"));
+  }
+
+  @Test
+  public void testMapDeserializeMultiple() {
+
+    List<KerberosConfigurationDescriptor> configurations = new ArrayList<KerberosConfigurationDescriptor>();
+
+    for (Map<String, Map<String, Object>> item : MAP_MULTIPLE_VALUES) {
+      configurations.add(new KerberosConfigurationDescriptor(item));
+    }
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(2, configurations.size());
+
+    for (KerberosConfigurationDescriptor configuration : configurations) {
+      Assert.assertFalse(configuration.isContainer());
+      String type = configuration.getType();
+      Map<String, String> properties = configuration.getProperties();
+
+      if ("configuration-type".equals(type)) {
+        Assert.assertNotNull(properties);
+        Assert.assertEquals(2, properties.size());
+        Assert.assertEquals("black", properties.get("property1"));
+        Assert.assertEquals("white", properties.get("property2"));
+        Assert.assertEquals("black", configuration.getProperty("property1"));
+        Assert.assertEquals("white", configuration.getProperty("property2"));
+      } else if ("configuration-type2".equals(type)) {
+        Assert.assertNotNull(properties);
+        Assert.assertEquals(3, properties.size());
+        Assert.assertEquals("red", properties.get("property1"));
+        Assert.assertEquals("yellow", properties.get("property2"));
+        Assert.assertEquals("green", properties.get("property3"));
+        Assert.assertEquals("red", configuration.getProperty("property1"));
+        Assert.assertEquals("yellow", configuration.getProperty("property2"));
+        Assert.assertEquals("green", configuration.getProperty("property3"));
+      } else {
+        Assert.fail("Missing expected configuration type");
+      }
+    }
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosConfigurationDescriptor descriptor = new KerberosConfigurationDescriptor(MAP_SINGLE_VALUE);
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_SINGLE_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() {
+    Map<String, Map<String, Object>> jsonData = new Gson().fromJson(JSON_SINGLE_VALUE,
+        new TypeToken<Map<String, Map<String, Object>>>() {
+        }.getType());
+
+    KerberosConfigurationDescriptor configuration = new KerberosConfigurationDescriptor(jsonData);
+    KerberosConfigurationDescriptor updatedConfiguration = new KerberosConfigurationDescriptor(MAP_SINGLE_VALUE);
+
+    Map<String, String> properties;
+
+    properties = configuration.getProperties();
+
+    Assert.assertEquals("configuration-type", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("${property-value1}", properties.get("property1"));
+    Assert.assertEquals("${property.value2}", properties.get("property2"));
+
+    configuration.update(updatedConfiguration);
+
+    properties = configuration.getProperties();
+
+    Assert.assertEquals("configuration-type", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("black", properties.get("property1"));
+    Assert.assertEquals("white", properties.get("property2"));
+
+    updatedConfiguration.setType("updated-type");
+
+    configuration.update(updatedConfiguration);
+
+    Assert.assertEquals("updated-type", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("black", properties.get("property1"));
+    Assert.assertEquals("white", properties.get("property2"));
+  }
+}

+ 328 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosDescriptorTest.java

@@ -0,0 +1,328 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.*;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KerberosDescriptorTest {
+  public static final String JSON_VALUE =
+      "{" +
+          "  \"properties\": {" +
+          "      \"realm\": \"${cluster-env/kerberos_domain}\"," +
+          "      \"keytab_dir\": \"/etc/security/keytabs\"" +
+          "    }," +
+          "  \"services\": [" +
+          KerberosServiceDescriptorTest.JSON_VALUE +
+          "    ]" +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("properties", new HashMap<String, Object>() {{
+            put("realm", "EXAMPLE.COM");
+            put("some.property", "Hello World");
+          }});
+
+          put(KerberosDescriptorType.SERVICE.getDescriptorPluralName(), new ArrayList<Object>() {{
+            add(KerberosServiceDescriptorTest.MAP_VALUE);
+          }});
+          put(KerberosDescriptorType.CONFIGURATION.getDescriptorPluralName(), new ArrayList<Map<String, Object>>() {{
+            add(new HashMap<String, Object>() {
+              {
+                put("cluster-conf", new HashMap<String, String>() {
+                  {
+                    put("property1", "red");
+                  }
+                });
+              }
+            });
+          }});
+          put(KerberosDescriptorType.IDENTITY.getDescriptorPluralName(), new ArrayList<Object>() {{
+            add(new HashMap<String, Object>() {
+              {
+                put("name", "shared");
+                put("principal", new HashMap<String, Object>(KerberosPrincipalDescriptorTest.MAP_VALUE));
+                put("keytab", new HashMap<String, Object>() {
+                  {
+                    put("file", "/etc/security/keytabs/subject.service.keytab");
+
+                    put("owner", new HashMap<String, Object>() {{
+                      put("name", "root");
+                      put("access", "rw");
+                    }});
+
+                    put("group", new HashMap<String, Object>() {{
+                      put("name", "hadoop");
+                      put("access", "r");
+                    }});
+
+                    put("configuration", "service-site/service2.component.keytab.file");
+                  }
+                });
+              }
+            });
+          }});
+        }
+      };
+
+  public static void validateFromJSON(KerberosDescriptor descriptor) {
+    Assert.assertNotNull(descriptor);
+    Assert.assertTrue(descriptor.isContainer());
+
+    Map<String, String> properties = descriptor.getProperties();
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("${cluster-env/kerberos_domain}", properties.get("realm"));
+    Assert.assertEquals("/etc/security/keytabs", properties.get("keytab_dir"));
+
+    Map<String, KerberosServiceDescriptor> serviceDescriptors = descriptor.getServices();
+    Assert.assertNotNull(serviceDescriptors);
+    Assert.assertEquals(1, serviceDescriptors.size());
+
+    for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) {
+      KerberosServiceDescriptorTest.validateFromJSON(serviceDescriptor);
+    }
+
+    Map<String, KerberosConfigurationDescriptor> configurations = descriptor.getConfigurations();
+
+    Assert.assertNull(configurations);
+  }
+
+  public static void validateFromMap(KerberosDescriptor descriptor) throws AmbariException {
+    Assert.assertNotNull(descriptor);
+    Assert.assertTrue(descriptor.isContainer());
+
+    Map<String, String> properties = descriptor.getProperties();
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("EXAMPLE.COM", properties.get("realm"));
+    Assert.assertEquals("Hello World", properties.get("some.property"));
+
+    Map<String, KerberosServiceDescriptor> services = descriptor.getServices();
+    Assert.assertNotNull(services);
+    Assert.assertEquals(1, services.size());
+
+    for (KerberosServiceDescriptor service : services.values()) {
+      KerberosComponentDescriptor component = service.getComponent("A_DIFFERENT_COMPONENT_NAME");
+      Assert.assertNotNull(component);
+
+      List<KerberosIdentityDescriptor> resolvedIdentities = component.getIdentities(true);
+      KerberosIdentityDescriptor resolvedIdentity = null;
+      Assert.assertNotNull(resolvedIdentities);
+      Assert.assertEquals(3, resolvedIdentities.size());
+
+      for (KerberosIdentityDescriptor item : resolvedIdentities) {
+        if ("/shared".equals(item.getName())) {
+          resolvedIdentity = item;
+          break;
+        }
+      }
+      Assert.assertNotNull(resolvedIdentity);
+
+      List<KerberosIdentityDescriptor> identities = component.getIdentities(false);
+      Assert.assertNotNull(identities);
+      Assert.assertEquals(3, identities.size());
+
+      KerberosIdentityDescriptor identityReference = component.getIdentity("/shared");
+      Assert.assertNotNull(identityReference);
+
+      KerberosIdentityDescriptor referencedIdentity = descriptor.getIdentity("shared");
+      Assert.assertNotNull(referencedIdentity);
+
+      Assert.assertEquals(identityReference.getKeytabDescriptor(), resolvedIdentity.getKeytabDescriptor());
+      Assert.assertEquals(referencedIdentity.getPrincipalDescriptor(), resolvedIdentity.getPrincipalDescriptor());
+
+      Map<String, KerberosConfigurationDescriptor> configurations = service.getConfigurations(true);
+      Assert.assertNotNull(configurations);
+      Assert.assertEquals(2, configurations.size());
+      Assert.assertNotNull(configurations.get("service-site"));
+      Assert.assertNotNull(configurations.get("cluster-conf"));
+    }
+
+    Map<String, KerberosConfigurationDescriptor> configurations = descriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("cluster-conf");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> configProperties = configuration.getProperties();
+
+    Assert.assertEquals("cluster-conf", configuration.getType());
+    Assert.assertNotNull(configProperties);
+    Assert.assertEquals(1, configProperties.size());
+    Assert.assertEquals("red", configProperties.get("property1"));
+  }
+
+  public void validateUpdatedData(KerberosDescriptor descriptor) {
+    Assert.assertNotNull(descriptor);
+
+    Map<String, String> properties = descriptor.getProperties();
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(3, properties.size());
+    Assert.assertEquals("EXAMPLE.COM", properties.get("realm"));
+    Assert.assertEquals("/etc/security/keytabs", properties.get("keytab_dir"));
+    Assert.assertEquals("Hello World", properties.get("some.property"));
+
+    Map<String, KerberosServiceDescriptor> serviceDescriptors = descriptor.getServices();
+    Assert.assertNotNull(serviceDescriptors);
+    Assert.assertEquals(2, serviceDescriptors.size());
+
+    KerberosServiceDescriptorTest.validateFromJSON(descriptor.getService("SERVICE_NAME"));
+    KerberosServiceDescriptorTest.validateFromMap(descriptor.getService("A_DIFFERENT_SERVICE_NAME"));
+
+    Assert.assertNull(descriptor.getService("invalid service"));
+
+    Map<String, KerberosConfigurationDescriptor> configurations = descriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("cluster-conf");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> configProperties = configuration.getProperties();
+
+    Assert.assertEquals("cluster-conf", configuration.getType());
+    Assert.assertNotNull(configProperties);
+    Assert.assertEquals(1, configProperties.size());
+    Assert.assertEquals("red", configProperties.get("property1"));
+  }
+
+  private KerberosDescriptor createFromJSON() {
+    return KerberosDescriptor.fromJSON(JSON_VALUE);
+  }
+
+  private KerberosDescriptor createFromMap() throws AmbariException {
+    return new KerberosDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testFromMapViaGSON() throws AmbariException {
+    Object data = new Gson().fromJson(JSON_VALUE, Object.class);
+
+    Assert.assertNotNull(data);
+
+    KerberosDescriptor descriptor = new KerberosDescriptor((Map<?, ?>) data);
+
+    validateFromJSON(descriptor);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+  @Test
+  public void testMapDeserialize() throws AmbariException {
+    validateFromMap(createFromMap());
+  }
+
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() throws AmbariException {
+    KerberosDescriptor descriptor = createFromJSON();
+    KerberosDescriptor updatedDescriptor = createFromMap();
+
+    Assert.assertNotNull(descriptor);
+    Assert.assertNotNull(updatedDescriptor);
+
+    descriptor.update(updatedDescriptor);
+
+    validateUpdatedData(descriptor);
+  }
+
+  @Test
+  public void testReplaceVariables() throws AmbariException {
+    Map<String, Map<String, String>> configurations = new HashMap<String, Map<String, String>>() {
+      {
+        put("", new HashMap<String, String>() {{
+          put("global_variable", "Hello World");
+        }});
+
+        put("config-type", new HashMap<String, String>() {{
+          put("variable.name", "Replacement1");
+          put("variable.name1", "${config-type2/variable.name}");
+          put("variable.name2", "");
+        }});
+
+        put("config-type2", new HashMap<String, String>() {{
+          put("variable.name", "Replacement2");
+          put("self_reference", "${config-type2/self_reference}");  // This essentially references itself.
+        }});
+      }
+    };
+
+    Assert.assertEquals("concrete",
+        KerberosDescriptor.replaceVariables("concrete", configurations));
+
+    Assert.assertEquals("Hello World",
+        KerberosDescriptor.replaceVariables("${global_variable}", configurations));
+
+    Assert.assertEquals("Replacement1",
+        KerberosDescriptor.replaceVariables("${config-type/variable.name}", configurations));
+
+    Assert.assertEquals("Replacement1|Replacement2",
+        KerberosDescriptor.replaceVariables("${config-type/variable.name}|${config-type2/variable.name}", configurations));
+
+    Assert.assertEquals("Replacement1|Replacement2|${config-type3/variable.name}",
+        KerberosDescriptor.replaceVariables("${config-type/variable.name}|${config-type2/variable.name}|${config-type3/variable.name}", configurations));
+
+    Assert.assertEquals("Replacement2|Replacement2",
+        KerberosDescriptor.replaceVariables("${config-type/variable.name1}|${config-type2/variable.name}", configurations));
+
+    // Replacement yields an empty string
+    Assert.assertEquals("",
+        KerberosDescriptor.replaceVariables("${config-type/variable.name2}", configurations));
+
+    // This might cause an infinite loop... we assume protection is in place...
+    try {
+      Assert.assertEquals("${config-type2/self_reference}",
+          KerberosDescriptor.replaceVariables("${config-type2/self_reference}", configurations));
+      Assert.fail(String.format("%s expected to be thrown", AmbariException.class.getName()));
+    } catch (AmbariException e) {
+      // This is expected...
+    }
+
+  }
+}

+ 154 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptorTest.java

@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KerberosIdentityDescriptorTest {
+  public static final String JSON_VALUE =
+      "{" +
+          "  \"name\": \"identity_1\"" +
+          "," +
+          "  \"principal\":" + KerberosPrincipalDescriptorTest.JSON_VALUE +
+          "," +
+          "  \"keytab\":" + KerberosKeytabDescriptorTest.JSON_VALUE +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("name", "identity_1");
+          put("principal", KerberosPrincipalDescriptorTest.MAP_VALUE);
+          put("keytab", KerberosKeytabDescriptorTest.MAP_VALUE);
+          put("password", "secret");
+        }
+      };
+
+  public static final Map<String, Object> MAP_VALUE_ALT =
+      new HashMap<String, Object>() {
+        {
+          put("name", "identity_2");
+          put("principal", KerberosPrincipalDescriptorTest.MAP_VALUE);
+          put("keytab", KerberosKeytabDescriptorTest.MAP_VALUE);
+          put("password", "secret2");
+        }
+      };
+
+  public static final Map<String, Object> MAP_VALUE_REFERENCE =
+      new HashMap<String, Object>() {
+        {
+          put("name", "/shared");
+          put("keytab", new HashMap<String, Object>() {
+            {
+              put("file", "/home/user/me/subject.service.keytab");
+
+              put("owner", new HashMap<String, Object>() {{
+                put("name", "me");
+                put("access", "rw");
+              }});
+
+              put("group", new HashMap<String, Object>() {{
+                put("name", "nobody");
+                put("access", "");
+              }});
+
+              put("configuration", "service-site/me.component.keytab.file");
+            }
+          });
+        }
+      };
+
+  public static void validateFromJSON(KerberosIdentityDescriptor identityDescriptor) {
+    Assert.assertNotNull(identityDescriptor);
+    Assert.assertFalse(identityDescriptor.isContainer());
+
+    KerberosPrincipalDescriptorTest.validateFromJSON(identityDescriptor.getPrincipalDescriptor());
+    KerberosKeytabDescriptorTest.validateFromJSON(identityDescriptor.getKeytabDescriptor());
+    Assert.assertNull(identityDescriptor.getPassword());
+  }
+
+  public static void validateFromMap(KerberosIdentityDescriptor identityDescriptor) {
+    Assert.assertNotNull(identityDescriptor);
+    Assert.assertFalse(identityDescriptor.isContainer());
+
+    KerberosPrincipalDescriptorTest.validateFromMap(identityDescriptor.getPrincipalDescriptor());
+    KerberosKeytabDescriptorTest.validateFromMap(identityDescriptor.getKeytabDescriptor());
+    Assert.assertEquals("secret", identityDescriptor.getPassword());
+  }
+
+  public static void validateUpdatedData(KerberosIdentityDescriptor identityDescriptor) {
+    Assert.assertNotNull(identityDescriptor);
+
+    KerberosPrincipalDescriptorTest.validateUpdatedData(identityDescriptor.getPrincipalDescriptor());
+    KerberosKeytabDescriptorTest.validateUpdatedData(identityDescriptor.getKeytabDescriptor());
+    Assert.assertEquals("secret", identityDescriptor.getPassword());
+  }
+
+  private static KerberosIdentityDescriptor createFromJSON() {
+    Map<?, ?> map = new Gson().fromJson(JSON_VALUE, new TypeToken<Map<?, ?>>() {
+    }.getType());
+    return new KerberosIdentityDescriptor(map);
+  }
+
+  private static KerberosIdentityDescriptor createFromMap() {
+    return new KerberosIdentityDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+  @Test
+  public void testMapDeserialize() {
+    validateFromMap(createFromMap());
+  }
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosIdentityDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() {
+    KerberosIdentityDescriptor identityDescriptor = createFromJSON();
+    KerberosIdentityDescriptor updatedIdentityDescriptor = createFromMap();
+
+    Assert.assertNotNull(identityDescriptor);
+    Assert.assertNotNull(updatedIdentityDescriptor);
+
+    identityDescriptor.update(updatedIdentityDescriptor);
+
+    validateUpdatedData(identityDescriptor);
+  }
+}

+ 146 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosKeytabDescriptorTest.java

@@ -0,0 +1,146 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KerberosKeytabDescriptorTest {
+  public static final String JSON_VALUE =
+      "{" +
+          "  \"file\": \"/etc/security/keytabs/${host}/subject.service.keytab\"," +
+          "  \"owner\": {" +
+          "      \"name\": \"subject\"," +
+          "      \"access\": \"rw\"" +
+          "  }," +
+          "  \"group\": {" +
+          "      \"name\": \"hadoop\"," +
+          "      \"access\": \"r\"" +
+          "  }," +
+          "  \"configuration\": \"service-site/service.component.keytab.file\"" +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("file", "/etc/security/keytabs/subject.service.keytab");
+
+          put("owner", new HashMap<String, Object>() {{
+            put("name", "root");
+            put("access", "rw");
+          }});
+
+          put("group", new HashMap<String, Object>() {{
+            put("name", "hadoop");
+            put("access", "r");
+          }});
+
+          put("configuration", "service-site/service2.component.keytab.file");
+        }
+      };
+
+  public static void validateFromJSON(KerberosKeytabDescriptor keytabDescriptor) {
+    Assert.assertNotNull(keytabDescriptor);
+    Assert.assertFalse(keytabDescriptor.isContainer());
+
+    Assert.assertEquals("/etc/security/keytabs/${host}/subject.service.keytab", keytabDescriptor.getFile());
+    Assert.assertEquals("subject", keytabDescriptor.getOwnerName());
+    Assert.assertEquals("rw", keytabDescriptor.getOwnerAccess());
+    Assert.assertEquals("hadoop", keytabDescriptor.getGroupName());
+    Assert.assertEquals("r", keytabDescriptor.getGroupAccess());
+    Assert.assertEquals("service-site/service.component.keytab.file", keytabDescriptor.getConfiguration());
+  }
+
+  public static void validateFromMap(KerberosKeytabDescriptor keytabDescriptor) {
+    Assert.assertNotNull(keytabDescriptor);
+    Assert.assertFalse(keytabDescriptor.isContainer());
+
+    Assert.assertEquals("/etc/security/keytabs/subject.service.keytab", keytabDescriptor.getFile());
+    Assert.assertEquals("root", keytabDescriptor.getOwnerName());
+    Assert.assertEquals("rw", keytabDescriptor.getOwnerAccess());
+    Assert.assertEquals("hadoop", keytabDescriptor.getGroupName());
+    Assert.assertEquals("r", keytabDescriptor.getGroupAccess());
+    Assert.assertEquals("service-site/service2.component.keytab.file", keytabDescriptor.getConfiguration());
+  }
+
+  public static void validateUpdatedData(KerberosKeytabDescriptor keytabDescriptor) {
+    Assert.assertNotNull(keytabDescriptor);
+
+    Assert.assertEquals("/etc/security/keytabs/subject.service.keytab", keytabDescriptor.getFile());
+    Assert.assertEquals("root", keytabDescriptor.getOwnerName());
+    Assert.assertEquals("rw", keytabDescriptor.getOwnerAccess());
+    Assert.assertEquals("hadoop", keytabDescriptor.getGroupName());
+    Assert.assertEquals("r", keytabDescriptor.getGroupAccess());
+    Assert.assertEquals("service-site/service2.component.keytab.file", keytabDescriptor.getConfiguration());
+  }
+
+  private static KerberosKeytabDescriptor createFromJSON() {
+    Map<?, ?> map = new Gson().fromJson(JSON_VALUE,
+        new TypeToken<Map<?, ?>>() {
+        }.getType());
+    return new KerberosKeytabDescriptor(map);
+
+  }
+
+  private static KerberosKeytabDescriptor createFromMap() {
+    return new KerberosKeytabDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+  @Test
+  public void testMapDeserialize() {
+    validateFromMap(createFromMap());
+  }
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosKeytabDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() {
+    KerberosKeytabDescriptor keytabDescriptor = createFromJSON();
+
+    KerberosKeytabDescriptor updatedKeytabDescriptor = createFromMap();
+
+    Assert.assertNotNull(keytabDescriptor);
+    Assert.assertNotNull(updatedKeytabDescriptor);
+
+    keytabDescriptor.update(updatedKeytabDescriptor);
+
+    validateUpdatedData(keytabDescriptor);
+  }
+}

+ 109 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosPrincipalDescriptorTest.java

@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.*;
+
+public class KerberosPrincipalDescriptorTest {
+  public static final String JSON_VALUE =
+      "{" +
+          "\"value\": \"service/_HOST@_REALM\"," +
+          "\"configuration\": \"service-site/service.component.kerberos.principal\"" +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("value", "HTTP/_HOST@_REALM");
+          put("configuration", "service-site/service.component.kerberos.https.principal");
+        }
+      };
+
+  public static void validateFromJSON(KerberosPrincipalDescriptor principalDescriptor) {
+    Assert.assertNotNull(principalDescriptor);
+    Assert.assertFalse(principalDescriptor.isContainer());
+    Assert.assertEquals("service/_HOST@_REALM", principalDescriptor.getValue());
+    Assert.assertEquals("service-site/service.component.kerberos.principal", principalDescriptor.getConfiguration());
+  }
+
+  public static void validateFromMap(KerberosPrincipalDescriptor principalDescriptor) {
+    Assert.assertNotNull(principalDescriptor);
+    Assert.assertFalse(principalDescriptor.isContainer());
+    Assert.assertEquals("HTTP/_HOST@_REALM", principalDescriptor.getValue());
+    Assert.assertEquals("service-site/service.component.kerberos.https.principal", principalDescriptor.getConfiguration());
+  }
+
+  public static void validateUpdatedData(KerberosPrincipalDescriptor principalDescriptor) {
+    Assert.assertNotNull(principalDescriptor);
+    Assert.assertEquals("HTTP/_HOST@_REALM", principalDescriptor.getValue());
+    Assert.assertEquals("service-site/service.component.kerberos.https.principal", principalDescriptor.getConfiguration());
+  }
+
+  private static KerberosPrincipalDescriptor createFromJSON() {
+    Map<?, ?> map = new Gson().fromJson(JSON_VALUE,
+        new TypeToken<Map<?, ?>>() {
+        }.getType());
+    return new KerberosPrincipalDescriptor(map);
+  }
+
+  private static KerberosPrincipalDescriptor createFromMap() {
+    return new KerberosPrincipalDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+  @Test
+  public void testMapDeserialize() {
+    validateFromMap(createFromMap());
+  }
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosPrincipalDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() {
+    KerberosPrincipalDescriptor principalDescriptor = createFromJSON();
+    KerberosPrincipalDescriptor updatedPrincipalDescriptor = createFromMap();
+
+    Assert.assertNotNull(principalDescriptor);
+    Assert.assertNotNull(updatedPrincipalDescriptor);
+
+    principalDescriptor.update(updatedPrincipalDescriptor);
+
+    validateUpdatedData(principalDescriptor);
+  }
+}

+ 238 - 0
ambari-server/src/test/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptorTest.java

@@ -0,0 +1,238 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.kerberos;
+
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KerberosServiceDescriptorTest {
+  public static final String JSON_VALUE =
+      "{" +
+          "  \"name\": \"SERVICE_NAME\"," +
+          "  \"identities\": [" +
+          KerberosIdentityDescriptorTest.JSON_VALUE +
+          "]," +
+          "  \"components\": [" +
+          KerberosComponentDescriptorTest.JSON_VALUE +
+          "]," +
+          "  \"configurations\": [" +
+          "    {" +
+          "      \"service-site\": {" +
+          "        \"service.property1\": \"value1\"," +
+          "        \"service.property2\": \"value2\"" +
+          "      }" +
+          "    }" +
+          "  ]" +
+          "}";
+
+  public static final Map<String, Object> MAP_VALUE =
+      new HashMap<String, Object>() {
+        {
+          put("name", "A_DIFFERENT_SERVICE_NAME");
+          put(KerberosDescriptorType.IDENTITY.getDescriptorPluralName(), new ArrayList<Object>() {{
+            add(KerberosIdentityDescriptorTest.MAP_VALUE);
+          }});
+          put(KerberosDescriptorType.COMPONENT.getDescriptorPluralName(), new ArrayList<Object>() {{
+            add(KerberosComponentDescriptorTest.MAP_VALUE);
+          }});
+          put(KerberosDescriptorType.CONFIGURATION.getDescriptorPluralName(), new ArrayList<Map<String, Object>>() {{
+            add(new HashMap<String, Object>() {
+              {
+                put("service-site", new HashMap<String, String>() {
+                  {
+                    put("service.property1", "red");
+                    put("service.property", "green");
+                  }
+                });
+              }
+            });
+          }});
+        }
+      };
+
+  public static void validateFromJSON(KerberosServiceDescriptor serviceDescriptor) {
+    Assert.assertNotNull(serviceDescriptor);
+    Assert.assertTrue(serviceDescriptor.isContainer());
+
+    Assert.assertEquals("SERVICE_NAME", serviceDescriptor.getName());
+
+    Map<String, KerberosComponentDescriptor> componentDescriptors = serviceDescriptor.getComponents();
+    Assert.assertNotNull(componentDescriptors);
+    Assert.assertEquals(1, componentDescriptors.size());
+
+    for (KerberosComponentDescriptor componentDescriptor : componentDescriptors.values()) {
+      KerberosComponentDescriptorTest.validateFromJSON(componentDescriptor);
+    }
+
+    List<KerberosIdentityDescriptor> identityDescriptors = serviceDescriptor.getIdentities();
+    Assert.assertNotNull(identityDescriptors);
+    Assert.assertEquals(1, identityDescriptors.size());
+
+    for (KerberosIdentityDescriptor identityDescriptor : identityDescriptors) {
+      KerberosIdentityDescriptorTest.validateFromJSON(identityDescriptor);
+    }
+
+    Map<String, KerberosConfigurationDescriptor> configurations = serviceDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("value1", properties.get("service.property1"));
+    Assert.assertEquals("value2", properties.get("service.property2"));
+  }
+
+  public static void validateFromMap(KerberosServiceDescriptor serviceDescriptor) {
+    Assert.assertNotNull(serviceDescriptor);
+    Assert.assertTrue(serviceDescriptor.isContainer());
+
+    Assert.assertEquals("A_DIFFERENT_SERVICE_NAME", serviceDescriptor.getName());
+
+    Map<String, KerberosComponentDescriptor> componentDescriptors = serviceDescriptor.getComponents();
+    Assert.assertNotNull(componentDescriptors);
+    Assert.assertEquals(1, componentDescriptors.size());
+
+    for (KerberosComponentDescriptor componentDescriptor : componentDescriptors.values()) {
+      KerberosComponentDescriptorTest.validateFromMap(componentDescriptor);
+    }
+
+    List<KerberosIdentityDescriptor> identityDescriptors = serviceDescriptor.getIdentities();
+    Assert.assertNotNull(identityDescriptors);
+    Assert.assertEquals(1, identityDescriptors.size());
+
+    for (KerberosIdentityDescriptor identityDescriptor : identityDescriptors) {
+      KerberosIdentityDescriptorTest.validateFromMap(identityDescriptor);
+    }
+
+    Map<String, KerberosConfigurationDescriptor> configurations = serviceDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(2, properties.size());
+    Assert.assertEquals("red", properties.get("service.property1"));
+    Assert.assertEquals("green", properties.get("service.property"));
+  }
+
+  public void validateUpdatedData(KerberosServiceDescriptor serviceDescriptor) {
+    Assert.assertNotNull(serviceDescriptor);
+
+    Assert.assertEquals("A_DIFFERENT_SERVICE_NAME", serviceDescriptor.getName());
+
+    Map<String, KerberosComponentDescriptor> componentDescriptors = serviceDescriptor.getComponents();
+    Assert.assertNotNull(componentDescriptors);
+    Assert.assertEquals(2, componentDescriptors.size());
+
+    KerberosComponentDescriptorTest.validateFromJSON(serviceDescriptor.getComponent("COMPONENT_NAME"));
+    KerberosComponentDescriptorTest.validateFromMap(serviceDescriptor.getComponent("A_DIFFERENT_COMPONENT_NAME"));
+
+    List<KerberosIdentityDescriptor> identityDescriptors = serviceDescriptor.getIdentities();
+    Assert.assertNotNull(identityDescriptors);
+    Assert.assertEquals(1, identityDescriptors.size());
+
+    for (KerberosIdentityDescriptor identityDescriptor : identityDescriptors) {
+      KerberosIdentityDescriptorTest.validateUpdatedData(identityDescriptor);
+    }
+
+    Map<String, KerberosConfigurationDescriptor> configurations = serviceDescriptor.getConfigurations();
+
+    Assert.assertNotNull(configurations);
+    Assert.assertEquals(1, configurations.size());
+
+    KerberosConfigurationDescriptor configuration = configurations.get("service-site");
+
+    Assert.assertNotNull(configuration);
+
+    Map<String, String> properties = configuration.getProperties();
+
+    Assert.assertEquals("service-site", configuration.getType());
+    Assert.assertNotNull(properties);
+    Assert.assertEquals(3, properties.size());
+    Assert.assertEquals("red", properties.get("service.property1"));
+    Assert.assertEquals("value2", properties.get("service.property2"));
+    Assert.assertEquals("green", properties.get("service.property"));
+  }
+
+  private KerberosServiceDescriptor createFromJSON() {
+    return KerberosServiceDescriptor.fromJSON("SERVICE_NAME", JSON_VALUE);
+  }
+
+  private KerberosServiceDescriptor createFromMap() throws AmbariException {
+    return new KerberosServiceDescriptor(MAP_VALUE);
+  }
+
+  @Test
+  public void testJSONDeserialize() {
+    validateFromJSON(createFromJSON());
+  }
+
+
+  @Test
+  public void testMapDeserialize() throws AmbariException {
+    validateFromMap(createFromMap());
+  }
+
+  @Test
+  public void testEquals() throws AmbariException {
+    Assert.assertTrue(createFromJSON().equals(createFromJSON()));
+    Assert.assertFalse(createFromJSON().equals(createFromMap()));
+  }
+
+  @Test
+  public void testToMap() throws AmbariException {
+    KerberosServiceDescriptor descriptor = createFromMap();
+    Assert.assertNotNull(descriptor);
+    Assert.assertEquals(MAP_VALUE, descriptor.toMap());
+  }
+
+  @Test
+  public void testUpdate() throws AmbariException {
+    KerberosServiceDescriptor serviceDescriptor = createFromJSON();
+    KerberosServiceDescriptor updatedServiceDescriptor = createFromMap();
+
+    Assert.assertNotNull(serviceDescriptor);
+    Assert.assertNotNull(updatedServiceDescriptor);
+
+    serviceDescriptor.update(updatedServiceDescriptor);
+
+    validateUpdatedData(serviceDescriptor);
+  }
+
+}