Browse Source

AMBARI-6124. Ambari Shell Improvements. (Janos Matyas and Krisztian Horvath via yusaku)

Yusaku Sako 11 năm trước cách đây
mục cha
commit
269451069f
56 tập tin đã thay đổi với 3308 bổ sung569 xóa
  1. 2 8
      ambari-client/groovy-client/pom.xml
  2. 268 45
      ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy
  3. 175 0
      ambari-client/groovy-client/src/main/resources/blueprints/lambda-architecture
  4. 56 46
      ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn
  5. 5 2
      ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn
  6. 94 0
      ambari-client/groovy-client/src/main/resources/blueprints/warmup
  7. 0 425
      ambari-client/groovy-client/src/test/groovy/com.sequenceiq.ambari.client/AmbariClientTest.groovy
  8. 39 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AbstractAmbariClientTest.groovy
  9. 191 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariBlueprintsTest.groovy
  10. 65 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariClustersTest.groovy
  11. 80 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariHostsTest.groovy
  12. 83 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariServiceConfigurationTest.groovy
  13. 147 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariServicesTest.groovy
  14. 93 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariTasksTest.groovy
  15. 43 0
      ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/TestResources.groovy
  16. 194 0
      ambari-client/groovy-client/src/test/resources/clusterAll.json
  17. 12 0
      ambari-client/groovy-client/src/test/resources/clusters.json
  18. 590 0
      ambari-client/groovy-client/src/test/resources/host-components.json
  19. 91 0
      ambari-client/groovy-client/src/test/resources/hosts.json
  20. 4 0
      ambari-client/groovy-client/src/test/resources/no-blueprint.json
  21. 4 0
      ambari-client/groovy-client/src/test/resources/no-clusters.json
  22. 4 0
      ambari-client/groovy-client/src/test/resources/no-hosts.json
  23. 10 0
      ambari-client/groovy-client/src/test/resources/no-request-tasks.json
  24. 6 0
      ambari-client/groovy-client/src/test/resources/no-service-components-hdfs.json
  25. 5 0
      ambari-client/groovy-client/src/test/resources/no-services.json
  26. 24 0
      ambari-client/groovy-client/src/test/resources/request-tasks.json
  27. 63 0
      ambari-client/groovy-client/src/test/resources/service-components-hdfs.json
  28. 56 0
      ambari-client/groovy-client/src/test/resources/service-config.json
  29. 37 0
      ambari-client/groovy-client/src/test/resources/service-versions-multiple.json
  30. 93 0
      ambari-client/groovy-client/src/test/resources/service-versions.json
  31. 4 3
      ambari-client/groovy-client/src/test/resources/services.json
  32. 12 0
      ambari-client/groovy-client/src/test/resources/versions/clusters.json
  33. 56 0
      ambari-client/groovy-client/src/test/resources/versions/service-config.json
  34. 37 0
      ambari-client/groovy-client/src/test/resources/versions/service-versions.json
  35. 5 0
      ambari-project/pom.xml
  36. 4 0
      ambari-shell/pom.xml
  37. 1 0
      ambari-shell/src/main/java/org/apache/ambari/shell/AmbariShell.java
  38. 38 4
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java
  39. 18 4
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java
  40. 45 11
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java
  41. 7 5
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java
  42. 34 0
      ambari-shell/src/main/java/org/apache/ambari/shell/completion/Blueprint.java
  43. 34 0
      ambari-shell/src/main/java/org/apache/ambari/shell/completion/Host.java
  44. 17 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ConverterConfiguration.java
  45. 12 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ShellConfiguration.java
  46. 58 0
      ambari-shell/src/main/java/org/apache/ambari/shell/converter/BlueprintConverter.java
  47. 58 0
      ambari-shell/src/main/java/org/apache/ambari/shell/converter/HostConverter.java
  48. 40 0
      ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariHistory.java
  49. 66 0
      ambari-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.java
  50. 47 0
      ambari-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.java
  51. 39 0
      ambari-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java
  52. 79 0
      ambari-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java
  53. 14 1
      ambari-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java
  54. 45 12
      ambari-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java
  55. 3 2
      ambari-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.java
  56. 1 1
      ambari-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.java

+ 2 - 8
ambari-client/groovy-client/pom.xml

@@ -83,14 +83,8 @@
         <artifactId>apache-rat-plugin</artifactId>
         <configuration>
           <excludes>
-            <exclude>src/main/resources/blueprints/multi-node-hdfs-yarn</exclude>
-            <exclude>src/main/resources/blueprints/single-node-hdfs-yarn</exclude>
-            <exclude>src/test/resources/blueprint.json</exclude>
-            <exclude>src/test/resources/blueprints.json</exclude>
-            <exclude>src/test/resources/cluster.json</exclude>
-            <exclude>src/test/resources/hdfsServiceComponents.json</exclude>
-            <exclude>src/test/resources/services.json</exclude>
-            <exclude>src/test/resources/tasks.json</exclude>
+            <exclude>src/main/resources/blueprints/**</exclude>
+            <exclude>src/test/resources/**</exclude>
           </excludes>
         </configuration>
         <executions>

+ 268 - 45
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy

@@ -19,19 +19,22 @@ package org.apache.ambari.groovy.client
 
 import groovy.json.JsonBuilder
 import groovy.json.JsonSlurper
+import groovy.util.logging.Slf4j
+import groovyx.net.http.ContentType
 import groovyx.net.http.HttpResponseException
 import groovyx.net.http.RESTClient
 import org.apache.http.NoHttpResponseException
 import org.apache.http.client.ClientProtocolException
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
+import java.net.ConnectException
+import java.net.NoRouteToHostException
+import java.net.UnknownHostException
 
 /**
  * Basic client to send requests to the Ambari server.
  */
