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/31 03:28:35 UTC

[2/2] git commit: AMBARI-6686. BE: Provide host-layout validations via /validations endpoint on stack-version

AMBARI-6686. BE: Provide host-layout validations via /validations 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/a7426939
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/a7426939
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/a7426939

Branch: refs/heads/trunk
Commit: a742693978b49188cf12129356c88ba754e2fe95
Parents: 15b5443
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Wed Jul 30 17:16:06 2014 -0700
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Wed Jul 30 18:13:59 2014 -0700

----------------------------------------------------------------------
 .../RecommendationResourceDefinition.java       |   2 +-
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../resources/ValidationResourceDefinition.java |  43 +++
 .../server/api/services/ValidationService.java  |  70 +++++
 .../stackadvisor/StackAdvisorHelper.java        | 208 ++-------------
 .../stackadvisor/StackAdvisorRequest.java       | 105 ++++++++
 .../stackadvisor/StackAdvisorRunner.java        |  39 +--
 ...GetComponentLayoutRecommnedationCommand.java |  63 +++++
 .../GetComponentLayoutValidationCommand.java    | 113 ++++++++
 .../commands/StackAdvisorCommand.java           | 263 +++++++++++++++++++
 .../commands/StackAdvisorCommandType.java       |  44 ++++
 .../recommendations/RecommendationRequest.java  |  91 -------
 .../validations/ValidationResponse.java         | 132 ++++++++++
 .../ambari/server/controller/AmbariServer.java  |   4 +-
 .../AbstractControllerResourceProvider.java     |   2 +
 .../RecommendationResourceProvider.java         |  58 +---
 .../internal/StackAdvisorResourceProvider.java  | 207 +++++++++++++++
 .../internal/ValidationResourceProvider.java    | 129 +++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../src/main/resources/key_properties.json      |   5 +
 .../src/main/resources/properties.json          |  17 ++
 .../stacks/HDP/2.0.6/services/stack_advisor.py  |  29 +-
 .../api/services/ValidationServiceTest.java     |  85 ++++++
 23 files changed, 1352 insertions(+), 363 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
index 2c9e1f2..5333b2b 100644
--- 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
@@ -23,7 +23,7 @@ import org.apache.ambari.server.controller.spi.Resource;
 /**
  * Recommendation resource definition.
  */
