You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sr...@apache.org on 2014/08/26 01:58:16 UTC

git commit: AMBARI-7006. Provide stack_advisor python script for HDP 1.3.x stack versions

Repository: ambari
Updated Branches:
  refs/heads/trunk 46a91dbb0 -> fb1e0ca52


AMBARI-7006. Provide stack_advisor python script for HDP 1.3.x stack versions


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

Branch: refs/heads/trunk
Commit: fb1e0ca5260d35938df2c10cd75ee5a594873c30
Parents: 46a91db
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Mon Aug 25 14:58:14 2014 -0700
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Mon Aug 25 16:57:54 2014 -0700

----------------------------------------------------------------------
 .../server/api/services/AmbariMetaInfo.java     |  18 +
 .../stackadvisor/StackAdvisorHelper.java        |  14 +-
 ...GetComponentLayoutRecommnedationCommand.java |   5 +-
 .../GetComponentLayoutValidationCommand.java    |   5 +-
 .../GetConfigurationRecommnedationCommand.java  |  13 +-
 .../GetConfigurationValidationCommand.java      |   5 +-
 .../commands/StackAdvisorCommand.java           |  20 +-
 .../src/main/resources/scripts/stack_advisor.py |   5 +-
 .../stacks/HDP/1.3.2/services/stack_advisor.py  | 519 +++++++++++++++++++
 .../stacks/HDP/1.3.3/services/stack_advisor.py  |  25 +
 .../stacks/HDP/1.3/services/stack_advisor.py    |  25 +
 .../server/api/services/AmbariMetaInfoTest.java |   9 +
 .../stackadvisor/StackAdvisorHelperTest.java    |  22 +-
 .../commands/StackAdvisorCommandTest.java       |  80 ++-
 14 files changed, 728 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
index 80af575..56b866d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
@@ -645,6 +645,24 @@ public class AmbariMetaInfo {
     return stackInfoResult;
   }
 