+@Slf4j
 class AmbariClient {
 
-  private static final Logger LOGGER = LoggerFactory.getLogger(AmbariClient.class)
   private static final int PAD = 30
   private static final int OK_RESPONSE = 200
   boolean debugEnabled = false;
@@ -94,12 +97,14 @@ class AmbariClient {
    * @param id id of the blueprint
    * @return true if exists false otherwise
    */
-  def boolean doesBlueprintExists(String id) {
+  def boolean doesBlueprintExist(String id) {
     def result = false
     try {
-      result = ambari.get(path: "blueprints/$id", query: ['fields': "Blueprints"]).status == OK_RESPONSE
+      def Map resourceRequest = getResourceRequestMap("blueprints/$id", ['fields': "Blueprints"])
+      def jsonResponse = getSlurpedResource(resourceRequest)
+      result = !(jsonResponse.status)
     } catch (e) {
-      LOGGER.info("Blueprint does not exist", e)
+      log.info("Blueprint does not exist", e)
     }
     return result
   }
@@ -139,8 +144,8 @@ class AmbariClient {
    * @return Map where the key is the host group and the value is the list of components
    */
   def Map<String, List<String>> getBlueprintMap(String id) {
-    def result = getBlueprint(id).host_groups?.collectEntries { [(it.name): it.components.collect { it.name }] }
-    result ?: new HashMap()
+    def result = getBlueprint(id)?.host_groups?.collectEntries { [(it.name): it.components.collect { it.name }] }
+    result ?: [:]
   }
 
   /**
@@ -149,7 +154,9 @@ class AmbariClient {
    * @return formatted blueprint list
    */
   def String showBlueprints() {
-    getBlueprints().items.collect { "${it.Blueprints.blueprint_name.padRight(PAD)} [${it.Blueprints.stack_name}:${it.Blueprints.stack_version}]" }.join("\n")
+    getBlueprints().items.collect {
+      "${it.Blueprints.blueprint_name.padRight(PAD)} [${it.Blueprints.stack_name}:${it.Blueprints.stack_version}]"
+    }.join("\n")
   }
 
   /**
@@ -158,10 +165,45 @@ class AmbariClient {
    * @return Map where the key is the blueprint's name value is the used stack
    */
   def Map<String, String> getBlueprintsMap() {
-    def result = getBlueprints().items?.collectEntries { [(it.Blueprints.blueprint_name): it.Blueprints.stack_name + ":" + it.Blueprints.stack_version] }
+    def result = getBlueprints().items?.collectEntries {
+      [(it.Blueprints.blueprint_name): it.Blueprints.stack_name + ":" + it.Blueprints.stack_version]
+    }
     result ?: new HashMap()
   }
 
+  /**
+   * Recommends a host - host group assignment based on the provided blueprint
+   * and the available hosts.
+   *
+   * @param blueprint id of the blueprint
+   * @return recommended assignments
+   */
+  def Map<String, List<String>> recommendAssignments(String blueprint) {
+    def result = [:]
+    def hostNames = getHostNames().keySet() as List
+    def groups = getBlueprint(blueprint)?.host_groups?.collect { ["name": it.name, "cardinality": it.cardinality] }
+    if (hostNames && groups) {
+      def groupSize = groups.size()
+      def hostSize = hostNames.size()
+      if (hostSize == groupSize) {
+        def i = 0
+        result = groups.collectEntries { [(it.name): [hostNames[i++]]] }
+      } else if (groupSize == 2 && hostSize > 2) {
+        def grouped = groups.groupBy { it.cardinality }
+        if (grouped["1"] && grouped["1"].size() == 1) {
+          groups.each {
+            if (it["cardinality"] == "1") {
+              result << [(it["name"]): [hostNames[0]]]
+            } else {
+              result << [(it["name"]): hostNames.subList(1, hostSize)]
+            }
+          }
+        }
+      }
+    }
+    return result
+  }
+
   /**
    * Returns the name of the host groups for a given blueprint.
    *
@@ -195,13 +237,15 @@ class AmbariClient {
   }
 
   /**
-   * Adds 2 default blueprints.
+   * Adds the default blueprints.
    *
    * @throws HttpResponseException in case of error
    */
   def void addDefaultBlueprints() throws HttpResponseException {
     addBlueprint(getResourceContent("blueprints/multi-node-hdfs-yarn"))
     addBlueprint(getResourceContent("blueprints/single-node-hdfs-yarn"))
+    addBlueprint(getResourceContent("blueprints/lambda-architecture"))
+    addBlueprint(getResourceContent("blueprints/warmup"))
   }
 
   /**
@@ -214,6 +258,9 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    */
   def void createCluster(String clusterName, String blueprintName, Map<String, List<String>> hostGroups) throws HttpResponseException {
+    if (debugEnabled) {
+      println "[DEBUG] POST ${ambari.getUri()}clusters/$clusterName"
+    }
     ambari.post(path: "clusters/$clusterName", body: createClusterJson(blueprintName, hostGroups), { it })
   }
 
@@ -224,6 +271,9 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    */
   def void deleteCluster(String clusterName) throws HttpResponseException {
+    if (debugEnabled) {
+      println "[DEBUG] DELETE ${ambari.getUri()}clusters/$clusterName"
+    }
     ambari.delete(path: "clusters/$clusterName")
   }
 
@@ -234,7 +284,9 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    */
   def String getClusterAsJson() throws HttpResponseException {
-    getRequest("clusters/${getClusterName()}")
+    String path = "clusters/" + getClusterName();
+    Map resourceRequestMap = getResourceRequestMap(path, null)
+    return getRawResource(resourceRequestMap)
   }
 
   /**
@@ -244,7 +296,8 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    */
   def getClustersAsJson() throws HttpResponseException {
-    getRequest("clusters")
+    Map resourceRequestMap = getResourceRequestMap("clusters", null)
+    return getRawResource(resourceRequestMap)
   }
 
   /**
@@ -253,7 +306,9 @@ class AmbariClient {
    * @return pre-formatted cluster list
    */
   def String showClusterList() {
-    getClusters().items.collect { "[$it.Clusters.cluster_id] $it.Clusters.cluster_name:$it.Clusters.version" }.join("\n")
+    getClusters().items.collect {
+      "[$it.Clusters.cluster_id] $it.Clusters.cluster_name:$it.Clusters.version"
+    }.join("\n")
   }
 
   /**
@@ -266,6 +321,21 @@ class AmbariClient {
     getAllResources("requests/$request", "tasks/Tasks")
   }
 
+  /**
+   * Returns the install progress state. If the install failed -1 returned.
+   *
+   * @param request request id; default is 1
+   * @return progress in percentage
+   */
+  def BigDecimal getInstallProgress(request = 1) {
+    def response = getAllResources("requests/$request", "Requests")
+    def String status = response.Requests?.request_status
+    if (status && status.equals("FAILED")) {
+      return new BigDecimal(-1)
+    }
+    return response.Requests?.progress_percent
+  }
+
   /**
    * Returns a pre-formatted task list.
    *
@@ -302,7 +372,9 @@ class AmbariClient {
    * @return pre-formatted String
    */
   def String showHostList() {
-    getHosts().items.collect { "$it.Hosts.host_name [$it.Hosts.host_status] $it.Hosts.ip $it.Hosts.os_type:$it.Hosts.os_arch" }.join("\n")
+    getHosts().items.collect {
+      "$it.Hosts.host_name [$it.Hosts.host_status] $it.Hosts.ip $it.Hosts.os_type:$it.Hosts.os_arch"
+    }.join("\n")
   }
 
   /**
@@ -330,12 +402,23 @@ class AmbariClient {
   def Map<String, Map<String, String>> getServiceComponentsMap() {
     def result = getServices().items.collectEntries {
       def name = it.ServiceInfo.service_name
-      def componentList = getServiceComponents(name).items.collectEntries { [(it.ServiceComponentInfo.component_name): it.ServiceComponentInfo.state] }
+      def componentList = getServiceComponents(name).items.collectEntries {
+        [(it.ServiceComponentInfo.component_name): it.ServiceComponentInfo.state]
+      }
       [(name): componentList]
     }
     result ?: new HashMap()
   }
 
+  /**
+   * Performs a health check on the Ambari server.
+   *
+   * @return status
+   */
+  def String healthCheck() {
+    ambari.get(path: "check", headers: ["Accept": ContentType.TEXT]).data.text
+  }
+
   /**
    * Returns a pre-formatted service list.
    *
@@ -362,7 +445,9 @@ class AmbariClient {
    * @return formatted String
    */
   def String showHostComponentList(host) {
-    getHostComponents(host).items.collect { "${it.HostRoles.component_name.padRight(PAD)} [$it.HostRoles.state]" }.join("\n")
+    getHostComponents(host).items.collect {
+      "${it.HostRoles.component_name.padRight(PAD)} [$it.HostRoles.state]"
+    }.join("\n")
   }
 
   /**
@@ -372,7 +457,7 @@ class AmbariClient {
    * @return component name - state association
    */
   def Map<String, String> getHostComponentsMap(host) {
-    def result = getHostComponents(host).items?.collectEntries { [(it.HostRoles.component_name): it.HostRoles.state] }
+    def result = getHostComponents(host)?.items?.collectEntries { [(it.HostRoles.component_name): it.HostRoles.state] }
     result ?: new HashMap()
   }
 
@@ -383,11 +468,162 @@ class AmbariClient {
    * @return json as String, exception if thrown is it fails
    */
   def String getBlueprintAsJson(id) {
-    return getRequest("blueprints/$id", "host_groups,Blueprints")
+    Map resourceRequestMap = getResourceRequestMap("blueprints/$id", ['fields': "host_groups,Blueprints"])
+    return getRawResource(resourceRequestMap)
+  }
+
+/**
+ * Returns a map with service configurations. The keys are the service names, values are maps with <propertyName, propertyValue> entries
+ *
+ * @return a Map with entries of format <servicename, Map<property, value>>
+ */
+  def Map<String, Map<String, String>> getServiceConfigMap() {
+    def Map<String, Integer> serviceToTags = new HashMap<>()
+
+    //get services and last versions configurations
+    Map<String, ?> configsResourceRequestMap = getResourceRequestMap("clusters/${getClusterName()}/configurations", [:])
+    def rawConfigs = getSlurpedResource(configsResourceRequestMap)
+
+    rawConfigs?.items.collect { object ->
+      // selecting the latest versions
+      processServiceVersions(serviceToTags, object.type, object.tag)
+    }
+
+    // collect properties for every service
+    def finalMap = serviceToTags.collectEntries { entry ->
+      // collect config props for every service
+      def propsMap = collectConfigPropertiesForService(entry.getKey(), entry.getValue())
+      // put them in the final map
+      [(entry.key): propsMap]
+    }
+    return finalMap
+  }
+
+  def startAllServices() {
+    log.debug("Starting all services ...")
+    manageAllServices("Start All Services", "STARTED")
+  }
+
+  def stopAllServices() {
+    log.debug("Stopping all services ...")
+    manageAllServices("Stop All Services", "INSTALLED")
+  }
+
+  def boolean servicesStarted() {
+    return servicesStatus(true)
+  }
+
+  def boolean servicesStopped() {
+    return servicesStatus(false)
+  }
+
+  def private boolean servicesStatus(boolean starting) {
+    def String status = (starting) ? "STARTED" : "INSTALLED"
+    Map serviceComponents = getServicesMap();
+    boolean allInState = true;
+    serviceComponents.values().each { val ->
+      log.debug("Service: {}", val)
+      allInState = allInState && val.equals(status)
+    }
+    return allInState;
+  }
+
+  def private manageAllServices(String context, String state) {
+    Map bodyMap = [
+      RequestInfo: [context: context],
+      ServiceInfo: [state: state]
+    ]
+    JsonBuilder builder = new JsonBuilder(bodyMap)
+    def Map<String, ?> putRequestMap = [:]
+    putRequestMap.put('requestContentType', ContentType.URLENC)
+    putRequestMap.put('path', "${ambari.getUri()}" + "clusters/${getClusterName()}/services")
+    putRequestMap.put('query', ['params/run_smoke_test': 'false'])
+    putRequestMap.put('body', builder.toPrettyString());
+
+    ambari.put(putRequestMap)
+  }
+
+  private def processServiceVersions(Map<String, Integer> serviceToVersions, String service, def version) {
+    boolean change = false
+    log.debug("Handling service version <{}:{}>", service, version)
+    if (serviceToVersions.containsKey(service)) {
+      log.debug("Entry already added, checking versions ...")
+      def newVersion = Long.valueOf(version.minus("version")).longValue()
+      def oldVersion = Long.valueOf(serviceToVersions.get(service).minus("version")).longValue()
+      change = oldVersion < newVersion
+    } else {
+      change = true;
+    }
+    if (change) {
+      log.debug("Adding / updating service version <{}:{}>", service, version)
+      serviceToVersions.put(service, version);
+    }
+  }
+
+  private def Map<String, String> collectConfigPropertiesForService(String service, def tag) {
+    Map<String, String> serviceConfigProperties
+
+    def Map resourceRequestMap = getResourceRequestMap("clusters/${getClusterName()}/configurations",
+      ['type': "$service", 'tag': "$tag"])
+    def rawResource = getSlurpedResource(resourceRequestMap);
+
+    if (rawResource) {
+      serviceConfigProperties = rawResource.items?.collectEntries { it -> it.properties }
+    } else {
+      log.debug("No resource object has been returned for the resource request map: {}", resourceRequestMap)
+    }
+    return serviceConfigProperties
+  }
+
+  private Map<String, ?> getResourceRequestMap(String path, Map<String, String> queryParams) {
+    def Map requestMap = [:]
+    if (queryParams) {
+      requestMap = ['path': "${ambari.getUri()}" + path, 'query': queryParams]
+    } else {
+      requestMap = ['path': "${ambari.getUri()}" + path]
+    }
+    return requestMap
+  }
+
+  /**
+   * Gets the resource as a text as it;s returned by the server.
+   *
+   * @param resourceRequestMap
+   */
+  private getRawResource(Map resourceRequestMap) {
+    def rawResource = null;
+    try {
+      if (debugEnabled) {
+        println "[DEBUG] GET ${resourceRequestMap.get('path')}"
+      }
+      rawResource = ambari.get(resourceRequestMap)?.data?.text
+    } catch (e) {
+      def clazz = e.class
+      log.error("Error occurred during GET request to {}, exception: ", resourceRequestMap.get('path'), e)
+      if (clazz == NoHttpResponseException.class || clazz == ConnectException.class
+        || clazz == ClientProtocolException.class || clazz == NoRouteToHostException.class
+        || clazz == UnknownHostException.class || (clazz == HttpResponseException.class && e.message == "Bad credentials")) {
+        throw new AmbariConnectionException("Cannot connect to Ambari ${ambari.getUri()}")
+      }
+    }
+    return rawResource
+  }
+
+  /**
+   * Slurps the response text.
+   *
+   * @param resourceRequestMap a map wrapping the resource request components
+   * @return an Object as it's created by the JsonSlurper
+   */
+  private getSlurpedResource(Map resourceRequestMap) {
+    def rawResource = getRawResource(resourceRequestMap)
+    def slurpedResource = (rawResource) ? slurper.parseText(rawResource) : null
+    return slurpedResource
   }
 
-  private def getAllResources(resourceName, fields) {
-    slurp("clusters/${getClusterName()}/$resourceName", "$fields/*")
+
+  private def getAllResources(resourceName, fields = "") {
+    slurp("clusters/${getClusterName()}/$resourceName", fields ? "$fields/*" : "")
   }
 
   /**
@@ -399,6 +635,9 @@ class AmbariClient {
    * @return response message
    */
   private void postBlueprint(String blueprint) {
+    if (debugEnabled) {
+      println "[DEBUG] POST ${ambari.getUri()}blueprints/bp"
+    }
     ambari.post(path: "blueprints/bp", body: blueprint, { it })
   }
 
@@ -408,25 +647,16 @@ class AmbariClient {
       def hostList = it.value.collect { ['fqdn': it] }
       [name: it.key, hosts: hostList]
     }
-    builder { "blueprint" name; "host-groups" groups }
+    builder { "blueprint" name; "default_password" "admin"; "host_groups" groups }
     builder.toPrettyString()
   }
 
   private def slurp(path, fields = "") {
-    def baseUri = ambari.getUri();
-    if (debugEnabled) {
-      println "[DEBUG] ${baseUri}${path}?fields=$fields"
-    }
-    def result = new HashMap()
-    try {
-      result = slurper.parseText(getRequest(path, fields))
-    } catch (e) {
-      if (e instanceof NoHttpResponseException || e instanceof ConnectException || e instanceof ClientProtocolException ||
-        e instanceof UnknownHostException || (e instanceof HttpResponseException && e.message == "Bad credentials")) {
-        throw new AmbariConnectionException("Cannot connect to Ambari $baseUri")
-      }
-      LOGGER.error("Error occurred during GET request to $baseUri/$path", e)
-    }
+
+    def fieldsMap = fields ? ['fields': fields] : [:]
+    def Map resourceReqMap = getResourceRequestMap(path, fieldsMap)
+    def result = getSlurpedResource(resourceReqMap)
+
     return result
   }
 
@@ -496,15 +726,8 @@ class AmbariClient {
     getAllResources("hosts/$host/host_components", "HostRoles")
   }
 
-  private String getRequest(path, fields = "") {
-    if (fields) {
-      ambari.get(path: "$path", query: ['fields': "$fields"]).data.text
-    } else {
-      ambari.get(path: "$path").data.text
-    }
-  }
-
   private String getResourceContent(name) {
     getClass().getClassLoader().getResourceAsStream(name)?.text
   }
-}
+
+}

+ 175 - 0
ambari-client/groovy-client/src/main/resources/blueprints/lambda-architecture

@@ -0,0 +1,175 @@
+{
+  "configurations": [
+    {
+      "global": {
+        "nagios_contact": ""
+      }
+    }
+  ],
+  "host_groups": [
+    {
+      "name": "host_group_1",
+      "components": [
+        {
+          "name": "ZOOKEEPER_SERVER"
+        },
+        {
+          "name": "ZOOKEEPER_CLIENT"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "HISTORYSERVER"
+        },
+        {
+          "name": "SUPERVISOR"
+        },
+        {
+          "name": "NAGIOS_SERVER"
+        },
+        {
+          "name": "TEZ_CLIENT"
+        },
+        {
+          "name": "AMBARI_SERVER"
+        },
+        {
+          "name": "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "GANGLIA_SERVER"
+        },
+        {
+          "name": "HDFS_CLIENT"
+        },
+        {
+          "name": "NODEMANAGER"
+        },
+        {
+          "name": "YARN_CLIENT"
+        },
+        {
+          "name": "MAPREDUCE2_CLIENT"
+        },
+        {
+          "name": "DATANODE"
+        },
+        {
+          "name": "GANGLIA_MONITOR"
+        },
+        {
+          "name": "RESOURCEMANAGER"
+        }
+      ],
+      "cardinality": "1"
+    },
+    {
+      "name": "host_group_2",
+      "components": [
+        {
+          "name": "ZOOKEEPER_SERVER"
+        },
+        {
+          "name": "ZOOKEEPER_CLIENT"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "STORM_REST_API"
+        },
+        {
+          "name": "STORM_UI_SERVER"
+        },
+        {
+          "name": "SUPERVISOR"
+        },
+        {
+          "name": "SECONDARY_NAMENODE"
+        },
+        {
+          "name": "TEZ_CLIENT"
+        },
+        {
+          "name": "HDFS_CLIENT"
+        },
+        {
+          "name": "NODEMANAGER"
+        },
+        {
+          "name": "YARN_CLIENT"
+        },
+        {
+          "name" : "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "MAPREDUCE2_CLIENT"
+        },
+        {
+          "name": "DATANODE"
+        },
+        {
+          "name": "GANGLIA_MONITOR"
+        },
+        {
+          "name": "DRPC_SERVER"
+        },
+        {
+          "name": "NIMBUS"
+        }
+      ],
+      "cardinality": "1"
+    },
+    {
+      "name": "host_group_3",
+      "components": [
+        {
+          "name": "ZOOKEEPER_SERVER"
+        },
+        {
+          "name": "ZOOKEEPER_CLIENT"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "NAMENODE"
+        },
+        {
+          "name": "SUPERVISOR"
+        },
+        {
+          "name": "TEZ_CLIENT"
+        },
+        {
+          "name": "HDFS_CLIENT"
+        },
+        {
+          "name": "NODEMANAGER"
+        },
+        {
+          "name": "YARN_CLIENT"
+        },
+        {
+          "name": "MAPREDUCE2_CLIENT"
+        },
+        {
+          "name": "DATANODE"
+        },
+        {
+          "name" : "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "GANGLIA_MONITOR"
+        }
+      ],
+      "cardinality": "1"
+    }
+  ],
+  "Blueprints": {
+    "blueprint_name": "lambda-architecture",
+    "stack_name": "HDP",
+    "stack_version": "2.1"
+  }
+}

+ 56 - 46
ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn

@@ -1,57 +1,67 @@
 {
-  "host_groups" : [
+  "configurations": [
     {
-      "name" : "master",
-      "components" : [
-      {
-        "name" : "NAMENODE"
-      },
-      {
-        "name" : "SECONDARY_NAMENODE"
-      },       
-      {
-        "name" : "RESOURCEMANAGER"
-      },
-      {
-        "name" : "HISTORYSERVER"
-      },
-      {
-        "name" : "NAGIOS_SERVER"
-      },
-      {
-        "name" : "ZOOKEEPER_SERVER"
+      "global": {
+        "nagios_contact": ""
       }
+    }
+  ],
+  "host_groups": [
+    {
+      "name": "master",
+      "components": [
+        {
+          "name": "NAMENODE"
+        },
+        {
+          "name": "SECONDARY_NAMENODE"
+        },
+        {
+          "name": "RESOURCEMANAGER"
+        },
+        {
+          "name": "HISTORYSERVER"
+        },
+        {
+          "name": "NAGIOS_SERVER"
+        },
+        {
+          "name": "ZOOKEEPER_SERVER"
+        }
       ],
-      "cardinality" : "1"
+      "cardinality": "1"
     },
     {
-      "name" : "slaves",
-      "components" : [
-      {
-        "name" : "DATANODE"
-      },
-      {
-        "name" : "HDFS_CLIENT"
-      },
-      {
-        "name" : "NODEMANAGER"
-      },
-      {
-        "name" : "YARN_CLIENT"
-      },
-      {
-        "name" : "MAPREDUCE2_CLIENT"
-      },
-      {
-        "name" : "ZOOKEEPER_CLIENT"
-      }
+      "name": "slaves",
+      "components": [
+        {
+          "name": "DATANODE"
+        },
+        {
+          "name": "HDFS_CLIENT"
+        },
+        {
+          "name": "NODEMANAGER"
+        },
+        {
+          "name": "YARN_CLIENT"
+        },
+        {
+          "name" : "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "MAPREDUCE2_CLIENT"
+        },
+        {
+          "name": "ZOOKEEPER_CLIENT"
+        }
       ],
-      "cardinality" : "2"
+      "cardinality": "2"
     }
   ],
-  "Blueprints" : {
-    "blueprint_name" : "multi-node-hdfs-yarn",
-    "stack_name" : "HDP",
-    "stack_version" : "2.0"
+  "Blueprints": {
+    "blueprint_name": "multi-node-hdfs-yarn",
+    "stack_name": "HDP",
+    "stack_version": "2.1"
   }
 }

+ 5 - 2
ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn

@@ -33,6 +33,9 @@
       {
         "name" : "ZOOKEEPER_SERVER"
       },
+      {
+        "name" : "APP_TIMELINE_SERVER"
+      },
       {
         "name" : "ZOOKEEPER_CLIENT"
       }
@@ -43,6 +46,6 @@
   "Blueprints" : {
     "blueprint_name" : "single-node-hdfs-yarn",
     "stack_name" : "HDP",
-    "stack_version" : "2.0"
+    "stack_version" : "2.1"
   }
-}
+}

+ 94 - 0
ambari-client/groovy-client/src/main/resources/blueprints/warmup

@@ -0,0 +1,94 @@
+{
+  "configurations": [
+    {
+      "global": {
+        "nagios_contact": ""
+      }
+    }
+  ],
+  "host_groups": [
+    {
+      "name": "host_group_1",
+      "components": [
+        {
+          "name": "ZOOKEEPER_SERVER"
+        },
+        {
+          "name": "ZOOKEEPER_CLIENT"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "HISTORYSERVER"
+        },
+        {
+          "name": "SUPERVISOR"
+        },
+        {
+          "name": "NAGIOS_SERVER"
+        },
+        {
+          "name": "TEZ_CLIENT"
+        },
+        {
+          "name": "AMBARI_SERVER"
+        },
+        {
+          "name": "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "GANGLIA_SERVER"
+        },
+        {
+          "name": "HDFS_CLIENT"
+        },
+        {
+          "name": "NODEMANAGER"
+        },
+        {
+          "name": "YARN_CLIENT"
+        },
+        {
+          "name" : "APP_TIMELINE_SERVER"
+        },
+        {
+          "name": "MAPREDUCE2_CLIENT"
+        },
+        {
+          "name": "DATANODE"
+        },
+        {
+          "name": "GANGLIA_MONITOR"
+        },
+        {
+          "name": "RESOURCEMANAGER"
+        },
+        {
+          "name": "STORM_REST_API"
+        },
+        {
+          "name": "STORM_UI_SERVER"
+        },
+        {
+          "name": "SECONDARY_NAMENODE"
+        },
+        {
+          "name": "DRPC_SERVER"
+        },
+        {
+          "name": "NIMBUS"
+        },
+        {
+          "name": "NAMENODE"
+        }
+      ],
+      "cardinality": "1"
+    }
+  ],
+  "Blueprints": {
+    "blueprint_name": "warmup",
+    "stack_name": "HDP",
+    "stack_version": "2.1"
+  }
+}

+ 0 - 425
ambari-client/groovy-client/src/test/groovy/com.sequenceiq.ambari.client/AmbariClientTest.groovy

@@ -1,425 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.ambari.groovy.client
-
-import groovy.json.JsonSlurper
-import groovyx.net.http.HttpResponseDecorator
-import groovyx.net.http.HttpResponseException
-import groovyx.net.http.RESTClient
-import spock.lang.Specification
-
-class AmbariClientTest extends Specification {
-
-  def rest = Mock(RESTClient)
-  def slurper = Mock(JsonSlurper)
-  def decorator = Mock(HttpResponseDecorator)
-  def ambari = new AmbariClient(rest, slurper)
-  def realSlurper = new JsonSlurper()
-
-  def "test get the name of the cluster"() {
-    given:
-    def request = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-
-    when:
-    def result = ambari.getClusterName()
-
-    then:
-    "cluster1" == result
-  }
-
-  def "test get the name when there is no cluster"() {
-    given:
-    def request = [path: "clusters", query: [fields: "Clusters"]]
-    def mockResponse = ["items": new ArrayList()]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-
-    when:
-    def result = ambari.getClusterName()
-
-    then:
-    null == result
-  }
-
-  def "test if blueprint does not exists"() {
-    given:
-    decorator.status >> 500
-    def request = [path: "blueprints/bp", query: ['fields': "Blueprints"]]
-    rest.get(request) >> decorator
-
-    when:
-    def result = ambari.doesBlueprintExists("bp")
-
-    then:
-    result == false
-  }
-
-  def "test if blueprint exists"() {
-    given:
-    decorator.status >> 200
-    def request = [path: "blueprints/bp", query: ['fields': "Blueprints"]]
-    rest.get(request) >> decorator
-
-    when:
-    def result = ambari.doesBlueprintExists("bp")
-
-    then:
-    result == true
-  }
-
-  def "test get blueprint as map"() {
-    given:
-    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    def mockResponse = [host_groups: [[name: "bp", components: [[name: "hdfs"], [name: "yarn"]]]]]
-    slurper.parseText("map") >> mockResponse
-
-    when:
-    def response = ambari.getBlueprintMap("bp")
-
-    then:
-    [bp: ["hdfs", "yarn"]] == response
-  }
-
-  def "test get blueprint as map for empty result"() {
-    given:
-    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> [:]
-
-    when:
-    def response = ambari.getBlueprintMap("bp")
-
-    then:
-    [:] == response
-  }
-
-  def "test get host groups"() {
-    given:
-    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> [host_groups: [[name: "group1"], [name: "group2"]]]
-
-    when:
-    def result = ambari.getHostGroups("bp")
-
-    then:
-    ["group1", "group2"] == result
-  }
-
-  def "test get host groups for no groups"() {
-    given:
-    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> null
-
-    when:
-    def result = ambari.getHostGroups("bp")
-
-    then:
-    [] == result
-  }
-
-  def "test get host names"() {
-    given:
-    def request = [path: "hosts", query: [fields: "Hosts"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    def hosts = [[Hosts: [host_name: "server.ambari.com", host_status: "HEALTHY"]]]
-    def mapResponse = [items: hosts]
-    slurper.parseText("map") >> mapResponse
-
-    when:
-    def result = ambari.getHostNames()
-
-    then:
-    "HEALTHY" == result["server.ambari.com"]
-    1 == result.size()
-  }
-
-  def "test get host names for empty result"() {
-    given:
-    def request = [path: "hosts", query: [fields: "Hosts"]]
-    rest.get(request) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> []
-
-    when:
-    def result = ambari.getHostNames()
-
-    then:
-    [:] == result
-  }
-
-  def "test add blueprint for success"() {
-    given:
-    def json = getClass().getResourceAsStream("blueprint.json")
-    def request = [path: "blueprints/bp", body: json]
-    rest.post(request, { it })
-
-    when:
-    ambari.addBlueprint(json)
-
-    then:
-    notThrown(HttpResponseException)
-  }
-
-  def "test add blueprint for empty json"() {
-    given:
-    def request = [path: "blueprints/bp", body: ""]
-    rest.post(request, { it })
-
-    when:
-    ambari.addBlueprint("")
-
-    then:
-    notThrown(HttpResponseException)
-  }
-
-  def "test create cluster json"() {
-    given:
-    def json = getClass().getResourceAsStream("/cluster.json").text
-    def groups = [host_group_1: ["server.ambari.com", "server2.ambari.com"]]
-    def request = [path: "clusters/c1", body: json]
-
-    when:
-    ambari.createCluster("c1", "c1", groups)
-
-    then:
-    1 * rest.post(request, { it })
-  }
-
-  def "test get task as map"() {
-    given:
-    //get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the actual tasks
-    def req2 = [path: "clusters/cluster1/requests/1", query: ['fields': "tasks/Tasks/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "map2"]
-    def json = realSlurper.parseText(getClass().getResourceAsStream("/tasks.json").text)
-    slurper.parseText("map2") >> json
-
-    when:
-    def result = ambari.getTaskMap()
-
-    then:
-    ["DATANODE INSTALL"          : "COMPLETED",
-     "DATANODE START"            : "COMPLETED",
-     "HDFS_CLIENT INSTALL"       : "COMPLETED",
-     "HISTORYSERVER INSTALL"     : "COMPLETED",
-     "HISTORYSERVER START"       : "COMPLETED",
-     "MAPREDUCE2_CLIENT INSTALL" : "COMPLETED",
-     "NAMENODE INSTALL"          : "COMPLETED",
-     "NAMENODE START"            : "COMPLETED",
-     "NODEMANAGER INSTALL"       : "COMPLETED",
-     "NODEMANAGER START"         : "COMPLETED",
-     "RESOURCEMANAGER INSTALL"   : "COMPLETED",
-     "RESOURCEMANAGER START"     : "COMPLETED",
-     "SECONDARY_NAMENODE INSTALL": "COMPLETED",
-     "SECONDARY_NAMENODE START"  : "COMPLETED",
-     "YARN_CLIENT INSTALL"       : "COMPLETED",
-     "ZOOKEEPER_CLIENT INSTALL"  : "COMPLETED",
-     "ZOOKEEPER_SERVER INSTALL"  : "COMPLETED",
-     "ZOOKEEPER_SERVER START"    : "COMPLETED"
-    ] == result
-  }
-
-  def "test get task as map for no task"() {
-    given:
-    //get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the actual tasks
-    def req2 = [path: "clusters/cluster1/requests/1", query: ['fields': "tasks/Tasks/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "map2"]
-    slurper.parseText("map2") >> [:]
-
-    when:
-    def result = ambari.getTaskMap()
-
-    then:
-    [:] == result
-  }
-
-  def "test get service components map"() {
-    given:
-    // get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the services
-    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "dec"]
-    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
-    slurper.parseText("dec") >> json
-    // get service components
-    def req3 = [path: "clusters/cluster1/services/HDFS/components", query: ['fields': "ServiceComponentInfo/*"]]
-    def decorator3 = Mock(HttpResponseDecorator)
-    rest.get(req3) >> decorator3
-    decorator3.data >> [text: "dec3"]
-    def json2 = realSlurper.parseText(getClass().getResourceAsStream("/hdfsServiceComponents.json").text)
-    slurper.parseText("dec3") >> json2
-
-    when:
-    def Map result = ambari.getServiceComponentsMap()
-
-    then:
-    [HDFS: [
-      DATANODE          : "STARTED",
-      HDFS_CLIENT       : "INSTALLED",
-      NAMENODE          : "STARTED",
-      SECONDARY_NAMENODE: "STARTED"]
-    ] == result
-  }
-
-  def "test get service components map for empty services"() {
-    given:
-    // get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the services
-    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "dec"]
-    slurper.parseText("dec") >> []
-
-    when:
-    def Map result = ambari.getServiceComponentsMap()
-
-    then:
-    [:] == result
-  }
-
-  def "test get service components map for empty components"() {
-    given:
-    // get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the services
-    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "dec"]
-    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
-    slurper.parseText("dec") >> json
-    // get service components
-    def req3 = [path: "clusters/cluster1/services/HDFS/components", query: ['fields': "ServiceComponentInfo/*"]]
-    def decorator3 = Mock(HttpResponseDecorator)
-    rest.get(req3) >> decorator3
-    decorator3.data >> [text: "dec3"]
-    slurper.parseText("dec3") >> []
-
-    when:
-    def Map result = ambari.getServiceComponentsMap()
-
-    then:
-    [HDFS: [:]] == result
-  }
-
-  def "test get services as map"() {
-    given:
-    // get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the services
-    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "dec"]
-    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
-    slurper.parseText("dec") >> json
-
-    when:
-    def result = ambari.getServicesMap()
-
-    then:
-    [HDFS: "STARTED"] == result
-  }
-
-  def "test get services as map for empty result"() {
-    given:
-    // get the name of the cluster
-    def req1 = [path: "clusters", query: [fields: "Clusters"]]
-    def items = new ArrayList()
-    items.add([Clusters: [cluster_name: "cluster1"]])
-    def mockResponse = ["items": items]
-    rest.get(req1) >> decorator
-    decorator.data >> [text: "map"]
-    slurper.parseText("map") >> mockResponse
-    // get the services
-    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
-    def decorator2 = Mock(HttpResponseDecorator)
-    rest.get(req2) >> decorator2
-    decorator2.data >> [text: "dec"]
-    slurper.parseText("dec") >> []
-
-    when:
-    def result = ambari.getServicesMap()
-
-    then:
-    [:] == result
-  }
-}

+ 39 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AbstractAmbariClientTest.groovy

@@ -0,0 +1,39 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+import spock.lang.Specification
+
+@Slf4j
+abstract class AbstractAmbariClientTest extends Specification {
+
+  def protected ambari = new AmbariClient()
+
+  // implement this in descendants!
+  def protected selectResponseJson
+
+  def protected mockResponses(String scenarioStr) {
+    // mocking the getRawResource method of the class being tested
+    ambari.metaClass.getRawResource = { Map resourceRequestMap ->
+      String jsonFileName = selectResponseJson(resourceRequestMap, scenarioStr)
+      String jsonAsText = getClass().getClassLoader().getResourceAsStream(jsonFileName).text
+      return jsonAsText;
+    }
+  }
+}

+ 191 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariBlueprintsTest.groovy

@@ -0,0 +1,191 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariBlueprintsTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    CLUSTERS, NO_CLUSTERS, BLUEPRINT_EXISTS, NO_BLUEPRINT, HOSTS, NO_HOSTS
+  }
+
+  def "test get the name of the cluster"() {
+    given:
+    mockResponses(Scenario.CLUSTERS.name())
+
+    when:
+    def result = ambari.getClusterName()
+
+    then:
+    "MySingleNodeCluster" == result
+  }
+
+  def "test get the name when there is no cluster"() {
+    given:
+    mockResponses(Scenario.NO_CLUSTERS.name())
+
+    when:
+    def result = ambari.getClusterName()
+
+    then:
+    null == result
+  }
+
+  def "test blueprint doesn't exist"() {
+    given:
+    mockResponses(Scenario.NO_BLUEPRINT.name())
+
+    when:
+    def result = ambari.doesBlueprintExist("inexistent-blueprint")
+
+    then:
+    !result
+  }
+
+  def "test blueprint exists"() {
+    given:
+    mockResponses(Scenario.BLUEPRINT_EXISTS.name())
+
+    when:
+    def result = ambari.doesBlueprintExist("single-node-hdfs-yarn")
+
+    then:
+    result
+  }
+
+  def "test get blueprint as map"() {
+    given:
+    mockResponses(Scenario.BLUEPRINT_EXISTS.name())
+
+    when:
+    def response = ambari.getBlueprintMap("single-node-hdfs-yarn")
+
+    then:
+    response.keySet().size() == 1
+    response.containsKey('host_group_1')
+  }
+
+
+  def "test get blueprint as map when there's no blueprint"() {
+    given:
+    mockResponses(Scenario.NO_BLUEPRINT.name())
+    when:
+    def response = ambari.getBlueprintMap("inexistent-blueprint")
+
+    then:
+    [:] == response
+  }
+
+  def "test get host groups"() {
+    given:
+    mockResponses(Scenario.BLUEPRINT_EXISTS.name())
+
+    when:
+    def result = ambari.getHostGroups("single-node-hdfs-yarn")
+
+    then:
+    ["host_group_1"] == result
+  }
+
+  def "test get host groups for no groups"() {
+    given:
+    mockResponses(Scenario.NO_BLUEPRINT.name())
+
+    when:
+    def result = ambari.getHostGroups("inexistent-blueprint")
+
+    then:
+    [] == result
+  }
+
+  def "test get host names"() {
+    given:
+    mockResponses(Scenario.HOSTS.name())
+
+    when:
+    def result = ambari.getHostNames()
+
+    then:
+    "UNHEALTHY" == result["server.ambari.com"]
+    1 == result.size()
+  }
+
+  def "test get host names for empty result"() {
+    given:
+    mockResponses(Scenario.NO_HOSTS.name())
+
+    when:
+    def result = ambari.getHostNames()
+
+    then:
+    [:] == result
+  }
+
+  def protected String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path");
+    def query = resourceRequestMap.get("query");
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      switch (scenario) {
+        case Scenario.CLUSTERS: json = "clusters.json"
+          break
+        case Scenario.NO_CLUSTERS: json = "no-clusters.json"
+          break
+      }
+    } else if (thePath == TestResources.BLUEPRINTS.uri) {
+      switch (scenario) {
+        case Scenario.BLUEPRINT_EXISTS: json = "blueprints.json"
+          break
+        case Scenario.NO_BLUEPRINT: json = "no-blueprints.json"
+          break
+      }
+    } else if (thePath == TestResources.BLUEPRINT.uri) {
+      switch (scenario) {
+        case Scenario.BLUEPRINT_EXISTS: json = "blueprint.json"
+          break
+        case Scenario.NO_BLUEPRINT: json = "no-blueprint.json"
+          break
+      }
+    } else if (thePath == TestResources.INEXISTENT_BLUEPRINT.uri) {
+      switch (scenario) {
+        case Scenario.NO_BLUEPRINT: json = "no-blueprint.json"
+          break
+      }
+    } else if (thePath == TestResources.CONFIGURATIONS.uri()) {
+      if (query) {
+        json = "service-config.json"
+      } else {
+        json = "service-versions.json"
+      }
+    } else if (thePath == TestResources.HOSTS.uri()) {
+      switch (scenario) {
+        case Scenario.HOSTS: json = "hosts.json"
+          break
+        case Scenario.NO_HOSTS: json = "no-hosts.json"
+          break
+      }
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+
+}

+ 65 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariClustersTest.groovy

@@ -0,0 +1,65 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariClustersTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    CLUSTERS, CLUSTER
+  }
+
+  def "test get cluster as JSON"() {
+    given:
+    mockResponses(Scenario.CLUSTER.name())
+
+    expect:
+    String json = ambari.getClusterAsJson();
+    log.debug("JSON: {}", json)
+
+  }
+
+  def "test get clusters as JSON"() {
+    given:
+    mockResponses(Scenario.CLUSTERS.name())
+
+    expect:
+    String json = ambari.getClustersAsJson();
+    log.debug("JSON: {}", json)
+  }
+
+  def protected String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path");
+    def query = resourceRequestMap.get("query");
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      json = "clusters.json"
+    } else if (thePath == TestResources.CLUSTER.uri()) {
+      switch (scenario) {
+        case Scenario.CLUSTER: json = "clusterAll.json"
+          break
+      }
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+}

+ 80 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariHostsTest.groovy

@@ -0,0 +1,80 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariHostsTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    CLUSTERS
+  }
+
+  def "test get host components as map when there is no cluster yet"() {
+    given:
+    ambari.metaClass.getHostComponenets = { return null }
+
+    when:
+    def result = ambari.getHostComponentsMap("host")
+
+    then:
+    [:] == result
+  }
+
+  def "test get host components as map when there is a cluster"() {
+    given:
+    mockResponses(Scenario.CLUSTERS.name())
+
+    when:
+    def result = ambari.getHostComponentsMap("host")
+
+    then:
+    ["DATANODE"          : "STARTED",
+     "HDFS_CLIENT"       : "INSTALLED",
+     "HISTORYSERVER"     : "STARTED",
+     "MAPREDUCE2_CLIENT" : "INSTALLED",
+     "NAMENODE"          : "STARTED",
+     "NODEMANAGER"       : "STARTED",
+     "RESOURCEMANAGER"   : "STARTED",
+     "SECONDARY_NAMENODE": "STARTED",
+     "YARN_CLIENT"       : "INSTALLED",
+     "ZOOKEEPER_CLIENT"  : "INSTALLED",
+     "ZOOKEEPER_SERVER"  : "STARTED"
+    ] == result
+  }
+
+  def protected String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path");
+    def query = resourceRequestMap.get("query");
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      switch (scenario) {
+        case Scenario.CLUSTERS: json = "clusters.json"
+          break
+      }
+    } else if (thePath == TestResources.HOST_COMPONENTS.uri()) {
+      json = "host-components.json"
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+
+}

+ 83 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariServiceConfigurationTest.groovy

@@ -0,0 +1,83 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariServiceConfigurationTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    CONFIGURATIONS, MULTIPLE_VERSIONS
+  }
+
+  def "test request service configurations map"() {
+    given:
+    mockResponses(Scenario.CONFIGURATIONS.name())
+
+    when:
+    Map<String, Map<String, String>> serviceConfigMap = ambari.getServiceConfigMap();
+
+    then:
+    serviceConfigMap != [:]
+    serviceConfigMap.get("yarn-site") != [:]
+  }
+
+  def "test request service configurations with multiple versions"() {
+    given:
+    mockResponses(Scenario.MULTIPLE_VERSIONS.name())
+
+    when:
+    Map<String, Map<String, String>> serviceConfigMap = ambari.getServiceConfigMap();
+
+    then:
+    serviceConfigMap != [:]
+    serviceConfigMap.get("yarn-site") != [:]
+  }
+
+  // ---- helper method definitions
+
+  def protected String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path")
+    def theQuery = resourceRequestMap.get("query")
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      json = "clusters.json"
+    } else if (thePath == TestResources.CONFIGURATIONS.uri()) {
+      if (!theQuery) {
+        switch (scenario) {
+          case Scenario.MULTIPLE_VERSIONS:
+            json = "service-versions-multiple.json"
+            break
+          case Scenario.CONFIGURATIONS:
+            json = "service-versions.json"
+            break
+        }
+      } else {
+        json = "service-config.json"
+      }
+
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+
+}

