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

git commit: AMBARI-6740. Cardinality based recommendation, partial-recommendation and validation of components needed from stack-service endpoint

Repository: ambari
Updated Branches:
  refs/heads/trunk 9355f8f7c -> 9f74a44a1


AMBARI-6740. Cardinality based recommendation, partial-recommendation and validation of components needed from stack-service endpoint


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

Branch: refs/heads/trunk
Commit: 9f74a44a1d27597259912c0c2141e43907536223
Parents: 9355f8f
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Thu Aug 7 12:25:23 2014 -0700
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Thu Aug 7 12:25:31 2014 -0700

----------------------------------------------------------------------
 .../stackadvisor/StackAdvisorHelper.java        |  53 ++++-
 .../stackadvisor/StackAdvisorRequest.java       |  38 ++++
 .../stackadvisor/StackAdvisorRunner.java        |   2 +-
 ...GetComponentLayoutRecommnedationCommand.java |   9 +-
 .../GetComponentLayoutValidationCommand.java    |  60 +-----
 .../commands/StackAdvisorCommand.java           |  57 +++++-
 .../RecommendationResourceProvider.java         |   8 +-
 .../internal/StackAdvisorResourceProvider.java  |  12 +-
 .../internal/ValidationResourceProvider.java    |  10 +-
 .../src/main/resources/properties.json          |   2 +
 .../stacks/HDP/2.0.6/services/stack_advisor.py  | 112 +++++++---
 .../stackadvisor/StackAdvisorExceptionTest.java |  46 +++++
 .../stackadvisor/StackAdvisorHelperTest.java    | 152 ++++++++++++++
 .../StackAdvisorRequestTypeTest.java            |  56 +++++
 .../stackadvisor/StackAdvisorRunnerTest.java    | 116 +++++++++++
 .../commands/StackAdvisorCommandTest.java       | 203 +++++++++++++++++++
 16 files changed, 828 insertions(+), 108 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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 b390f2f..213b0f0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
@@ -21,8 +21,10 @@ package org.apache.ambari.server.api.services.stackadvisor;
 import java.io.File;
 import java.io.IOException;
 
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
 import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutRecommnedationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutValidationCommand;
+import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommand;
 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;
@@ -48,36 +50,67 @@ public class StackAdvisorHelper {
   }
 
   /**
-   * Return component-layout validation result.
+   * Returns validation (component-layout or configurations) result for the
+   * request.
    * 
    * @param validationRequest the validation request
    * @return {@link ValidationResponse} instance
    * @throws StackAdvisorException in case of stack advisor script errors
    */
-  public synchronized ValidationResponse getComponentLayoutValidation(StackAdvisorRequest request)
+  public synchronized ValidationResponse validate(StackAdvisorRequest request)
       throws StackAdvisorException {
     requestId += 1;
 
-    GetComponentLayoutValidationCommand command = new GetComponentLayoutValidationCommand(
-        recommendationsDir, stackAdvisorScript, requestId, saRunner);
+    StackAdvisorCommand<ValidationResponse> command = createValidationCommand(request
+        .getRequestType());
+
     return command.invoke(request);
   }
 
+  StackAdvisorCommand<ValidationResponse> createValidationCommand(
+      StackAdvisorRequestType requestType) throws StackAdvisorException {
+    StackAdvisorCommand<ValidationResponse> command;
+    if (requestType == StackAdvisorRequestType.HOST_GROUPS) {
+      command = new GetComponentLayoutValidationCommand(recommendationsDir, stackAdvisorScript,
+          requestId, saRunner);
+    } else {
+      throw new StackAdvisorException(String.format("Unsupported request type, type=%s",
+          requestType));
+    }
+
+    return command;
+  }
+
   /**
-   * Return component-layout recommendation based on hosts and services
-   * information.
+   * Returns recommendation (component-layout or configurations) based on the
+   * request.
    * 
    * @param request the recommendation request
    * @return {@link RecommendationResponse} instance
    * @throws StackAdvisorException in case of stack advisor script errors
    */
-  public synchronized RecommendationResponse getComponentLayoutRecommnedation(
-      StackAdvisorRequest request) throws StackAdvisorException {
+  public synchronized RecommendationResponse recommend(StackAdvisorRequest request)
+      throws StackAdvisorException {
     requestId += 1;
 
-    GetComponentLayoutRecommnedationCommand command = new GetComponentLayoutRecommnedationCommand(
-        recommendationsDir, stackAdvisorScript, requestId, saRunner);
+    StackAdvisorCommand<RecommendationResponse> command = createRecommendationCommand(request
+        .getRequestType());
+
     return command.invoke(request);
   }
 
+  StackAdvisorCommand<RecommendationResponse> createRecommendationCommand(
+      StackAdvisorRequestType requestType) throws StackAdvisorException {
+    StackAdvisorCommand<RecommendationResponse> command;
+    if (requestType == StackAdvisorRequestType.HOST_GROUPS) {
+      command = new GetComponentLayoutRecommnedationCommand(recommendationsDir, stackAdvisorScript,
+          requestId, saRunner);
+    } else {
+      throw new StackAdvisorException(String.format("Unsupported request type, type=%s",
+          requestType));
+    }
+
+    return command;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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
index a6896fb..b82047e 100644
--- 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
@@ -19,6 +19,7 @@
 package org.apache.ambari.server.api.services.stackadvisor;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -33,6 +34,7 @@ public class StackAdvisorRequest {
 
   private String stackName;
   private String stackVersion;
+  private StackAdvisorRequestType requestType;
   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>>();
@@ -45,6 +47,10 @@ public class StackAdvisorRequest {
     return stackVersion;
   }
 
+  public StackAdvisorRequestType getRequestType() {
+    return requestType;
+  }
+
   public List<String> getHosts() {
     return hosts;
   }
@@ -81,6 +87,11 @@ public class StackAdvisorRequest {
       return new StackAdvisorRequestBuilder(stackName, stackVersion);
     }
 
+    public StackAdvisorRequestBuilder ofType(StackAdvisorRequestType requestType) {
+      this.instance.requestType = requestType;
+      return this;
+    }
+
     public StackAdvisorRequestBuilder forHosts(List<String> hosts) {
       this.instance.hosts = hosts;
       return this;
@@ -102,4 +113,31 @@ public class StackAdvisorRequest {
     }
   }
 
+  public enum StackAdvisorRequestType {
+    HOST_GROUPS("host_groups"), CONFIGURATIONS("configurations");
+
+    private String type;
+
+    private StackAdvisorRequestType(String type) {
+      this.type = type;
+    }
+
+    @Override
+    public String toString() {
+      return type;
+    }
+
+    public static StackAdvisorRequestType fromString(String text) throws StackAdvisorException {
+      if (text != null) {
+        for (StackAdvisorRequestType next : StackAdvisorRequestType.values()) {
+          if (text.equalsIgnoreCase(next.type)) {
+            return next;
+          }
+        }
+      }
+      throw new StackAdvisorException(String.format(
+          "Unknown request type: %s, possible values: %s", text,
+          Arrays.toString(StackAdvisorRequestType.values())));
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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 1cc666b..c57f54e 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
@@ -94,7 +94,7 @@ public class StackAdvisorRunner {
    * @param errorFile
    * @return
    */
-  private ProcessBuilder prepareShellCommand(String script,
+  ProcessBuilder prepareShellCommand(String script,
       StackAdvisorCommandType saCommandType,
       File actionDirectory, String outputFile, String errorFile) {
     String hostsFile = actionDirectory + File.separator + "hosts.json";

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
index c6761b9..b3b5057 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutRecommnedationCommand.java
@@ -44,18 +44,13 @@ public class GetComponentLayoutRecommnedationCommand extends
 
   @Override
   protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
-    if (request.getHosts().isEmpty() || request.getServices().isEmpty()) {
+    if (request.getHosts() == null || request.getHosts().isEmpty() || request.getServices() == null
+        || 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/9f74a44a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
index ebe1333..ac24d07 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/GetComponentLayoutValidationCommand.java
@@ -19,21 +19,11 @@
 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.
@@ -52,59 +42,13 @@ public class GetComponentLayoutValidationCommand extends StackAdvisorCommand<Val
 
   @Override
   protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
-    if (request.getHosts().isEmpty() || request.getServices().isEmpty()
+    if (request.getHosts() == null || request.getHosts().isEmpty() || request.getServices() == null
+        || request.getServices().isEmpty() || request.getComponentHostsMap() == null
         || 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/9f74a44a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
index f32e205..a9ff24b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
@@ -28,7 +28,9 @@ import java.util.HashMap;
 import java.util.Iterator;
 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;
 
@@ -48,6 +50,8 @@ import org.apache.commons.logging.LogFactory;
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.node.ArrayNode;
+import org.codehaus.jackson.node.ObjectNode;
 
 /**
  * Parent for all commands.
@@ -69,6 +73,11 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
       + ",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 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";
 
   private File recommendationsDir;
   private String stackAdvisorScript;
@@ -99,7 +108,7 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
   /**
    * Simple holder for 'hosts.json' and 'services.json' data.
    */
-  protected class StackAdvisorData {
+  public static class StackAdvisorData {
     protected String hostsJSON;
     protected String servicesJSON;
 
@@ -119,7 +128,47 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
 
   protected abstract void validate(StackAdvisorRequest request) throws StackAdvisorException;
 
-  protected abstract StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request);
+  protected StackAdvisorData adjust(StackAdvisorData data, StackAdvisorRequest request) {
+    try {
+      ObjectNode root = (ObjectNode) this.mapper.readTree(data.servicesJSON);
+
+      populateComponentHostsMap(root, request.getComponentHostsMap());
+
+      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;
+  }
+
+  private void populateComponentHostsMap(ObjectNode root, Map<String, Set<String>> componentHostsMap) {
+    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);
+          }
+        }
+      }
+    }
+  }
 
   public synchronized T invoke(StackAdvisorRequest request) throws StackAdvisorException {
     validate(request);
@@ -172,7 +221,7 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
     }
   }
 
-  private String getHostsInformation(StackAdvisorRequest request) throws StackAdvisorException {
+  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,
@@ -223,7 +272,7 @@ public abstract class StackAdvisorCommand<T> extends BaseService {
     }
   }
 
-  private String getServicesInformation(StackAdvisorRequest request) throws StackAdvisorException {
+  String getServicesInformation(StackAdvisorRequest request) throws StackAdvisorException {
     String stackName = request.getStackName();
     String stackVersion = request.getStackVersion();
     String servicesURI = String.format(GET_SERVICES_INFO_URI, stackName, stackVersion,

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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 6b4b1a6..3e5aef4 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
@@ -54,6 +54,7 @@ public class RecommendationResourceProvider extends StackAdvisorResourceProvider
 
   protected static final String HOSTS_PROPERTY_ID = "hosts";
   protected static final String SERVICES_PROPERTY_ID = "services";
+  protected static final String RECOMMEND_PROPERTY_ID = "recommend";
 
   protected static final String BLUEPRINT_CONFIGURATIONS_PROPERTY_ID = PropertyHelper
       .getPropertyId("recommendations/blueprint", "configurations");
@@ -77,13 +78,18 @@ public class RecommendationResourceProvider extends StackAdvisorResourceProvider
   }
 
   @Override
+  protected String getRequestTypePropertyId() {
+    return RECOMMEND_PROPERTY_ID;
+  }
+
+  @Override
   public RequestStatus createResources(final Request request) throws SystemException,
       UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
     StackAdvisorRequest recommendationRequest = prepareStackAdvisorRequest(request);
 
     final RecommendationResponse response;
     try {
-      response = saHelper.getComponentLayoutRecommnedation(recommendationRequest);
+      response = saHelper.recommend(recommendationRequest);
     } catch (StackAdvisorException e) {
       LOG.warn("Error occured during component-layout recommnedation", e);
       throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(e.getMessage())

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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
index 3d0d907..9cf387a 100644
--- 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
@@ -31,6 +31,7 @@ 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.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.Resource.Type;
@@ -74,11 +75,15 @@ public abstract class StackAdvisorResourceProvider extends ReadOnlyResourceProvi
     super(propertyIds, keyPropertyIds, managementController);
   }
 
+  protected abstract String getRequestTypePropertyId();
+
   @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);
+      StackAdvisorRequestType requestType = StackAdvisorRequestType
+          .fromString((String) getRequestProperty(request, getRequestTypePropertyId()));
 
       /*
        * ClassCastException will occur if hosts or services are empty in the
@@ -91,13 +96,14 @@ public abstract class StackAdvisorResourceProvider extends ReadOnlyResourceProvi
       Map<String, Set<String>> componentHostsMap = calculateComponentHostsMap(request);
 
       StackAdvisorRequest saRequest = StackAdvisorRequestBuilder.forStack(stackName, stackVersion)
-          .forHosts(hosts).forServices(services).withComponentHostsMap(componentHostsMap).build();
+          .ofType(requestType).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();
+      Response response = Response.status(Status.BAD_REQUEST)
+          .entity(String.format("Request body is not correct, error: %s", e.getMessage())).build();
       // TODO: Hosts and services must not be empty
       throw new WebApplicationException(response);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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
index 2ec4085..bab1473 100644
--- 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
@@ -50,6 +50,7 @@ public class ValidationResourceProvider extends StackAdvisorResourceProvider {
 
   protected static final String VALIDATION_ID_PROPERTY_ID = PropertyHelper.getPropertyId(
       "Validations", "id");
+  protected static final String VALIDATE_PROPERTY_ID = "validate";
 
   protected static final String ITEMS_PROPERTY_ID = "items";
   protected static final String ITEMS_TYPE_PROPERTY_ID = "type";
@@ -69,13 +70,18 @@ public class ValidationResourceProvider extends StackAdvisorResourceProvider {
   }
 
   @Override
+  protected String getRequestTypePropertyId() {
+    return VALIDATE_PROPERTY_ID;
+  }
+
+  @Override
   public RequestStatus createResources(final Request request) throws SystemException,
       UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
     StackAdvisorRequest validationRequest = prepareStackAdvisorRequest(request);
 
     final ValidationResponse response;
     try {
-      response = saHelper.getComponentLayoutValidation(validationRequest);
+      response = saHelper.validate(validationRequest);
     } catch (StackAdvisorException e) {
       LOG.warn("Error occured during component-layout validation", e);
       throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(e.getMessage())
@@ -102,6 +108,8 @@ public class ValidationResourceProvider extends StackAdvisorResourceProvider {
 
           if (item.getComponentName() != null) {
             mapItemProps.put(ITEMS_COMPONENT_NAME_PROPERTY_ID, item.getComponentName());
+          }
+          if (item.getHost() != null) {
             mapItemProps.put(ITEMS_HOST_PROPERTY_ID, item.getHost());
           }
           if (item.getConfigType() != null) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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 7b6150c..b1ef323 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -371,6 +371,7 @@
         "Recommendation/id",
         "Versions/stack_name",
         "Versions/stack_version",
+        "recommend",
         "hosts",
         "services",
         "recommendations",
@@ -397,6 +398,7 @@
         "items/config-type",
         "items/config-name",
         "items/host-group",
+        "validate",
         "hosts",
         "services",
         "recommendations"

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/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 f7470ea..85c3a8e 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
@@ -18,13 +18,17 @@ limitations under the License.
 """
 
 import socket
+import sys
 
 from stack_advisor import StackAdvisor
 
 class HDP206StackAdvisor(StackAdvisor):
 
   def recommendComponentLayout(self, services, hosts):
-    """Returns Services object with hostnames array populated for components"""
+    """
+    Returns Services object with hostnames array populated for components
+    If hostnames are populated for some components (partial blueprint) - these components will not be processed
+    """
     stackName = services["Versions"]["stack_name"]
     stackVersion = services["Versions"]["stack_version"]
     hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
@@ -54,26 +58,31 @@ class HDP206StackAdvisor(StackAdvisor):
     }
 
     hostsComponentsMap = {}
+
+    #extend 'hostsComponentsMap' with MASTER components
     for service in services["services"]:
       masterComponents = [component for component in service["components"] if isMaster(component)]
       for component in masterComponents:
         componentName = component["StackServiceComponents"]["component_name"]
         hostsForComponent = []
 
-        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]
+        if isAlreadyPopulated(component):
+          hostsForComponent = component["StackServiceComponents"]["hostnames"]
+        else:
+          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)]
-        else:
-          hostsForComponent = [getHostForComponent(component, availableHosts)]
 
         #extend 'hostsComponentsMap' with 'hostsForComponent'
         for hostName in hostsForComponent:
@@ -82,7 +91,10 @@ class HDP206StackAdvisor(StackAdvisor):
           hostsComponentsMap[hostName].append( { "name":componentName } )
 
     #extend 'hostsComponentsMap' with Slave and Client Components
-    utilizedHosts = hostsComponentsMap.keys()
+    componentsListList = [service["components"] for service in services["services"]]
+    componentsList = [item for sublist in componentsListList for item in sublist]
+    usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not isNotValuable(component)]
+    utilizedHosts = [item for sublist in usedHostsListList for item in sublist]
     freeHosts = [hostName for hostName in hostsList if hostName not in utilizedHosts]
 
     for service in services["services"]:
@@ -90,12 +102,18 @@ class HDP206StackAdvisor(StackAdvisor):
       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]
+
+        if isAlreadyPopulated(component):
+          hostsForComponent = component["StackServiceComponents"]["hostnames"]
+        elif component["StackServiceComponents"]["cardinality"] == "ALL":
+          hostsForComponent = hostsList
+        else:
+          if len(freeHosts) == 0:
+            hostsForComponent = hostsList[-1:]
+          else: # len(freeHosts) >= 1
+            hostsForComponent = freeHosts
+            if isClient(component):
+              hostsForComponent = freeHosts[0:1]
 
         #extend 'hostsComponentsMap' with 'hostsForComponent'
         for hostName in hostsForComponent:
@@ -129,6 +147,7 @@ class HDP206StackAdvisor(StackAdvisor):
 
     # Validating NAMENODE and SECONDARY_NAMENODE are on different hosts if possible
     hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    hostsCount = len(hostsList)
     servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
 
     componentsListList = [service["components"] for service in services["services"]]
@@ -136,13 +155,50 @@ class HDP206StackAdvisor(StackAdvisor):
     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:
+    if hostsCount > 1 and len(nameNodeHosts) > 0 and len(secondaryNameNodeHosts) > 0:
       nameNodeHosts = nameNodeHosts[0]
       secondaryNameNodeHosts = secondaryNameNodeHosts[0]
       commonHosts = list(set(nameNodeHosts).intersection(secondaryNameNodeHosts))
       for host in commonHosts:
-        items.append( { "type": 'host-component', "level": '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 } )
+        items.append( { "type": 'host-component', "level": 'ERROR', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'NAMENODE', "host": str(host) } )
+        items.append( { "type": 'host-component', "level": 'ERROR', "message": 'NameNode and Secondary NameNode cannot be hosted on same machine', "component-name": 'SECONDARY_NAMENODE', "host": str(host) } )
+
+    # Validating cardinality
+    for component in componentsList:
+      if component["StackServiceComponents"]["cardinality"] is not None:
+         componentName = component["StackServiceComponents"]["component_name"]
+         componentHostsCount = 0
+         if component["StackServiceComponents"]["hostnames"] is not None:
+           componentHostsCount = len(component["StackServiceComponents"]["hostnames"])
+         cardinality = str(component["StackServiceComponents"]["cardinality"])
+         # cardinality types: null, 1+, 1-2, 1, ALL
+         hostsMax = -sys.maxint - 1
+         hostsMin = sys.maxint
+         hostsMin = 0
+         hostsMax = 0
+         if "+" in cardinality:
+           hostsMin = int(cardinality[:-1])
+           hostsMax = sys.maxint
+         elif "-" in cardinality:
+           nums = cardinality.split("-")
+           hostsMin = int(nums[0])
+           hostsMax = int(nums[1])
+         elif "ALL" == cardinality:
+           hostsMin = hostsCount
+           hostsMax = hostsCount
+         else:
+           hostsMin = int(cardinality)
+           hostsMax = int(cardinality)
+
+         if componentHostsCount > hostsMax or componentHostsCount < hostsMin:
+           items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Cardinality violation, cardinality={0}, hosts count={1}'.format(cardinality, str(componentHostsCount)), "component-name": str(componentName) } )
+
+    # Validating host-usage
+    usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not isNotValuable(component)]
+    usedHostsList = [item for sublist in usedHostsListList for item in sublist]
+    nonUsedHostsList = [item for item in hostsList if item not in usedHostsList]
+    for host in nonUsedHostsList:
+      items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Host is not used', "host": str(host) } )
 
     return validations
   pass
@@ -170,6 +226,16 @@ def getHostForComponent(component, hostsList):
           return hostsList[scheme[key]]
     return hostsList[scheme['else']]
 
+def isNotValuable(component):
+  componentName = component["StackServiceComponents"]["component_name"]
+  service = ['JOURNALNODE', 'ZKFC', 'APP_TIMELINE_SERVER', 'GANGLIA_MONITOR']
+  return componentName in service
+
+def isAlreadyPopulated(component):
+  if component["StackServiceComponents"]["hostnames"] is not None:
+    return len(component["StackServiceComponents"]["hostnames"]) > 0
+  return False
+
 def isClient(component):
   return component["StackServiceComponents"]["component_category"] == 'CLIENT'
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorExceptionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorExceptionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorExceptionTest.java
new file mode 100644
index 0000000..9df5f57
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorExceptionTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * StackAdvisorException unit tests.
+ */
+public class StackAdvisorExceptionTest {
+
+  @Test
+  public void testCreateFromString() {
+    String message = "message";
+    StackAdvisorException e = new StackAdvisorException(message);
+
+    assertEquals(message, e.getMessage());
+  }
+
+  @Test
+  public void testCreateFromException() {
+    String message = "message";
+    Exception e = new Exception("another message");
+    StackAdvisorException sae = new StackAdvisorException(message, e);
+
+    assertEquals(message, sae.getMessage());
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
new file mode 100644
index 0000000..6f8c42b
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
@@ -0,0 +1,152 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestBuilder;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
+import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutRecommnedationCommand;
+import org.apache.ambari.server.api.services.stackadvisor.commands.GetComponentLayoutValidationCommand;
+import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommand;
+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.junit.Test;
+
+/**
+ * StackAdvisorHelper unit tests.
+ */
+public class StackAdvisorHelperTest {
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testValidate_returnsCommandResult() throws StackAdvisorException, IOException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+
+    StackAdvisorCommand<ValidationResponse> command = mock(StackAdvisorCommand.class);
+    ValidationResponse expected = mock(ValidationResponse.class);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .ofType(requestType).build();
+
+    when(command.invoke(request)).thenReturn(expected);
+    doReturn(command).when(helper).createValidationCommand(requestType);
+    ValidationResponse response = helper.validate(request);
+
+    assertEquals(expected, response);
+  }
+
+  @Test(expected = StackAdvisorException.class)
+  @SuppressWarnings("unchecked")
+  public void testValidate_commandThrowsException_throwsException() throws StackAdvisorException,
+      IOException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+
+    StackAdvisorCommand<ValidationResponse> command = mock(StackAdvisorCommand.class);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .ofType(requestType).build();
+
+    when(command.invoke(request)).thenThrow(new StackAdvisorException("message"));
+    doReturn(command).when(helper).createValidationCommand(requestType);
+    helper.validate(request);
+
+    assertTrue(false);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testRecommend_returnsCommandResult() throws StackAdvisorException, IOException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+
+    StackAdvisorCommand<RecommendationResponse> command = mock(StackAdvisorCommand.class);
+    RecommendationResponse expected = mock(RecommendationResponse.class);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .ofType(requestType).build();
+
+    when(command.invoke(request)).thenReturn(expected);
+    doReturn(command).when(helper).createRecommendationCommand(requestType);
+    RecommendationResponse response = helper.recommend(request);
+
+    assertEquals(expected, response);
+  }
+
+  @Test(expected = StackAdvisorException.class)
+  @SuppressWarnings("unchecked")
+  public void testRecommend_commandThrowsException_throwsException() throws StackAdvisorException,
+      IOException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = spy(new StackAdvisorHelper(configuration, saRunner));
+
+    StackAdvisorCommand<RecommendationResponse> command = mock(StackAdvisorCommand.class);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .ofType(requestType).build();
+
+    when(command.invoke(request)).thenThrow(new StackAdvisorException("message"));
+    doReturn(command).when(helper).createRecommendationCommand(requestType);
+    helper.recommend(request);
+
+    assertTrue(false);
+  }
+
+  @Test
+  public void testCreateRecommendationCommand_returnsGetComponentLayoutRecommnedationCommand()
+      throws IOException, StackAdvisorException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+
+    StackAdvisorCommand<RecommendationResponse> command = helper
+        .createRecommendationCommand(requestType);
+
+    assertEquals(GetComponentLayoutRecommnedationCommand.class, command.getClass());
+  }
+
+  @Test
+  public void testCreateValidationCommand_returnsGetComponentLayoutValidationCommand()
+      throws IOException, StackAdvisorException {
+    Configuration configuration = mock(Configuration.class);
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorHelper helper = new StackAdvisorHelper(configuration, saRunner);
+    StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
+
+    StackAdvisorCommand<ValidationResponse> command = helper.createValidationCommand(requestType);
+
+    assertEquals(GetComponentLayoutValidationCommand.class, command.getClass());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequestTypeTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequestTypeTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequestTypeTest.java
new file mode 100644
index 0000000..959823e
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequestTypeTest.java
@@ -0,0 +1,56 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
+import org.junit.Test;
+
+/**
+ * StackAdvisorRequestTypeTest unit tests.
+ */
+public class StackAdvisorRequestTypeTest {
+
+  @Test
+  public void testFromString_returnsHostGroupType() throws StackAdvisorException {
+    String text = "host_groups";
+    StackAdvisorRequestType type = StackAdvisorRequestType.fromString(text);
+
+    assertEquals(type, StackAdvisorRequestType.HOST_GROUPS);
+  }
+
+  @Test
+  public void testFromString_returnsConfigurationsType() throws StackAdvisorException {
+    String text = "configurations";
+    StackAdvisorRequestType type = StackAdvisorRequestType.fromString(text);
+
+    assertEquals(type, StackAdvisorRequestType.CONFIGURATIONS);
+  }
+
+  @Test(expected = StackAdvisorException.class)
+  public void testFromString_throwsException() throws StackAdvisorException {
+    String text = "unknown_type";
+    StackAdvisorRequestType.fromString(text);
+
+    assertTrue(false);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunnerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunnerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunnerTest.java
new file mode 100644
index 0000000..6afe5c4
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunnerTest.java
@@ -0,0 +1,116 @@
+/**
+ * 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 static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.powermock.api.easymock.PowerMock.createNiceMock;
+import static org.powermock.api.easymock.PowerMock.replay;
+import static org.powermock.api.support.membermodification.MemberModifier.stub;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommandType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * StackAdvisorRunner unit tests.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(StackAdvisorRunner.class)
+public class StackAdvisorRunnerTest {
+
+  private TemporaryFolder temp = new TemporaryFolder();
+
+  @Before
+  public void setUp() throws IOException {
+    temp.create();
+  }
+
+  @After
+  public void tearDown() throws IOException {
+    temp.delete();
+  }
+
+  @Test
+  public void testRunScript_processStartThrowsException_returnFalse() throws IOException {
+    String script = "echo";
+    StackAdvisorCommandType saCommandType = StackAdvisorCommandType.RECOMMEND_COMPONENT_LAYOUT;
+    File actionDirectory = temp.newFolder("actionDir");
+    ProcessBuilder processBuilder = createNiceMock(ProcessBuilder.class);
+    StackAdvisorRunner saRunner = new StackAdvisorRunner();
+
+    stub(PowerMock.method(StackAdvisorRunner.class, "prepareShellCommand"))
+        .toReturn(processBuilder);
+    expect(processBuilder.start()).andThrow(new IOException());
+    replay(processBuilder);
+    boolean result = saRunner.runScript(script, saCommandType, actionDirectory);
+
+    assertEquals(false, result);
+  }
+
+  @Test
+  public void testRunScript_processExitCodeNonZero_returnFalse() throws IOException,
+      InterruptedException {
+    String script = "echo";
+    StackAdvisorCommandType saCommandType = StackAdvisorCommandType.RECOMMEND_COMPONENT_LAYOUT;
+    File actionDirectory = temp.newFolder("actionDir");
+    ProcessBuilder processBuilder = createNiceMock(ProcessBuilder.class);
+    Process process = createNiceMock(Process.class);
+    StackAdvisorRunner saRunner = new StackAdvisorRunner();
+
+    stub(PowerMock.method(StackAdvisorRunner.class, "prepareShellCommand"))
+        .toReturn(processBuilder);
+    expect(processBuilder.start()).andReturn(process);
+    expect(process.waitFor()).andReturn(1);
+    replay(processBuilder, process);
+    boolean result = saRunner.runScript(script, saCommandType, actionDirectory);
+
+    assertEquals(false, result);
+  }
+
+  @Test
+  public void testRunScript_processExitCodeZero_returnTrue() throws IOException,
+      InterruptedException {
+    String script = "echo";
+    StackAdvisorCommandType saCommandType = StackAdvisorCommandType.RECOMMEND_COMPONENT_LAYOUT;
+    File actionDirectory = temp.newFolder("actionDir");
+    ProcessBuilder processBuilder = createNiceMock(ProcessBuilder.class);
+    Process process = createNiceMock(Process.class);
+    StackAdvisorRunner saRunner = new StackAdvisorRunner();
+
+    stub(PowerMock.method(StackAdvisorRunner.class, "prepareShellCommand"))
+        .toReturn(processBuilder);
+    expect(processBuilder.start()).andReturn(process);
+    expect(process.waitFor()).andReturn(0);
+    replay(processBuilder, process);
+    boolean result = saRunner.runScript(script, saCommandType, actionDirectory);
+
+    assertEquals(true, result);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f74a44a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
new file mode 100644
index 0000000..90dafcb
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandTest.java
@@ -0,0 +1,203 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.ws.rs.WebApplicationException;
+
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestBuilder;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
+import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommand.StackAdvisorData;
+import org.apache.commons.io.FileUtils;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * StackAdvisorCommand unit tests.
+ */
+public class StackAdvisorCommandTest {
+  private TemporaryFolder temp = new TemporaryFolder();
+
+  @Before
+  public void setUp() throws IOException {
+    temp.create();
+  }
+
+  @After
+  public void tearDown() throws IOException {
+    temp.delete();
+  }
+
+  @Test(expected = StackAdvisorException.class)
+  public void testInvoke_invalidRequest_throwsException() throws StackAdvisorException {
+    File recommendationsDir = temp.newFolder("recommendationDir");
+    String stackAdvisorScript = "echo";
+    int requestId = 0;
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
+        stackAdvisorScript, requestId, saRunner));
+
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .build();
+
+    doThrow(new StackAdvisorException("message")).when(command).validate(request);
+    command.invoke(request);
+
+    assertTrue(false);
+  }
+
+  @Test(expected = StackAdvisorException.class)
+  public void testInvoke_saRunnerNotSucceed_throwsException() throws StackAdvisorException {
+    File recommendationsDir = temp.newFolder("recommendationDir");
+    String stackAdvisorScript = "echo";
+    int requestId = 0;
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
+        stackAdvisorScript, requestId, saRunner));
+
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .build();
+
+    String hostsJSON = "{\"hosts\" : \"localhost\"";
+    String servicesJSON = "{\"services\" : \"HDFS\"";
+    StackAdvisorData data = new StackAdvisorData(hostsJSON, servicesJSON);
+    doReturn(hostsJSON).when(command).getHostsInformation(request);
+    doReturn(servicesJSON).when(command).getServicesInformation(request);
+    doReturn(data).when(command)
+        .adjust(any(StackAdvisorData.class), any(StackAdvisorRequest.class));
+    when(saRunner.runScript(any(String.class), any(StackAdvisorCommandType.class), any(File.class)))
+        .thenReturn(false);
+    command.invoke(request);
+
+    assertTrue(false);
+  }
+
+  @Test(expected = WebApplicationException.class)
+  public void testInvoke_adjustThrowsException_throwsException() throws StackAdvisorException {
+    File recommendationsDir = temp.newFolder("recommendationDir");
+    String stackAdvisorScript = "echo";
+    int requestId = 0;
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(recommendationsDir,
+        stackAdvisorScript, requestId, saRunner));
+
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .build();
+
+    doReturn("{\"hosts\" : \"localhost\"").when(command).getHostsInformation(request);
+    doReturn("{\"services\" : \"HDFS\"").when(command).getServicesInformation(request);
+    doThrow(new WebApplicationException()).when(command).adjust(any(StackAdvisorData.class),
+        any(StackAdvisorRequest.class));
+    when(saRunner.runScript(any(String.class), any(StackAdvisorCommandType.class), any(File.class)))
+        .thenReturn(false);
+    command.invoke(request);
+
+    assertTrue(false);
+  }
+
+  @Test
+  public void testInvoke_success() throws StackAdvisorException {
+    String expected = "success";
+    final String testResourceString = String.format("{\"type\": \"%s\"}", expected);
+    final File recommendationsDir = temp.newFolder("recommendationDir");
+    String stackAdvisorScript = "echo";
+    final int requestId = 0;
+    StackAdvisorRunner saRunner = mock(StackAdvisorRunner.class);
+    final StackAdvisorCommand<TestResource> command = spy(new TestStackAdvisorCommand(
+        recommendationsDir, stackAdvisorScript, requestId, saRunner));
+
+    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
+        .build();
+
+    String hostsJSON = "{\"hosts\" : \"localhost\"";
+    String servicesJSON = "{\"services\" : \"HDFS\"";
+    StackAdvisorData data = new StackAdvisorData(hostsJSON, servicesJSON);
+    doReturn(hostsJSON).when(command).getHostsInformation(request);
+    doReturn(servicesJSON).when(command).getServicesInformation(request);
+    doReturn(data).when(command)
+        .adjust(any(StackAdvisorData.class), any(StackAdvisorRequest.class));
+    when(saRunner.runScript(any(String.class), any(StackAdvisorCommandType.class), any(File.class)))
+        .thenAnswer(new Answer<Boolean>() {
+          public Boolean answer(InvocationOnMock invocation) throws Throwable {
+            String resultFilePath = String.format("%s/%s", requestId, command.getResultFileName());
+            File resultFile = new File(recommendationsDir, resultFilePath);
+            resultFile.getParentFile().mkdirs();
+            FileUtils.writeStringToFile(resultFile, testResourceString);
+            return true;
+          }
+        });
+    TestResource result = command.invoke(request);
+
+    assertEquals(expected, result.getType());
+  }
+
+  class TestStackAdvisorCommand extends StackAdvisorCommand<TestResource> {
+    public TestStackAdvisorCommand(File recommendationsDir, String stackAdvisorScript,
+        int requestId, StackAdvisorRunner saRunner) {
+      super(recommendationsDir, stackAdvisorScript, requestId, saRunner);
+    }
+
+    @Override
+    protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
+      // do nothing
+    }
+
+    @Override
+    protected String getResultFileName() {
+      return "result.json";
+    }
+
+    @Override
+    protected StackAdvisorCommandType getCommandType() {
+      return StackAdvisorCommandType.RECOMMEND_COMPONENT_LAYOUT;
+    }
+  }
+
+  public static class TestResource {
+    @JsonProperty
+    private String type;
+
+    public String getType() {
+      return type;
+    }
+
+    public void setType(String type) {
+      this.type = type;
+    }
+  }
+
+}