-public class RecommendationResourceDefinition  extends BaseResourceDefinition {
+public class RecommendationResourceDefinition extends BaseResourceDefinition {
   /**
    * Constructor.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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 bc2e634..6a71b77 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
@@ -235,6 +235,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new RecommendationResourceDefinition();
         break;
 
+      case Validation:
+        resourceDefinition = new ValidationResourceDefinition();
+        break;
+
       case HostComponentProcess:
         resourceDefinition = new HostComponentProcessResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ValidationResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ValidationResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ValidationResourceDefinition.java
new file mode 100644
index 0000000..027ee4b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ValidationResourceDefinition.java
@@ -0,0 +1,43 @@
+/**
+ * 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;
+
+/**
+ * Validation resource definition.
+ */
+public class ValidationResourceDefinition extends BaseResourceDefinition {
+  /**
+   * Constructor.
+   */
+  public ValidationResourceDefinition() {
+    super(Resource.Type.Validation);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "validations";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "validation";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/ambari-server/src/main/java/org/apache/ambari/server/api/services/ValidationService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ValidationService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ValidationService.java
new file mode 100644
index 0000000..87cb6ac
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ValidationService.java
@@ -0,0 +1,70 @@
+/**
+ * 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 validation of host-layout and configurations.
+ */
+@Path("/stacks/{stackName}/versions/{stackVersion}/validations")
+public class ValidationService extends BaseService {
+
+  /**
+   * Returns validation of host-layout.
+   * 
+   * @param body http body
+   * @param headers http headers
+   * @param ui uri info
+   * @param stackName stack name
+   * @param stackVersion stack version
+   * @return validation items if any
+   */
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response getValidation(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+      @PathParam("stackName") String stackName, @PathParam("stackVersion") String stackVersion) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createValidationResource(stackName, stackVersion));
+  }
+
+  ResourceInstance createValidationResource(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.Validation, mapIds);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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 c0799c9..b390f2f 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
@@ -20,57 +20,24 @@ 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.commands.GetComponentLayoutRecommnedationCommand;
+import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutValidationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
+import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
 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)";
+public class StackAdvisorHelper {
 
   private File recommendationsDir;
   private String stackAdvisorScript;
 
   /* Monotonically increasing requestid */
   private int requestId = 0;
-  private File requestDirectory;
   private StackAdvisorRunner saRunner;
 
   @Inject
@@ -81,161 +48,36 @@ public class StackAdvisorHelper extends BaseService {
   }
 
   /**
-   * Return component-layout recommendation based on hosts and services
-   * information.
+   * Return component-layout validation result.
    * 
-   * @param hosts list of hosts
-   * @param services list of services
-   * @return {@link String} representation of recommendations
-   * @throws StackAdvisorException
+   * @param validationRequest the validation request
+   * @return {@link ValidationResponse} instance
+   * @throws StackAdvisorException in case of stack advisor script errors
    */
-  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"));
+  public synchronized ValidationResponse getComponentLayoutValidation(StackAdvisorRequest request)
+      throws StackAdvisorException {
+    requestId += 1;
 
-      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);
-    }
+    GetComponentLayoutValidationCommand command = new GetComponentLayoutValidationCommand(
+        recommendationsDir, stackAdvisorScript, requestId, saRunner);
+    return command.invoke(request);
   }
 
   /**
-   * Create request id directory for each call
+   * Return component-layout recommendation based on hosts and services
+   * information.
+   * 
+   * @param request the recommendation request
+   * @return {@link RecommendationResponse} instance
+   * @throws StackAdvisorException in case of stack advisor script errors
    */
-  private void createRequestDirectory() throws IOException {
-    if (!recommendationsDir.exists()) {
-      if (!recommendationsDir.mkdirs()) {
-        throw new IOException("Cannot create " + recommendationsDir);
-      }
-    }
-
+  public synchronized RecommendationResponse getComponentLayoutRecommnedation(
+      StackAdvisorRequest request) throws StackAdvisorException {
     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");
-    }
+    GetComponentLayoutRecommnedationCommand command = new GetComponentLayoutRecommnedationCommand(
+        recommendationsDir, stackAdvisorScript, requestId, saRunner);
+    return command.invoke(request);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
new file mode 100644
index 0000000..a6896fb
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
@@ -0,0 +1,105 @@
+/**
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Stack advisor request.
+ */
+public class StackAdvisorRequest {
+
+  private String stackName;
+  private String stackVersion;
+  private List<String> hosts = new ArrayList<String>();
+  private List<String> services = new ArrayList<String>();
+  private Map<String, Set<String>> componentHostsMap = new HashMap<String, Set<String>>();
+
+  public String getStackName() {
+    return stackName;
+  }
+
+  public String getStackVersion() {
+    return stackVersion;
+  }
+
+  public List<String> getHosts() {
+    return hosts;
+  }
+
+  public List<String> getServices() {
+    return services;
+  }
+
+  public Map<String, Set<String>> getComponentHostsMap() {
+    return componentHostsMap;
+  }
+
+  public String getHostsCommaSeparated() {
+    return StringUtils.join(hosts, ",");
+  }
+
+  public String getServicesCommaSeparated() {
+    return StringUtils.join(services, ",");
+  }
+
+  private StackAdvisorRequest(String stackName, String stackVersion) {
+    this.stackName = stackName;
+    this.stackVersion = stackVersion;
+  }
+
+  public static class StackAdvisorRequestBuilder {
+    StackAdvisorRequest instance;
+
+    private StackAdvisorRequestBuilder(String stackName, String stackVersion) {
+      this.instance = new StackAdvisorRequest(stackName, stackVersion);
+    }
+
+    public static StackAdvisorRequestBuilder forStack(String stackName, String stackVersion) {
+      return new StackAdvisorRequestBuilder(stackName, stackVersion);
+    }
+
+    public StackAdvisorRequestBuilder forHosts(List<String> hosts) {
+      this.instance.hosts = hosts;
+      return this;
+    }
+
+    public StackAdvisorRequestBuilder forServices(List<String> services) {
+      this.instance.services = services;
+      return this;
+    }
+
+    public StackAdvisorRequestBuilder withComponentHostsMap(
+        Map<String, Set<String>> componentHostsMap) {
+      this.instance.componentHostsMap = componentHostsMap;
+      return this;
+    }
+
+    public StackAdvisorRequest build() {
+      return this.instance;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
index bb2cc4e..1cc666b 100644
--- 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
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommandType;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,19 +39,19 @@ public class StackAdvisorRunner {
    * Runs stack_advisor.py script in the specified {@code actionDirectory}.
    * 
    * @param script stack advisor script
-   * @param saCommand {@link StackAdvisorCommand} to run.
+   * @param saCommandType {@link StackAdvisorCommandType} 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("Script={}, actionDirectory={}, command={}", script,
-        actionDirectory, saCommand);
+  public boolean runScript(String script, StackAdvisorCommandType saCommandType, File actionDirectory) {
+    LOG.info(String.format("Script=%s, actionDirectory=%s, command=%s", script, actionDirectory,
+        saCommandType));
 
     String outputFile = actionDirectory + File.separator + "stackadvisor.out";
     String errorFile = actionDirectory + File.separator + "stackadvisor.err";
 
-    ProcessBuilder builder = prepareShellCommand(script, saCommand,
+    ProcessBuilder builder = prepareShellCommand(script, saCommandType,
         actionDirectory, outputFile,
         errorFile);
 
@@ -87,14 +88,14 @@ public class StackAdvisorRunner {
    * environment variables from the current process.
    * 
    * @param script
-   * @param saCommand
+   * @param saCommandType
    * @param actionDirectory
    * @param outputFile
    * @param errorFile
    * @return
    */
   private ProcessBuilder prepareShellCommand(String script,
-      StackAdvisorCommand saCommand,
+      StackAdvisorCommandType saCommandType,
       File actionDirectory, String outputFile, String errorFile) {
     String hostsFile = actionDirectory + File.separator + "hosts.json";
     String servicesFile = actionDirectory + File.separator + "services.json";
@@ -107,7 +108,7 @@ public class StackAdvisorRunner {
     // for the 3rd argument, build a single parameter since we use -c
     // ProcessBuilder doesn't support output redirection until JDK 1.7
     String commandStringParameters[] = new String[] { script,
-        saCommand.toString(), hostsFile,
+        saCommandType.toString(), hostsFile,
         servicesFile, "1>", outputFile, "2>", errorFile };
 
     StringBuilder commandString = new StringBuilder();
@@ -121,26 +122,4 @@ public class StackAdvisorRunner {
 
     return new ProcessBuilder(builderParameters);
   }
-
-  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/a7426939/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
new file mode 100644
index 0000000..c6761b9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
@@ -0,0 +1,63 @@
+/**
+ * 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.commands;
+
+import java.io.File;
+
+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;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
+
+/**
+ * {@link StackAdvisorCommand} implementation for component-layout
+ * recommendation.
+ */
+public class GetComponentLayoutRecommnedationCommand extends
+    StackAdvisorCommand<RecommendationResponse> {
+
+  public GetComponentLayoutRecommnedationCommand(File recommendationsDir,
+      String stackAdvisorScript, int requestId, StackAdvisorRunner saRunner) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+  }
+
+  @Override
+  protected StackAdvisorCommandType getCommandType() {
+    return StackAdvisorCommandType.RECOMMEND_COMPONENT_LAYOUT;
+  }
+
+  @Override
+  protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
+    if (request.getHosts().isEmpty() || request.getServices().isEmpty()) {
+      throw new StackAdvisorException("Hosts and services must not be empty");
+    }
+  }
+
+  @Override
+  protected StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request) {
+    // do nothing
+    return data;
+  }
+
+  @Override
+  protected String getResultFileName() {
+    return "component-layout.json";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
new file mode 100644
index 0000000..ebe1333
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
@@ -0,0 +1,113 @@
+/**
+ * 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.commands;
+
+import java.io.File;
+import java.util.Iterator;
+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.api.services.stackadvisor.StackAdvisorException;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
+import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.node.ArrayNode;
+import org.codehaus.jackson.node.ObjectNode;
+
+/**
+ * {@link StackAdvisorCommand} implementation for component-layout validation.
+ */
+public class GetComponentLayoutValidationCommand extends StackAdvisorCommand<ValidationResponse> {
+
+  public GetComponentLayoutValidationCommand(File recommendationsDir, String stackAdvisorScript,
+      int requestId, StackAdvisorRunner saRunner) {
+    super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+  }
+
+  @Override
+  protected StackAdvisorCommandType getCommandType() {
+    return StackAdvisorCommandType.VALIDATE_COMPONENT_LAYOUT;
+  }
+
+  @Override
+  protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
+    if (request.getHosts().isEmpty() || request.getServices().isEmpty()
+        || request.getComponentHostsMap().isEmpty()) {
+      throw new StackAdvisorException("Hosts, services and recommendations must not be empty");
+    }
+  }
+
+  private static final String SERVICES_PROPETRY = "services";
+  private static final String SERVICES_COMPONENTS_PROPETRY = "components";
+  private static final String COMPONENT_INFO_PROPETRY = "StackServiceComponents";
+  private static final String COMPONENT_NAME_PROPERTY = "component_name";
+  private static final String COMPONENT_HOSTNAMES_PROPETRY = "hostnames";
+
+  @Override
+  protected StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request) {
+    // do nothing
+    Map<String, Set<String>> componentHostsMap = request.getComponentHostsMap();
+
+    try {
+      JsonNode root = this.mapper.readTree(data.servicesJSON);
+      ArrayNode services = (ArrayNode) root.get(SERVICES_PROPETRY);
+      Iterator<JsonNode> servicesIter = services.getElements();
+
+      while (servicesIter.hasNext()) {
+        JsonNode service = servicesIter.next();
+        ArrayNode components = (ArrayNode) service.get(SERVICES_COMPONENTS_PROPETRY);
+        Iterator<JsonNode> componentsIter = components.getElements();
+
+        while (componentsIter.hasNext()) {
+          JsonNode component = componentsIter.next();
+          ObjectNode componentInfo = (ObjectNode) component.get(COMPONENT_INFO_PROPETRY);
+          String componentName = componentInfo.get(COMPONENT_NAME_PROPERTY).getTextValue();
+
+          Set<String> componentHosts = componentHostsMap.get(componentName);
+          ArrayNode hostnames = componentInfo.putArray(COMPONENT_HOSTNAMES_PROPETRY);
+          if (null != componentHosts) {
+            for (String hostName : componentHosts) {
+              hostnames.add(hostName);
+            }
+          }
+        }
+      }
+
+      data.servicesJSON = mapper.writeValueAsString(root);
+    } catch (Exception e) {
+      // should not happen
+      String message = "Error parsing services.json file content: " + e.getMessage();
+      LOG.warn(message, e);
+      throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(message).build());
+    }
+
+    return data;
+  }
+
+  @Override
+  protected String getResultFileName() {
+    return "component-layout-validation.json";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
new file mode 100644
index 0000000..f32e205
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
@@ -0,0 +1,263 @@
+/**
+ * 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.commands;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+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.Request;
+import org.apache.ambari.server.api.services.StacksService.StackUriInfo;
+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;
+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 org.codehaus.jackson.map.SerializationConfig;
+
+/**
+ * Parent for all commands.
+ */
+public abstract class StackAdvisorCommand<T> extends BaseService {
+
+  /**
+   * Type of response object provided by extending classes when
+   * {@link #invoke(StackAdvisorRequest)} is called.
+   */
+  private Class<T> type;
+
+  protected static Log LOG = LogFactory.getLog(StackAdvisorCommand.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;
+
+  private int requestId;
+  private File requestDirectory;
+  private StackAdvisorRunner saRunner;
+
+  protected ObjectMapper mapper;
+
+  @SuppressWarnings("unchecked")
+  public StackAdvisorCommand(File recommendationsDir, String stackAdvisorScript, int requestId,
+      StackAdvisorRunner saRunner) {
+    this.type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
+        .getActualTypeArguments()[0];
+
+    this.mapper = new ObjectMapper();
+    this.mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
+
+    this.recommendationsDir = recommendationsDir;
+    this.stackAdvisorScript = stackAdvisorScript;
+    this.requestId = requestId;
+    this.saRunner = saRunner;
+  }
+
+  protected abstract StackAdvisorCommandType getCommandType();
+
+  /**
+   * Simple holder for 'hosts.json' and 'services.json' data.
+   */
+  protected class StackAdvisorData {
+    protected String hostsJSON;
+    protected String servicesJSON;
+
+    public StackAdvisorData(String hostsJSON, String servicesJSON) {
+      this.hostsJSON = hostsJSON;
+      this.servicesJSON = servicesJSON;
+    }
+  }
+
+  /**
+   * Name with the result JSON, e.g. "component-layout.json" or
+   * "validations.json" .
+   * 
+   * @return the file name
+   */
+  protected abstract String getResultFileName();
+
+  protected abstract void validate(StackAdvisorRequest request) throws StackAdvisorException;
+
+  protected abstract StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request);
+
+  public synchronized T invoke(StackAdvisorRequest request) throws StackAdvisorException {
+    validate(request);
+    String hostsJSON = getHostsInformation(request);
+    String servicesJSON = getServicesInformation(request);
+
+    StackAdvisorData adjusted = adjust(new StackAdvisorData(hostsJSON, servicesJSON), request);
+
+    try {
+      createRequestDirectory();
+
+      FileUtils.writeStringToFile(new File(requestDirectory, "hosts.json"), adjusted.hostsJSON);
+      FileUtils.writeStringToFile(new File(requestDirectory, "services.json"),
+          adjusted.servicesJSON);
+
+      boolean success = saRunner.runScript(stackAdvisorScript, getCommandType(), 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, getResultFileName()));
+
+      return this.mapper.readValue(result, this.type);
+    } catch (Exception e) {
+      String message = "Error occured during stack advisor command invocation";
+      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);
+      }
+    }
+
+    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(StackAdvisorRequest 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(StackAdvisorRequest 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);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.java
new file mode 100644
index 0000000..380b81d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.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.services.stackadvisor.commands;
+
+/**
+ * StackAdvisorCommand types enumeration. 
+ */
+public enum StackAdvisorCommandType {
+
+  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 StackAdvisorCommandType(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
deleted file mode 100644
index 5ab6b7a..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationRequest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-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/a7426939/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/validations/ValidationResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/validations/ValidationResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/validations/ValidationResponse.java
new file mode 100644
index 0000000..145bcd9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/validations/ValidationResponse.java
@@ -0,0 +1,132 @@
+/**
+ * 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.validations;
+
+import java.util.Set;
+
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.Version;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Validation response POJO.
+ */
+public class ValidationResponse {
+
+  @JsonProperty("Versions")
+  private Version version;
+
+  @JsonProperty
+  private Set<ValidationItem> items;
+
+  public Version getVersion() {
+    return version;
+  }
+
+  public void setVersion(Version version) {
+    this.version = version;
+  }
+
+  public Set<ValidationItem> getItems() {
+    return items;
+  }
+
+  public void setItems(Set<ValidationItem> items) {
+    this.items = items;
+  }
+
+  public static class ValidationItem {
+    @JsonProperty
+    private String type;
+
+    @JsonProperty
+    private String level;
+
+    @JsonProperty
+    private String message;
+
+    @JsonProperty("component-name")
+    private String componentName;
+
+    @JsonProperty
+    private String host;
+
+    @JsonProperty("config-type")
+    private String configType;
+
+    @JsonProperty("config-name")
+    private String configName;
+
+    public String getType() {
+      return type;
+    }
+
+    public void setType(String type) {
+      this.type = type;
+    }
+
+    public String getLevel() {
+      return level;
+    }
+
+    public void setLevel(String level) {
+      this.level = level;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public void setMessage(String message) {
+      this.message = message;
+    }
+
+    public String getComponentName() {
+      return componentName;
+    }
+
+    public void setComponentName(String componentName) {
+      this.componentName = componentName;
+    }
+
+    public String getHost() {
+      return host;
+    }
+
+    public void setHost(String host) {
+      this.host = host;
+    }
+
+    public String getConfigType() {
+      return configType;
+    }
+
+    public void setConfigType(String configType) {
+      this.configType = configType;
+    }
+
+    public String getConfigName() {
+      return configName;
+    }
+
+    public void setConfigName(String configName) {
+      this.configName = configName;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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 77d2a77..906cba4 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
@@ -50,7 +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.StackAdvisorResourceProvider;
 import org.apache.ambari.server.controller.internal.StackDefinedPropertyProvider;
 import org.apache.ambari.server.controller.internal.StackDependencyResourceProvider;
 import org.apache.ambari.server.controller.internal.ViewPermissionResourceProvider;
@@ -532,7 +532,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));
+    StackAdvisorResourceProvider.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/a7426939/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 6c33bdf..034b97c 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
@@ -148,6 +148,8 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
         return new BlueprintResourceProvider(propertyIds, keyPropertyIds, managementController);
       case Recommendation:
         return new RecommendationResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case Validation:
+        return new ValidationResourceProvider(propertyIds, keyPropertyIds, managementController);
       case AlertDefinition:
         return new AlertDefinitionResourceProvider(propertyIds, keyPropertyIds, managementController);
       case Controller:

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
index d4fd1ce..6b4b1a6 100644
--- 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
@@ -32,9 +32,7 @@ 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.StackAdvisorRequest;
 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;
@@ -49,23 +47,10 @@ 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;
-  }
+public class RecommendationResourceProvider extends StackAdvisorResourceProvider {
 
   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";
@@ -94,7 +79,7 @@ public class RecommendationResourceProvider extends ReadOnlyResourceProvider {
   @Override
   public RequestStatus createResources(final Request request) throws SystemException,
       UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
-    RecommendationRequest recommendationRequest = prepareRecommendationRequest(request);
+    StackAdvisorRequest recommendationRequest = prepareStackAdvisorRequest(request);
 
     final RecommendationResponse response;
     try {
@@ -154,43 +139,6 @@ public class RecommendationResourceProvider extends ReadOnlyResourceProvider {
     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/a7426939/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackAdvisorResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackAdvisorResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackAdvisorResourceProvider.java
new file mode 100644
index 0000000..3d0d907
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackAdvisorResourceProvider.java
@@ -0,0 +1,207 @@
+/**
+ * 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.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.api.services.stackadvisor.StackAdvisorHelper;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestBuilder;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+
+import com.google.inject.Inject;
+
+/**
+ * Abstract superclass for recommendations and validations.
+ */
+public abstract class StackAdvisorResourceProvider extends ReadOnlyResourceProvider {
+
+  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");
+
+  private static final String HOST_PROPERTY = "hosts";
+  private static final String SERVICES_PROPERTY = "services";
+
+  private static final String BLUEPRINT_HOST_GROUPS_PROPERTY = "recommendations/blueprint/host_groups";
+  private static final String BINDING_HOST_GROUPS_PROPERTY = "recommendations/blueprint_cluster_binding/host_groups";
+
+  private static final String BLUEPRINT_HOST_GROUPS_NAME_PROPERTY = "name";
+  private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_PROPERTY = "components";
+  private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY = "name";
+
+  private static final String BINDING_HOST_GROUPS_NAME_PROPERTY = "name";
+  private static final String BINDING_HOST_GROUPS_HOSTS_PROPERTY = "hosts";
+  private static final String BINDING_HOST_GROUPS_HOSTS_NAME_PROPERTY = "fqdn";
+
+  protected static StackAdvisorHelper saHelper;
+
+  @Inject
+  public static void init(StackAdvisorHelper instance) {
+    saHelper = instance;
+  }
+
+  protected StackAdvisorResourceProvider(Set<String> propertyIds, Map<Type, String> keyPropertyIds,
+      AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @SuppressWarnings("unchecked")
+  protected StackAdvisorRequest prepareStackAdvisorRequest(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, HOST_PROPERTY);
+      List<String> services = (List<String>) getRequestProperty(request, SERVICES_PROPERTY);
+      Map<String, Set<String>> componentHostsMap = calculateComponentHostsMap(request);
+
+      StackAdvisorRequest saRequest = StackAdvisorRequestBuilder.forStack(stackName, stackVersion)
+          .forHosts(hosts).forServices(services).withComponentHostsMap(componentHostsMap).build();
+
+      return saRequest;
+    } catch (Exception e) {
+      LOG.warn("Error occured during preparation of stack advisor request", e);
+      Response response = Response.status(Status.BAD_REQUEST).entity("Request body is not correct")
+          .build();
+      // TODO: Hosts and services must not be empty
+      throw new WebApplicationException(response);
+    }
+  }
+
+  /**
+   * Will prepare host-group names to components names map from the
+   * recommendation blueprint host groups.
+   * 
+   * @param hostGroups the blueprint host groups
+   * @return host-group to components map
+   */
+  @SuppressWarnings("unchecked")
+  private Map<String, Set<String>> calculateHostGroupComponentsMap(
+      Set<Map<String, Object>> hostGroups) {
+    Map<String, Set<String>> map = new HashMap<String, Set<String>>();
+    for (Map<String, Object> hostGroup : hostGroups) {
+      String hostGroupName = (String) hostGroup.get(BLUEPRINT_HOST_GROUPS_NAME_PROPERTY);
+
+      Set<Map<String, Object>> componentsSet = (Set<Map<String, Object>>) hostGroup
+          .get(BLUEPRINT_HOST_GROUPS_COMPONENTS_PROPERTY);
+
+      Set<String> components = new HashSet<String>();
+      for (Map<String, Object> component : componentsSet) {
+        components.add((String) component.get(BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY));
+      }
+
+      map.put(hostGroupName, components);
+    }
+
+    return map;
+  }
+
+  /**
+   * Will prepare host-group names to hosts names map from the recommendation
+   * binding host groups.
+   * 
+   * @param bindingHostGroups the binding host groups
+   * @return host-group to hosts map
+   */
+  @SuppressWarnings("unchecked")
+  private Map<String, Set<String>> calculateHostGroupHostsMap(
+      Set<Map<String, Object>> bindingHostGroups) {
+    Map<String, Set<String>> map = new HashMap<String, Set<String>>();
+
+    for (Map<String, Object> hostGroup : bindingHostGroups) {
+      String hostGroupName = (String) hostGroup.get(BINDING_HOST_GROUPS_NAME_PROPERTY);
+
+      Set<Map<String, Object>> hostsSet = (Set<Map<String, Object>>) hostGroup
+          .get(BINDING_HOST_GROUPS_HOSTS_PROPERTY);
+
+      Set<String> hosts = new HashSet<String>();
+      for (Map<String, Object> host : hostsSet) {
+        hosts.add((String) host.get(BINDING_HOST_GROUPS_HOSTS_NAME_PROPERTY));
+      }
+
+      map.put(hostGroupName, hosts);
+    }
+
+    return map;
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<String, Set<String>> calculateComponentHostsMap(Request request) {
+    /*
+     * ClassCastException may occur in case of body inconsistency: property
+     * missed, etc.
+     */
+    Set<Map<String, Object>> hostGroups = (Set<Map<String, Object>>) getRequestProperty(request,
+        BLUEPRINT_HOST_GROUPS_PROPERTY);
+    Set<Map<String, Object>> bindingHostGroups = (Set<Map<String, Object>>) getRequestProperty(
+        request, BINDING_HOST_GROUPS_PROPERTY);
+
+    Map<String, Set<String>> componentHostsMap = new HashMap<String, Set<String>>();
+    if (null != bindingHostGroups && null != hostGroups) {
+      Map<String, Set<String>> hgComponentsMap = calculateHostGroupComponentsMap(hostGroups);
+      Map<String, Set<String>> hgHostsMap = calculateHostGroupHostsMap(bindingHostGroups);
+
+      for (Map.Entry<String, Set<String>> hgComponents : hgComponentsMap.entrySet()) {
+        String hgName = hgComponents.getKey();
+        Set<String> components = hgComponents.getValue();
+
+        Set<String> hosts = hgHostsMap.get(hgName);
+        for (String component : components) {
+          Set<String> componentHosts = componentHostsMap.get(component);
+          if (componentHosts == null) { // if was not initialized
+            componentHosts = new HashSet<String>();
+            componentHostsMap.put(component, componentHosts);
+          }
+          componentHosts.addAll(hosts);
+        }
+      }
+    }
+
+    return componentHostsMap;
+  }
+
+  protected Object getRequestProperty(Request request, String propertyName) {
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      if (propertyMap.containsKey(propertyName)) {
+        return propertyMap.get(propertyName);
+      }
+    }
+    return null;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ValidationResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ValidationResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ValidationResourceProvider.java
new file mode 100644
index 0000000..2ec4085
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ValidationResourceProvider.java
@@ -0,0 +1,129 @@
+/**
+ * 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.StackAdvisorRequest;
+import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
+import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse.ValidationItem;
+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;
+
+public class ValidationResourceProvider extends StackAdvisorResourceProvider {
+
+  protected static final String VALIDATION_ID_PROPERTY_ID = PropertyHelper.getPropertyId(
+      "Validations", "id");
+
+  protected static final String ITEMS_PROPERTY_ID = "items";
+  protected static final String ITEMS_TYPE_PROPERTY_ID = "type";
+  protected static final String ITEMS_LEVE_PROPERTY_ID = "level";
+  protected static final String ITEMS_MESSAGE_PROPERTY_ID = "message";
+  protected static final String ITEMS_COMPONENT_NAME_PROPERTY_ID = "component-name";
+  protected static final String ITEMS_HOST_PROPERTY_ID = "host";
+  protected static final String ITEMS_CONFIG_TYPE_PROPERTY_ID = "config-type";
+  protected static final String ITEMS_CONFIG_NAME_PROPERTY_ID = "config-name";
+
+  private static Set<String> pkPropertyIds = new HashSet<String>(
+      Arrays.asList(new String[] { VALIDATION_ID_PROPERTY_ID }));
+
+  protected ValidationResourceProvider(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 {
+    StackAdvisorRequest validationRequest = prepareStackAdvisorRequest(request);
+
+    final ValidationResponse response;
+    try {
+      response = saHelper.getComponentLayoutValidation(validationRequest);
+    } catch (StackAdvisorException e) {
+      LOG.warn("Error occured during component-layout validation", e);
+      throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(e.getMessage())
+          .build());
+    }
+
+    Resource validation = createResources(new Command<Resource>() {
+      @Override
+      public Resource invoke() throws AmbariException {
+
+        Resource resource = new ResourceImpl(Resource.Type.Validation);
+        setResourceProperty(resource, VALIDATION_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());
+
+        List<Map<String, Object>> listItemProps = new ArrayList<Map<String, Object>>();
+
+        Set<ValidationItem> items = response.getItems();
+        for (ValidationItem item : items) {
+          Map<String, Object> mapItemProps = new HashMap<String, Object>();
+          mapItemProps.put(ITEMS_TYPE_PROPERTY_ID, item.getType());
+          mapItemProps.put(ITEMS_LEVE_PROPERTY_ID, item.getLevel());
+          mapItemProps.put(ITEMS_MESSAGE_PROPERTY_ID, item.getMessage());
+
+          if (item.getComponentName() != null) {
+            mapItemProps.put(ITEMS_COMPONENT_NAME_PROPERTY_ID, item.getComponentName());
+            mapItemProps.put(ITEMS_HOST_PROPERTY_ID, item.getHost());
+          }
+          if (item.getConfigType() != null) {
+            mapItemProps.put(ITEMS_CONFIG_TYPE_PROPERTY_ID, item.getConfigType());
+            mapItemProps.put(ITEMS_CONFIG_NAME_PROPERTY_ID, item.getConfigName());
+          }
+          listItemProps.add(mapItemProps);
+        }
+        setResourceProperty(resource, ITEMS_PROPERTY_ID, listItemProps, getPropertyIds());
+
+        return resource;
+      }
+    });
+    notifyCreate(Resource.Type.Validation, request);
+
+    Set<Resource> resources = new HashSet<Resource>(Arrays.asList(validation));
+    return new RequestStatusImpl(null, resources);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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 41b38fa..4c649df 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
@@ -111,6 +111,7 @@ public interface Resource {
     ViewInstance,
     Blueprint,
     Recommendation,
+    Validation,
     HostComponentProcess,
     Permission,
     AlertDefinition,
@@ -190,6 +191,7 @@ public interface Resource {
     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 Validation = InternalType.Validation.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/a7426939/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 b693dec..8f6ddc8 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -133,6 +133,11 @@
     "Stack": "Versions/stack_name",
     "StackVersion": "Versions/stack_version"
   },
+  "Validation": {
+    "Validation": "Validation/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/a7426939/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 1716973..6397ecc 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -380,6 +380,23 @@
         "recommendations/blueprint_cluster_binding/host_groups/name",
         "recommendations/blueprint_cluster_binding/host_groups/hosts"
     ],
+    "Validation":[
+        "Validation/id",
+        "Versions/stack_name",
+        "Versions/stack_version",
+        "items",
+        "items/type",
+        "items/level",
+        "items/message",
+        "items/component-name",
+        "items/host",
+        "items/config-type",
+        "items/config-name",
+        "items/host-group",
+        "hosts",
+        "services",
+        "recommendations"
+    ],
     "HostComponentProcess": [
       "HostComponentProcess/cluster_name",
       "HostComponentProcess/host_name",

http://git-wip-us.apache.org/repos/asf/ambari/blob/a7426939/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
index 01a38cf..7cf2e7b 100644
--- 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
@@ -118,7 +118,34 @@ class HDP206StackAdvisor(StackAdvisor):
 
   def validateComponentLayout(self, services, hosts):
     """Returns array of Validation objects about issues with hostnames components assigned to"""
-    pass
+    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"]]
+    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 len(hostsList) > 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": 'ERROR', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'NAMENODE', "host": host } )
+        items.append( { "type": 'host-component', "level": 'ERROR', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'SECONDARY_NAMENODE', "host": host } )
+
+    return validations
+  pass
 
   def recommendConfigurations(self, services, hosts):
     """Returns Services object with configurations object populated"""