+ 147 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariServicesTest.groovy

@@ -0,0 +1,147 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariServicesTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    SERVICES, NO_SERVICES, NO_SERVICE_COMPONENTS
+  }
+
+  def "test get service components map"() {
+    given:
+    // get the name of the cluster
+    super.mockResponses(Scenario.SERVICES.name())
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [HDFS: [
+      DATANODE          : "STARTED",
+      HDFS_CLIENT       : "INSTALLED",
+      NAMENODE          : "STARTED",
+      SECONDARY_NAMENODE: "STARTED"]
+    ] == result
+  }
+
+
+  def "test get service components map for empty services"() {
+    given:
+    mockResponses(Scenario.NO_SERVICES.name())
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [:] == result
+  }
+
+  def "test get service components map for empty components"() {
+    given:
+    // get the name of the cluster
+    mockResponses(Scenario.NO_SERVICE_COMPONENTS.name())
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [HDFS: [:]] == result
+  }
+
+  def "test get services as map"() {
+    given:
+    super.mockResponses(Scenario.SERVICES.name())
+
+    when:
+    def result = ambari.getServicesMap()
+
+    then:
+    [HDFS: "STARTED"] == result
+  }
+
+  def "test get services as map for empty result"() {
+    given:
+    mockResponses(Scenario.NO_SERVICES.name())
+
+    when:
+    def result = ambari.getServicesMap()
+
+    then:
+    [:] == result
+  }
+
+
+  def "test services started"() {
+    given:
+    // get the name of the cluster
+    super.mockResponses(Scenario.SERVICES.name())
+
+    when:
+    def boolean result = ambari.servicesStarted()
+
+    then:
+    result
+  }
+
+  def "test services stopped"() {
+    given:
+    // get the name of the cluster
+    super.mockResponses(Scenario.SERVICES.name())
+
+    when:
+    def boolean result = ambari.servicesStopped()
+
+    then:
+    !result
+  }
+
+  def private String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path");
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      json = "clusters.json"
+    } else if (thePath == TestResources.SERVICES.uri()) {
+      switch (scenario) {
+        case Scenario.SERVICES: json = "services.json"
+          break
+        case Scenario.NO_SERVICES: json = "no-services.json"
+          break
+        case Scenario.NO_SERVICE_COMPONENTS:
+          json = "services.json"
+          break
+      }
+    } else if (thePath == TestResources.SERVICE_COMPONENTS.uri()) {
+      switch (scenario) {
+        case Scenario.NO_SERVICE_COMPONENTS: json = "no-service-components-hdfs.json"
+          break
+        default:
+          json = "service-components-hdfs.json"
+          break
+      }
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+}

