Browse Source

AMBARI-2819. Extending Stack definition to remove duplication in stack metadata info. (swagle)

Siddharth Wagle 11 years ago
parent
commit
980918e0e9
16 changed files with 951 additions and 278 deletions
  1. 79 249
      ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
  2. 413 0
      ambari-server/src/main/java/org/apache/ambari/server/api/util/StackExtensionHelper.java
  3. 11 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/StackVersionResponse.java
  4. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackVersionResourceProvider.java
  5. 9 0
      ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java
  6. 12 5
      ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java
  7. 13 3
      ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
  8. 11 1
      ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
  9. 1 0
      ambari-server/src/main/resources/properties.json
  10. 178 17
      ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
  11. 2 2
      ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
  12. 24 0
      ambari-server/src/test/resources/stacks/HDP/2.0.6/metainfo.xml
  13. 61 0
      ambari-server/src/test/resources/stacks/HDP/2.0.6/repos/repoinfo.xml
  14. 30 0
      ambari-server/src/test/resources/stacks/HDP/2.0.6/services/SQOOP/metainfo.xml
  15. 60 0
      ambari-server/src/test/resources/stacks/HDP/2.0.6/services/YARN/configuration/yarn-site.xml
  16. 42 0
      ambari-server/src/test/resources/stacks/HDP/2.0.6/services/YARN/metainfo.xml

+ 79 - 249
ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java

@@ -36,6 +36,7 @@ import javax.xml.parsers.ParserConfigurationException;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ObjectNotFoundException;
 import org.apache.ambari.server.StackAccessException;
