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 <[email protected]> Authored: Thu Aug 7 12:25:23 2014 -0700 Committer: Srimanth Gunturi <[email protected]> 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; + } + } + +}