+ 93 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/AmbariTasksTest.groovy

@@ -0,0 +1,93 @@
+/**
+ * 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.groovy.client
+
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class AmbariTasksTest extends AbstractAmbariClientTest {
+
+  private enum Scenario {
+    TASKS, NO_TASKS
+  }
+
+  def "test get task as map"() {
+    given:
+    mockResponses(Scenario.TASKS.name())
+
+    when:
+    def result = ambari.getTaskMap()
+
+    then:
+    [
+      'DATANODE INSTALL'          : 'COMPLETED',
+      'GANGLIA_MONITOR INSTALL'   : 'COMPLETED',
+      'GANGLIA_SERVER INSTALL'    : 'COMPLETED',
+      'HDFS_CLIENT INSTALL'       : 'COMPLETED',
+      'HISTORYSERVER INSTALL'     : 'COMPLETED',
+      'MAPREDUCE2_CLIENT INSTALL' : 'COMPLETED',
+      'NAMENODE INSTALL'          : 'COMPLETED',
+      'NODEMANAGER INSTALL'       : 'COMPLETED',
+      'RESOURCEMANAGER INSTALL'   : 'COMPLETED',
+      'SECONDARY_NAMENODE INSTALL': 'COMPLETED',
+      'YARN_CLIENT INSTALL'       : 'COMPLETED',
+      'ZOOKEEPER_CLIENT INSTALL'  : 'COMPLETED',
+      'ZOOKEEPER_SERVER INSTALL'  : 'COMPLETED',
+      'DATANODE START'            : 'COMPLETED',
+      'GANGLIA_MONITOR START'     : 'COMPLETED',
+      'GANGLIA_SERVER START'      : 'COMPLETED',
+      'NAMENODE START'            : 'COMPLETED',
+      'ZOOKEEPER_SERVER START'    : 'COMPLETED',
+      'HISTORYSERVER START'       : 'COMPLETED',
+      'RESOURCEMANAGER START'     : 'COMPLETED',
+      'SECONDARY_NAMENODE START'  : 'COMPLETED',
+      'NODEMANAGER START'         : 'COMPLETED'] == result
+  }
+
+  def "test get task as map for no tasks"() {
+    given:
+    mockResponses(Scenario.NO_TASKS.name())
+
+    when:
+    def result = ambari.getTaskMap()
+
+    then:
+    [:] == result
+  }
+
+  def protected String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
+    def thePath = resourceRequestMap.get("path");
+    def Scenario scenario = Scenario.valueOf(scenarioStr)
+
+    def json = null
+    if (thePath == TestResources.CLUSTERS.uri()) {
+      json = "clusters.json"
+    } else if (thePath == TestResources.TASKS.uri()) {
+      switch (scenario) {
+        case Scenario.TASKS: json = "request-tasks.json"
+          break
+        case Scenario.NO_TASKS: json = "no-request-tasks.json"
+          break
+      }
+    } else {
+      log.error("Unsupported resource path: {}", thePath)
+    }
+    return json
+  }
+
+}

+ 43 - 0
ambari-client/groovy-client/src/test/groovy/org/apache/ambari/groovy/client/TestResources.groovy

@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.groovy.client
+
+enum TestResources {
+  CLUSTERS("http://localhost:8080/api/v1/clusters"),
+  CLUSTER("http://localhost:8080/api/v1/clusters/MySingleNodeCluster"),
+  CONFIGURATIONS("http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations"),
+  BLUEPRINTS("http://localhost:8080/api/v1/blueprints"),
+  BLUEPRINT("http://localhost:8080/api/v1/blueprints/single-node-hdfs-yarn"),
+  INEXISTENT_BLUEPRINT("http://localhost:8080/api/v1/blueprints/inexistent-blueprint"),
+  HOSTS("http://localhost:8080/api/v1/hosts"),
+  TASKS("http://localhost:8080/api/v1/clusters/MySingleNodeCluster/requests/1"),
+  SERVICES("http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services"),
+  SERVICE_COMPONENTS("http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components"),
+  HOST_COMPONENTS("http://localhost:8080/api/v1/clusters/MySingleNodeCluster/hosts/host/host_components")
+
+  String uri;
+
+  TestResources(String uri) {
+    this.uri = uri
+  }
+
+  public uri() {
+    return this.uri
+  }
+
+}

+ 194 - 0
ambari-client/groovy-client/src/test/resources/clusterAll.json

@@ -0,0 +1,194 @@
+{
+  "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster",
+  "Clusters" : {
+    "cluster_id" : 2,
+    "cluster_name" : "MySingleNodeCluster",
+    "version" : "HDP-2.0",
+    "desired_configs" : {
+      "capacity-scheduler" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "core-site" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "global" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "hadoop-policy" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "hdfs-log4j" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "hdfs-site" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "mapred-queue-acls" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "mapred-site" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "yarn-log4j" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "yarn-site" : {
+        "user" : "admin",
+        "tag" : "1"
+      },
+      "zookeeper-log4j" : {
+        "user" : "admin",
+        "tag" : "1"
+      }
+    }
+  },
+  "requests" : [
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/requests/1",
+      "Requests" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "id" : 1
+      }
+    }
+  ],
+  "config_groups" : [ ],
+  "services" : [
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS",
+      "ServiceInfo" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "service_name" : "HDFS"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services/MAPREDUCE2",
+      "ServiceInfo" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "service_name" : "MAPREDUCE2"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services/YARN",
+      "ServiceInfo" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "service_name" : "YARN"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/services/ZOOKEEPER",
+      "ServiceInfo" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "service_name" : "ZOOKEEPER"
+      }
+    }
+  ],
+  "workflows" : [ ],
+  "hosts" : [
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/hosts/server.ambari.com",
+      "Hosts" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "host_name" : "server.ambari.com"
+      }
+    }
+  ],
+  "configurations" : [
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=capacity-scheduler&tag=1",
+      "tag" : "1",
+      "type" : "capacity-scheduler",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=core-site&tag=1",
+      "tag" : "1",
+      "type" : "core-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=global&tag=1",
+      "tag" : "1",
+      "type" : "global",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=hadoop-policy&tag=1",
+      "tag" : "1",
+      "type" : "hadoop-policy",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=hdfs-log4j&tag=1",
+      "tag" : "1",
+      "type" : "hdfs-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=hdfs-site&tag=1",
+      "tag" : "1",
+      "type" : "hdfs-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=mapred-queue-acls&tag=1",
+      "tag" : "1",
+      "type" : "mapred-queue-acls",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=mapred-site&tag=1",
+      "tag" : "1",
+      "type" : "mapred-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-log4j&tag=1",
+      "tag" : "1",
+      "type" : "yarn-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=1",
+      "tag" : "1",
+      "type" : "yarn-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:8080/api/v1/clusters/MySingleNodeCluster/configurations?type=zookeeper-log4j&tag=1",
+      "tag" : "1",
+      "type" : "zookeeper-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    }
+  ]
+}

+ 12 - 0
ambari-client/groovy-client/src/test/resources/clusters.json

@@ -0,0 +1,12 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster",
+      "Clusters" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "version" : "HDP-2.1"
+      }
+    }
+  ]
+}

+ 590 - 0
ambari-client/groovy-client/src/test/resources/host-components.json

@@ -0,0 +1,590 @@
+{
+  "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components?fields=HostRoles/*",
+  "items" : [
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/DATANODE",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "DATANODE",
+        "desired_admin_state" : "INSERVICE",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/HDFS_CLIENT",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "HDFS_CLIENT",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "INSTALLED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "INSTALLED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/HISTORYSERVER",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "HISTORYSERVER",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "MAPREDUCE2",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/MAPREDUCE2_CLIENT",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "MAPREDUCE2_CLIENT",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "INSTALLED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "MAPREDUCE2",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "INSTALLED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/NAMENODE",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "NAMENODE",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/NODEMANAGER",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "NODEMANAGER",
+        "desired_admin_state" : "INSERVICE",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "YARN",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/RESOURCEMANAGER",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "RESOURCEMANAGER",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "YARN",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/SECONDARY_NAMENODE",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "SECONDARY_NAMENODE",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/YARN_CLIENT",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "YARN_CLIENT",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "INSTALLED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "YARN",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "INSTALLED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/ZOOKEEPER_CLIENT",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "ZOOKEEPER_CLIENT",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "INSTALLED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "ZOOKEEPER",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "INSTALLED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    },
+    {
+      "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom/host_components/ZOOKEEPER_SERVER",
+      "HostRoles" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "ZOOKEEPER_SERVER",
+        "desired_stack_id" : "HDP-2.0",
+        "desired_state" : "STARTED",
+        "host_name" : "amb0.mycorp.kom",
+        "maintenance_state" : "OFF",
+        "service_name" : "ZOOKEEPER",
+        "stack_id" : "HDP-2.0",
+        "stale_configs" : false,
+        "state" : "STARTED",
+        "actual_configs" : {
+          "capacity-scheduler" : {
+            "default" : "1"
+          },
+          "core-site" : {
+            "default" : "1"
+          },
+          "global" : {
+            "default" : "1"
+          },
+          "hadoop-policy" : {
+            "default" : "1"
+          },
+          "hdfs-log4j" : {
+            "default" : "1"
+          },
+          "hdfs-site" : {
+            "default" : "1"
+          },
+          "mapred-queue-acls" : {
+            "default" : "1"
+          },
+          "mapred-site" : {
+            "default" : "1"
+          },
+          "yarn-log4j" : {
+            "default" : "1"
+          },
+          "yarn-site" : {
+            "default" : "1"
+          },
+          "zookeeper-log4j" : {
+            "default" : "1"
+          }
+        }
+      },
+      "host" : {
+        "href" : "http://172.24.0.2:8080/api/v1/clusters/single-node-hdfs-yarn/hosts/amb0.mycorp.kom"
+      }
+    }
+  ]
+}

+ 91 - 0
ambari-client/groovy-client/src/test/resources/hosts.json

@@ -0,0 +1,91 @@
+{
+  "href" : "http://localhost:49178/api/v1/hosts?fields=Hosts",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/hosts/server.ambari.com",
+      "Hosts" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "cpu_count" : 2,
+        "desired_configs" : null,
+        "disk_info" : [
+          {
+            "available" : "30467416",
+            "used" : "6729092",
+            "percent" : "19%",
+            "size" : "39211376",
+            "type" : "rootfs",
+            "mountpoint" : "/"
+          },
+          {
+            "available" : "30467416",
+            "used" : "6729092",
+            "percent" : "19%",
+            "size" : "39211376",
+            "type" : "aufs",
+            "mountpoint" : "/"
+          },
+          {
+            "available" : "2027492",
+            "used" : "0",
+            "percent" : "0%",
+            "size" : "2027492",
+            "type" : "tmpfs",
+            "mountpoint" : "/dev"
+          },
+          {
+            "available" : "65536",
+            "used" : "0",
+            "percent" : "0%",
+            "size" : "65536",
+            "type" : "tmpfs",
+            "mountpoint" : "/dev/shm"
+          },
+          {
+            "available" : "2027492",
+            "used" : "0",
+            "percent" : "0%",
+            "size" : "2027492",
+            "type" : "tmpfs",
+            "mountpoint" : "/proc/kcore"
+          }
+        ],
+        "host_health_report" : "",
+        "host_name" : "server.ambari.com",
+        "host_state" : "HEALTHY",
+        "host_status" : "UNHEALTHY",
+        "ip" : "172.17.0.2",
+        "last_agent_env" : {
+          "stackFoldersAndFiles" : [ ],
+          "alternatives" : [ ],
+          "existingUsers" : [ ],
+          "existingRepos" : [
+            "unable_to_determine"
+          ],
+          "installedPackages" : [ ],
+          "hostHealth" : {
+            "activeJavaProcs" : [ ],
+            "agentTimeStampAtReporting" : 1401222525617,
+            "serverTimeStampAtReporting" : 1401222525661,
+            "liveServices" : [
+              {
+                "desc" : "ntpd: unrecognized service\n",
+                "status" : "Unhealthy",
+                "name" : "ntpd"
+              }
+            ]
+          },
+          "umask" : 18,
+          "iptablesIsRunning" : true
+        },
+        "last_heartbeat_time" : 1401222535701,
+        "last_registration_time" : 1401180386121,
+        "os_arch" : "x86_64",
+        "os_type" : "redhat6",
+        "ph_cpu_count" : 2,
+        "public_host_name" : "server.ambari.com",
+        "rack_info" : "/default-rack",
+        "total_mem" : 4054984
+      }
+    }
+  ]
+}

+ 4 - 0
ambari-client/groovy-client/src/test/resources/no-blueprint.json

@@ -0,0 +1,4 @@
+{
+  "status" : 404,
+  "message" : "The requested resource doesn't exist: Blueprint not found, Blueprints/blueprint_name=azaz"
+}

+ 4 - 0
ambari-client/groovy-client/src/test/resources/no-clusters.json

@@ -0,0 +1,4 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters",
+  "items" : [  ]
+}

+ 4 - 0
ambari-client/groovy-client/src/test/resources/no-hosts.json

@@ -0,0 +1,4 @@
+{
+  "href" : "http://localhost:49178/api/v1/hosts?fields=Hosts",
+  "items" : [  ]
+}

+ 10 - 0
ambari-client/groovy-client/src/test/resources/no-request-tasks.json

@@ -0,0 +1,10 @@
+{
+  "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/requests/1",
+  "Requests" : {
+    "cluster_name" : "MySingleNodeCluster",
+    "id" : 1
+  },
+  "tasks" : [
+
+  ]
+}

+ 6 - 0
ambari-client/groovy-client/src/test/resources/no-service-components-hdfs.json

@@ -0,0 +1,6 @@
+{
+  "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components?fields=ServiceComponentInfo",
+  "items" : [
+
+  ]
+}

+ 5 - 0
ambari-client/groovy-client/src/test/resources/no-services.json

@@ -0,0 +1,5 @@
+{
+  "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services?fields=ServiceInfo",
+  "items" : [
+  ]
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 24 - 0
ambari-client/groovy-client/src/test/resources/request-tasks.json


+ 63 - 0
ambari-client/groovy-client/src/test/resources/service-components-hdfs.json

@@ -0,0 +1,63 @@
+{
+  "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components?fields=ServiceComponentInfo",
+  "items" : [
+    {
+      "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components/DATANODE",
+      "ServiceComponentInfo" : {
+        "category" : "SLAVE",
+        "cluster_name" : "MySingleNodeCluster",
+        "component_name" : "DATANODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components/HDFS_CLIENT",
+      "ServiceComponentInfo" : {
+        "category" : "CLIENT",
+        "cluster_name" : "MySingleNodeCluster",
+        "component_name" : "HDFS_CLIENT",
+        "service_name" : "HDFS",
+        "state" : "INSTALLED"
+      }
+    },
+    {
+      "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components/NAMENODE",
+      "ServiceComponentInfo" : {
+        "CapacityRemaining" : 15762272256,
+        "CapacityTotal" : 18433347584,
+        "CapacityUsed" : 24576,
+        "DeadNodes" : "{}",
+        "DecomNodes" : "{}",
+        "HeapMemoryMax" : 1052770304,
+        "HeapMemoryUsed" : 161373832,
+        "LiveNodes" : "{\"server.ambari.com\":{\"infoAddr\":\"172.18.0.2:50075\",\"infoSecureAddr\":\"172.18.0.2:0\",\"xferaddr\":\"172.18.0.2:50010\",\"lastContact\":2,\"usedSpace\":24576,\"adminState\":\"In Service\",\"nonDfsUsedSpace\":2671050752,\"capacity\":18433347584,\"numBlocks\":0,\"version\":\"2.4.0.2.1.1.0-390\",\"used\":24576,\"remaining\":15762272256,\"blockScheduled\":0,\"blockPoolUsed\":24576,\"blockPoolUsedPercent\":1.3332359E-4,\"volfails\":0}}",
+        "NonDfsUsedSpace" : 2671050752,
+        "NonHeapMemoryMax" : 136314880,
+        "NonHeapMemoryUsed" : 34330384,
+        "PercentRemaining" : 85.509544,
+        "PercentUsed" : 1.3332359E-4,
+        "Safemode" : "",
+        "StartTime" : 1401353117084,
+        "TotalFiles" : 10,
+        "UpgradeFinalized" : true,
+        "Version" : "2.4.0.2.1.1.0-390, r68ceccf06a4441273e81a5ec856d41fc7e11c792",
+        "category" : "MASTER",
+        "cluster_name" : "MySingleNodeCluster",
+        "component_name" : "NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS/components/SECONDARY_NAMENODE",
+      "ServiceComponentInfo" : {
+        "category" : "MASTER",
+        "cluster_name" : "MySingleNodeCluster",
+        "component_name" : "SECONDARY_NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    }
+  ]
+}

+ 56 - 0
ambari-client/groovy-client/src/test/resources/service-config.json

@@ -0,0 +1,56 @@
+{
+  "zk_user": "zookeeper",
+  "zk_pid_file": "/var/run/zookeeper/zookeeper_server.pid",
+  "zk_pid_dir": "/var/run/zookeeper",
+  "zk_log_dir": "/var/log/zookeeper",
+  "zk_data_dir": "/hadoop/zookeeper",
+  "yarn_user": "yarn",
+  "yarn_pid_dir_prefix": "/var/run/hadoop-yarn",
+  "yarn_log_dir_prefix": "/var/log/hadoop-yarn",
+  "yarn_heapsize": "1024",
+  "user_group": "hadoop",
+  "tickTime": "2000",
+  "syncLimit": "5",
+  "smokeuser": "ambari-qa",
+  "security_enabled": "false",
+  "rrdcached_base_dir": "/var/lib/ganglia/rrds",
+  "resourcemanager_heapsize": "1024",
+  "proxyuser_group": "users",
+  "nodemanager_heapsize": "1024",
+  "namenode_opt_newsize": "200m",
+  "namenode_opt_maxnewsize": "200m",
+  "namenode_heapsize": "1024m",
+  "namenode_formatted_mark_dir": "/var/run/hadoop/hdfs/namenode/formatted/",
+  "ganglia_conf_dir": "/etc/ganglia/hdp",
+  "dtnode_heapsize": "1024m",
+  "dfs_webhdfs_enabled": "true",
+  "dfs_replication": "3",
+  "dfs_namenode_name_dir": "/hadoop/hdfs/namenode",
+  "dfs_namenode_checkpoint_period": "21600",
+  "dfs_namenode_checkpoint_dir": "/hadoop/hdfs/namesecondary",
+  "dfs_datanode_http_address": "50075",
+  "apptimelineserver_heapsize": "1024",
+  "clientPort": "2181",
+  "datanode_du_reserved": "1073741824",
+  "dfs_block_local_path_access_user": "hbase",
+  "dfs_datanode_address": "50010",
+  "dfs_datanode_data_dir": "/hadoop/hdfs/data",
+  "dfs_datanode_data_dir_perm": "750",
+  "dfs_datanode_failed_volume_tolerated": "0",
+  "ganglia_runtime_dir": "/var/run/ganglia/hdp",
+  "gmetad_user": "nobody",
+  "gmond_user": "nobody",
+  "hadoop_heapsize": "1024",
+  "hadoop_pid_dir_prefix": "/var/run/hadoop",
+  "hdfs_log_dir_prefix": "/var/log/hadoop",
+  "hdfs_user": "hdfs",
+  "initLimit": "10",
+  "kerberos_domain": "EXAMPLE.COM",
+  "keytab_path": "/etc/security/keytabs",
+  "lzo_enabled": "true",
+  "mapred_log_dir_prefix": "/var/log/hadoop-mapreduce",
+  "mapred_pid_dir_prefix": "/var/run/hadoop-mapreduce",
+  "mapred_user": "mapred",
+  "nagios_contact": "default@REPLACEME.NOWHERE",
+  "nagios_web_password": "admin"
+}

+ 37 - 0
ambari-client/groovy-client/src/test/resources/service-versions-multiple.json

@@ -0,0 +1,37 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-log4j&tag=1",
+      "tag" : "1",
+      "type" : "yarn-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=1",
+      "tag" : "1",
+      "type" : "yarn-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=version1402400472475",
+      "tag" : "version1402400472475",
+      "type" : "yarn-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=zookeeper-log4j&tag=1",
+      "tag" : "1",
+      "type" : "zookeeper-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    }
+  ]
+}

+ 93 - 0
ambari-client/groovy-client/src/test/resources/service-versions.json

@@ -0,0 +1,93 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=capacity-scheduler&tag=1",
+      "tag" : "1",
+      "type" : "capacity-scheduler",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=core-site&tag=1",
+      "tag" : "1",
+      "type" : "core-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=global&tag=1",
+      "tag" : "1",
+      "type" : "global",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=hadoop-policy&tag=1",
+      "tag" : "1",
+      "type" : "hadoop-policy",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=hdfs-log4j&tag=1",
+      "tag" : "1",
+      "type" : "hdfs-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=hdfs-site&tag=1",
+      "tag" : "1",
+      "type" : "hdfs-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=mapred-queue-acls&tag=1",
+      "tag" : "1",
+      "type" : "mapred-queue-acls",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=mapred-site&tag=1",
+      "tag" : "1",
+      "type" : "mapred-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-log4j&tag=1",
+      "tag" : "1",
+      "type" : "yarn-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=1",
+      "tag" : "1",
+      "type" : "yarn-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=zookeeper-log4j&tag=1",
+      "tag" : "1",
+      "type" : "zookeeper-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    }
+  ]
+}

+ 4 - 3
ambari-client/groovy-client/src/test/resources/services.json

@@ -1,10 +1,11 @@
 {
-  "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services?fields=ServiceInfo/*",
+  "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services?fields=ServiceInfo",
   "items" : [
+
     {
-      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS",
+      "href" : "http://172.18.0.2:8080/api/v1/clusters/MySingleNodeCluster/services/HDFS",
       "ServiceInfo" : {
-        "cluster_name" : "single-node-hdfs-yarn",
+        "cluster_name" : "MySingleNodeCluster",
         "maintenance_state" : "OFF",
         "service_name" : "HDFS",
         "state" : "STARTED"

+ 12 - 0
ambari-client/groovy-client/src/test/resources/versions/clusters.json

@@ -0,0 +1,12 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster",
+      "Clusters" : {
+        "cluster_name" : "MySingleNodeCluster",
+        "version" : "HDP-2.1"
+      }
+    }
+  ]
+}

+ 56 - 0
ambari-client/groovy-client/src/test/resources/versions/service-config.json

@@ -0,0 +1,56 @@
+{
+  "zk_user": "zookeeper-me",
+  "zk_pid_file": "/var/run/zookeeper/zookeeper_server.pid",
+  "zk_pid_dir": "/var/run/zookeeper",
+  "zk_log_dir": "/var/log/zookeeper",
+  "zk_data_dir": "/hadoop/zookeeper",
+  "yarn_user": "yarn",
+  "yarn_pid_dir_prefix": "/var/run/hadoop-yarn",
+  "yarn_log_dir_prefix": "/var/log/hadoop-yarn",
+  "yarn_heapsize": "1024",
+  "user_group": "hadoop",
+  "tickTime": "2000",
+  "syncLimit": "5",
+  "smokeuser": "ambari-qa",
+  "security_enabled": "false",
+  "rrdcached_base_dir": "/var/lib/ganglia/rrds",
+  "resourcemanager_heapsize": "1024",
+  "proxyuser_group": "users",
+  "nodemanager_heapsize": "1024",
+  "namenode_opt_newsize": "200m",
+  "namenode_opt_maxnewsize": "200m",
+  "namenode_heapsize": "1024m",
+  "namenode_formatted_mark_dir": "/var/run/hadoop/hdfs/namenode/formatted/",
+  "ganglia_conf_dir": "/etc/ganglia/hdp",
+  "dtnode_heapsize": "1024m",
+  "dfs_webhdfs_enabled": "true",
+  "dfs_replication": "3",
+  "dfs_namenode_name_dir": "/hadoop/hdfs/namenode",
+  "dfs_namenode_checkpoint_period": "21600",
+  "dfs_namenode_checkpoint_dir": "/hadoop/hdfs/namesecondary",
+  "dfs_datanode_http_address": "50075",
+  "apptimelineserver_heapsize": "1024",
+  "clientPort": "2181",
+  "datanode_du_reserved": "1073741824",
+  "dfs_block_local_path_access_user": "hbase",
+  "dfs_datanode_address": "50010",
+  "dfs_datanode_data_dir": "/hadoop/hdfs/data",
+  "dfs_datanode_data_dir_perm": "750",
+  "dfs_datanode_failed_volume_tolerated": "0",
+  "ganglia_runtime_dir": "/var/run/ganglia/hdp",
+  "gmetad_user": "nobody",
+  "gmond_user": "nobody",
+  "hadoop_heapsize": "1024",
+  "hadoop_pid_dir_prefix": "/var/run/hadoop",
+  "hdfs_log_dir_prefix": "/var/log/hadoop",
+  "hdfs_user": "hdfs",
+  "initLimit": "10",
+  "kerberos_domain": "EXAMPLE.COM",
+  "keytab_path": "/etc/security/keytabs",
+  "lzo_enabled": "true",
+  "mapred_log_dir_prefix": "/var/log/hadoop-mapreduce",
+  "mapred_pid_dir_prefix": "/var/run/hadoop-mapreduce",
+  "mapred_user": "mapred",
+  "nagios_contact": "default@REPLACEME.NOWHERE",
+  "nagios_web_password": "admin"
+}

+ 37 - 0
ambari-client/groovy-client/src/test/resources/versions/service-versions.json

@@ -0,0 +1,37 @@
+{
+  "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations",
+  "items" : [
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-log4j&tag=1",
+      "tag" : "1",
+      "type" : "yarn-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=1",
+      "tag" : "1",
+      "type" : "yarn-site",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    },
+    {
+          "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=yarn-site&tag=2",
+          "tag" : "2",
+          "type" : "yarn-site",
+          "Config" : {
+            "cluster_name" : "MySingleNodeCluster"
+          }
+    },
+    {
+      "href" : "http://localhost:49178/api/v1/clusters/MySingleNodeCluster/configurations?type=zookeeper-log4j&tag=1",
+      "tag" : "1",
+      "type" : "zookeeper-log4j",
+      "Config" : {
+        "cluster_name" : "MySingleNodeCluster"
+      }
+    }
+  ]
+}

+ 5 - 0
ambari-project/pom.xml

@@ -297,6 +297,11 @@
         <artifactId>jackson-mappper</artifactId>
         <version>1.9.9</version>
       </dependency>
+      <dependency>
+        <groupId>org.codehaus.jackson</groupId>
+        <artifactId>jackson-mapper-asl</artifactId>
+        <version>1.9.13</version>
+      </dependency>
       <dependency>
         <groupId>com.sun.grizzly</groupId>
         <artifactId>grizzly-comet-org.apache.ambari.server.controller.utilities.webserver</artifactId>

+ 4 - 0
ambari-shell/pom.xml

@@ -67,6 +67,10 @@
       <groupId>jline</groupId>
       <artifactId>jline</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.jackson</groupId>
+      <artifactId>jackson-mapper-asl</artifactId>
+    </dependency>
   </dependencies>
   <build>
     <plugins>

+ 1 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/AmbariShell.java

@@ -55,6 +55,7 @@ public class AmbariShell implements CommandLineRunner, ShellStatusListener {
           break;
         }
       }
+      System.exit(0);
     } else {
       shell.addShellStatusListener(this);
       shell.start();

+ 38 - 4
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java

@@ -74,7 +74,7 @@ public class BasicCommands implements CommandMarker {
    *
    * @return true if available false otherwise
    */
