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/07/28 22:20:05 UTC

git commit: AMBARI-6631. Provide host-layout recommendations via /recommendations endpoint on stack-version

Repository: ambari
Updated Branches:
  refs/heads/trunk ee94c2f5b -> b0b58189c


AMBARI-6631. Provide host-layout recommendations via /recommendations endpoint on stack-version


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

Branch: refs/heads/trunk
Commit: b0b58189cc8713642102eadb46e4bfabfaf84c21
Parents: ee94c2f
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Mon Jul 28 12:10:42 2014 -0700
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Mon Jul 28 13:19:40 2014 -0700

----------------------------------------------------------------------
 ambari-server/conf/unix/ambari.properties       |   3 +
 ambari-server/pom.xml                           |   4 +
 .../RecommendationResourceDefinition.java       |  44 ++++
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../ambari/server/api/services/BaseService.java |   2 +-
 .../server/api/services/LocalUriInfo.java       | 144 +++++++++++
 .../api/services/RecommendationService.java     |  71 ++++++
 .../server/api/services/StacksService.java      |   4 +-
 .../services/parsers/JsonRequestBodyParser.java |  19 +-
 .../stackadvisor/StackAdvisorException.java     |  31 +++
 .../stackadvisor/StackAdvisorHelper.java        | 241 +++++++++++++++++++
 .../stackadvisor/StackAdvisorRunner.java        | 125 ++++++++++
 .../recommendations/RecommendationRequest.java  |  91 +++++++
 .../recommendations/RecommendationResponse.java | 208 ++++++++++++++++
 .../server/configuration/Configuration.java     |  13 +
 .../ambari/server/controller/AmbariServer.java  |   3 +
 .../AbstractControllerResourceProvider.java     |   2 +
 .../RecommendationResourceProvider.java         | 199 +++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../src/main/resources/key_properties.json      |   5 +
 .../src/main/resources/properties.json          |  17 ++
 .../src/main/resources/scripts/stack_advisor.py | 148 ++++++++++++
 .../stacks/HDP/2.0.6/services/stack_advisor.py  | 202 ++++++++++++++++
 .../main/resources/stacks/HDP/stack_advisor.py  |  37 +++
 .../server/api/services/GroupServiceTest.java   |   2 +-
 .../server/api/services/MemberServiceTest.java  |   2 +-
 .../api/services/RecommendationServiceTest.java |  85 +++++++
 27 files changed, 1699 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/conf/unix/ambari.properties
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/ambari.properties b/ambari-server/conf/unix/ambari.properties
index b5816af..b77ae32 100644
--- a/ambari-server/conf/unix/ambari.properties
+++ b/ambari-server/conf/unix/ambari.properties
@@ -29,6 +29,9 @@ webapp.dir=/usr/lib/ambari-server/web
 bootstrap.dir=/var/run/ambari-server/bootstrap
 bootstrap.script=/usr/lib/python2.6/site-packages/ambari_server/bootstrap.py
 bootstrap.setup_agent.script=/usr/lib/python2.6/site-packages/ambari_server/setupAgent.py
+recommendations.dir=/var/run/ambari-server/stack-recommendations
+stackadvisor.script=/var/lib/ambari-server/resources/scripts/stack_advisor.py
+
 api.authenticate=true
 server.connection.max.idle.millis=900000
 server.fqdn.service.url=http://169.254.169.254/latest/meta-data/public-hostname

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index 789de18..a6d03ab 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -352,6 +352,9 @@
               <directory>/var/run/ambari-server/bootstrap</directory>
             </mapping>
             <mapping>
+              <directory>/var/run/ambari-server/stack-recommendations</directory>
+            </mapping>
+            <mapping>
               <directory>/var/log/ambari-server</directory>
             </mapping>
             <mapping>
@@ -515,6 +518,7 @@
                 <path>/usr/lib/ambari-server</path>
                 <path>/var/run/ambari-server</path>
                 <path>/var/run/ambari-server/bootstrap</path>
+                <path>/var/run/ambari-server/stack-recommendations</path>
                 <path>/var/log/ambari-server</path>
                 <path>/var/lib/ambari-server/resources/upgrade</path>
               </paths>

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RecommendationResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RecommendationResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RecommendationResourceDefinition.java
new file mode 100644
index 0000000..2c9e1f2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RecommendationResourceDefinition.java
@@ -0,0 +1,44 @@
+/**
+ * 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.resources;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Recommendation resource definition.
+ */
+public class RecommendationResourceDefinition  extends BaseResourceDefinition {
+  /**
+   * Constructor.
+   */
+  public RecommendationResourceDefinition() {
+    super(Resource.Type.Recommendation);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "recommendations";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "recommendation";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index cdc3cd0..892ac22 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -226,6 +226,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new BlueprintResourceDefinition();
         break;
 
+      case Recommendation:
+        resourceDefinition = new RecommendationResourceDefinition();
+        break;
+
       case HostComponentProcess:
         resourceDefinition = new HostComponentProcessResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
index fa3b3ba..3afc23d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
@@ -135,7 +135,7 @@ public abstract class BaseService {
    *
    * @return a newly created resource instance
    */
-  ResourceInstance createResource(Resource.Type type, Map<Resource.Type, String> mapIds) {
+  protected ResourceInstance createResource(Resource.Type type, Map<Resource.Type, String> mapIds) {
     return m_resourceFactory.createResource(type, mapIds);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/LocalUriInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/LocalUriInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LocalUriInfo.java
new file mode 100644
index 0000000..9e927f6
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LocalUriInfo.java
@@ -0,0 +1,144 @@
+/**
+ * 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.services;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+/**
+ * Internal {@link UriInfo} implementation. Most of the methods are not
+ * currently supported.
+ * 
+ * Can be used for internal REST API calls with avoiding REST layer.
+ */
+public class LocalUriInfo implements UriInfo {
+
+  private final URI uri;
+
+  public LocalUriInfo(String uri) {
+    try {
+      this.uri = new URI(uri);
+    } catch (URISyntaxException e) {
+      throw new RuntimeException("URI syntax is not correct", e);
+    }
+  }
+
+  @Override
+  public URI getAbsolutePath() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public UriBuilder getAbsolutePathBuilder() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public URI getBaseUri() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public UriBuilder getBaseUriBuilder() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public List<Object> getMatchedResources() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public List<String> getMatchedURIs() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public List<String> getMatchedURIs(boolean arg0) {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public String getPath() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public String getPath(boolean arg0) {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public MultivaluedMap<String, String> getPathParameters() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public MultivaluedMap<String, String> getPathParameters(boolean arg0) {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public List<PathSegment> getPathSegments() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public List<PathSegment> getPathSegments(boolean arg0) {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public MultivaluedMap<String, String> getQueryParameters() {
+    List<NameValuePair> parametersList = URLEncodedUtils.parse(uri, "UTF-8");
+
+    MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
+    for (NameValuePair pair : parametersList) {
+      parameters.add(pair.getName(), pair.getValue());
+    }
+    return parameters;
+  }
+
+  @Override
+  public MultivaluedMap<String, String> getQueryParameters(boolean arg0) {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+  @Override
+  public URI getRequestUri() {
+    return uri;
+  }
+
+  @Override
+  public UriBuilder getRequestUriBuilder() {
+    throw new UnsupportedOperationException("Method is not supported");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/RecommendationService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/RecommendationService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/RecommendationService.java
new file mode 100644
index 0000000..23248b3
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/RecommendationService.java
@@ -0,0 +1,71 @@
+/**
+ * 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.services;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Service responsible for preparing recommendations for host-layout and
+ * configurations.
+ */
+@Path("/stacks/{stackName}/versions/{stackVersion}/recommendations")
+public class RecommendationService extends BaseService {
+
+  /**
+   * Returns host-layout recommendations for list of hosts and services.
+   * 
+   * @param body http body
+   * @param headers http headers
+   * @param ui uri info
+   * @param stackName stack name
+   * @param stackVersion stack version
+   * @return recommendations for host-layout
+   */
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response getRecommendation(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+      @PathParam("stackName") String stackName, @PathParam("stackVersion") String stackVersion) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createRecommendationResource(stackName, stackVersion));
+  }
+
+  ResourceInstance createRecommendationResource(String stackName, String stackVersion) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, stackName);
+    mapIds.put(Resource.Type.StackVersion, stackVersion);
+
+    return createResource(Resource.Type.Recommendation, mapIds);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
index fc6e821..822199c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
@@ -358,10 +358,10 @@ public class StacksService extends BaseService {
    * This should be removed when /stacks2 is removed and we can change the property names
    * in the resource definitions to the new form.
    */
-  private static class StackUriInfo implements UriInfo {
+  public static class StackUriInfo implements UriInfo {
     private UriInfo m_delegate;
 
-    private StackUriInfo(UriInfo delegate) {
+    public StackUriInfo(UriInfo delegate) {
       m_delegate = delegate;
     }
     @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonRequestBodyParser.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonRequestBodyParser.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonRequestBodyParser.java
index 0d75a8e..59dd1af 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonRequestBodyParser.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonRequestBodyParser.java
@@ -113,13 +113,24 @@ public class JsonRequestBodyParser implements RequestBodyParser {
         //array
         Iterator<JsonNode>       arrayIter = child.getElements();
         Set<Map<String, Object>> arraySet  = new HashSet<Map<String, Object>>();
+        List<String> primitives = new ArrayList<String>();
 
         while (arrayIter.hasNext()) {
-          NamedPropertySet arrayPropertySet = new NamedPropertySet(name, new HashMap<String, Object>());
-          processNode(arrayIter.next(), "", arrayPropertySet, requestInfoProps);
-          arraySet.add(arrayPropertySet.getProperties());
+          JsonNode next = arrayIter.next();
+
+          if (next.isValueNode()) {
+            // All remain nodes will be also primitives
+            primitives.add(next.getTextValue());
+          } else {
+            NamedPropertySet arrayPropertySet = new NamedPropertySet(name,
+                new HashMap<String, Object>());
+            processNode(next, "", arrayPropertySet, requestInfoProps);
+            arraySet.add(arrayPropertySet.getProperties());
+          }
         }
-        propertySet.getProperties().put(PropertyHelper.getPropertyId(path, name), arraySet);
+
+        Object properties = primitives.isEmpty() ? arraySet : primitives;
+        propertySet.getProperties().put(PropertyHelper.getPropertyId(path, name), properties);
       } else if (child.isContainerNode()) {
         // object
         if (name.equals(BODY_TITLE)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorException.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorException.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorException.java
new file mode 100644
index 0000000..b13e1e5
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorException.java
@@ -0,0 +1,31 @@
+/**
+ * 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.services.stackadvisor;
+
+@SuppressWarnings("serial")
+public class StackAdvisorException extends Exception {
+
+  public StackAdvisorException(String message) {
+    super(message);
+  }
+
+  public StackAdvisorException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/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
new file mode 100644
index 0000000..c0799c9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
@@ -0,0 +1,241 @@
+/**
+ * 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.services.stackadvisor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+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.BaseService;
+import org.apache.ambari.server.api.services.LocalUriInfo;
+import org.apache.ambari.server.api.services.RecommendationService;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.StacksService.StackUriInfo;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner.StackAdvisorCommand;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationRequest;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class StackAdvisorHelper extends BaseService {
+
+  private static Log LOG = LogFactory.getLog(RecommendationService.class);
+
+  private static final String GET_HOSTS_INFO_URI = "/api/v1/hosts"
+      + "?fields=*&Hosts/host_name.in(%s)";
+  private static final String GET_SERVICES_INFO_URI = "/api/v1/stacks/%s/versions/%s"
+      + "?fields=Versions/stack_name,Versions/stack_version,Versions/parent_stack_version"
+      + ",services/StackServices/service_name,services/StackServices/service_version"
+      + ",services/components/StackServiceComponents,services/components/dependencies,services/components/auto_deploy"
+      + "&services/StackServices/service_name.in(%s)";
+
+  private File recommendationsDir;
+  private String stackAdvisorScript;
+
+  /* Monotonically increasing requestid */
+  private int requestId = 0;
+  private File requestDirectory;
+  private StackAdvisorRunner saRunner;
+
+  @Inject
+  public StackAdvisorHelper(Configuration conf, StackAdvisorRunner saRunner) throws IOException {
+    this.recommendationsDir = conf.getRecommendationsDir();
+    this.stackAdvisorScript = conf.getStackAdvisorScript();
+    this.saRunner = saRunner;
+  }
+
+  /**
+   * Return component-layout recommendation based on hosts and services
+   * information.
+   * 
+   * @param hosts list of hosts
+   * @param services list of services
+   * @return {@link String} representation of recommendations
+   * @throws StackAdvisorException
+   */
+  public synchronized RecommendationResponse getComponentLayoutRecommnedation(
+      RecommendationRequest request) throws StackAdvisorException {
+
+    validateRecommendationRequest(request);
+    String hostsJSON = getHostsInformation(request);
+    String servicesJSON = getServicesInformation(request);
+
+    try {
+      createRequestDirectory();
+
+      FileUtils.writeStringToFile(new File(requestDirectory, "hosts.json"), hostsJSON);
+      FileUtils.writeStringToFile(new File(requestDirectory, "services.json"), servicesJSON);
+
+      boolean success = saRunner.runScript(stackAdvisorScript,
+          StackAdvisorCommand.RECOMMEND_COMPONENT_LAYOUT, requestDirectory);
+      if (!success) {
+        String message = "Stack advisor script finished with errors";
+        LOG.warn(message);
+        throw new StackAdvisorException(message);
+      }
+
+      String result = FileUtils
+          .readFileToString(new File(requestDirectory, "component-layout.json"));
+
+      ObjectMapper mapper = new ObjectMapper();
+      return mapper.readValue(result, RecommendationResponse.class);
+    } catch (Exception e) {
+      String message = "Error occured during preparing component-layout recommendations";
+      LOG.warn(message, e);
+      throw new StackAdvisorException(message, e);
+    }
+  }
+
+  /**
+   * Create request id directory for each call
+   */
+  private void createRequestDirectory() throws IOException {
+    if (!recommendationsDir.exists()) {
+      if (!recommendationsDir.mkdirs()) {
+        throw new IOException("Cannot create " + recommendationsDir);
+      }
+    }
+
+    requestId += 1;
+    requestDirectory = new File(recommendationsDir, Integer.toString(requestId));
+
+    if (requestDirectory.exists()) {
+      FileUtils.deleteDirectory(requestDirectory);
+    }
+    if (!requestDirectory.mkdirs()) {
+      throw new IOException("Cannot create " + requestDirectory);
+    }
+  }
+
+  private String getHostsInformation(RecommendationRequest request) throws StackAdvisorException {
+    String hostsURI = String.format(GET_HOSTS_INFO_URI, request.getHostsCommaSeparated());
+
+    Response response = handleRequest(null, null, new LocalUriInfo(hostsURI), Request.Type.GET,
+        createHostResource());
+
+    if (response.getStatus() != Status.OK.getStatusCode()) {
+      String message = String.format(
+          "Error occured during hosts information retrieving, status=%s, response=%s",
+          response.getStatus(), (String) response.getEntity());
+      LOG.warn(message);
+      throw new StackAdvisorException(message);
+    }
+
+    String hostsJSON = (String) response.getEntity();
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Hosts information: " + hostsJSON);
+    }
+
+    Collection<String> unregistered = getUnregisteredHosts(hostsJSON, request.getHosts());
+    if (unregistered.size() > 0) {
+      String message = String.format("There are unregistered hosts in the request, %s",
+          Arrays.toString(unregistered.toArray()));
+      LOG.warn(message);
+      throw new StackAdvisorException(message);
+    }
+
+    return hostsJSON;
+  }
+
+  @SuppressWarnings("unchecked")
+  private Collection<String> getUnregisteredHosts(String hostsJSON, List<String> hosts)
+      throws StackAdvisorException {
+    ObjectMapper mapper = new ObjectMapper();
+    List<String> registeredHosts = new ArrayList<String>();
+
+    try {
+      JsonNode root = mapper.readTree(hostsJSON);
+      Iterator<JsonNode> iterator = root.get("items").getElements();
+      while (iterator.hasNext()) {
+        JsonNode next = iterator.next();
+        String hostName = next.get("Hosts").get("host_name").getTextValue();
+        registeredHosts.add(hostName);
+      }
+
+      return CollectionUtils.subtract(hosts, registeredHosts);
+    } catch (Exception e) {
+      throw new StackAdvisorException("Error occured during calculating unregistered hosts", e);
+    }
+  }
+
+  private String getServicesInformation(RecommendationRequest request) throws StackAdvisorException {
+    String stackName = request.getStackName();
+    String stackVersion = request.getStackVersion();
+    String servicesURI = String.format(GET_SERVICES_INFO_URI, stackName, stackVersion,
+        request.getServicesCommaSeparated());
+
+    Response response = handleRequest(null, null, new StackUriInfo(new LocalUriInfo(servicesURI)),
+        Request.Type.GET, createStackVersionResource(stackName, stackVersion));
+
+    if (response.getStatus() != Status.OK.getStatusCode()) {
+      String message = String.format(
+          "Error occured during services information retrieving, status=%s, response=%s",
+          response.getStatus(), (String) response.getEntity());
+      LOG.warn(message);
+      throw new StackAdvisorException(message);
+    }
+
+    String servicesJSON = (String) response.getEntity();
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Services information: " + servicesJSON);
+    }
+    return servicesJSON;
+  }
+
+  private ResourceInstance createHostResource() {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    return createResource(Resource.Type.Host, mapIds);
+  }
+
+  private ResourceInstance createStackVersionResource(String stackName, String stackVersion) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, stackName);
+    mapIds.put(Resource.Type.StackVersion, stackVersion);
+
+    return createResource(Resource.Type.StackVersion, mapIds);
+  }
+
+  private void validateRecommendationRequest(RecommendationRequest request)
+      throws StackAdvisorException {
+    if (request.getHosts().isEmpty() || request.getServices().isEmpty()) {
+      throw new StackAdvisorException("Hosts and services must not be empty");
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
new file mode 100644
index 0000000..57feaa0
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
@@ -0,0 +1,125 @@
+/**
+ * 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.services.stackadvisor;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.inject.Singleton;
+
+@Singleton
+public class StackAdvisorRunner {
+
+  private static Log LOG = LogFactory.getLog(StackAdvisorRunner.class);
+
+  /**
+   * Runs stack_advisor.py script in the specified {@code actionDirectory}.
+   * 
+   * @param script stack advisor script
+   * @param saCommand {@link StackAdvisorCommand} to run.
+   * @param actionDirectory directory for the action
+   * @return {@code true} if script completed successfully, {@code false}
+   *         otherwise.
+   */
+  public boolean runScript(String script, StackAdvisorCommand saCommand, File actionDirectory) {
+    LOG.info(String.format("Script=%s, actionDirectory=%s, command=%s", script, actionDirectory,
+        saCommand));
+
+    String outputFile = actionDirectory + File.separator + "stackadvisor.out";
+    String errorFile = actionDirectory + File.separator + "stackadvisor.err";
+
+    String shellCommand[] = prepareShellCommand(script, saCommand, actionDirectory, outputFile,
+        errorFile);
+    String[] env = new String[] {};
+
+    try {
+      Process process = Runtime.getRuntime().exec(shellCommand, env);
+
+      try {
+        LOG.info(String.format("Stack-advisor output=%s, error=%s", outputFile, errorFile));
+
+        int exitCode = process.waitFor();
+        try {
+          String outMessage = FileUtils.readFileToString(new File(outputFile));
+          String errMessage = FileUtils.readFileToString(new File(errorFile));
+          LOG.info("Script log message: " + outMessage + "\n\n" + errMessage);
+        } catch (IOException io) {
+          LOG.info("Error in reading script log files", io);
+        }
+
+        return exitCode == 0;
+      } finally {
+        process.destroy();
+      }
+    } catch (Exception io) {
+      LOG.info("Error executing stack advisor " + io.getMessage());
+      return false;
+    }
+  }
+
+  private String[] prepareShellCommand(String script, StackAdvisorCommand saCommand,
+      File actionDirectory, String outputFile, String errorFile) {
+    String hostsFile = actionDirectory + File.separator + "hosts.json";
+    String servicesFile = actionDirectory + File.separator + "services.json";
+
+    String shellCommand[] = new String[] { "sh", "-c", null /* to be calculated */};
+    String commands[] = new String[] { script, saCommand.toString(), hostsFile, servicesFile };
+
+    StringBuilder commandString = new StringBuilder();
+    for (String command : commands) {
+      commandString.append(" " + command);
+    }
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug(commandString);
+    }
+
+    commandString.append(" 1> " + outputFile + " 2>" + errorFile);
+    shellCommand[2] = commandString.toString();
+
+    return shellCommand;
+  }
+
+  public enum StackAdvisorCommand {
+
+    RECOMMEND_COMPONENT_LAYOUT("recommend-component-layout"),
+
+    VALIDATE_COMPONENT_LAYOUT("validate-component-layout"),
+
+    RECOMMEND_CONFIGURATIONS("recommend-configurations"),
+
+    VALIDATE_CONFIGURATIONS("validate-configurations");
+
+    private final String name;
+
+    private StackAdvisorCommand(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationRequest.java
new file mode 100644
index 0000000..5ab6b7a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationRequest.java
@@ -0,0 +1,91 @@
+/**
+ * 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.services.stackadvisor.recommendations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Recommendations request.
+ */
+public class RecommendationRequest {
+
+  private String stackName;
+  private String stackVersion;
+  private List<String> hosts = new ArrayList<String>();
+  private List<String> services = new ArrayList<String>();
+
+
+  public String getStackName() {
+    return stackName;
+  }
+
+  public String getStackVersion() {
+    return stackVersion;
+  }
+
+  public List<String> getHosts() {
+    return hosts;
+  }
+
+  public List<String> getServices() {
+    return services;
+  }
+
+  public String getHostsCommaSeparated() {
+    return StringUtils.join(hosts, ",");
+  }
+
+  public String getServicesCommaSeparated() {
+    return StringUtils.join(services, ",");
+  }
+
+  private RecommendationRequest(String stackName, String stackVersion) {
+    this.stackName = stackName;
+    this.stackVersion = stackVersion;
+  }
+
+  public static class RecommendationRequestBuilder {
+    RecommendationRequest instance;
+
+    private RecommendationRequestBuilder(String stackName, String stackVersion) {
+      this.instance = new RecommendationRequest(stackName, stackVersion);
+    }
+
+    public static RecommendationRequestBuilder forStack(String stackName, String stackVersion) {
+      return new RecommendationRequestBuilder(stackName, stackVersion);
+    }
+
+    public RecommendationRequestBuilder forHosts(List<String> hosts) {
+      this.instance.hosts = hosts;
+      return this;
+    }
+
+    public RecommendationRequestBuilder forServices(List<String> services) {
+      this.instance.services = services;
+      return this;
+    }
+
+    public RecommendationRequest build() {
+      return this.instance;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
new file mode 100644
index 0000000..a34291c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
@@ -0,0 +1,208 @@
+/**
+ * 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.services.stackadvisor.recommendations;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Recommendation response POJO.
+ */
+public class RecommendationResponse {
+
+  @JsonProperty("Versions")
+  private Version version;
+
+  @JsonProperty
+  private Set<String> hosts;
+
+  @JsonProperty
+  private Set<String> services;
+
+  @JsonProperty
+  private Recommendation recommendations;
+
+  public Version getVersion() {
+    return version;
+  }
+
+  public void setVersion(Version version) {
+    this.version = version;
+  }
+
+  public Set<String> getHosts() {
+    return hosts;
+  }
+
+  public void setHosts(Set<String> hosts) {
+    this.hosts = hosts;
+  }
+
+  public Set<String> getServices() {
+    return services;
+  }
+
+  public void setServices(Set<String> services) {
+    this.services = services;
+  }
+
+  public Recommendation getRecommendations() {
+    return recommendations;
+  }
+
+  public void setRecommendations(Recommendation recommendations) {
+    this.recommendations = recommendations;
+  }
+
+  public static class Version {
+    @JsonProperty("stack_name")
+    private String stackName;
+
+    @JsonProperty("stack_version")
+    private String stackVersion;
+
+    public String getStackName() {
+      return stackName;
+    }
+
+    public void setStackName(String stackName) {
+      this.stackName = stackName;
+    }
+
+    public String getStackVersion() {
+      return stackVersion;
+    }
+
+    public void setStackVersion(String stackVersion) {
+      this.stackVersion = stackVersion;
+    }
+  }
+
+  public static class Recommendation {
+    @JsonProperty
+    private Blueprint blueprint;
+
+    @JsonProperty("blueprint_cluster_binding")
+    private BlueprintClusterBinding blueprintClusterBinding;
+
+    public Blueprint getBlueprint() {
+      return blueprint;
+    }
+
+    public void setBlueprint(Blueprint blueprint) {
+      this.blueprint = blueprint;
+    }
+
+    public BlueprintClusterBinding getBlueprintClusterBinding() {
+      return blueprintClusterBinding;
+    }
+
+    public void setBlueprintClusterBinding(BlueprintClusterBinding blueprintClusterBinding) {
+      this.blueprintClusterBinding = blueprintClusterBinding;
+    }
+  }
+
+  public static class Blueprint {
+    @JsonProperty
+    private Map<String, Map<String, Map<String, String>>> configurations;
+
+    @JsonProperty("host_groups")
+    private Set<HostGroup> hostGroups;
+
+    public Map<String, Map<String, Map<String, String>>> getConfigurations() {
+      return configurations;
+    }
+
+    public void setConfigurations(Map<String, Map<String, Map<String, String>>> configurations) {
+      this.configurations = configurations;
+    }
+
+    public Set<HostGroup> getHostGroups() {
+      return hostGroups;
+    }
+
+    public void setHostGroups(Set<HostGroup> hostGroups) {
+      this.hostGroups = hostGroups;
+    }
+  }
+
+  public static class HostGroup {
+    @JsonProperty
+    private String name;
+
+    @JsonProperty
+    private Set<Map<String, String>> components;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public Set<Map<String, String>> getComponents() {
+      return components;
+    }
+
+    public void setComponents(Set<Map<String, String>> components) {
+      this.components = components;
+    }
+  }
+
+  public static class BlueprintClusterBinding {
+    @JsonProperty("host_groups")
+    private Set<BindingHostGroup> hostGroups;
+
+    public Set<BindingHostGroup> getHostGroups() {
+      return hostGroups;
+    }
+
+    public void setHostGroups(Set<BindingHostGroup> hostGroups) {
+      this.hostGroups = hostGroups;
+    }
+  }
+
+  public static class BindingHostGroup {
+    @JsonProperty
+    private String name;
+
+    @JsonProperty
+    private Set<Map<String, String>> hosts;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public Set<Map<String, String>> getHosts() {
+      return hosts;
+    }
+
+    public void setHosts(Set<Map<String, String>> hosts) {
+      this.hosts = hosts;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index c585402..68df120 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -67,6 +67,10 @@ public class Configuration {
   public static final String BOOTSTRAP_SETUP_AGENT_SCRIPT = "bootstrap.setup_agent.script";
   public static final String BOOTSTRAP_SETUP_AGENT_PASSWORD = "bootstrap.setup_agent.password";
   public static final String BOOTSTRAP_MASTER_HOSTNAME = "bootstrap.master_host_name";
+  public static final String RECOMMENDATIONS_DIR = "recommendations.dir";
+  public static final String RECOMMENDATIONS_DIR_DEFAULT = "/var/run/ambari-server/stack-recommendations";
+  public static final String STACK_ADVISOR_SCRIPT = "stackadvisor.script";
+  public static final String STACK_ADVISOR_SCRIPT_DEFAULT = "/var/lib/ambari-server/resources/scripts/stack_advisor.py";
   public static final String API_AUTHENTICATE = "api.authenticate";
   public static final String API_USE_SSL = "api.ssl";
   public static final String API_CSRF_PREVENTION_KEY = "api.csrfPrevention.enabled";
@@ -526,6 +530,15 @@ public class Configuration {
     return properties.getProperty(BOOTSTRAP_SETUP_AGENT_PASSWORD, "password");
   }
 
+  public File getRecommendationsDir() {
+    String fileName = properties.getProperty(RECOMMENDATIONS_DIR, RECOMMENDATIONS_DIR_DEFAULT);
+    return new File(fileName);
+  }
+
+  public String getStackAdvisorScript() {
+    return properties.getProperty(STACK_ADVISOR_SCRIPT, STACK_ADVISOR_SCRIPT_DEFAULT);
+  }
+
   /**
    * Get the map with server config parameters.
    * Keys - public constants of this class

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
index 1ea8311..6803a01 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
@@ -40,6 +40,7 @@ import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.KeyService;
 import org.apache.ambari.server.api.services.PersistKeyValueImpl;
 import org.apache.ambari.server.api.services.PersistKeyValueService;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
 import org.apache.ambari.server.bootstrap.BootStrapImpl;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
 import org.apache.ambari.server.configuration.Configuration;
@@ -49,6 +50,7 @@ import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
 import org.apache.ambari.server.controller.internal.PermissionResourceProvider;
 import org.apache.ambari.server.controller.internal.PrivilegeResourceProvider;
+import org.apache.ambari.server.controller.internal.RecommendationResourceProvider;
 import org.apache.ambari.server.controller.internal.StackDefinedPropertyProvider;
 import org.apache.ambari.server.controller.internal.StackDependencyResourceProvider;
 import org.apache.ambari.server.controller.nagios.NagiosPropertyProvider;
@@ -525,6 +527,7 @@ public class AmbariServer {
     PersistKeyValueService.init(injector.getInstance(PersistKeyValueImpl.class));
     KeyService.init(injector.getInstance(PersistKeyValueImpl.class));
     BootStrapResource.init(injector.getInstance(BootStrapImpl.class));
+    RecommendationResourceProvider.init(injector.getInstance(StackAdvisorHelper.class));
     StageUtils.setGson(injector.getInstance(Gson.class));
     WorkflowJsonService.setDBProperties(
         injector.getInstance(Configuration.class));

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
index 5ba926c..028bf41 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
@@ -144,6 +144,8 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
         return new HostComponentProcessResourceProvider(propertyIds, keyPropertyIds, managementController);
       case Blueprint:
         return new BlueprintResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case Recommendation:
+        return new RecommendationResourceProvider(propertyIds, keyPropertyIds, managementController);
       case AlertDefinition:
         return new AlertDefinitionResourceProvider(propertyIds, keyPropertyIds, managementController);
       default:

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RecommendationResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RecommendationResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RecommendationResourceProvider.java
new file mode 100644
index 0000000..d4fd1ce
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RecommendationResourceProvider.java
@@ -0,0 +1,199 @@
+/**
+ * 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.controller.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationRequest;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationRequest.RecommendationRequestBuilder;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.BindingHostGroup;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.HostGroup;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+
+import com.google.inject.Inject;
+
+public class RecommendationResourceProvider extends ReadOnlyResourceProvider {
+
+  private static StackAdvisorHelper saHelper;
+
+  @Inject
+  public static void init(StackAdvisorHelper instance) {
+    saHelper = instance;
+  }
+
+  protected static final String RECOMMENDATION_ID_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "Recommendations", "id");
+  protected static final String STACK_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Versions",
+      "stack_name");
+  protected static final String STACK_VERSION_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "Versions", "stack_version");
+
+  protected static final String HOSTS_PROPERTY_ID = "hosts";
+  protected static final String SERVICES_PROPERTY_ID = "services";
+
+  protected static final String BLUEPRINT_CONFIGURATIONS_PROPERTY_ID = PropertyHelper
+      .getPropertyId("recommendations/blueprint", "configurations");
+
+  protected static final String BLUEPRINT_HOST_GROUPS_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "recommendations/blueprint", "host_groups");
+  protected static final String BLUEPRINT_HOST_GROUPS_NAME_PROPERTY_ID = "name";
+  protected static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_PROPERTY_ID = "components";
+
+  protected static final String BINDING_HOST_GROUPS_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "recommendations/blueprint_cluster_binding", "host_groups");
+  protected static final String BINDING_HOST_GROUPS_NAME_PROPERTY_ID = "name";
+  protected static final String BINDING_HOST_GROUPS_HOSTS_PROPERTY_ID = "hosts";
+
+  private static Set<String> pkPropertyIds = new HashSet<String>(
+      Arrays.asList(new String[] { RECOMMENDATION_ID_PROPERTY_ID }));
+
+  protected RecommendationResourceProvider(Set<String> propertyIds,
+      Map<Type, String> keyPropertyIds, AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(final Request request) throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
+    RecommendationRequest recommendationRequest = prepareRecommendationRequest(request);
+
+    final RecommendationResponse response;
+    try {
+      response = saHelper.getComponentLayoutRecommnedation(recommendationRequest);
+    } catch (StackAdvisorException e) {
+      LOG.warn("Error occured during component-layout recommnedation", e);
+      throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(e.getMessage())
+          .build());
+    }
+
+    Resource recommendation = createResources(new Command<Resource>() {
+      @Override
+      public Resource invoke() throws AmbariException {
+
+        Resource resource = new ResourceImpl(Resource.Type.Recommendation);
+        setResourceProperty(resource, RECOMMENDATION_ID_PROPERTY_ID, "1", getPropertyIds());
+        setResourceProperty(resource, STACK_NAME_PROPERTY_ID, response.getVersion().getStackName(),
+            getPropertyIds());
+        setResourceProperty(resource, STACK_VERSION_PROPERTY_ID, response.getVersion()
+            .getStackVersion(), getPropertyIds());
+        setResourceProperty(resource, HOSTS_PROPERTY_ID, response.getHosts(), getPropertyIds());
+        setResourceProperty(resource, SERVICES_PROPERTY_ID, response.getServices(),
+            getPropertyIds());
+        setResourceProperty(resource, BLUEPRINT_CONFIGURATIONS_PROPERTY_ID, response
+            .getRecommendations().getBlueprint().getConfigurations(), getPropertyIds());
+
+        Set<HostGroup> hostGroups = response.getRecommendations().getBlueprint().getHostGroups();
+        List<Map<String, Object>> listGroupProps = new ArrayList<Map<String, Object>>();
+        for (HostGroup hostGroup : hostGroups) {
+          Map<String, Object> mapGroupProps = new HashMap<String, Object>();
+          mapGroupProps.put(BLUEPRINT_HOST_GROUPS_NAME_PROPERTY_ID, hostGroup.getName());
+          mapGroupProps
+              .put(BLUEPRINT_HOST_GROUPS_COMPONENTS_PROPERTY_ID, hostGroup.getComponents());
+          listGroupProps.add(mapGroupProps);
+        }
+        setResourceProperty(resource, BLUEPRINT_HOST_GROUPS_PROPERTY_ID, listGroupProps,
+            getPropertyIds());
+
+        Set<BindingHostGroup> bindingHostGroups = response.getRecommendations()
+            .getBlueprintClusterBinding().getHostGroups();
+        List<Map<String, Object>> listBindingGroupProps = new ArrayList<Map<String, Object>>();
+        for (BindingHostGroup hostGroup : bindingHostGroups) {
+          Map<String, Object> mapGroupProps = new HashMap<String, Object>();
+          mapGroupProps.put(BINDING_HOST_GROUPS_NAME_PROPERTY_ID, hostGroup.getName());
+          mapGroupProps.put(BINDING_HOST_GROUPS_HOSTS_PROPERTY_ID, hostGroup.getHosts());
+          listBindingGroupProps.add(mapGroupProps);
+        }
+        setResourceProperty(resource, BINDING_HOST_GROUPS_PROPERTY_ID, listBindingGroupProps,
+            getPropertyIds());
+
+        return resource;
+      }
+    });
+    notifyCreate(Resource.Type.Recommendation, request);
+
+    Set<Resource> resources = new HashSet<Resource>(Arrays.asList(recommendation));
+    return new RequestStatusImpl(null, resources);
+  }
+
+  @SuppressWarnings("unchecked")
+  private RecommendationRequest prepareRecommendationRequest(Request request) {
+    try {
+      String stackName = (String) getRequestProperty(request, STACK_NAME_PROPERTY_ID);
+      String stackVersion = (String) getRequestProperty(request, STACK_VERSION_PROPERTY_ID);
+
+      /*
+       * ClassCastException will occur if hosts or services are empty in the
+       * request.
+       * 
+       * @see JsonRequestBodyParser for arrays parsing
+       */
+      List<String> hosts = (List<String>) getRequestProperty(request, "hosts");
+      List<String> services = (List<String>) getRequestProperty(request, "services");
+
+      RecommendationRequest recommendationRequest = RecommendationRequestBuilder
+          .forStack(stackName, stackVersion).forHosts(hosts).forServices(services).build();
+
+      return recommendationRequest;
+    } catch (Exception e) {
+      LOG.warn("Error occured during preparation of recommendation request", e);
+
+      Response response = Response.status(Status.BAD_REQUEST)
+          .entity("Hosts and services must not be empty").build();
+      throw new WebApplicationException(response);
+    }
+  }
+
+  private Object getRequestProperty(Request request, String propertyName) {
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      if (propertyMap.containsKey(propertyName)) {
+        return propertyMap.get(propertyName);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index 53f8a9c..50519cc 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -109,6 +109,7 @@ public interface Resource {
     ViewVersion,
     ViewInstance,
     Blueprint,
+    Recommendation,
     HostComponentProcess,
     Permission,
     AlertDefinition,
@@ -184,6 +185,7 @@ public interface Resource {
     public static final Type ViewVersion = InternalType.ViewVersion.getType();
     public static final Type ViewInstance = InternalType.ViewInstance.getType();
     public static final Type Blueprint = InternalType.Blueprint.getType();
+    public static final Type Recommendation = InternalType.Recommendation.getType();
     public static final Type HostComponentProcess = InternalType.HostComponentProcess.getType();
     public static final Type Permission = InternalType.Permission.getType();
     public static final Type AlertDefinition = InternalType.AlertDefinition.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/resources/key_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/key_properties.json b/ambari-server/src/main/resources/key_properties.json
index 13adde8..4e9c379 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -124,6 +124,11 @@
   "Blueprint": {
     "Blueprint": "Blueprints/blueprint_name"
   },
+  "Recommendation": {
+    "Recommendation": "Recommendation/id",
+    "Stack": "Versions/stack_name",
+    "StackVersion": "Versions/stack_version"
+  },
   "HostComponentProcess": {
     "Cluster": "HostComponentProcess/cluster_name",
     "Host": "HostComponentProcess/host_name",

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/resources/properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/properties.json b/ambari-server/src/main/resources/properties.json
index 543773e..170fa4b 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -352,6 +352,23 @@
         "configurations",
         "validate_topology"
     ],
+    "Recommendation":[
+        "Recommendation/id",
+        "Versions/stack_name",
+        "Versions/stack_version",
+        "hosts",
+        "services",
+        "recommendations",
+        "recommendations/blueprint",
+        "recommendations/blueprint/configurations",
+        "recommendations/blueprint/host_groups",
+        "recommendations/blueprint/host_groups/name",
+        "recommendations/blueprint/host_groups/components",
+        "recommendations/blueprint_cluster_binding",
+        "recommendations/blueprint_cluster_binding/host_groups",
+        "recommendations/blueprint_cluster_binding/host_groups/name",
+        "recommendations/blueprint_cluster_binding/host_groups/hosts"
+    ],
     "HostComponentProcess": [
       "HostComponentProcess/cluster_name",
       "HostComponentProcess/host_name",

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/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
new file mode 100644
index 0000000..1eb5e4d
--- /dev/null
+++ b/ambari-server/src/main/resources/scripts/stack_advisor.py
@@ -0,0 +1,148 @@
+#!/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 StringIO
+
+import json
+import os
+import sys
+import traceback
+
+RECOMMEND_COMPONENT_LAYOUT_ACTION = 'recommend-component-layout'
+VALIDATE_COMPONENT_LAYOUT_ACTION = 'validate-component-layout'
+RECOMMEND_CONFIGURATIONS = 'recommend-configurations'
+VALIDATE_CONFIGURATIONS = 'validate-configurations'
+
+ALL_ACTIONS = [ RECOMMEND_COMPONENT_LAYOUT_ACTION, VALIDATE_COMPONENT_LAYOUT_ACTION, RECOMMEND_CONFIGURATIONS, VALIDATE_CONFIGURATIONS ]
+USAGE = "Usage: <action> <hosts_file> <services_file>\nPossible actions are: {0}\n".format( str(ALL_ACTIONS) )
+
+SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
+STACK_ADVISOR_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, '../stacks/{0}/stack_advisor.py')
+STACK_ADVISOR_IMPL_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, './../stacks/{0}/{1}/services/stack_advisor.py')
+STACK_ADVISOR_IMPL_CLASS_TEMPLATE = '{0}{1}StackAdvisor'
+
+
+class StackAdvisorException(Exception):
+  pass
+
+def loadJson(path):
+  try:
+    with open(path, 'r') as f:
+      return json.load(f)
+  except Exception, err:
+    raise StackAdvisorException("File not found at: {0}".format(hostsFile))
+
+
+def dumpJson(json_object, dump_file):
+  try:
+    with open(dump_file, 'w') as out:
+      json.dump(json_object, out, indent=1)
+  except Exception, err:
+    raise StackAdvisorException("Can not write to file {0} : {1}".format(dump_file, str(err)))
+
+
+def main(argv=None):
+  args = argv[1:]
+
+  if len(args) < 3:
+    sys.stderr.write(USAGE)
+    sys.exit(2)
+    pass
+
+  action = args[0]
+  if action not in ALL_ACTIONS:
+    sys.stderr.write(USAGE)
+    sys.exit(2)
+    pass
+
+  hostsFile = args[1]
+  servicesFile = args[2]
+
+  # Parse hostsFile and servicesFile
+  hosts = loadJson(hostsFile)
+  services = loadJson(servicesFile)
+
+  # Instantiate StackAdvisor and call action related method
+  stackName = services["Versions"]["stack_name"]
+  stackVersion = services["Versions"]["stack_version"]
+  parentVersions = []
+  if "parent_stack_version" in services["Versions"]:
+    parentVersions = [ services["Versions"]["parent_stack_version"] ]
+
+  stackAdvisor = instantiateStackAdvisor(stackName, stackVersion, parentVersions)
+
+  # Perform action
+  actionDir = os.path.realpath(os.path.dirname(args[1]))
+  result = {}
+  result_file = "non_valid_result_file.json"
+
+  if action == RECOMMEND_COMPONENT_LAYOUT_ACTION:
+    result = stackAdvisor.recommendComponentLayout(services, hosts)
+    result_file = os.path.join(actionDir, "component-layout.json")
+  elif action == VALIDATE_COMPONENT_LAYOUT_ACTION:
+    result = stackAdvisor.validateComponentLayout(services, hosts)
+    result_file = os.path.join(actionDir, "component-layout-validation.json")
+  elif action == RECOMMEND_CONFIGURATIONS:
+    result = stackAdvisor.recommendConfigurations(services, hosts)
+    result_file = os.path.join(actionDir, "configurations.json")
+  else: # action == VALIDATE_CONFIGURATIONS
+    result = stackAdvisor.validateConfigurations(services, hosts)
+    result_file = os.path.join(actionDir, "configurations-validation.json")
+
+  dumpJson(result, result_file)
+  pass
+
+
+def instantiateStackAdvisor(stackName, stackVersion, parentVersions):
+  """Instantiates StackAdvisor implementation for the specified Stack"""
+  import imp
+
+  stackAdvisorPath = STACK_ADVISOR_PATH_TEMPLATE.format(stackName)
+  with open(stackAdvisorPath, 'rb') as fp:
+    stack_advisor = imp.load_module( 'stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE) )
+
+  versions = [stackVersion]
+  versions.extend(parentVersions)
+
+  for version in versions:
+    try:
+      path = STACK_ADVISOR_IMPL_PATH_TEMPLATE.format(stackName, version)
+      className = STACK_ADVISOR_IMPL_CLASS_TEMPLATE.format(stackName, version.replace('.', ''))
+
+      with open(path, 'rb') as fp:
+        stack_advisor_impl = imp.load_module( 'stack_advisor_impl', fp, path, ('.py', 'rb', imp.PY_SOURCE) )
+      clazz = getattr(stack_advisor_impl, className)
+
+      print "StackAdvisor for stack {0}, version {1} will be used".format(stackName, version)
+      return clazz();
+    except Exception, e:
+      print "StackAdvisor implementation for stack {0}, version {1} was not found".format(stackName, version)
+
+  print "StackAdvisor default implementation will be used!"
+  return stack_advisor.StackAdvisor()
+
+
+if __name__ == '__main__':
+  try:
+    main(sys.argv)
+  except Exception, e:
+    traceback.print_exc()
+    print "Error occured in stack advisor.\nError details: {0}".format(str(e))
+    sys.exit(1)
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/resources/stacks/HDP/2.0.6/services/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.0.6/services/stack_advisor.py b/ambari-server/src/main/resources/stacks/HDP/2.0.6/services/stack_advisor.py
new file mode 100644
index 0000000..01a38cf
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/HDP/2.0.6/services/stack_advisor.py
@@ -0,0 +1,202 @@
+#!/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 HDP206StackAdvisor(StackAdvisor):
+
+  def recommendComponentLayout(self, services, hosts):
+    """Returns Services object with hostnames array populated for components"""
+    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": {
+          "configurations": {
+            "global": {
+              "properties": { }
+            },
+            "core-site": { },
+            "hdfs-site": { },
+            "yarn-site": { },
+            "hbase-site": { }
+          },
+          "host_groups": [ ]
+        },
+        "blueprint_cluster_binding": {
+          "host_groups": [ ]
+        }
+      }
+    }
+
+    hostsComponentsMap = {}
+    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 = []
+
+        availableHosts = hostsList
+        if len(hostsList) > 1 and 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 = [getHostForComponent(component, availableHosts)]
+        else:
+          hostsForComponent = [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
+    utilizedHosts = hostsComponentsMap.keys()
+    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 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 validateComponentLayout(self, services, hosts):
+    """Returns array of Validation objects about issues with hostnames components assigned to"""
+    pass
+
+  def recommendConfigurations(self, services, hosts):
+    """Returns Services object with configurations object populated"""
+    pass
+
+  def validateConfigurations(self, services, hosts):
+    """Returns array of Validation objects about issues with configuration values provided in services"""
+    pass
+
+
+# Helper methods
+def getHostForComponent(component, hostsList):
+  componentName = component["StackServiceComponents"]["component_name"]
+  scheme = selectionScheme(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 isClient(component):
+  return component["StackServiceComponents"]["component_category"] == 'CLIENT'
+
+def isSlave(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  isSlave = component["StackServiceComponents"]["component_category"] == 'SLAVE'
+  return isSlave and componentName != 'APP_TIMELINE_SERVER'
+
+def isMaster(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  isMaster = component["StackServiceComponents"]["is_master"]
+  return isMaster or componentName == 'APP_TIMELINE_SERVER'
+
+def isLocalHost(hostName):
+  return socket.getfqdn(hostName) == socket.getfqdn()
+
+def isNotPreferableOnAmbariServerHost(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  service = ['STORM_UI_SERVER', 'DRPC_SERVER', 'STORM_REST_API', 'NIMBUS', 'GANGLIA_SERVER', 'NAGIOS_SERVER', 'HUE_SERVER']
+  return componentName in service
+
+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})
+
+def selectionScheme(componentName):
+  return {
+    'NAMENODE': {"else": 0},
+    'SECONDARY_NAMENODE': {"else": 1},
+    'HBASE_MASTER': {6: 0, 31: 2, "else": 3},
+
+    'JOBTRACKER': {31: 1, "else": 2},
+    'HISTORYSERVER': {31: 1, "else": 2},
+    'RESOURCEMANAGER': {31: 1, "else": 2},
+    'APP_TIMELINE_SERVER': {31: 1, "else": 2},
+
+    'OOZIE_SERVER': {6: 1, 31: 2, "else": 3},
+    'FALCON_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, {"else": 0})
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/main/resources/stacks/HDP/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/stack_advisor.py b/ambari-server/src/main/resources/stacks/HDP/stack_advisor.py
new file mode 100644
index 0000000..fdef482
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/HDP/stack_advisor.py
@@ -0,0 +1,37 @@
+#!/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.
+"""
+
+class StackAdvisor():
+
+  def recommendComponentLayout(self, services, hosts):
+    """Returns Services object with hostnames array populated for components"""
+    pass
+
+  def validateComponentLayout(self, services, hosts):
+    """Returns array of Validation objects about issues with hostnames components assigned to"""
+    pass
+
+  def recommendConfigurations(self, services, hosts):
+    """Returns Services object with configurations object populated"""
+    pass
+
+  def validateConfigurations(self, services, hosts):
+    """Returns array of Validation objects about issues with configuration values provided in services"""
+    pass
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
index 2458920..68ee4a5 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
@@ -78,7 +78,7 @@ public class GroupServiceTest extends BaseServiceTest {
 
   private class TestGroupService extends GroupService {
     @Override
-    ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
+    protected ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
       return getTestResource();
     }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
index 9c5c860..b3bf3fb 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
@@ -88,7 +88,7 @@ public class MemberServiceTest extends BaseServiceTest {
     }
 
     @Override
-    ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
+    protected ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
       return getTestResource();
     }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/b0b58189/ambari-server/src/test/java/org/apache/ambari/server/api/services/RecommendationServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/RecommendationServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/RecommendationServiceTest.java
new file mode 100644
index 0000000..a1a0908
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/RecommendationServiceTest.java
@@ -0,0 +1,85 @@
+/**
+ * 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.services;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
+import org.apache.ambari.server.api.services.serializers.ResultSerializer;
+
+/**
+ * Unit tests for RecommendationService.
+ */
+public class RecommendationServiceTest extends BaseServiceTest {
+
+  @Override
+  public List<ServiceTestInvocation> getTestInvocations() throws Exception {
+    List<ServiceTestInvocation> listInvocations = new ArrayList<ServiceTestInvocation>();
+
+    //getRecommendation
+    RecommendationService service = new TestRecommendationService("stackName", "stackVersion");
+    Method m = service.getClass().getMethod("getRecommendation", String.class, HttpHeaders.class, UriInfo.class, String.class, String.class);
+    Object[] args = new Object[] {"body", getHttpHeaders(), getUriInfo(), "stackName", "stackVersion"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, service, m, args, "body"));
+
+    return listInvocations;
+  }
+
+  private class TestRecommendationService extends RecommendationService {
+    private String stackName;
+    private String stackVersion;
+
+    private TestRecommendationService(String stackName, String stackVersion) {
+      super();
+      this.stackName = stackName;
+      this.stackVersion = stackVersion;
+    }
+
+    @Override
+    ResourceInstance createRecommendationResource(String stackName, String stackVersion) {
+      assertEquals(this.stackName, stackName);
+      assertEquals(this.stackVersion, stackVersion);
+      return getTestResource();
+    }
+
+    @Override
+    RequestFactory getRequestFactory() {
+      return getTestRequestFactory();
+    }
+
+    @Override
+    protected RequestBodyParser getBodyParser() {
+      return getTestBodyParser();
+    }
+
+    @Override
+    protected ResultSerializer getResultSerializer() {
+      return getTestResultSerializer();
+    }
+  }
+
+}