+import org.apache.ambari.server.api.util.StackExtensionHelper;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.entities.MetainfoEntity;
@@ -66,35 +67,38 @@ public class AmbariMetaInfo {
 
   private final static Logger LOG = LoggerFactory
       .getLogger(AmbariMetaInfo.class);
-  private static final String STACK_METAINFO_FILE_NAME = "metainfo.xml";
-  private static final String STACK_XML_MAIN_BLOCK_NAME = "metainfo";
-  private static final String STACK_XML_PROPERTY_UPGRADE = "upgrade";
-  private static final String STACK_XML_PROPERTY_ACTIVE = "active";
-  private static final String SERVICES_FOLDER_NAME = "services";
-  private static final String SERVICE_METAINFO_FILE_NAME = "metainfo.xml";
-  private static final String SERVICE_CONFIG_FOLDER_NAME = "configuration";
-  private static final String SERVICE_CONFIG_FILE_NAME_POSTFIX = ".xml";
-  private static final String REPOSITORY_FILE_NAME = "repoinfo.xml";
-  private static final String REPOSITORY_FOLDER_NAME = "repos";
-  private static final String REPOSITORY_XML_MAIN_BLOCK_NAME = "os";
-  private static final String REPOSITORY_XML_ATTRIBUTE_OS_TYPE = "type";
-  private static final String REPOSITORY_XML_REPO_BLOCK_NAME = "repo";
-  private static final String REPOSITORY_XML_PROPERTY_BASEURL = "baseurl";
-  private static final String REPOSITORY_XML_PROPERTY_REPOID = "repoid";
-  private static final String REPOSITORY_XML_PROPERTY_REPONAME = "reponame";
-  private static final String REPOSITORY_XML_PROPERTY_MIRRORSLIST = "mirrorslist";
-  private static final String METAINFO_XML_MAIN_BLOCK_NAME = "metainfo";
-  private static final String METAINFO_XML_PROPERTY_VERSION = "version";
-  private static final String METAINFO_XML_PROPERTY_USER = "user";
-  private static final String METAINFO_XML_PROPERTY_COMMENT = "comment";
-  private static final String METAINFO_XML_PROPERTY_COMPONENT_MAIN = "component";
-  private static final String METAINFO_XML_PROPERTY_COMPONENT_NAME = "name";
-  private static final String METAINFO_XML_PROPERTY_COMPONENT_CATEGORY = "category";
-  private static final String PROPERTY_XML_MAIN_BLOCK_NAME = "property";
-  private static final String PROPERTY_XML_PROPERTY_NAME = "name";
-  private static final String PROPERTY_XML_PROPERTY_VALUE = "value";
-  private static final String PROPERTY_XML_PROPERTY_DESCRIPTION = "description";
-  private static final FilenameFilter FILENAME_FILTER = new FilenameFilter() {
+  public static final String STACK_METAINFO_FILE_NAME = "metainfo.xml";
+  public static final String STACK_XML_MAIN_BLOCK_NAME = "metainfo";
+  public static final String STACK_XML_PROPERTY_UPGRADE = "upgrade";
+  public static final String STACK_XML_PROPERTY_ACTIVE = "active";
+  public static final String STACK_XML_PROPERTY_PARENT_STACK = "extends";
+  public static final String SERVICES_FOLDER_NAME = "services";
+  public static final String SERVICE_METAINFO_FILE_NAME = "metainfo.xml";
+  public static final String SERVICE_CONFIG_FOLDER_NAME = "configuration";
+  public static final String SERVICE_CONFIG_FILE_NAME_POSTFIX = ".xml";
+  public static final String REPOSITORY_FILE_NAME = "repoinfo.xml";
+  public static final String REPOSITORY_FOLDER_NAME = "repos";
+  public static final String REPOSITORY_XML_MAIN_BLOCK_NAME = "os";
+  public static final String REPOSITORY_XML_ATTRIBUTE_OS_TYPE = "type";
+  public static final String REPOSITORY_XML_REPO_BLOCK_NAME = "repo";
+  public static final String REPOSITORY_XML_PROPERTY_BASEURL = "baseurl";
+  public static final String REPOSITORY_XML_PROPERTY_REPOID = "repoid";
+  public static final String REPOSITORY_XML_PROPERTY_REPONAME = "reponame";
+  public static final String REPOSITORY_XML_PROPERTY_MIRRORSLIST = "mirrorslist";
+  public static final String METAINFO_XML_MAIN_BLOCK_NAME = "metainfo";
+  public static final String METAINFO_XML_PROPERTY_VERSION = "version";
+  public static final String METAINFO_XML_PROPERTY_USER = "user";
+  public static final String METAINFO_XML_PROPERTY_COMMENT = "comment";
+  public static final String METAINFO_XML_PROPERTY_COMPONENT_MAIN = "component";
+  public static final String METAINFO_XML_PROPERTY_COMPONENT_NAME = "name";
+  public static final String METAINFO_XML_PROPERTY_COMPONENT_CATEGORY = "category";
+  public static final String METAINFO_XML_PROPERTY_IS_DELETED = "deleted";
+  public static final String PROPERTY_XML_MAIN_BLOCK_NAME = "property";
+  public static final String PROPERTY_XML_PROPERTY_NAME = "name";
+  public static final String PROPERTY_XML_PROPERTY_VALUE = "value";
+  public static final String PROPERTY_XML_PROPERTY_DESCRIPTION = "description";
+  public static final String PROPERTY_XML_PROPERTY_IS_DELETED = "deleted";
+  public static final FilenameFilter FILENAME_FILTER = new FilenameFilter() {
     @Override
     public boolean accept(File dir, String s) {
       if (s.equals(".svn") || s.equals(".git"))
@@ -107,7 +111,7 @@ public class AmbariMetaInfo {
   private List<StackInfo> stacksResult = new ArrayList<StackInfo>();
   private File stackRoot;
   private File serverVersionFile;
-  
+
   @Inject
   private MetainfoDAO metainfoDAO;
 
@@ -609,89 +613,60 @@ public class AmbariMetaInfo {
   }
 
   private void getConfigurationInformation(File stackRoot) throws Exception {
-
     if (LOG.isDebugEnabled()) {
       LOG.debug("Loading stack information"
-          + ", stackRoot=" + stackRoot.getAbsolutePath());
+        + ", stackRoot = " + stackRoot.getAbsolutePath());
     }
 
     if (!stackRoot.isDirectory() && !stackRoot.exists())
       throw new IOException("" + Configuration.METADETA_DIR_PATH
-          + " should be a directory with stack"
-          + ", stackRoot=" + stackRoot.getAbsolutePath());
-    File[] stacks = stackRoot.listFiles(FILENAME_FILTER);
-    for (File stackFolder : stacks) {
-      if (stackFolder.isFile())
-        continue;
-      File[] concretStacks = stackFolder.listFiles(FILENAME_FILTER);
-      for (File stack : concretStacks) {
-        if (stack.isFile())
-          continue;
+        + " should be a directory with stack"
+        + ", stackRoot = " + stackRoot.getAbsolutePath());
 
-        StackInfo stackInfo = getStackInfo(stack);
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Adding new stack to known stacks"
-              + ", stackName=" + stackFolder.getName()
-              + ", stackVersion=" + stack.getName());
-        }
+    StackExtensionHelper stackExtensionHelper = new StackExtensionHelper
+      (stackRoot);
 
-        stacksResult.add(stackInfo);
-        // get repository data for current stack of techs
-        File repositoryFolder = new File(stack.getAbsolutePath()
-            + File.separator + REPOSITORY_FOLDER_NAME + File.separator
-            + REPOSITORY_FILE_NAME);
-
-        if (repositoryFolder.exists()) {
-          if (LOG.isDebugEnabled()) {
-            LOG.debug("Adding repositories to stack"
-                + ", stackName=" + stackFolder.getName()
-                + ", stackVersion=" + stack.getName()
-                + ", repoFolder=" + repositoryFolder.getPath());
-          }
-          List<RepositoryInfo> repositoryInfoList = getRepository(repositoryFolder, stackInfo.getVersion());
-          stackInfo.getRepositories().addAll(repositoryInfoList);
-        }
+    List<StackInfo> stacks = stackExtensionHelper.getAllAvailableStacks();
+    if (stacks.isEmpty()) {
+      throw new AmbariException("Unable to find stack definitions under " +
+        "stackRoot = " + stackRoot.getAbsolutePath());
+    }
 
-        // Get services for this stack
-        File servicesRootFolder = new File(stack.getAbsolutePath()
-            + File.separator + SERVICES_FOLDER_NAME);
-        File[] servicesFolders = servicesRootFolder.listFiles(FILENAME_FILTER);
+    for (StackInfo stack : stacks) {
+      LOG.debug("Adding new stack to known stacks"
+        + ", stackName = " + stack.getName()
+        + ", stackVersion = " + stack.getVersion());
 
-        if (servicesFolders != null) {
-          for (File serviceFolder : servicesFolders) {
-            // Get information about service
-            ServiceInfo serviceInfo = new ServiceInfo();
-            serviceInfo.setName(serviceFolder.getName());
-            stackInfo.getServices().add(serviceInfo);
+      stacksResult.add(stack);
 
-            if (LOG.isDebugEnabled()) {
-              LOG.debug("Adding new service to stack"
-                  + ", stackName=" + stackFolder.getName()
-                  + ", stackVersion=" + stack.getName()
-                  + ", serviceName=" + serviceInfo.getName());
-            }
-
-            // Get metainfo data from metainfo.xml
-            File metainfoFile = new File(serviceFolder.getAbsolutePath()
-                + File.separator + SERVICE_METAINFO_FILE_NAME);
-            if (metainfoFile.exists()) {
-              setMetaInfo(metainfoFile, serviceInfo);
-            }
+      // get repository data for current stack of techs
+      File repositoryFolder = new File(stackRoot.getAbsolutePath()
+        + File.separator + stack.getName() + File.separator + stack.getVersion()
+        + File.separator + REPOSITORY_FOLDER_NAME + File.separator
+        + REPOSITORY_FILE_NAME);
 
-            // Get all properties from all "configs/*-site.xml" files
-            File serviceConfigFolder = new File(serviceFolder.getAbsolutePath()
-                + File.separator + SERVICE_CONFIG_FOLDER_NAME);
-            File[] configFiles = serviceConfigFolder.listFiles(FILENAME_FILTER);
-            if (configFiles != null) {
-              for (File config : configFiles) {
-                if (config.getName().endsWith(SERVICE_CONFIG_FILE_NAME_POSTFIX)) {
-                  serviceInfo.getProperties().addAll(getProperties(config));
-                }
-              }
-            }
-          }
+      if (repositoryFolder.exists()) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Adding repositories to stack"
+            + ", stackName=" + stack.getName()
+            + ", stackVersion=" + stack.getVersion()
+            + ", repoFolder=" + repositoryFolder.getPath());
+        } else {
+          LOG.warn("No repository information defined for "
+            + ", stackName=" + stack.getName()
+            + ", stackVersion=" + stack.getVersion()
+            + ", repoFolder=" + repositoryFolder.getPath());
         }
+        List<RepositoryInfo> repositoryInfoList = getRepository
+          (repositoryFolder, stack.getVersion());
+
+        stack.getRepositories().addAll(repositoryInfoList);
       }
+
+      List<ServiceInfo> services = stackExtensionHelper
+        .getAllApplicableServices(stack);
+
+      stack.setServices(services);
     }
   }
 
@@ -699,55 +674,6 @@ public class AmbariMetaInfo {
     return serverVersion;
   }
 
-  private StackInfo getStackInfo(File stackVersionFolder) {
-
-    StackInfo stackInfo = new StackInfo();
-
-    stackInfo.setName(stackVersionFolder.getParentFile().getName());
-    stackInfo.setVersion(stackVersionFolder.getName());
-
-    // Get metainfo from file
-    File stackMetainfoFile = new File(stackVersionFolder.getAbsolutePath()
-        + File.separator + STACK_METAINFO_FILE_NAME);
-
-    if (stackMetainfoFile.exists()) {
-      if (LOG.isDebugEnabled()) {
-        LOG.debug("Reading stack version metainfo from file "
-            + stackMetainfoFile.getAbsolutePath());
-      }
-
-      try {
-        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
-        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
-        Document doc = dBuilder.parse(stackMetainfoFile);
-        doc.getDocumentElement().normalize();
-
-        NodeList stackNodes = doc
-            .getElementsByTagName(STACK_XML_MAIN_BLOCK_NAME);
-
-        for (int index = 0; index < stackNodes.getLength(); index++) {
-
-          Node node = stackNodes.item(index);
-
-          if (node.getNodeType() == Node.ELEMENT_NODE) {
-            Element property = (Element) node;
-
-            stackInfo.setMinUpgradeVersion(getTagValue(
-                STACK_XML_PROPERTY_UPGRADE, property));
-
-            stackInfo.setActive(Boolean.parseBoolean(getTagValue(
-                STACK_XML_PROPERTY_ACTIVE, property)));
-          }
-        }
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-
-    }
-    return stackInfo;
-  }
-
   private List<RepositoryInfo> getRepository(File repositoryFile, String stackVersion)
       throws ParserConfigurationException, IOException, SAXException {
 
@@ -823,106 +749,6 @@ public class AmbariMetaInfo {
     return repositorysInfo;
   }
 
-  private void setMetaInfo(File metainfoFile, ServiceInfo serviceInfo) {
-
-    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
-
-    Document doc = null;
-    DocumentBuilder dBuilder = null;
-    try {
-      dBuilder = dbFactory.newDocumentBuilder();
-      doc = dBuilder.parse(metainfoFile);
-    } catch (SAXException e) {
-      LOG.error("Error while parsing metainf.xml", e);
-    } catch (IOException e) {
-      LOG.error("Error while open metainf.xml", e);
-    } catch (ParserConfigurationException e) {
-      LOG.error("Error while parsing metainf.xml", e);
-    }
-
-    if (doc == null) return;
-
-    doc.getDocumentElement().normalize();
-
-    NodeList metaInfoNodes = doc
-        .getElementsByTagName(METAINFO_XML_MAIN_BLOCK_NAME);
-
-    if (metaInfoNodes.getLength() > 0) {
-      Node metaInfoNode = metaInfoNodes.item(0);
-      if (metaInfoNode.getNodeType() == Node.ELEMENT_NODE) {
-
-        Element metaInfoElem = (Element) metaInfoNode;
-
-        serviceInfo.setVersion(getTagValue(METAINFO_XML_PROPERTY_VERSION,
-            metaInfoElem));
-        serviceInfo.setUser(getTagValue(METAINFO_XML_PROPERTY_USER,
-            metaInfoElem));
-        serviceInfo.setComment(getTagValue(METAINFO_XML_PROPERTY_COMMENT,
-            metaInfoElem));
-      }
-    }
-
-    NodeList componentInfoNodes = doc
-        .getElementsByTagName(METAINFO_XML_PROPERTY_COMPONENT_MAIN);
-
-    if (componentInfoNodes.getLength() > 0) {
-      for (int index = 0; index < componentInfoNodes.getLength(); index++) {
-        Node componentInfoNode = componentInfoNodes.item(index);
-        if (componentInfoNode.getNodeType() == Node.ELEMENT_NODE) {
-          Element componentInfoElem = (Element) componentInfoNode;
-
-          ComponentInfo componentInfo = new ComponentInfo();
-          componentInfo.setName(getTagValue(
-              METAINFO_XML_PROPERTY_COMPONENT_NAME, componentInfoElem));
-          componentInfo.setCategory(getTagValue(
-              METAINFO_XML_PROPERTY_COMPONENT_CATEGORY, componentInfoElem));
-          serviceInfo.getComponents().add(componentInfo);
-
-        }
-      }
-    }
-  }
-
-  private List<PropertyInfo> getProperties(File propertyFile) {
-
-    List<PropertyInfo> resultPropertyList = new ArrayList<PropertyInfo>();
-    try {
-      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
-      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
-      Document doc = dBuilder.parse(propertyFile);
-      doc.getDocumentElement().normalize();
-
-      NodeList propertyNodes = doc
-          .getElementsByTagName(PROPERTY_XML_MAIN_BLOCK_NAME);
-
-      for (int index = 0; index < propertyNodes.getLength(); index++) {
-
-        Node node = propertyNodes.item(index);
-        if (node.getNodeType() == Node.ELEMENT_NODE) {
-          Element property = (Element) node;
-          PropertyInfo propertyInfo = new PropertyInfo();
-          propertyInfo
-              .setName(getTagValue(PROPERTY_XML_PROPERTY_NAME, property));
-          propertyInfo.setValue(getTagValue(PROPERTY_XML_PROPERTY_VALUE,
-              property));
-
-          propertyInfo.setDescription(getTagValue(
-              PROPERTY_XML_PROPERTY_DESCRIPTION, property));
-          propertyInfo.setFilename(propertyFile.getName());
-
-          if (propertyInfo.getName() == null || propertyInfo.getValue() == null)
-            continue;
-
-          resultPropertyList.add(propertyInfo);
-        }
-      }
-    } catch (Exception e) {
-      e.printStackTrace();
-      return null;
-    }
-    return resultPropertyList;
-  }
-
   private String getTagValue(String sTag, Element rawElement) {
     String result = null;
 
@@ -997,5 +823,9 @@ public class AmbariMetaInfo {
     
   }
 
- 
+
+  public File getStackRoot() {
+    return stackRoot;
+  }
+
 }

+ 413 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/util/StackExtensionHelper.java

@@ -0,0 +1,413 @@
+/**
+ * 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.api.util;
+
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+/**
+ * Helper methods for providing stack extension behavior -
+ * Apache Jira: AMBARI-2819
+ */
+public class StackExtensionHelper {
+  private File stackRoot;
+  private final static Logger LOG = LoggerFactory
+    .getLogger(StackExtensionHelper.class);
+  private final Map<String, StackInfo> stackVersionMap = new HashMap<String,
+    StackInfo>();
+  private final Map<String, List<StackInfo>> stackParentsMap;
+
+  public StackExtensionHelper(File stackRoot) throws Exception {
+    this.stackRoot = stackRoot;
+    File[] stackFiles = stackRoot.listFiles(AmbariMetaInfo.FILENAME_FILTER);
+    for (File stack : stackFiles) {
+      if (stack.isFile()) {
+        continue;
+      }
+      for (File stackFolder : stack.listFiles(AmbariMetaInfo.FILENAME_FILTER)) {
+        if (stackFolder.isFile()) {
+          continue;
+        }
+        String stackName = stackFolder.getParentFile().getName();
+        String stackVersion = stackFolder.getName();
+        stackVersionMap.put(stackName + stackVersion, getStackInfo(stackFolder));
+      }
+    }
+    this.stackParentsMap = getParentStacksInOrder(stackVersionMap.values());
+  }
+
+  private ServiceInfo mergeServices(ServiceInfo parentService,
+                                    ServiceInfo childService) {
+    ServiceInfo mergedServiceInfo = new ServiceInfo();
+    mergedServiceInfo.setName(childService.getName());
+    mergedServiceInfo.setComment(childService.getComment());
+    mergedServiceInfo.setUser(childService.getUser());
+    mergedServiceInfo.setVersion(childService.getVersion());
+    // Add all child components to service
+    List<String> deleteList = new ArrayList<String>();
+    List<String> appendList = new ArrayList<String>();
+    for (ComponentInfo childComponentInfo : childService.getComponents()) {
+      if (!childComponentInfo.isDeleted()) {
+        mergedServiceInfo.getComponents().add(childComponentInfo);
+        appendList.add(childComponentInfo.getName());
+      } else {
+        deleteList.add(childComponentInfo.getName());
+      }
+    }
+    // Add remaining parent components
+    for (ComponentInfo parentComponent : parentService.getComponents()) {
+      if (!deleteList.contains(parentComponent.getName()) && !appendList
+          .contains(parentComponent.getName())) {
+        mergedServiceInfo.getComponents().add(parentComponent);
+      }
+    }
+    // Add child properties not deleted
+    deleteList = new ArrayList<String>();
+    appendList = new ArrayList<String>();
+    for (PropertyInfo propertyInfo : childService.getProperties()) {
+      if (!propertyInfo.isDeleted()) {
+        mergedServiceInfo.getProperties().add(propertyInfo);
+        appendList.add(propertyInfo.getName());
+      } else {
+        deleteList.add(propertyInfo.getName());
+      }
+    }
+    // Add all parent properties
+    for (PropertyInfo parentPropertyInfo : parentService.getProperties()) {
+      if (!deleteList.contains(parentPropertyInfo.getName()) && !appendList
+          .contains(parentPropertyInfo.getName())) {
+        mergedServiceInfo.getProperties().add(parentPropertyInfo);
+      }
+    }
+    return mergedServiceInfo;
+  }
+
+  public List<ServiceInfo> getAllApplicableServices(StackInfo stackInfo) {
+    LinkedList<StackInfo> parents = (LinkedList<StackInfo>)
+      stackParentsMap.get(stackInfo.getVersion());
+
+    if (parents == null || parents.isEmpty()) {
+      return stackInfo.getServices();
+    }
+    // Add child to the end of extension list
+    parents.addFirst(stackInfo);
+    ListIterator<StackInfo> lt = parents.listIterator(parents.size());
+    // Map services with unique names
+    Map<String, ServiceInfo> serviceInfoMap = new HashMap<String,
+      ServiceInfo>();
+    // Iterate with oldest parent first - all stacks are populated
+    while(lt.hasPrevious()) {
+      StackInfo parentStack = lt.previous();
+      List<ServiceInfo> serviceInfoList = parentStack.getServices();
+      for (ServiceInfo service : serviceInfoList) {
+        ServiceInfo existingService = serviceInfoMap.get(service.getName());
+        if (service.isDeleted()) {
+          serviceInfoMap.remove(service.getName());
+          continue;
+        }
+
+        if (existingService == null) {
+          serviceInfoMap.put(service.getName(), service);
+        } else {
+          // Redefined service - merge with parent
+          ServiceInfo newServiceInfo = mergeServices(existingService, service);
+          serviceInfoMap.put(service.getName(), newServiceInfo);
+        }
+      }
+    }
+    return new ArrayList<ServiceInfo>(serviceInfoMap.values());
+  }
+
+  private void populateServicesForStack(StackInfo stackInfo) {
+    List<ServiceInfo> services = new ArrayList<ServiceInfo>();
+    File servicesFolder = new File(stackRoot.getAbsolutePath() + File
+      .separator + stackInfo.getName() + File.separator + stackInfo.getVersion()
+      + File.separator + AmbariMetaInfo.SERVICES_FOLDER_NAME);
+    if (!servicesFolder.exists()) {
+      LOG.info("No services defined for stack: " + stackInfo.getName() +
+      "-" + stackInfo.getVersion());
+
+    } else {
+      File[] servicesFolders = servicesFolder.listFiles(AmbariMetaInfo
+        .FILENAME_FILTER);
+      if (servicesFolders != null) {
+        for (File serviceFolder : servicesFolders) {
+          // Get information about service
+          ServiceInfo serviceInfo = new ServiceInfo();
+          serviceInfo.setName(serviceFolder.getName());
+          File metainfoFile = new File(serviceFolder.getAbsolutePath()
+            + File.separator + AmbariMetaInfo.SERVICE_METAINFO_FILE_NAME);
+
+          setMetaInfo(metainfoFile, serviceInfo);
+          // Add now to be removed while iterating extension graph
+          services.add(serviceInfo);
+
+          // Get all properties from all "configs/*-site.xml" files
+          File serviceConfigFolder = new File(serviceFolder.getAbsolutePath()
+            + File.separator + AmbariMetaInfo.SERVICE_CONFIG_FOLDER_NAME);
+          File[] configFiles = serviceConfigFolder.listFiles
+            (AmbariMetaInfo.FILENAME_FILTER);
+          if (configFiles != null) {
+            for (File config : configFiles) {
+              if (config.getName().endsWith
+                (AmbariMetaInfo.SERVICE_CONFIG_FILE_NAME_POSTFIX)) {
+                serviceInfo.getProperties().addAll(getProperties(config));
+              }
+            }
+          }
+        }
+      }
+    }
+
+    stackInfo.getServices().addAll(services);
+  }
+
+  public List<StackInfo> getAllAvailableStacks() {
+    return new ArrayList<StackInfo>(stackVersionMap.values());
+  }
+
+  private Map<String, List<StackInfo>> getParentStacksInOrder(
+      Collection<StackInfo> stacks) {
+    Map<String, List<StackInfo>> parentStacksMap = new HashMap<String,
+      List<StackInfo>>();
+
+    for (StackInfo child : stacks) {
+      List<StackInfo> parentStacks = new LinkedList<StackInfo>();
+      parentStacksMap.put(child.getVersion(), parentStacks);
+      while (child.getParentStackVersion() != null && !child
+        .getParentStackVersion().isEmpty() && !child.getVersion().equals
+        (child.getParentStackVersion())) {
+        String key = child.getName() + child.getParentStackVersion();
+        if (stackVersionMap.containsKey(key)) {
+          StackInfo parent = stackVersionMap.get(key);
+          parentStacks.add(parent);
+          child = parent;
+        } else {
+          LOG.info("Unknown parent stack version: " + child
+            .getParentStackVersion() + ", for stack: " + child.getName() + " " +
+            child.getVersion());
+          break;
+        }
+      }
+    }
+    return parentStacksMap;
+  }
+
+  private StackInfo getStackInfo(File stackVersionFolder) {
+    StackInfo stackInfo = new StackInfo();
+
+    stackInfo.setName(stackVersionFolder.getParentFile().getName());
+    stackInfo.setVersion(stackVersionFolder.getName());
+
+    // Get metainfo from file
+    File stackMetainfoFile = new File(stackVersionFolder.getAbsolutePath()
+      + File.separator + AmbariMetaInfo.STACK_METAINFO_FILE_NAME);
+
+    if (stackMetainfoFile.exists()) {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Reading stack version metainfo from file "
+          + stackMetainfoFile.getAbsolutePath());
+      }
+
+      try {
+        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+        Document doc = dBuilder.parse(stackMetainfoFile);
+        doc.getDocumentElement().normalize();
+
+        NodeList stackNodes = doc
+          .getElementsByTagName(AmbariMetaInfo.STACK_XML_MAIN_BLOCK_NAME);
+
+        for (int index = 0; index < stackNodes.getLength(); index++) {
+          Node node = stackNodes.item(index);
+
+          if (node.getNodeType() == Node.ELEMENT_NODE) {
+            Element property = (Element) node;
+
+            stackInfo.setMinUpgradeVersion(getTagValue(
+              AmbariMetaInfo.STACK_XML_PROPERTY_UPGRADE, property));
+
+            stackInfo.setActive(Boolean.parseBoolean(getTagValue(
+              AmbariMetaInfo.STACK_XML_PROPERTY_ACTIVE, property)));
+
+            stackInfo.setParentStackVersion(getTagValue
+              (AmbariMetaInfo.STACK_XML_PROPERTY_PARENT_STACK, property));
+          }
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+        return null;
+      }
+    }
+    try {
+      // Read the service and available configs for this stack
+      populateServicesForStack(stackInfo);
+    } catch (Exception e) {
+      LOG.error("Exception caught while populating services for stack: " +
+        stackInfo.getName() + "-" + stackInfo.getVersion());
+      e.printStackTrace();
+    }
+    return stackInfo;
+  }
+
+  private String getTagValue(String sTag, Element rawElement) {
+    String result = null;
+
+    if (rawElement.getElementsByTagName(sTag) != null && rawElement.getElementsByTagName(sTag).getLength() > 0) {
+      if (rawElement.getElementsByTagName(sTag).item(0) != null) {
+        NodeList element = rawElement.getElementsByTagName(sTag).item(0).getChildNodes();
+
+        if (element != null && element.item(0) != null) {
+          Node value = (Node) element.item(0);
+
+          result = value.getNodeValue();
+        }
+      }
+    }
+
+    return result;
+  }
+
+  private void setMetaInfo(File metainfoFile, ServiceInfo serviceInfo) {
+
+    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+
+    Document doc = null;
+    DocumentBuilder dBuilder = null;
+    try {
+      dBuilder = dbFactory.newDocumentBuilder();
+      doc = dBuilder.parse(metainfoFile);
+    } catch (SAXException e) {
+      LOG.error("Error while parsing metainf.xml", e);
+    } catch (IOException e) {
+      LOG.error("Error while open metainf.xml", e);
+    } catch (ParserConfigurationException e) {
+      LOG.error("Error while parsing metainf.xml", e);
+    }
+
+    if (doc == null) return;
+
+    doc.getDocumentElement().normalize();
+
+    NodeList metaInfoNodes = doc
+      .getElementsByTagName(AmbariMetaInfo.METAINFO_XML_MAIN_BLOCK_NAME);
+
+    if (metaInfoNodes.getLength() > 0) {
+      Node metaInfoNode = metaInfoNodes.item(0);
+      if (metaInfoNode.getNodeType() == Node.ELEMENT_NODE) {
+
+        Element metaInfoElem = (Element) metaInfoNode;
+
+        serviceInfo.setVersion(getTagValue(AmbariMetaInfo.METAINFO_XML_PROPERTY_VERSION,
+          metaInfoElem));
+        serviceInfo.setUser(getTagValue(AmbariMetaInfo.METAINFO_XML_PROPERTY_USER,
+          metaInfoElem));
+        serviceInfo.setComment(getTagValue(AmbariMetaInfo.METAINFO_XML_PROPERTY_COMMENT,
+          metaInfoElem));
+        serviceInfo.setDeleted(getTagValue(AmbariMetaInfo.METAINFO_XML_PROPERTY_IS_DELETED,
+          metaInfoElem));
+      }
+    }
+
+    NodeList componentInfoNodes = doc
+      .getElementsByTagName(AmbariMetaInfo.METAINFO_XML_PROPERTY_COMPONENT_MAIN);
+
+    if (componentInfoNodes.getLength() > 0) {
+      for (int index = 0; index < componentInfoNodes.getLength(); index++) {
+        Node componentInfoNode = componentInfoNodes.item(index);
+        if (componentInfoNode.getNodeType() == Node.ELEMENT_NODE) {
+          Element componentInfoElem = (Element) componentInfoNode;
+
+          ComponentInfo componentInfo = new ComponentInfo();
+          componentInfo.setName(getTagValue(
+            AmbariMetaInfo.METAINFO_XML_PROPERTY_COMPONENT_NAME, componentInfoElem));
+          componentInfo.setCategory(getTagValue(
+            AmbariMetaInfo.METAINFO_XML_PROPERTY_COMPONENT_CATEGORY, componentInfoElem));
+          componentInfo.setDeleted(getTagValue(AmbariMetaInfo
+            .METAINFO_XML_PROPERTY_IS_DELETED, componentInfoElem));
+          serviceInfo.getComponents().add(componentInfo);
+
+        }
+      }
+    }
+  }
+
+  private List<PropertyInfo> getProperties(File propertyFile) {
+
+    List<PropertyInfo> resultPropertyList = new ArrayList<PropertyInfo>();
+    try {
+      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+      Document doc = dBuilder.parse(propertyFile);
+      doc.getDocumentElement().normalize();
+
+      NodeList propertyNodes = doc
+        .getElementsByTagName(AmbariMetaInfo.PROPERTY_XML_MAIN_BLOCK_NAME);
+
+      for (int index = 0; index < propertyNodes.getLength(); index++) {
+
+        Node node = propertyNodes.item(index);
+        if (node.getNodeType() == Node.ELEMENT_NODE) {
+          Element property = (Element) node;
+          PropertyInfo propertyInfo = new PropertyInfo();
+          propertyInfo
+            .setName(getTagValue(AmbariMetaInfo.PROPERTY_XML_PROPERTY_NAME, property));
+          propertyInfo.setValue(getTagValue(AmbariMetaInfo.PROPERTY_XML_PROPERTY_VALUE,
+            property));
+          propertyInfo.setDescription(getTagValue(
+            AmbariMetaInfo.PROPERTY_XML_PROPERTY_DESCRIPTION, property));
+          propertyInfo.setDeleted(getTagValue(AmbariMetaInfo
+            .PROPERTY_XML_PROPERTY_IS_DELETED, property));
+
+          propertyInfo.setFilename(propertyFile.getName());
+
+          if (propertyInfo.getName() == null || propertyInfo.getValue() == null)
+            continue;
+
+          resultPropertyList.add(propertyInfo);
+        }
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+    return resultPropertyList;
+  }
+}

+ 11 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/StackVersionResponse.java

@@ -28,12 +28,14 @@ public class StackVersionResponse {
   private String minUpgradeVersion;
   private boolean active;
   private List<RepositoryInfo> repositories;
+  private String parentVersion;
 
   public StackVersionResponse(String stackVersion, String minUpgradeVersion,
-                              boolean active) {
+                              boolean active, String parentVersion) {
     setStackVersion(stackVersion);
     setMinUpgradeVersion(minUpgradeVersion);
     setActive(active);
+    setParentVersion(parentVersion);
   }
 
   public String getStackVersion() {
@@ -67,4 +69,12 @@ public class StackVersionResponse {
   public void setActive(boolean active) {
     this.active = active;
   }
+
+  public String getParentVersion() {
+    return parentVersion;
+  }
+
+  public void setParentVersion(String parentVersion) {
+    this.parentVersion = parentVersion;
+  }
 }

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

@@ -64,6 +64,8 @@ public class StackVersionResourceProvider extends ReadOnlyResourceProvider {
       Arrays.asList(new String[] { STACK_NAME_PROPERTY_ID,
           STACK_VERSION_PROPERTY_ID }));
 
+  private static final String STACK_PARENT_PROPERTY_ID = PropertyHelper
+    .getPropertyId("Versions", "parent_stack_version");
 
   @Override
   public Set<Resource> getResources(Request request, Predicate predicate)
@@ -99,6 +101,9 @@ public class StackVersionResourceProvider extends ReadOnlyResourceProvider {
       setResourceProperty(resource, STACK_ACTIVE_PROPERTY_ID,
           response.isActive(), requestedIds);
 
+      setResourceProperty(resource, STACK_PARENT_PROPERTY_ID,
+        response.getParentVersion(), requestedIds);
+
       resources.add(resource);
     }
 

+ 9 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java

@@ -23,6 +23,7 @@ import org.apache.ambari.server.controller.StackServiceComponentResponse;
 public class ComponentInfo {
   private String name;
   private String category;
+  private boolean isDeleted;
 
   public String getName() {
     return name;
@@ -52,4 +53,12 @@ public class ComponentInfo {
     return new StackServiceComponentResponse(getName(), getCategory(), isClient(), isMaster());
   }
 
+  public boolean isDeleted() {
+    return isDeleted;
+  }
+
+  public void setDeleted(String deleted) {
+    isDeleted = Boolean.valueOf(deleted != null && !deleted.isEmpty() ?
+      deleted : "false");
+  }
 }

+ 12 - 5
ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java

@@ -21,12 +21,11 @@ package org.apache.ambari.server.state;
 import org.apache.ambari.server.controller.StackConfigurationResponse;
 
 public class PropertyInfo {
-  
-
   private String name;
   private String value;
   private String description;
   private String filename;
+  private boolean isDeleted;
 
   public String getName() {
     return name;
@@ -60,11 +59,19 @@ public class PropertyInfo {
     this.filename = filename;
   }
   
-  public StackConfigurationResponse convertToResponse()
-  {
+  public StackConfigurationResponse convertToResponse() {
     return new StackConfigurationResponse(getName(), getValue(), getDescription() , getFilename());
   }
-  
+
+  public boolean isDeleted() {
+    return isDeleted;
+  }
+
+  public void setDeleted(String deleted) {
+    isDeleted = Boolean.valueOf(deleted != null && !deleted.isEmpty() ?
+      deleted : "false");
+  }
+
   @Override
   public int hashCode() {
     final int prime = 31;

+ 13 - 3
ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java

@@ -29,11 +29,21 @@ import org.codehaus.jackson.map.annotate.JsonFilter;
 @JsonFilter("propertiesfilter")
 public class ServiceInfo {
   private String name;
-    private String version;
-    private String user;
-    private String comment;
+  private String version;
+  private String user;
+  private String comment;
   private List<PropertyInfo> properties;
   private List<ComponentInfo> components;
+  private Boolean isDeleted = false;
+
+  public Boolean isDeleted() {
+    return isDeleted;
+  }
+
+  public void setDeleted(String deleted) {
+    isDeleted = Boolean.valueOf(deleted != null && !deleted.isEmpty() ?
+      deleted : "false");
+  }
 
   public String getName() {
     return name;

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

@@ -30,6 +30,7 @@ public class StackInfo {
   private boolean active;
   private List<RepositoryInfo> repositories;
   private List<ServiceInfo> services;
+  private String parentStackVersion;
 
   public String getName() {
     return name;
@@ -108,7 +109,8 @@ public class StackInfo {
 
   public StackVersionResponse convertToResponse() {
 
-    return new StackVersionResponse(getVersion(), getMinUpgradeVersion(), isActive());
+    return new StackVersionResponse(getVersion(), getMinUpgradeVersion(),
+      isActive(), getParentStackVersion());
   }
 
   public String getMinUpgradeVersion() {
@@ -126,4 +128,12 @@ public class StackInfo {
   public void setActive(boolean active) {
     this.active = active;
   }
+
+  public String getParentStackVersion() {
+    return parentStackVersion;
+  }
+
+  public void setParentStackVersion(String parentStackVersion) {
+    this.parentStackVersion = parentStackVersion;
+  }
 }

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

@@ -119,6 +119,7 @@
         "Versions/stack_version",
         "Versions/min_upgrade_version",
         "Versions/active",
+        "Versions/parent_stack_version",
         "_"
     ],
     "OperatingSystem":[

+ 178 - 17
ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java

@@ -18,24 +18,10 @@
 
 package org.apache.ambari.server.api.services;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import junit.framework.Assert;
-
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.StackAccessException;
+import org.apache.ambari.server.api.util.StackExtensionHelper;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.OperatingSystemInfo;
 import org.apache.ambari.server.state.PropertyInfo;
@@ -50,11 +36,28 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class AmbariMetaInfoTest {
 
   private static String STACK_NAME_HDP = "HDP";
   private static String STACK_VERSION_HDP = "0.1";
+  private static String EXT_STACK_NAME = "2.0.6";
   private static final String STACK_MINIMAL_VERSION_HDP = "0.0";
   private static String SERVICE_NAME_HDFS = "HDFS";
   private static String SERVICE_COMPONENT_NAME = "NAMENODE";
@@ -81,7 +84,7 @@ public class AmbariMetaInfoTest {
   @Before
   public void before() throws Exception {
     File stackRoot = new File("src/test/resources/stacks");
-   LOG.info("Stacks file " + stackRoot.getAbsolutePath());
+    LOG.info("Stacks file " + stackRoot.getAbsolutePath());
     metaInfo = new AmbariMetaInfo(stackRoot, new File("target/version"));
     try {
       metaInfo.init();
@@ -428,5 +431,163 @@ public class AmbariMetaInfoTest {
     Assert.assertTrue(metaInfo.isOsSupported("sles11"));
     Assert.assertFalse(metaInfo.isOsSupported("windows"));
   }
-  
+
+  @Test
+  public void testExtendedStackDefinition() throws Exception {
+    StackInfo stackInfo = metaInfo.getStackInfo(STACK_NAME_HDP, EXT_STACK_NAME);
+    Assert.assertTrue(stackInfo != null);
+    List<ServiceInfo> serviceInfos = stackInfo.getServices();
+    Assert.assertFalse(serviceInfos.isEmpty());
+    Assert.assertTrue(serviceInfos.size() > 1);
+    ServiceInfo deletedService = null;
+    ServiceInfo redefinedService = null;
+    for (ServiceInfo serviceInfo : serviceInfos) {
+      if (serviceInfo.getName().equals("SQOOP")) {
+        deletedService = serviceInfo;
+      }
+      if (serviceInfo.getName().equals("YARN")) {
+        redefinedService = serviceInfo;
+      }
+    }
+    Assert.assertNull("SQOOP is a deleted service, should not be a part of " +
+      "the extended stack.", deletedService);
+    Assert.assertNotNull(redefinedService);
+    // Components
+    Assert.assertEquals("YARN service is expected to be defined with 3 active" +
+      " components.", 3, redefinedService.getComponents().size());
+    Assert.assertEquals("TEZ is expected to be a part of extended stack " +
+      "definition", "TEZ", redefinedService.getClientComponent().getName());
+    Assert.assertFalse("YARN CLIENT is a deleted component.",
+      redefinedService.getClientComponent().getName().equals("YARN_CLIENT"));
+    // Properties
+    Assert.assertNotNull(redefinedService.getProperties());
+    Assert.assertTrue(redefinedService.getProperties().size() > 4);
+    PropertyInfo deleteProperty1 = null;
+    PropertyInfo deleteProperty2 = null;
+    PropertyInfo redefinedProperty1 = null;
+    PropertyInfo redefinedProperty2 = null;
+    PropertyInfo inheritedProperty = null;
+    PropertyInfo newProperty = null;
+    PropertyInfo originalProperty = null;
+
+    for (PropertyInfo propertyInfo : redefinedService.getProperties()) {
+      if (propertyInfo.getName().equals("yarn.resourcemanager" +
+        ".resource-tracker.address")) {
+        deleteProperty1 = propertyInfo;
+      } else if (propertyInfo.getName().equals("yarn.resourcemanager" +
+        ".scheduler.address")) {
+        deleteProperty2 = propertyInfo;
+      } else if (propertyInfo.getName().equals("yarn.resourcemanager" +
+        ".address")) {
+        redefinedProperty1 = propertyInfo;
+      } else if (propertyInfo.getName().equals("yarn.resourcemanager.admin" +
+        ".address")) {
+        redefinedProperty2 = propertyInfo;
+      } else if (propertyInfo.getName().equals("yarn.nodemanager.address")) {
+        inheritedProperty = propertyInfo;
+      } else if (propertyInfo.getName().equals("new-yarn-property")) {
+        newProperty = propertyInfo;
+      } else if (propertyInfo.getName().equals("yarn.nodemanager.aux-services")) {
+        originalProperty = propertyInfo;
+      }
+    }
+
+    Assert.assertNull(deleteProperty1);
+    Assert.assertNull(deleteProperty2);
+    Assert.assertNotNull(redefinedProperty1);
+    Assert.assertNotNull(redefinedProperty2);
+    Assert.assertNotNull("yarn.nodemanager.address expected to be inherited " +
+      "from parent", inheritedProperty);
+    Assert.assertEquals("localhost:100009", redefinedProperty1.getValue());
+    // Parent property value will result in property being present in the
+    // child stack
+    Assert.assertEquals("localhost:8141", redefinedProperty2.getValue());
+    // New property
+    Assert.assertNotNull(newProperty);
+    Assert.assertEquals("some-value", newProperty.getValue());
+    Assert.assertEquals("some description.", newProperty.getDescription());
+    Assert.assertEquals("yarn-site.xml", newProperty.getFilename());
+    // Original property
+    Assert.assertNotNull(originalProperty);
+    Assert.assertEquals("mapreduce.shuffle", originalProperty.getValue());
+    Assert.assertEquals("Auxilliary services of NodeManager",
+      originalProperty.getDescription());
+  }
+
+  @Test
+  public void testGetParentStacksInOrder() throws Exception {
+    List<StackInfo> allStacks = metaInfo.getSupportedStacks();
+    StackInfo stackInfo = metaInfo.getStackInfo(STACK_NAME_HDP, EXT_STACK_NAME);
+    StackInfo newStack = new StackInfo();
+    newStack.setName(STACK_NAME_HDP);
+    newStack.setVersion("2.0.99");
+    newStack.setParentStackVersion(EXT_STACK_NAME);
+    newStack.setActive(true);
+    newStack.setRepositories(stackInfo.getRepositories());
+    allStacks.add(newStack);
+
+    Method method = StackExtensionHelper.class.getDeclaredMethod
+      ("getParentStacksInOrder", Collection.class);
+    method.setAccessible(true);
+    StackExtensionHelper helper = new StackExtensionHelper(metaInfo.getStackRoot());
+    Map<String, List<StackInfo>> stacks = (Map<String, List<StackInfo>>)
+      method.invoke(helper, allStacks);
+
+    Assert.assertNotNull(stacks.get("2.0.99"));
+    // Verify order
+    LinkedList<String> target = new LinkedList<String>();
+    target.add("2.0.5");
+    target.add("2.0.6");
+    target.add("2.0.99");
+    LinkedList<String> actual = new LinkedList<String>();
+    LinkedList<StackInfo> parents = (LinkedList<StackInfo>) stacks.get("2.0.99");
+    parents.addFirst(newStack);
+    ListIterator lt = parents.listIterator(parents.size());
+    while (lt.hasPrevious()) {
+      StackInfo stack = (StackInfo) lt.previous();
+      actual.add(stack.getVersion());
+    }
+    org.junit.Assert.assertArrayEquals("Order of stack extension not " +
+      "preserved.", target.toArray(), actual.toArray());
+  }
+
+  @Test
+  public void testGetApplicableServices() throws Exception {
+    StackExtensionHelper helper = new StackExtensionHelper(
+      metaInfo.getStackRoot());
+    List<ServiceInfo> allServices = helper.getAllApplicableServices(metaInfo
+      .getStackInfo(STACK_NAME_HDP, EXT_STACK_NAME));
+
+    ServiceInfo testService = null;
+    ServiceInfo existingService = null;
+    for (ServiceInfo serviceInfo : allServices) {
+      if (serviceInfo.getName().equals("YARN")) {
+        testService = serviceInfo;
+      } else if (serviceInfo.getName().equals("MAPREDUCE2")) {
+        existingService = serviceInfo;
+      }
+    }
+
+    Assert.assertNotNull(testService);
+    Assert.assertNotNull(existingService);
+
+    PropertyInfo testProperty = null;
+    PropertyInfo existingProperty = null;
+    for (PropertyInfo property : testService.getProperties()) {
+      if (property.getName().equals("new-yarn-property")) {
+        testProperty = property;
+      }
+    }
+    for (PropertyInfo property : existingService.getProperties()) {
+      if (property.getName().equals("mapreduce.map.log.level")) {
+        existingProperty = property;
+      }
+    }
+
+    Assert.assertNotNull(testProperty);
+    Assert.assertEquals("some-value", testProperty.getValue());
+    Assert.assertNotNull(existingProperty);
+    Assert.assertEquals("INFO", existingProperty.getValue());
+  }
+
 }

+ 2 - 2
ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java

@@ -113,7 +113,7 @@ public class AmbariManagementControllerTest {
   private static final String REPO_ID = "HDP-1.1.1.16";
   private static final String PROPERTY_NAME = "hbase.regionserver.msginterval";
   private static final String SERVICE_NAME = "HDFS";
-  private static final int STACK_VERSIONS_CNT = 7;
+  private static final int STACK_VERSIONS_CNT = 8;
   private static final int REPOS_CNT = 3;
   private static final int STACKS_CNT = 1;
   private static final int STACK_SERVICES_CNT = 5 ;
@@ -5205,7 +5205,7 @@ public class AmbariManagementControllerTest {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
-      .setDesiredStackVersion(new StackId("HDP-0.1"));
+      .setDesiredStackVersion(new StackId("HDP-2.0.6"));
     String serviceName = "HDFS";
     createService(clusterName, serviceName, null);
     String componentName1 = "NAMENODE";

+ 24 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.6/metainfo.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<metainfo>
+    <versions>
+      <active>true</active>
+    </versions>
+    <extends>2.0.5</extends>
+</metainfo>
+

+ 61 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.6/repos/repoinfo.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<reposinfo>
+  <os type="centos6">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/centos6/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+  <os type="centos5">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/centos5/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+  <os type="redhat6">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/centos6/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+  <os type="redhat5">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/centos5/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+  <os type="suse11">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/suse11/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+  <os type="sles11">
+    <repo>
+      <baseurl>http://public-repo-1.hortonworks.com/HDP/suse11/2.x/updates/2.0.6.0</baseurl>
+      <repoid>HDP-2.0.6</repoid>
+      <reponame>HDP</reponame>
+    </repo>
+  </os>
+</reposinfo>

+ 30 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.6/services/SQOOP/metainfo.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<metainfo>
+    <user>root</user>
+    <comment>Tool for transferring bulk data between Apache Hadoop and structured data stores such as relational databases</comment>
+    <version></version>
+    <deleted>true</deleted>
+    <components>
+        <component>
+            <name>SQOOP</name>
+            <category>CLIENT</category>
+        </component>
+    </components>
+
+</metainfo>

+ 60 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.6/services/YARN/configuration/yarn-site.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<!--
+   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.
+-->
+
+<!-- Put site-specific property overrides in this file. -->
+
+<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<!-- ResourceManager -->
+
+  <property>
+    <name>yarn.resourcemanager.resource-tracker.address</name>
+    <value>localhost:8025</value>
+    <deleted>true</deleted>
+  </property>
+
+  <property>
+    <name>yarn.resourcemanager.scheduler.address</name>
+    <value>localhost:8030</value>
+    <description>The address of the scheduler interface.</description>
+    <deleted>true</deleted>
+  </property>
+  
+  <property>
+    <name>yarn.resourcemanager.address</name>
+    <value>localhost:100009</value>
+    <description>
+      The address of the applications manager interface in the
+      RM.
+    </description>
+  </property>
+
+  <property>
+    <name>yarn.resourcemanager.admin.address</name>
+    <value></value>
+    <description>The address of the RM admin interface.</description>
+  </property>
+
+  <property>
+    <name>new-yarn-property</name>
+    <value>some-value</value>
+    <description>some description.</description>
+  </property>
+
+</configuration>

+ 42 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.6/services/YARN/metainfo.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<metainfo>
+    <user>mapred</user>
+    <comment>Apache Hadoop NextGen MapReduce (YARN)</comment>
+    <version>2.1.0.2.0.6.0</version>
+    <deleted>false</deleted>
+    <components>
+      <component>
+        <name>RESOURCEMANAGER</name>
+        <category>MASTER</category>
+      </component>
+      <component>
+        <name>NODEMANAGER</name>
+        <category>SLAVE</category>
+      </component>
+      <component>
+        <name>YARN_CLIENT</name>
+        <category>CLIENT</category>
+        <deleted>true</deleted>
+      </component>
+      <component>
+        <name>TEZ</name>
+        <category>CLIENT</category>
+      </component>
+    </components>
+</metainfo>