-  @CliAvailabilityIndicator("service list")
+  @CliAvailabilityIndicator("services list")
   public boolean isServiceListCommandAvailable() {
     return context.isConnectedToCluster();
   }
@@ -84,7 +84,7 @@ public class BasicCommands implements CommandMarker {
    *
    * @return service list
    */
-  @CliCommand(value = "service list", help = "Lists the available services")
+  @CliCommand(value = "services list", help = "Lists the available services")
   public String servicesList() {
     return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
   }
@@ -94,7 +94,7 @@ public class BasicCommands implements CommandMarker {
    *
    * @return true if available false otherwise
    */
-  @CliAvailabilityIndicator("service components")
+  @CliAvailabilityIndicator("services components")
   public boolean isServiceComponentsCommandAvailable() {
     return context.isConnectedToCluster();
   }
@@ -104,7 +104,7 @@ public class BasicCommands implements CommandMarker {
    *
    * @return service component list
    */
-  @CliCommand(value = "service components", help = "Lists all services with their components")
+  @CliCommand(value = "services components", help = "Lists all services with their components")
   public String serviceComponents() {
     return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
   }
@@ -170,4 +170,38 @@ public class BasicCommands implements CommandMarker {
   public String hint() {
     return context.getHint();
   }
+
+  @CliAvailabilityIndicator("services stop")
+  public boolean isServiceStopCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  @CliCommand(value = "services stop", help = "Stops all the running services")
+  public String stopServices() {
+    String message;
+    try {
+      client.stopAllServices();
+      message = "Stopping all services..";
+    } catch (Exception e) {
+      message = "Cannot stop services";
+    }
+    return String.format("%s\n\n%s", message, servicesList());
+  }
+
+  @CliAvailabilityIndicator("services start")
+  public boolean isServiceStartCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  @CliCommand(value = "services start", help = "Starts all the services")
+  public String startServices() {
+    String message;
+    try {
+      client.startAllServices();
+      message = "Starting all services..";
+    } catch (Exception e) {
+      message = "Cannot start services";
+    }
+    return String.format("%s\n\n%s", message, servicesList());
+  }
 }

+ 18 - 4
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java

@@ -26,9 +26,11 @@ import java.io.IOException;
 import java.net.URL;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Blueprint;
 import org.apache.ambari.shell.model.AmbariContext;
 import org.apache.ambari.shell.model.Hints;
 import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.shell.core.CommandMarker;
 import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
@@ -46,11 +48,13 @@ public class BlueprintCommands implements CommandMarker {
 
   private AmbariClient client;
   private AmbariContext context;
+  private ObjectMapper jsonMapper;
 
   @Autowired
-  public BlueprintCommands(AmbariClient client, AmbariContext context) {
+  public BlueprintCommands(AmbariClient client, AmbariContext context, ObjectMapper jsonMapper) {
     this.client = client;
     this.context = context;
+    this.jsonMapper = jsonMapper;
   }
 
   /**
@@ -91,8 +95,8 @@ public class BlueprintCommands implements CommandMarker {
    */
   @CliCommand(value = "blueprint show", help = "Shows the blueprint by its id")
   public String showBlueprint(
-    @CliOption(key = "id", mandatory = true, help = "Id of the blueprint") String id) {
-    return renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT");
+    @CliOption(key = "id", mandatory = true, help = "Id of the blueprint") Blueprint id) {
+    return renderMultiValueMap(client.getBlueprintMap(id.getName()), "HOSTGROUP", "COMPONENT");
   }
 
   /**
@@ -122,9 +126,9 @@ public class BlueprintCommands implements CommandMarker {
       String json = file == null ? readContent(url) : readContent(file);
       if (json != null) {
         client.addBlueprint(json);
-        message = "Blueprint added";
         context.setHint(Hints.BUILD_CLUSTER);
         context.setBlueprintsAvailable(true);
+        message = String.format("Blueprint: '%s' has been added", getBlueprintName(json));
       } else {
         message = "No blueprint specified";
       }
@@ -181,4 +185,14 @@ public class BlueprintCommands implements CommandMarker {
     }
     return content;
   }
+
+  private String getBlueprintName(String json) {
+    String result = "";
+    try {
+      result = jsonMapper.readTree(json.getBytes()).get("Blueprints").get("blueprint_name").asText();
+    } catch (IOException e) {
+      // not important
+    }
+    return result;
+  }
 }

+ 45 - 11
ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java

@@ -26,6 +26,9 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Blueprint;
+import org.apache.ambari.shell.completion.Host;
+import org.apache.ambari.shell.flash.FlashService;
 import org.apache.ambari.shell.model.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.apache.ambari.shell.model.Hints;
@@ -48,12 +51,14 @@ public class ClusterCommands implements CommandMarker {
 
   private AmbariClient client;
   private AmbariContext context;
+  private FlashService flashService;
   private Map<String, List<String>> hostGroups;
 
   @Autowired
-  public ClusterCommands(AmbariClient client, AmbariContext context) {
+  public ClusterCommands(AmbariClient client, AmbariContext context, FlashService flashService) {
     this.client = client;
     this.context = context;
+    this.flashService = flashService;
   }
 
   /**
@@ -75,14 +80,15 @@ public class ClusterCommands implements CommandMarker {
    */
   @CliCommand(value = "cluster build", help = "Starts to build a cluster")
   public String buildCluster(
-    @CliOption(key = "blueprint", mandatory = true, help = "Id of the blueprint, use 'blueprints' command to see the list") String id) {
+    @CliOption(key = "blueprint", mandatory = true, help = "Id of the blueprint, use 'blueprints' command to see the list") Blueprint id) {
     String message;
-    if (client.doesBlueprintExists(id)) {
-      context.setFocus(id, FocusType.CLUSTER_BUILD);
+    String blueprint = id.getName();
+    if (client.doesBlueprintExist(blueprint)) {
+      context.setFocus(blueprint, FocusType.CLUSTER_BUILD);
       context.setHint(Hints.ASSIGN_HOSTS);
       message = String.format("%s\n%s",
         renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
-        renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT"));
+        renderMultiValueMap(client.getBlueprintMap(blueprint), "HOSTGROUP", "COMPONENT"));
       createNewHostGroups();
     } else {
       message = "Not a valid blueprint id";
@@ -109,22 +115,48 @@ public class ClusterCommands implements CommandMarker {
    */
   @CliCommand(value = "cluster assign", help = "Assign host to host group")
   public String assign(
-    @CliOption(key = "host", mandatory = true, help = "Fully qualified host name") String host,
+    @CliOption(key = "host", mandatory = true, help = "Fully qualified host name") Host host,
     @CliOption(key = "hostGroup", mandatory = true, help = "Host group which to assign the host") String group) {
     String message;
-    if (client.getHostNames().keySet().contains(host)) {
-      if (addHostToGroup(host, group)) {
+    String hostName = host.getName();
+    if (client.getHostNames().keySet().contains(hostName)) {
+      if (addHostToGroup(hostName, group)) {
         context.setHint(Hints.CREATE_CLUSTER);
-        message = String.format("%s has been added to %s", host, group);
+        message = String.format("%s has been added to %s", hostName, group);
       } else {
         message = String.format("%s is not a valid host group", group);
       }
     } else {
-      message = String.format("%s is not a valid hostname", host);
+      message = String.format("%s is not a valid hostname", hostName);
     }
     return message;
   }
 
+  /**
+   * Checks whether the cluster auto command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "cluster autoAssign")
+  public boolean isClusterAutoAssignAvailable() {
+    return context.isFocusOnClusterBuild() && !isHostAssigned();
+  }
+
+  /**
+   * Tries to auto associate hosts to host groups.
+   *
+   * @return prints the auto assignments
+   */
+  @CliCommand(value = "cluster autoAssign", help = "Automatically assigns hosts to different host groups base on the provided strategy")
+  public String autoAssign() {
+    Map<String, List<String>> assignments = client.recommendAssignments(context.getFocusValue());
+    if (!assignments.isEmpty()) {
+      hostGroups = assignments;
+      context.setHint(Hints.CREATE_CLUSTER);
+    }
+    return showAssignments();
+  }
+
   /**
    * Checks whether the cluster preview command is available or not.
    *
@@ -162,7 +194,8 @@ public class ClusterCommands implements CommandMarker {
    * @return status message
    */
   @CliCommand(value = "cluster create", help = "Create a cluster based on current blueprint and assigned hosts")
-  public String createCluster() {
+  public String createCluster(
+    @CliOption(key = "exitOnFinish", mandatory = false, help = "Quits the shell when the cluster creation finishes") Boolean exit) {
     String message = "Successfully created the cluster";
     String blueprint = context.getFocusValue();
     try {
@@ -170,6 +203,7 @@ public class ClusterCommands implements CommandMarker {
       context.setCluster(blueprint);
       context.resetFocus();
       context.setHint(Hints.PROGRESS);
+      flashService.showInstallProgress(exit == null ? false : exit);
     } catch (HttpResponseException e) {
       createNewHostGroups();
       message = "Failed to create the cluster: " + e.getMessage();

+ 7 - 5
ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java

@@ -20,6 +20,7 @@ package org.apache.ambari.shell.commands;
 import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Host;
 import org.apache.ambari.shell.model.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -84,13 +85,14 @@ public class HostCommands implements CommandMarker {
    */
   @CliCommand(value = "host focus", help = "Sets the useHost to the specified host")
   public String focusHost(
-    @CliOption(key = "host", mandatory = true, help = "hostname") String host) {
+    @CliOption(key = "host", mandatory = true, help = "hostname") Host host) {
     String message;
-    if (client.getHostNames().keySet().contains(host)) {
-      context.setFocus(host, FocusType.HOST);
-      message = "Focus set to: " + host;
+    String hostName = host.getName();
+    if (client.getHostNames().keySet().contains(hostName)) {
+      context.setFocus(hostName, FocusType.HOST);
+      message = "Focus set to: " + hostName;
     } else {
-      message = host + " is not a valid host name";
+      message = hostName + " is not a valid host name";
     }
     return message;
   }

+ 34 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/completion/Blueprint.java

@@ -0,0 +1,34 @@
+/**
+ * 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.shell.completion;
+
+/**
+ * Wrapper class for TAB completion to blueprint names.
+ */
+public class Blueprint {
+
+  private final String name;
+
+  public Blueprint(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+}

+ 34 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/completion/Host.java

@@ -0,0 +1,34 @@
+/**
+ * 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.shell.completion;
+
+/**
+ * Wrapper class for TAB completion to host names.
+ */
+public class Host {
+
+  private final String name;
+
+  public Host(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+}

+ 17 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ConverterConfiguration.java

@@ -17,6 +17,10 @@
  */
 package org.apache.ambari.shell.configuration;
 
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.converter.BlueprintConverter;
+import org.apache.ambari.shell.converter.HostConverter;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.shell.converters.AvailableCommandsConverter;
@@ -43,6 +47,9 @@ import org.springframework.shell.core.Converter;
 @Configuration
 public class ConverterConfiguration {
 
+  @Autowired
+  private AmbariClient client;
+
   @Bean
   Converter simpleFileConverter() {
     return new SimpleFileConverter();
@@ -122,4 +129,14 @@ public class ConverterConfiguration {
   Converter staticFieldConverterImpl() {
     return new StaticFieldConverterImpl();
   }
+
+  @Bean
+  Converter blueprintConverter() {
+    return new BlueprintConverter(client);
+  }
+
+  @Bean
+  Converter hostConverter() {
+    return new HostConverter(client);
+  }
 }

+ 12 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ShellConfiguration.java

@@ -18,10 +18,12 @@
 package org.apache.ambari.shell.configuration;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
 import org.springframework.shell.CommandLine;
 import org.springframework.shell.SimpleShellCommandLineOptions;
 import org.springframework.shell.commands.ExitCommands;
@@ -80,6 +82,16 @@ public class ShellConfiguration {
     return SimpleShellCommandLineOptions.parseCommandLine(args);
   }
 
+  @Bean
+  ThreadPoolExecutorFactoryBean getThreadPoolExecutorFactoryBean() {
+    return new ThreadPoolExecutorFactoryBean();
+  }
+
+  @Bean
+  ObjectMapper getObjectMapper() {
+    return new ObjectMapper();
+  }
+
   @Bean
   CommandMarker exitCommand() {
     return new ExitCommands();

+ 58 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/converter/BlueprintConverter.java

@@ -0,0 +1,58 @@
+/**
+ * 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.shell.converter;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Blueprint;
+import org.springframework.shell.core.Completion;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell.core.MethodTarget;
+
+/**
+ * Converter used to complete blueprint names.
+ */
+public class BlueprintConverter implements Converter<Blueprint> {
+
+  private AmbariClient client;
+
+  public BlueprintConverter(AmbariClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public boolean supports(Class<?> type, String optionContext) {
+    return Blueprint.class.isAssignableFrom(type);
+  }
+
+  @Override
+  public Blueprint convertFromText(String value, Class<?> targetType, String optionContext) {
+    return new Blueprint(value);
+  }
+
+  @Override
+  public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData, String optionContext, MethodTarget target) {
+    Set<String> blueprints = client.getBlueprintsMap().keySet();
+    for (String blueprint : blueprints) {
+      completions.add(new Completion(blueprint));
+    }
+    return true;
+  }
+}

+ 58 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/converter/HostConverter.java

@@ -0,0 +1,58 @@
+/**
+ * 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.shell.converter;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Host;
+import org.springframework.shell.core.Completion;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell.core.MethodTarget;
+
+/**
+ * Converter used to complete host names.
+ */
+public class HostConverter implements Converter<Host> {
+
+  private AmbariClient client;
+
+  public HostConverter(AmbariClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public boolean supports(Class<?> type, String optionContext) {
+    return Host.class.isAssignableFrom(type);
+  }
+
+  @Override
+  public Host convertFromText(String value, Class<?> targetType, String optionContext) {
+    return new Host(value);
+  }
+
+  @Override
+  public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData, String optionContext, MethodTarget target) {
+    Set<String> hosts = client.getHostNames().keySet();
+    for (String host : hosts) {
+      completions.add(new Completion(host));
+    }
+    return true;
+  }
+}

+ 40 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariHistory.java

@@ -0,0 +1,40 @@
+/**
+ * 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.shell.customization;
+
+import org.springframework.shell.plugin.HistoryFileNameProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * Specifies the name of the Ambari command log. Later this log can be used
+ * to re-execute the commands with either the --cmdfile option at startup
+ * or with the script --file command.
+ */
+@Component
+public class AmbariHistory implements HistoryFileNameProvider {
+
+  @Override
+  public String getHistoryFileName() {
+    return "ambari-log.ash";
+  }
+
+  @Override
+  public String getProviderName() {
+    return "AmbariShell";
+  }
+}

+ 66 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.java

@@ -0,0 +1,66 @@
+/**
+ * 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.shell.flash;
+
+import static java.lang.Thread.sleep;
+
+import java.util.logging.Level;
+
+import org.springframework.shell.core.JLineShellComponent;
+
+/**
+ * Base class for showing flash messages.
+ */
+public abstract class AbstractFlash implements Runnable {
+
+  private static final int SLEEP_TIME = 1500;
+  private volatile boolean stop;
+  private FlashType flashType;
+  private JLineShellComponent shell;
+
+  protected AbstractFlash(JLineShellComponent shell, FlashType flashType) {
+    this.shell = shell;
+    this.flashType = flashType;
+  }
+
+  @Override
+  public void run() {
+    while (!stop) {
+      String text = null;
+      try {
+        text = getText();
+        if (text.isEmpty()) {
+          stop = true;
+        }
+        sleep(SLEEP_TIME);
+      } catch (Exception e) {
+        // ignore
+      } finally {
+        shell.flash(Level.SEVERE, text == null ? "" : text, flashType.getName());
+      }
+    }
+  }
+
+  /**
+   * Returns the actual text of the flash messages. To remove the flash
+   * return an empty string.
+   *
+   * @return message
+   */
+  public abstract String getText();
+}

+ 47 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.java

@@ -0,0 +1,47 @@
+/**
+ * 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.shell.flash;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service for managing the flashes.
+ */
+@Service
+public class FlashService {
+
+  private AmbariClient client;
+  private JLineShellComponent shell;
+  private ExecutorService executorService;
+
+  @Autowired
+  public FlashService(AmbariClient client, JLineShellComponent shell, ExecutorService executorService) {
+    this.client = client;
+    this.shell = shell;
+    this.executorService = executorService;
+  }
+
+  public void showInstallProgress(boolean exit) {
+    executorService.submit(new InstallProgress(shell, client, exit));
+  }
+}

+ 39 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java

@@ -0,0 +1,39 @@
+/**
+ * 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.shell.flash;
+
+/**
+ * Holds the unique names of the flashes.
+ */
+public enum FlashType {
+
+  /**
+   * Install progress percentage flash.
+   */
+  INSTALL("install");
+
+  private String name;
+
+  private FlashType(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+}

+ 79 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java

@@ -0,0 +1,79 @@
+/**
+ * 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.shell.flash;
+
+import static java.lang.Math.round;
+
+import java.math.BigDecimal;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.shell.core.JLineShellComponent;
+
+/**
+ * Show the install progress in % value.
+ */
+public class InstallProgress extends AbstractFlash {
+
+  private static final int SUCCESS = 100;
+  private static final int FAILED = -1;
+  private final boolean exit;
+  private AmbariClient client;
+  private volatile boolean done;
+
+  public InstallProgress(JLineShellComponent shell, AmbariClient client, boolean exit) {
+    super(shell, FlashType.INSTALL);
+    this.client = client;
+    this.exit = exit;
+  }
+
+  @Override
+  public String getText() {
+    StringBuilder sb = new StringBuilder();
+    if (!done) {
+      BigDecimal progress = client.getInstallProgress();
+      if (progress != null) {
+        BigDecimal decimal = progress.setScale(2, BigDecimal.ROUND_HALF_UP);
+        int intValue = decimal.intValue();
+        if (intValue != SUCCESS && intValue != FAILED) {
+          sb.append("Installation: ").append(decimal).append("% ");
+          int rounded = round(progress.setScale(0, BigDecimal.ROUND_UP).intValue() / 10);
+          for (int i = 0; i < 10; i++) {
+            if (i < rounded) {
+              sb.append("=");
+            } else {
+              sb.append("-");
+            }
+          }
+        } else if (intValue == FAILED) {
+          sb.append("Installation: FAILED");
+          done = true;
+        } else {
+          sb.append("Installation: COMPLETE");
+          done = true;
+        }
+      } else {
+        sb.append("Installation: WAITING..");
+      }
+    } else {
+      if (exit) {
+        System.exit(0);
+      }
+    }
+    return sb.toString();
+  }
+}

+ 14 - 1
ambari-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java

@@ -19,6 +19,7 @@ package org.apache.ambari.shell.commands;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -29,7 +30,10 @@ import java.io.IOException;
 
 import org.apache.ambari.groovy.client.AmbariClient;
 import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
 import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
@@ -50,16 +54,25 @@ public class BlueprintCommandsTest {
   private HttpResponseException responseException;
   @Mock
   private AmbariContext context;
+  @Mock
+  private ObjectMapper objectMapper;
 
   @Test
   public void testAddBlueprintForFileReadPrecedence() throws IOException {
     File file = new File("src/test/resources/testBlueprint.json");
     String json = IOUtils.toString(new FileInputStream(file));
+    JsonNode jsonNode = mock(JsonNode.class);
+    when(objectMapper.readTree(json.getBytes())).thenReturn(jsonNode);
+    when(jsonNode.get("Blueprints")).thenReturn(jsonNode);
+    when(jsonNode.get("blueprint_name")).thenReturn(jsonNode);
+    when(jsonNode.asText()).thenReturn("blueprintName");
 
     String result = blueprintCommands.addBlueprint("url", file);
 
     verify(ambariClient).addBlueprint(json);
-    assertEquals("Blueprint added", result);
+    verify(context).setHint(Hints.BUILD_CLUSTER);
+    verify(context).setBlueprintsAvailable(true);
+    assertEquals("Blueprint: 'blueprintName' has been added", result);
   }
 
   @Test

+ 45 - 12
ambari-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java

@@ -35,7 +35,11 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Blueprint;
+import org.apache.ambari.shell.completion.Host;
+import org.apache.ambari.shell.flash.FlashService;
 import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
@@ -45,7 +49,6 @@ import org.springframework.test.util.ReflectionTestUtils;
 
 import groovyx.net.http.HttpResponseException;
 
-
 @RunWith(MockitoJUnitRunner.class)
 public class ClusterCommandsTest {
 
@@ -58,6 +61,8 @@ public class ClusterCommandsTest {
   private AmbariContext context;
   @Mock
   private HttpResponseException responseException;
+  @Mock
+  private FlashService flashService;
 
   @Test
   public void testIsClusterBuildCommandAvailable() {
@@ -94,11 +99,11 @@ public class ClusterCommandsTest {
 
   @Test
   public void testBuildClusterForNonExistingBlueprint() {
-    when(client.doesBlueprintExists("id")).thenReturn(false);
+    when(client.doesBlueprintExist("id")).thenReturn(false);
 
-    String result = clusterCommands.buildCluster("id");
+    String result = clusterCommands.buildCluster(new Blueprint("id"));
 
-    verify(client).doesBlueprintExists("id");
+    verify(client).doesBlueprintExist("id");
     assertEquals("Not a valid blueprint id", result);
   }
 
@@ -106,14 +111,14 @@ public class ClusterCommandsTest {
   public void testBuildCluster() {
     Map<String, String> hostNames = singletonMap("host1", "HEALTHY");
     Map<String, List<String>> map = singletonMap("group1", asList("comp1", "comp2"));
-    when(client.doesBlueprintExists("id")).thenReturn(true);
+    when(client.doesBlueprintExist("id")).thenReturn(true);
     when(client.getBlueprintMap("id")).thenReturn(map);
     when(context.getFocusValue()).thenReturn("id");
     when(client.getHostNames()).thenReturn(hostNames);
 
-    String result = clusterCommands.buildCluster("id");
+    String result = clusterCommands.buildCluster(new Blueprint("id"));
 
-    verify(client).doesBlueprintExists("id");
+    verify(client).doesBlueprintExist("id");
     verify(client).getBlueprintMap("id");
     verify(client).getHostGroups("id");
     assertEquals(String.format("%s\n%s", renderSingleMap(hostNames, "HOSTNAME", "STATE"),
@@ -126,7 +131,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
 
-    String result = clusterCommands.assign("host3", "group0");
+    String result = clusterCommands.assign(new Host("host3"), "group0");
 
     assertEquals("group0 is not a valid host group", result);
   }
@@ -138,7 +143,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
 
-    String result = clusterCommands.assign("host3", "group1");
+    String result = clusterCommands.assign(new Host("host3"), "group1");
 
     assertEquals("host3 has been added to group1", result);
   }
@@ -150,7 +155,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host2", "HEALTHY"));
 
-    String result = clusterCommands.assign("host3", "group1");
+    String result = clusterCommands.assign(new Host("host3"), "group1");
 
     assertEquals("host3 is not a valid hostname", result);
   }
@@ -164,7 +169,7 @@ public class ClusterCommandsTest {
     doThrow(responseException).when(client).createCluster(blueprint, blueprint, map);
     doThrow(responseException).when(client).deleteCluster(blueprint);
 
-    String result = clusterCommands.createCluster();
+    String result = clusterCommands.createCluster(false);
 
     verify(client).createCluster(blueprint, blueprint, map);
     verify(client).getHostGroups(blueprint);
@@ -180,7 +185,7 @@ public class ClusterCommandsTest {
     when(context.getFocusValue()).thenReturn(blueprint);
     when(client.getClusterName()).thenReturn("cluster");
 
-    String result = clusterCommands.createCluster();
+    String result = clusterCommands.createCluster(false);
 
     verify(client).createCluster(blueprint, blueprint, map);
     verify(context).resetFocus();
@@ -243,4 +248,32 @@ public class ClusterCommandsTest {
 
     assertTrue(result);
   }
+
+  @Test
+  public void testAutoAssignForEmptyResult() {
+    Map<String, List<String>> hostGroups = singletonMap("group1", asList("host1"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", hostGroups);
+    when(context.getFocusValue()).thenReturn("blueprint");
+    when(client.recommendAssignments("blueprint")).thenReturn(new HashMap<String, List<String>>());
+
+    clusterCommands.autoAssign();
+
+    Map<String, List<String>> result = (Map<String, List<String>>) ReflectionTestUtils.getField(clusterCommands, "hostGroups");
+    assertEquals(hostGroups, result);
+  }
+
+  @Test
+  public void testAutoAssign() {
+    Map<String, List<String>> hostGroups = singletonMap("group1", asList("host1"));
+    Map<String, List<String>> newAssignments = singletonMap("group1", asList("host1"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", hostGroups);
+    when(context.getFocusValue()).thenReturn("blueprint");
+    when(client.recommendAssignments("blueprint")).thenReturn(newAssignments);
+
+    clusterCommands.autoAssign();
+
+    Map<String, List<String>> result = (Map<String, List<String>>) ReflectionTestUtils.getField(clusterCommands, "hostGroups");
+    assertEquals(newAssignments, result);
+    verify(context).setHint(Hints.CREATE_CLUSTER);
+  }
 }

+ 3 - 2
ambari-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.java

@@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Host;
 import org.apache.ambari.shell.model.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.junit.Test;
@@ -47,7 +48,7 @@ public class HostCommandsTest {
   public void testFocusHostForValidHost() {
     when(client.getHostNames()).thenReturn(singletonMap("host1", "HEALTHY"));
 
-    String result = hostCommands.focusHost("host1");
+    String result = hostCommands.focusHost(new Host("host1"));
 
     verify(context).setFocus("host1", FocusType.HOST);
     assertEquals("Focus set to: host1", result);
@@ -57,7 +58,7 @@ public class HostCommandsTest {
   public void testFocusHostForInvalidHost() {
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
 
-    String result = hostCommands.focusHost("host1");
+    String result = hostCommands.focusHost(new Host("host1"));
 
     verify(context, times(0)).setFocus("host1", FocusType.HOST);
     assertEquals("host1 is not a valid host name", result);

+ 1 - 1
ambari-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.java

@@ -51,4 +51,4 @@ public class AmbariPromptTest {
 
     assertEquals("prompt", result);
   }
-}
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác