浏览代码

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

Siddharth Wagle 11 年之前
父节点
当前提交
980918e0e9
共有 16 个文件被更改,包括 951 次插入278 次删除
  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>