+  public List<String> getStackParentVersions(String stackName, String version) {
+    List<String> parents = new ArrayList<String>();
+    try {
+      StackInfo stackInfo = getStackInfo(stackName, version);
+      if (stackInfo != null) {
+        String parentVersion = stackInfo.getParentStackVersion();
+        if (parentVersion != null) {
+          parents.add(parentVersion);
+          parents.addAll(getStackParentVersions(stackName, parentVersion));
+        }
+      }
+    } catch (AmbariException e) {
+      // parent was not found. just returning empty list
+    } finally {
+      return parents;
+    }
+  }
+
   public Set<PropertyInfo> getProperties(String stackName, String version, String serviceName)
       throws AmbariException {
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
index 6903c1d..60cdd52 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
@@ -21,6 +21,7 @@ package org.apache.ambari.server.api.services.stackadvisor;
 import java.io.File;
 import java.io.IOException;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
 import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutRecommnedationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutValidationCommand;
@@ -39,16 +40,19 @@ public class StackAdvisorHelper {
 
   private File recommendationsDir;
   private String stackAdvisorScript;
+  private final AmbariMetaInfo metaInfo;
 
   /* Monotonically increasing requestid */
   private int requestId = 0;
   private StackAdvisorRunner saRunner;
 
   @Inject
-  public StackAdvisorHelper(Configuration conf, StackAdvisorRunner saRunner) throws IOException {
+  public StackAdvisorHelper(Configuration conf, StackAdvisorRunner saRunner,
+                            AmbariMetaInfo metaInfo) throws IOException {
     this.recommendationsDir = conf.getRecommendationsDir();
     this.stackAdvisorScript = conf.getStackAdvisorScript();
     this.saRunner = saRunner;
+    this.metaInfo = metaInfo;
   }
 
   /**
@@ -74,10 +78,10 @@ public class StackAdvisorHelper {
     StackAdvisorCommand<ValidationResponse> command;
     if (requestType == StackAdvisorRequestType.HOST_GROUPS) {
       command = new GetComponentLayoutValidationCommand(recommendationsDir, stackAdvisorScript,
-          requestId, saRunner);
+          requestId, saRunner, metaInfo);
     } else if (requestType == StackAdvisorRequestType.CONFIGURATIONS) {
       command = new GetConfigurationValidationCommand(recommendationsDir, stackAdvisorScript,
-          requestId, saRunner);
+          requestId, saRunner, metaInfo);
     } else {
       throw new StackAdvisorException(String.format("Unsupported request type, type=%s",
           requestType));
@@ -109,10 +113,10 @@ public class StackAdvisorHelper {
     StackAdvisorCommand<RecommendationResponse> command;
     if (requestType == StackAdvisorRequestType.HOST_GROUPS) {
       command = new GetComponentLayoutRecommnedationCommand(recommendationsDir, stackAdvisorScript,
-          requestId, saRunner);
+          requestId, saRunner, metaInfo);
     } else if (requestType == StackAdvisorRequestType.CONFIGURATIONS) {
       command = new GetConfigurationRecommnedationCommand(recommendationsDir, stackAdvisorScript,
-          requestId, saRunner);
+          requestId, saRunner, metaInfo);
     } else {
       throw new StackAdvisorException(String.format("Unsupported request type, type=%s",
           requestType));

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
index 6e7533a..b91f912 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.api.services.stackadvisor.commands;
 
 import java.io.File;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
@@ -33,8 +34,8 @@ public class GetComponentLayoutRecommnedationCommand extends
     StackAdvisorCommand<RecommendationResponse> {
 
   public GetComponentLayoutRecommnedationCommand(File recommendationsDir,
-      String stackAdvisorScript, int requestId, StackAdvisorRunner saRunner) {
-    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+      String stackAdvisorScript, int requestId, StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
index a5453f6..1a1fc98 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.api.services.stackadvisor.commands;
 
 import java.io.File;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
@@ -31,8 +32,8 @@ import org.apache.ambari.server.api.services.stackadvisor.validations.Validation
 public class GetComponentLayoutValidationCommand extends StackAdvisorCommand<ValidationResponse> {
 
   public GetComponentLayoutValidationCommand(File recommendationsDir, String stackAdvisorScript,
-      int requestId, StackAdvisorRunner saRunner) {
-    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+      int requestId, StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationRecommnedationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationRecommnedationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationRecommnedationCommand.java
index e65f97f..52df3d0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationRecommnedationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationRecommnedationCommand.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.api.services.stackadvisor.commands;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
@@ -37,9 +38,9 @@ import java.util.Set;
 public class GetConfigurationRecommnedationCommand extends
     StackAdvisorCommand<RecommendationResponse> {
 
-  public GetConfigurationRecommnedationCommand(File recommendationsDir,
-                                               String stackAdvisorScript, int requestId, StackAdvisorRunner saRunner) {
-    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+  public GetConfigurationRecommnedationCommand(File recommendationsDir, String stackAdvisorScript, int requestId,
+                                               StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo);
   }
 
   @Override
@@ -56,12 +57,6 @@ public class GetConfigurationRecommnedationCommand extends
   }
 
   @Override
-  protected StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request) {
-    // do nothing
-    return data;
-  }
-
-  @Override
   protected RecommendationResponse updateResponse(StackAdvisorRequest request, RecommendationResponse response) {
     response.getRecommendations().getBlueprint().setHostGroups(processHostGroups(request));
     response.getRecommendations().getBlueprintClusterBinding().setHostGroups(processHostGroupBindings(request));

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationValidationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationValidationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationValidationCommand.java
index 2cd6481..36fe6cb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationValidationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetConfigurationValidationCommand.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.api.services.stackadvisor.commands;
 
 import java.io.File;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
@@ -31,8 +32,8 @@ import org.apache.ambari.server.api.services.stackadvisor.validations.Validation
 public class GetConfigurationValidationCommand extends StackAdvisorCommand<ValidationResponse> {
 
   public GetConfigurationValidationCommand(File recommendationsDir, String stackAdvisorScript,
-      int requestId, StackAdvisorRunner saRunner) {
-    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+      int requestId, StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
index 607e337..81d4605 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
@@ -35,6 +35,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.BaseService;
 import org.apache.ambari.server.api.services.LocalUriInfo;
 import org.apache.ambari.server.api.services.Request;
@@ -52,6 +53,7 @@ import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.codehaus.jackson.node.ArrayNode;
 import org.codehaus.jackson.node.ObjectNode;
+import org.codehaus.jackson.node.TextNode;
 
 /**
  * Parent for all commands.
@@ -89,9 +91,11 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
 
   protected ObjectMapper mapper;
 
+  private final AmbariMetaInfo metaInfo;
+
   @SuppressWarnings("unchecked")
   public StackAdvisorCommand(File recommendationsDir, String stackAdvisorScript, int requestId,
-      StackAdvisorRunner saRunner) {
+      StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
     this.type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
         .getActualTypeArguments()[0];
 
@@ -102,6 +106,7 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
     this.stackAdvisorScript = stackAdvisorScript;
     this.requestId = requestId;
     this.saRunner = saRunner;
+    this.metaInfo = metaInfo;
   }
 
   protected abstract StackAdvisorCommandType getCommandType();
@@ -133,6 +138,7 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
     try {
       ObjectNode root = (ObjectNode) this.mapper.readTree(data.servicesJSON);
 
+      populateStackHierarchy(root);
       populateComponentHostsMap(root, request.getComponentHostsMap());
       populateConfigurations(root, request.getConfigurations());
 
@@ -166,6 +172,18 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
     }
   }
 
+  protected void populateStackHierarchy(ObjectNode root) {
+    ObjectNode version = (ObjectNode) root.get("Versions");
+    TextNode stackName = (TextNode) version.get("stack_name");
+    TextNode stackVersion = (TextNode) version.get("stack_version");
+    ObjectNode stackHierarchy = version.putObject("stack_hierarchy");
+    stackHierarchy.put("stack_name", stackName);
+    ArrayNode parents = stackHierarchy.putArray("stack_versions");
+    for (String parentVersion : metaInfo.getStackParentVersions(stackName.asText(), stackVersion.asText())) {
+      parents.add(parentVersion);
+    }
+  }
+
   private void populateComponentHostsMap(ObjectNode root, Map<String, Set<String>> componentHostsMap) {
     ArrayNode services = (ArrayNode) root.get(SERVICES_PROPETRY);
     Iterator<JsonNode> servicesIter = services.getElements();

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/resources/scripts/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/scripts/stack_advisor.py b/ambari-server/src/main/resources/scripts/stack_advisor.py
index 05d3a20..61d1e09 100755
--- a/ambari-server/src/main/resources/scripts/stack_advisor.py
+++ b/ambari-server/src/main/resources/scripts/stack_advisor.py
@@ -82,9 +82,8 @@ def main(argv=None):
   stackName = services["Versions"]["stack_name"]
   stackVersion = services["Versions"]["stack_version"]
   parentVersions = []
-  if "parent_stack_version" in services["Versions"] and \
-      services["Versions"]["parent_stack_version"] is not None:
-    parentVersions = [ services["Versions"]["parent_stack_version"] ]
+  if "stack_hierarchy" in services["Versions"]:
+    parentVersions = services["Versions"]["stack_hierarchy"]["stack_versions"]
 
   stackAdvisor = instantiateStackAdvisor(stackName, stackVersion, parentVersions)
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/resources/stacks/HDP/1.3.2/services/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/1.3.2/services/stack_advisor.py b/ambari-server/src/main/resources/stacks/HDP/1.3.2/services/stack_advisor.py
new file mode 100644
index 0000000..f3c1e1d
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/HDP/1.3.2/services/stack_advisor.py
@@ -0,0 +1,519 @@
+#!/usr/bin/env ambari-python-wrap
+"""
+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.
+"""
+
+import re
+import socket
+import sys
+
+from stack_advisor import StackAdvisor
+
+class HDP132StackAdvisor(StackAdvisor):
+
+  def recommendComponentLayout(self, services, hosts):
+    """
+    Returns Services object with hostnames array populated for components
+    If hostnames are populated for some components (partial blueprint) - these components will not be processed
+    """
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+
+    recommendations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "hosts": hostsList,
+      "services": servicesList,
+      "recommendations": {
+        "blueprint": {
+          "host_groups": [ ]
+        },
+        "blueprint_cluster_binding": {
+          "host_groups": [ ]
+        }
+      }
+    }
+
+    hostsComponentsMap = {}
+
+    #extend 'hostsComponentsMap' with MASTER components
+    for service in services["services"]:
+      masterComponents = [component for component in service["components"] if isMaster(component)]
+      for component in masterComponents:
+        componentName = component["StackServiceComponents"]["component_name"]
+        hostsForComponent = []
+
+        if isAlreadyPopulated(component):
+          hostsForComponent = component["StackServiceComponents"]["hostnames"]
+        else:
+          availableHosts = hostsList
+          if len(hostsList) > 1 and self.isNotPreferableOnAmbariServerHost(component):
+            availableHosts = [hostName for hostName in hostsList if not isLocalHost(hostName)]
+
+          if isMasterWithMultipleInstances(component):
+            hostsCount = defaultNoOfMasterHosts(component)
+            if hostsCount > 1: # get first 'hostsCount' available hosts
+              if len(availableHosts) < hostsCount:
+                hostsCount = len(availableHosts)
+              hostsForComponent = availableHosts[:hostsCount]
+            else:
+              hostsForComponent = [self.getHostForComponent(component, availableHosts)]
+          else:
+            hostsForComponent = [self.getHostForComponent(component, availableHosts)]
+
+        #extend 'hostsComponentsMap' with 'hostsForComponent'
+        for hostName in hostsForComponent:
+          if hostName not in hostsComponentsMap:
+            hostsComponentsMap[hostName] = []
+          hostsComponentsMap[hostName].append( { "name":componentName } )
+
+    #extend 'hostsComponentsMap' with Slave and Client Components
+    componentsListList = [service["components"] for service in services["services"]]
+    componentsList = [item for sublist in componentsListList for item in sublist]
+    usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not self.isNotValuable(component)]
+    utilizedHosts = [item for sublist in usedHostsListList for item in sublist]
+    freeHosts = [hostName for hostName in hostsList if hostName not in utilizedHosts]
+
+    for service in services["services"]:
+      slaveClientComponents = [component for component in service["components"] if isSlave(component) or isClient(component)]
+      for component in slaveClientComponents:
+        componentName = component["StackServiceComponents"]["component_name"]
+        hostsForComponent = []
+
+        if isAlreadyPopulated(component):
+          hostsForComponent = component["StackServiceComponents"]["hostnames"]
+        elif component["StackServiceComponents"]["cardinality"] == "ALL":
+          hostsForComponent = hostsList
+        else:
+          if len(freeHosts) == 0:
+            hostsForComponent = hostsList[-1:]
+          else: # len(freeHosts) >= 1
+            hostsForComponent = freeHosts
+            if isClient(component):
+              hostsForComponent = freeHosts[0:1]
+
+        #extend 'hostsComponentsMap' with 'hostsForComponent'
+        for hostName in hostsForComponent:
+          if hostName not in hostsComponentsMap:
+            hostsComponentsMap[hostName] = []
+          hostsComponentsMap[hostName].append( { "name": componentName } )
+
+    #prepare 'host-group's from 'hostsComponentsMap'
+    host_groups = recommendations["recommendations"]["blueprint"]["host_groups"]
+    bindings = recommendations["recommendations"]["blueprint_cluster_binding"]["host_groups"]
+    index = 0
+    for key in hostsComponentsMap.keys():
+      index += 1
+      host_group_name = "host-group-{0}".format(index)
+      host_groups.append( { "name": host_group_name, "components": hostsComponentsMap[key] } )
+      bindings.append( { "name": host_group_name, "hosts": [{ "fqdn": socket.getfqdn(key) }] } )
+
+    return recommendations
+  pass
+
+  def getHostForComponent(self, component, hostsList):
+    componentName = component["StackServiceComponents"]["component_name"]
+    scheme = self.defineSelectionScheme(componentName)
+
+    if len(hostsList) == 1:
+      return hostsList[0]
+    else:
+      for key in scheme.keys():
+        if isinstance(key, ( int, long )):
+          if len(hostsList) < key:
+            return hostsList[scheme[key]]
+      return hostsList[scheme['else']]
+
+  def defineSelectionScheme(self, componentName):
+    scheme = self.selectionScheme(componentName)
+    if scheme is None:
+      scheme = {"else": 0}
+    return scheme
+
+  def selectionScheme(self, componentName):
+    return {
+      'NAMENODE': {"else": 0},
+      'SECONDARY_NAMENODE': {"else": 1},
+      'HBASE_MASTER': {6: 0, 31: 2, "else": 3},
+
+      'HISTORYSERVER': {31: 1, "else": 2},
+      'RESOURCEMANAGER': {31: 1, "else": 2},
+
+      'OOZIE_SERVER': {6: 1, 31: 2, "else": 3},
+
+      'HIVE_SERVER': {6: 1, 31: 2, "else": 4},
+      'HIVE_METASTORE': {6: 1, 31: 2, "else": 4},
+      'WEBHCAT_SERVER': {6: 1, 31: 2, "else": 4},
+      }.get(componentName, None)
+
+  def isNotPreferableOnAmbariServerHost(self, component):
+    componentName = component["StackServiceComponents"]["component_name"]
+    service = ['GANGLIA_SERVER', 'NAGIOS_SERVER']
+    return componentName in service
+
+  def validateComponentLayout(self, services, hosts):
+    """Returns array of Validation objects about issues with hostnames components assigned to"""
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+
+    validations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "items": [ ]
+    }
+    items = validations["items"]
+
+    # Validating NAMENODE and SECONDARY_NAMENODE are on different hosts if possible
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    hostsCount = len(hostsList)
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+
+    componentsListList = [service["components"] for service in services["services"]]
+    componentsList = [item for sublist in componentsListList for item in sublist]
+    nameNodeHosts = [component["StackServiceComponents"]["hostnames"] for component in componentsList if component["StackServiceComponents"]["component_name"] == "NAMENODE"]
+    secondaryNameNodeHosts = [component["StackServiceComponents"]["hostnames"] for component in componentsList if component["StackServiceComponents"]["component_name"] == "SECONDARY_NAMENODE"]
+
+    if hostsCount > 1 and len(nameNodeHosts) > 0 and len(secondaryNameNodeHosts) > 0:
+      nameNodeHosts = nameNodeHosts[0]
+      secondaryNameNodeHosts = secondaryNameNodeHosts[0]
+      commonHosts = list(set(nameNodeHosts).intersection(secondaryNameNodeHosts))
+      for host in commonHosts:
+        items.append( { "type": 'host-component', "level": 'WARN', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'NAMENODE', "host": str(host) } )
+        items.append( { "type": 'host-component', "level": 'WARN', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'SECONDARY_NAMENODE', "host": str(host) } )
+
+    # Validating cardinality
+    for component in componentsList:
+      if component["StackServiceComponents"]["cardinality"] is not None:
+         componentName = component["StackServiceComponents"]["component_name"]
+         componentHostsCount = 0
+         if component["StackServiceComponents"]["hostnames"] is not None:
+           componentHostsCount = len(component["StackServiceComponents"]["hostnames"])
+         cardinality = str(component["StackServiceComponents"]["cardinality"])
+         # cardinality types: null, 1+, 1-2, 1, ALL
+         hostsMax = -sys.maxint - 1
+         hostsMin = sys.maxint
+         hostsMin = 0
+         hostsMax = 0
+         if "+" in cardinality:
+           hostsMin = int(cardinality[:-1])
+           hostsMax = sys.maxint
+         elif "-" in cardinality:
+           nums = cardinality.split("-")
+           hostsMin = int(nums[0])
+           hostsMax = int(nums[1])
+         elif "ALL" == cardinality:
+           hostsMin = hostsCount
+           hostsMax = hostsCount
+         else:
+           hostsMin = int(cardinality)
+           hostsMax = int(cardinality)
+
+         if componentHostsCount > hostsMax or componentHostsCount < hostsMin:
+           items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Cardinality violation, cardinality={0}, hosts count={1}'.format(cardinality, str(componentHostsCount)), "component-name": str(componentName) } )
+
+    # Validating host-usage
+    usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not self.isNotValuable(component)]
+    usedHostsList = [item for sublist in usedHostsListList for item in sublist]
+    nonUsedHostsList = [item for item in hostsList if item not in usedHostsList]
+    for host in nonUsedHostsList:
+      items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Host is not used', "host": str(host) } )
+
+    return validations
+  pass
+
+  def isNotValuable(self, component):
+    componentName = component["StackServiceComponents"]["component_name"]
+    service = ['JOURNALNODE', 'ZKFC', 'GANGLIA_MONITOR']
+    return componentName in service
+
+  def recommendConfigurations(self, services, hosts):
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+    components = [component["StackServiceComponents"]["component_name"]
+                  for service in services["services"]
+                  for component in service["components"]]
+
+    clusterData = self.getClusterData(servicesList, hosts, components)
+
+    recommendations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "hosts": hostsList,
+      "services": servicesList,
+      "recommendations": {
+        "blueprint": {
+          "configurations": {},
+          "host_groups": []
+        },
+        "blueprint_cluster_binding": {
+          "host_groups": []
+        }
+      }
+    }
+
+    configurations = recommendations["recommendations"]["blueprint"]["configurations"]
+
+    for service in servicesList:
+      calculation = self.recommendServiceConfigurations(service)
+      if calculation is not None:
+        calculation(configurations, clusterData)
+
+    return recommendations
+
+  def recommendServiceConfigurations(self, service):
+    return {
+    }.get(service, None)
+
+  def putProperty(self, config, configType):
+    config[configType] = {"properties": {}}
+    def appendProperty(key, value):
+      config[configType]["properties"][key] = str(value)
+    return appendProperty
+
+  def getClusterData(self, servicesList, hosts, components):
+
+    hBaseInstalled = False
+    if 'HBASE' in servicesList:
+      hBaseInstalled = True
+
+    cluster = {
+      "cpu": 0,
+      "disk": 0,
+      "ram": 0,
+      "hBaseInstalled": hBaseInstalled,
+      "components": components
+    }
+
+    if len(hosts["items"]) > 0:
+      host = hosts["items"][0]["Hosts"]
+      cluster["cpu"] = host["cpu_count"]
+      cluster["disk"] = len(host["disk_info"])
+      cluster["ram"] = int(host["total_mem"] / (1024 * 1024))
+
+    ramRecommendations = [
+      {"os":1, "hbase":1},
+      {"os":2, "hbase":1},
+      {"os":2, "hbase":2},
+      {"os":4, "hbase":4},
+      {"os":6, "hbase":8},
+      {"os":8, "hbase":8},
+      {"os":8, "hbase":8},
+      {"os":12, "hbase":16},
+      {"os":24, "hbase":24},
+      {"os":32, "hbase":32},
+      {"os":64, "hbase":64}
+    ]
+    index = {
+      cluster["ram"] <= 4: 0,
+      4 < cluster["ram"] <= 8: 1,
+      8 < cluster["ram"] <= 16: 2,
+      16 < cluster["ram"] <= 24: 3,
+      24 < cluster["ram"] <= 48: 4,
+      48 < cluster["ram"] <= 64: 5,
+      64 < cluster["ram"] <= 72: 6,
+      72 < cluster["ram"] <= 96: 7,
+      96 < cluster["ram"] <= 128: 8,
+      128 < cluster["ram"] <= 256: 9,
+      256 < cluster["ram"]: 10
+    }[1]
+    cluster["reservedRam"] = ramRecommendations[index]["os"]
+    cluster["hbaseRam"] = ramRecommendations[index]["hbase"]
+
+    cluster["minContainerSize"] = {
+      cluster["ram"] <= 4: 256,
+      4 < cluster["ram"] <= 8: 512,
+      8 < cluster["ram"] <= 24: 1024,
+      24 < cluster["ram"]: 2048
+    }[1]
+
+    '''containers = max(3, min (2*cores,min (1.8*DISKS,(Total available RAM) / MIN_CONTAINER_SIZE))))'''
+    cluster["containers"] = max(3,
+                                min(2 * cluster["cpu"],
+                                    int(min(1.8 * cluster["disk"],
+                                            cluster["ram"] / cluster["minContainerSize"]))))
+
+    '''ramPerContainers = max(2GB, RAM - reservedRam - hBaseRam) / containers'''
+    cluster["ramPerContainer"] = max(2048,
+                                     cluster["ram"] - cluster["reservedRam"] - cluster["hbaseRam"])
+    cluster["ramPerContainer"] /= cluster["containers"]
+    '''If greater than 1GB, value will be in multiples of 512.'''
+    if cluster["ramPerContainer"] > 1024:
+      cluster["ramPerContainer"] = ceil(cluster["ramPerContainer"] / 512) * 512
+
+    cluster["mapMemory"] = int(cluster["ramPerContainer"])
+    cluster["reduceMemory"] = cluster["ramPerContainer"]
+    cluster["amMemory"] = max(cluster["mapMemory"], cluster["reduceMemory"])
+
+    return cluster
+
+
+  def validateConfigurations(self, services, hosts):
+    """Returns array of Validation objects about issues with configuration values provided in services"""
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+
+    validations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "items": [ ]
+    }
+    items = validations["items"]
+
+    recommendations = self.recommendConfigurations(services, hosts)
+    recommendedDefaults = recommendations["recommendations"]["blueprint"]["configurations"]
+
+    configurations = services["configurations"]
+    for service in services["services"]:
+      serviceName = service["StackServices"]["service_name"]
+      validator = self.validateServiceConfigurations(serviceName)
+      if validator is not None:
+        siteName = validator[0]
+        method = validator[1]
+        if siteName in recommendedDefaults:
+          siteProperties = getSiteProperties(configurations, siteName)
+          if siteProperties is not None:
+            resultItems = method(siteProperties, recommendedDefaults[siteName]["properties"])
+            items.extend(resultItems)
+    return validations
+    pass
+
+  def validateServiceConfigurations(self, serviceName):
+    return {
+    }.get(serviceName, None)
+
+  def toConfigurationValidationErrors(self, items, siteName):
+    result = []
+    for item in items:
+      if item["message"] is not None:
+        error = { "type": 'configuration', "level": 'ERROR', "message": item["message"], "config-type": siteName, "config-name": item["config-name"] }
+        result.append(error)
+    return result
+
+  def validatorLessThenDefaultValue(self, properties, recommendedDefaults, propertyName):
+    if not propertyName in properties:
+      return "Value should be set"
+    value = to_number(properties[propertyName])
+    if value is None:
+      return "Value should be integer"
+    defaultValue = to_number(recommendedDefaults[propertyName])
+    if defaultValue is None:
+      return None
+    if value < defaultValue:
+      return "Value is less than the recommended default of {0}".format(defaultValue)
+    return None
+
+  def validateXmxValue(self, properties, recommendedDefaults, propertyName):
+    if not propertyName in properties:
+      return "Value should be set"
+    value = properties[propertyName]
+    defaultValue = recommendedDefaults[propertyName]
+    if defaultValue is None:
+      return "Config's default value can't be null or undefined"
+    if not checkXmxValueFormat(value):
+      return 'Invalid value format'
+    valueInt = formatXmxSizeToBytes(getXmxSize(value))
+    defaultValueXmx = getXmxSize(defaultValue)
+    defaultValueInt = formatXmxSizeToBytes(defaultValueXmx)
+    if valueInt < defaultValueInt:
+      return "Value is less than the recommended default of -Xmx" + defaultValueXmx
+    return None
+
+
+# Validation helper methods
+def getSiteProperties(configurations, siteName):
+  siteConfig = configurations.get(siteName)
+  if siteConfig is None:
+    return None
+  return siteConfig.get("properties")
+
+def to_number(s):
+  try:
+    return int(re.sub("\D", "", s))
+  except ValueError:
+    return None
+
+def checkXmxValueFormat(value):
+  p = re.compile('-Xmx(\d+)(b|k|m|g|p|t|B|K|M|G|P|T)?')
+  matches = p.findall(value)
+  return len(matches) == 1
+
+def getXmxSize(value):
+  p = re.compile("-Xmx(\d+)(.?)")
+  result = p.findall(value)[0]
+  if len(result) > 1:
+    # result[1] - is a space or size formatter (b|k|m|g etc)
+    return result[0] + result[1].lower()
+  return result[0]
+
+def formatXmxSizeToBytes(value):
+  value = value.lower()
+  if len(value) == 0:
+    return 0
+  modifier = value[-1]
+
+  if modifier == ' ' or modifier in "0123456789":
+    modifier = 'b'
+  m = {
+    modifier == 'b': 1,
+    modifier == 'k': 1024,
+    modifier == 'm': 1024 * 1024,
+    modifier == 'g': 1024 * 1024 * 1024,
+    modifier == 't': 1024 * 1024 * 1024 * 1024,
+    modifier == 'p': 1024 * 1024 * 1024 * 1024 * 1024
+    }[1]
+  return to_number(value) * m
+
+
+# Recommendation helper methods
+def isAlreadyPopulated(component):
+  if component["StackServiceComponents"]["hostnames"] is not None:
+    return len(component["StackServiceComponents"]["hostnames"]) > 0
+  return False
+
+def isClient(component):
+  return component["StackServiceComponents"]["component_category"] == 'CLIENT'
+
+def isSlave(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  isSlave = component["StackServiceComponents"]["component_category"] == 'SLAVE'
+  return isSlave
+
+def isMaster(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  isMaster = component["StackServiceComponents"]["is_master"]
+  return isMaster
+
+def isLocalHost(hostName):
+  return socket.getfqdn(hostName) == socket.getfqdn()
+
+def isMasterWithMultipleInstances(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  masters = ['ZOOKEEPER_SERVER', 'HBASE_MASTER']
+  return componentName in masters
+
+def defaultNoOfMasterHosts(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  return cardinality(componentName)[min]
+
+
+# Helper dictionaries
+def cardinality(componentName):
+  return {
+    'ZOOKEEPER_SERVER': {min: 3},
+    'HBASE_MASTER': {min: 1},
+    }.get(componentName, {min:1, max:1})
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/resources/stacks/HDP/1.3.3/services/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/1.3.3/services/stack_advisor.py b/ambari-server/src/main/resources/stacks/HDP/1.3.3/services/stack_advisor.py
new file mode 100644
index 0000000..f91efd8
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/HDP/1.3.3/services/stack_advisor.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env ambari-python-wrap
+"""
+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.
+"""
+
+import socket
+
+from stack_advisor import StackAdvisor
+
+class HDP133StackAdvisor(HDP132StackAdvisor):
+  pass
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/main/resources/stacks/HDP/1.3/services/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/1.3/services/stack_advisor.py b/ambari-server/src/main/resources/stacks/HDP/1.3/services/stack_advisor.py
new file mode 100644
index 0000000..998ecaa
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/HDP/1.3/services/stack_advisor.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env ambari-python-wrap
+"""
+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.
+"""
+
+import socket
+
+from stack_advisor import StackAdvisor
+
+class HDP13StackAdvisor(HDP133StackAdvisor):
+  pass
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
index 53a78eb..37d1af4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
@@ -435,6 +435,15 @@ public class AmbariMetaInfoTest {
   }
 
   @Test
+  public void testGetStackParentVersions() throws Exception {
+    List<String> parents = metaInfo.getStackParentVersions(STACK_NAME_HDP, "2.0.8");
+    Assert.assertEquals(3, parents.size());
+    Assert.assertEquals("2.0.7", parents.get(0));
+    Assert.assertEquals("2.0.6", parents.get(1));
+    Assert.assertEquals("2.0.5", parents.get(2));
+  }
+
+  @Test
   public void testGetProperties() throws Exception {
     Set<PropertyInfo> properties = metaInfo.getProperties(STACK_NAME_HDP, STACK_VERSION_HDP, SERVICE_NAME_HDFS);
     Assert.assertEquals(properties.size(), PROPERTIES_CNT);

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
index e4cf97a..87729b1 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestBuilder;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
 import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutRecommnedationCommand;
@@ -48,7 +49,8 @@ public class StackAdvisorHelperTest {
   public void testValidate_returnsCommandResult() throws StackAdvisorException, IOException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner, metaInfo));
 
     StackAdvisorCommand<ValidationResponse> command = mock(StackAdvisorCommand.class);
     ValidationResponse expected = mock(ValidationResponse.class);
@@ -69,7 +71,8 @@ public class StackAdvisorHelperTest {
       IOException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner, metaInfo));
 
     StackAdvisorCommand<ValidationResponse> command = mock(StackAdvisorCommand.class);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
@@ -88,7 +91,8 @@ public class StackAdvisorHelperTest {
   public void testRecommend_returnsCommandResult() throws StackAdvisorException, IOException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner, metaInfo));
 
     StackAdvisorCommand<RecommendationResponse> command = mock(StackAdvisorCommand.class);
     RecommendationResponse expected = mock(RecommendationResponse.class);
@@ -109,7 +113,8 @@ public class StackAdvisorHelperTest {
       IOException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner, metaInfo));
 
     StackAdvisorCommand<RecommendationResponse> command = mock(StackAdvisorCommand.class);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
@@ -128,7 +133,8 @@ public class StackAdvisorHelperTest {
       throws IOException, StackAdvisorException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner, metaInfo);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
 
     StackAdvisorCommand<RecommendationResponse> command = helper
@@ -142,7 +148,8 @@ public class StackAdvisorHelperTest {
       throws IOException, StackAdvisorException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner, metaInfo);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
 
     StackAdvisorCommand<ValidationResponse> command = helper.createValidationCommand(requestType);
@@ -155,7 +162,8 @@ public class StackAdvisorHelperTest {
       throws IOException, StackAdvisorException {
     Configuration configuration = mock(Configuration.class);
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
-    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner, metaInfo);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.CONFIGURATIONS;
 
     StackAdvisorCommand<ValidationResponse> command = helper.createValidationCommand(requestType);

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb1e0ca5/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
index 5e4e3d0..cf792ff 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
@@ -19,8 +19,10 @@
 package org.apache.ambari.server.api.services.stackadvisor.commands;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -29,16 +31,23 @@ import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
 
 import javax.ws.rs.WebApplicationException;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestBuilder;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
 import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommand.StackAdvisorData;
 import org.apache.commons.io.FileUtils;
+import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.node.ArrayNode;
+import org.codehaus.jackson.node.ObjectNode;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -68,8 +77,10 @@ public class StackAdvisorCommandTest {
     String stackAdvisorScript = "echo";
     int requestId = 0;
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    doReturn(Collections.emptyList()).when(metaInfo).getStackParentVersions(anyString(), anyString());
     StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
-        stackAdvisorScript, requestId, saRunner));
+        stackAdvisorScript, requestId, saRunner, metaInfo));
 
     StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
         .build();
@@ -86,8 +97,10 @@ public class StackAdvisorCommandTest {
     String stackAdvisorScript = "echo";
     int requestId = 0;
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    doReturn(Collections.emptyList()).when(metaInfo).getStackParentVersions(anyString(), anyString());
     StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
-        stackAdvisorScript, requestId, saRunner));
+        stackAdvisorScript, requestId, saRunner, metaInfo));
 
     StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
         .build();
@@ -112,8 +125,10 @@ public class StackAdvisorCommandTest {
     String stackAdvisorScript = "echo";
     int requestId = 0;
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    doReturn(Collections.emptyList()).when(metaInfo).getStackParentVersions(anyString(), anyString());
     StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
-        stackAdvisorScript, requestId, saRunner));
+        stackAdvisorScript, requestId, saRunner, metaInfo));
 
     StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
         .build();
@@ -137,8 +152,10 @@ public class StackAdvisorCommandTest {
     String stackAdvisorScript = "echo";
     final int requestId = 0;
     StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo metaInfo = mock(AmbariMetaInfo.class);
+    doReturn(Collections.emptyList()).when(metaInfo).getStackParentVersions(anyString(), anyString());
     final StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(
-        recommendationsDir, stackAdvisorScript, requestId, saRunner));
+        recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo));
 
     StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
         .build();
@@ -165,10 +182,61 @@ public class StackAdvisorCommandTest {
     assertEquals(expected, result.getType());
   }
 
+  @Test
+  public void testPopulateStackHierarchy() throws Exception {
+    File file = mock(File.class);
+    StackAdvisorRunner stackAdvisorRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo ambariMetaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorCommand<TestResource> cmd = new TestStackAdvisorCommand(file, "test", 1,
+        stackAdvisorRunner, ambariMetaInfo);
+    ObjectNode objectNode = (ObjectNode) cmd.mapper.readTree("{\"Versions\": " +
+        "{\"stack_name\": \"stack\", \"stack_version\":\"1.0.0\"}}");
+
+    doReturn(Arrays.asList("0.9", "0.8")).when(ambariMetaInfo).getStackParentVersions("stack", "1.0.0");
+
+    cmd.populateStackHierarchy(objectNode);
+
+    JsonNode stackHierarchy = objectNode.get("Versions").get("stack_hierarchy");
+    assertNotNull(stackHierarchy);
+    JsonNode stackName = stackHierarchy.get("stack_name");
+    assertNotNull(stackName);
+    assertEquals("stack", stackName.asText());
+    ArrayNode stackVersions = (ArrayNode) stackHierarchy.get("stack_versions");
+    assertNotNull(stackVersions);
+    assertEquals(2, stackVersions.size());
+    Iterator<JsonNode> stackVersionsElements = stackVersions.getElements();
+    assertEquals("0.9", stackVersionsElements.next().asText());
+    assertEquals("0.8", stackVersionsElements.next().asText());
+  }
+
+  @Test
+  public void testPopulateStackHierarchy_noParents() throws Exception {
+    File file = mock(File.class);
+    StackAdvisorRunner stackAdvisorRunner = mock(StackAdvisorRunner.class);
+    AmbariMetaInfo ambariMetaInfo = mock(AmbariMetaInfo.class);
+    StackAdvisorCommand<TestResource> cmd = new TestStackAdvisorCommand(file, "test", 1,
+        stackAdvisorRunner, ambariMetaInfo);
+    ObjectNode objectNode = (ObjectNode) cmd.mapper.readTree("{\"Versions\": " +
+        "{\"stack_name\": \"stack\", \"stack_version\":\"1.0.0\"}}");
+
+    doReturn(Collections.emptyList()).when(ambariMetaInfo).getStackParentVersions("stack", "1.0.0");
+
+    cmd.populateStackHierarchy(objectNode);
+
+    JsonNode stackHierarchy = objectNode.get("Versions").get("stack_hierarchy");
+    assertNotNull(stackHierarchy);
+    JsonNode stackName = stackHierarchy.get("stack_name");
+    assertNotNull(stackName);
+    assertEquals("stack", stackName.asText());
+    ArrayNode stackVersions = (ArrayNode) stackHierarchy.get("stack_versions");
+    assertNotNull(stackVersions);
+    assertEquals(0, stackVersions.size());
+  }
+
   class TestStackAdvisorCommand extends StackAdvisorCommand<TestResource> {
     public TestStackAdvisorCommand(File recommendationsDir, String stackAdvisorScript,
-        int requestId, StackAdvisorRunner saRunner) {
-      super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+        int requestId, StackAdvisorRunner saRunner, AmbariMetaInfo metaInfo) {
+      super(recommendationsDir, stackAdvisorScript, requestId, saRunner, metaInfo);
     }
 
     @Override