Selaa lähdekoodia

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

Yusaku Sako 11 vuotta sitten
vanhempi
commit
269451069f
56 muutettua tiedostoa jossa 3308 lisäystä ja 569 poistoa
  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>
         <artifactId>apache-rat-plugin</artifactId>
         <configuration>
         <configuration>
           <excludes>
           <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>
           </excludes>
         </configuration>
         </configuration>
         <executions>
         <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.JsonBuilder
 import groovy.json.JsonSlurper
 import groovy.json.JsonSlurper
+import groovy.util.logging.Slf4j
+import groovyx.net.http.ContentType
 import groovyx.net.http.HttpResponseException
 import groovyx.net.http.HttpResponseException
 import groovyx.net.http.RESTClient
 import groovyx.net.http.RESTClient
 import org.apache.http.NoHttpResponseException
 import org.apache.http.NoHttpResponseException
 import org.apache.http.client.ClientProtocolException
 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.
  * Basic client to send requests to the Ambari server.
  */
  */
+@Slf4j
 class AmbariClient {
 class AmbariClient {
 
 
-  private static final Logger LOGGER = LoggerFactory.getLogger(AmbariClient.class)
   private static final int PAD = 30
   private static final int PAD = 30
   private static final int OK_RESPONSE = 200
   private static final int OK_RESPONSE = 200
   boolean debugEnabled = false;
   boolean debugEnabled = false;
@@ -94,12 +97,14 @@ class AmbariClient {
    * @param id id of the blueprint
    * @param id id of the blueprint
    * @return true if exists false otherwise
    * @return true if exists false otherwise
    */
    */
-  def boolean doesBlueprintExists(String id) {
+  def boolean doesBlueprintExist(String id) {
     def result = false
     def result = false
     try {
     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) {
     } catch (e) {
-      LOGGER.info("Blueprint does not exist", e)
+      log.info("Blueprint does not exist", e)
     }
     }
     return result
     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
    * @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 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
    * @return formatted blueprint list
    */
    */
   def String showBlueprints() {
   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
    * @return Map where the key is the blueprint's name value is the used stack
    */
    */
   def Map<String, String> getBlueprintsMap() {
   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()
     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.
    * 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
    * @throws HttpResponseException in case of error
    */
    */
   def void addDefaultBlueprints() throws HttpResponseException {
   def void addDefaultBlueprints() throws HttpResponseException {
     addBlueprint(getResourceContent("blueprints/multi-node-hdfs-yarn"))
     addBlueprint(getResourceContent("blueprints/multi-node-hdfs-yarn"))
     addBlueprint(getResourceContent("blueprints/single-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
    * @throws HttpResponseException in case of error
    */
    */
   def void createCluster(String clusterName, String blueprintName, Map<String, List<String>> hostGroups) throws HttpResponseException {
   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 })
     ambari.post(path: "clusters/$clusterName", body: createClusterJson(blueprintName, hostGroups), { it })
   }
   }
 
 
@@ -224,6 +271,9 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    * @throws HttpResponseException in case of error
    */
    */
   def void deleteCluster(String clusterName) throws HttpResponseException {
   def void deleteCluster(String clusterName) throws HttpResponseException {
+    if (debugEnabled) {
+      println "[DEBUG] DELETE ${ambari.getUri()}clusters/$clusterName"
+    }
     ambari.delete(path: "clusters/$clusterName")
     ambari.delete(path: "clusters/$clusterName")
   }
   }
 
 
@@ -234,7 +284,9 @@ class AmbariClient {
    * @throws HttpResponseException in case of error
    * @throws HttpResponseException in case of error
    */
    */
   def String getClusterAsJson() throws HttpResponseException {
   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
    * @throws HttpResponseException in case of error
    */
    */
   def getClustersAsJson() throws HttpResponseException {
   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
    * @return pre-formatted cluster list
    */
    */
   def String showClusterList() {
   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")
     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.
    * Returns a pre-formatted task list.
    *
    *
@@ -302,7 +372,9 @@ class AmbariClient {
    * @return pre-formatted String
    * @return pre-formatted String
    */
    */
   def String showHostList() {
   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 Map<String, Map<String, String>> getServiceComponentsMap() {
     def result = getServices().items.collectEntries {
     def result = getServices().items.collectEntries {
       def name = it.ServiceInfo.service_name
       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]
       [(name): componentList]
     }
     }
     result ?: new HashMap()
     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.
    * Returns a pre-formatted service list.
    *
    *
@@ -362,7 +445,9 @@ class AmbariClient {
    * @return formatted String
    * @return formatted String
    */
    */
   def String showHostComponentList(host) {
   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
    * @return component name - state association
    */
    */
   def Map<String, String> getHostComponentsMap(host) {
   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()
     result ?: new HashMap()
   }
   }
 
 
@@ -383,11 +468,162 @@ class AmbariClient {
    * @return json as String, exception if thrown is it fails
    * @return json as String, exception if thrown is it fails
    */
    */
   def String getBlueprintAsJson(id) {
   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
    * @return response message
    */
    */
   private void postBlueprint(String blueprint) {
   private void postBlueprint(String blueprint) {
+    if (debugEnabled) {
+      println "[DEBUG] POST ${ambari.getUri()}blueprints/bp"
+    }
     ambari.post(path: "blueprints/bp", body: blueprint, { it })
     ambari.post(path: "blueprints/bp", body: blueprint, { it })
   }
   }
 
 
@@ -408,25 +647,16 @@ class AmbariClient {
       def hostList = it.value.collect { ['fqdn': it] }
       def hostList = it.value.collect { ['fqdn': it] }
       [name: it.key, hosts: hostList]
       [name: it.key, hosts: hostList]
     }
     }
-    builder { "blueprint" name; "host-groups" groups }
+    builder { "blueprint" name; "default_password" "admin"; "host_groups" groups }
     builder.toPrettyString()
     builder.toPrettyString()
   }
   }
 
 
   private def slurp(path, fields = "") {
   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
     return result
   }
   }
 
 
@@ -496,15 +726,8 @@ class AmbariClient {
     getAllResources("hosts/$host/host_components", "HostRoles")
     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) {
   private String getResourceContent(name) {
     getClass().getClassLoader().getResourceAsStream(name)?.text
     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" : "ZOOKEEPER_SERVER"
       },
       },
+      {
+        "name" : "APP_TIMELINE_SERVER"
+      },
       {
       {
         "name" : "ZOOKEEPER_CLIENT"
         "name" : "ZOOKEEPER_CLIENT"
       }
       }
@@ -43,6 +46,6 @@
   "Blueprints" : {
   "Blueprints" : {
     "blueprint_name" : "single-node-hdfs-yarn",
     "blueprint_name" : "single-node-hdfs-yarn",
     "stack_name" : "HDP",
     "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" : [
+  ]
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 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" : [
   "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" : {
       "ServiceInfo" : {
-        "cluster_name" : "single-node-hdfs-yarn",
+        "cluster_name" : "MySingleNodeCluster",
         "maintenance_state" : "OFF",
         "maintenance_state" : "OFF",
         "service_name" : "HDFS",
         "service_name" : "HDFS",
         "state" : "STARTED"
         "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>
         <artifactId>jackson-mappper</artifactId>
         <version>1.9.9</version>
         <version>1.9.9</version>
       </dependency>
       </dependency>
+      <dependency>
+        <groupId>org.codehaus.jackson</groupId>
+        <artifactId>jackson-mapper-asl</artifactId>
+        <version>1.9.13</version>
+      </dependency>
       <dependency>
       <dependency>
         <groupId>com.sun.grizzly</groupId>
         <groupId>com.sun.grizzly</groupId>
         <artifactId>grizzly-comet-org.apache.ambari.server.controller.utilities.webserver</artifactId>
         <artifactId>grizzly-comet-org.apache.ambari.server.controller.utilities.webserver</artifactId>

+ 4 - 0
ambari-shell/pom.xml

@@ -67,6 +67,10 @@
       <groupId>jline</groupId>
       <groupId>jline</groupId>
       <artifactId>jline</artifactId>
       <artifactId>jline</artifactId>
     </dependency>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.jackson</groupId>
+      <artifactId>jackson-mapper-asl</artifactId>
+    </dependency>
   </dependencies>
   </dependencies>
   <build>
   <build>
     <plugins>
     <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;
           break;
         }
         }
       }
       }
+      System.exit(0);
     } else {
     } else {
       shell.addShellStatusListener(this);
       shell.addShellStatusListener(this);
       shell.start();
       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
    * @return true if available false otherwise
    */
    */
-  @CliAvailabilityIndicator("service list")
+  @CliAvailabilityIndicator("services list")
   public boolean isServiceListCommandAvailable() {
   public boolean isServiceListCommandAvailable() {
     return context.isConnectedToCluster();
     return context.isConnectedToCluster();
   }
   }
@@ -84,7 +84,7 @@ public class BasicCommands implements CommandMarker {
    *
    *
    * @return service list
    * @return service list
    */
    */
-  @CliCommand(value = "service list", help = "Lists the available services")
+  @CliCommand(value = "services list", help = "Lists the available services")
   public String servicesList() {
   public String servicesList() {
     return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
     return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
   }
   }
@@ -94,7 +94,7 @@ public class BasicCommands implements CommandMarker {
    *
    *
    * @return true if available false otherwise
    * @return true if available false otherwise
    */
    */
-  @CliAvailabilityIndicator("service components")
+  @CliAvailabilityIndicator("services components")
   public boolean isServiceComponentsCommandAvailable() {
   public boolean isServiceComponentsCommandAvailable() {
     return context.isConnectedToCluster();
     return context.isConnectedToCluster();
   }
   }
@@ -104,7 +104,7 @@ public class BasicCommands implements CommandMarker {
    *
    *
    * @return service component list
    * @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() {
   public String serviceComponents() {
     return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
     return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
   }
   }
@@ -170,4 +170,38 @@ public class BasicCommands implements CommandMarker {
   public String hint() {
   public String hint() {
     return context.getHint();
     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 java.net.URL;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 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.AmbariContext;
 import org.apache.ambari.shell.model.Hints;
 import org.apache.ambari.shell.model.Hints;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.shell.core.CommandMarker;
 import org.springframework.shell.core.CommandMarker;
 import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
 import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
@@ -46,11 +48,13 @@ public class BlueprintCommands implements CommandMarker {
 
 
   private AmbariClient client;
   private AmbariClient client;
   private AmbariContext context;
   private AmbariContext context;
+  private ObjectMapper jsonMapper;
 
 
   @Autowired
   @Autowired
-  public BlueprintCommands(AmbariClient client, AmbariContext context) {
+  public BlueprintCommands(AmbariClient client, AmbariContext context, ObjectMapper jsonMapper) {
     this.client = client;
     this.client = client;
     this.context = context;
     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")
   @CliCommand(value = "blueprint show", help = "Shows the blueprint by its id")
   public String showBlueprint(
   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);
       String json = file == null ? readContent(url) : readContent(file);
       if (json != null) {
       if (json != null) {
         client.addBlueprint(json);
         client.addBlueprint(json);
-        message = "Blueprint added";
         context.setHint(Hints.BUILD_CLUSTER);
         context.setHint(Hints.BUILD_CLUSTER);
         context.setBlueprintsAvailable(true);
         context.setBlueprintsAvailable(true);
+        message = String.format("Blueprint: '%s' has been added", getBlueprintName(json));
       } else {
       } else {
         message = "No blueprint specified";
         message = "No blueprint specified";
       }
       }
@@ -181,4 +185,14 @@ public class BlueprintCommands implements CommandMarker {
     }
     }
     return content;
     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 java.util.Map;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 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.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.apache.ambari.shell.model.FocusType;
 import org.apache.ambari.shell.model.Hints;
 import org.apache.ambari.shell.model.Hints;
@@ -48,12 +51,14 @@ public class ClusterCommands implements CommandMarker {
 
 
   private AmbariClient client;
   private AmbariClient client;
   private AmbariContext context;
   private AmbariContext context;
+  private FlashService flashService;
   private Map<String, List<String>> hostGroups;
   private Map<String, List<String>> hostGroups;
 
 
   @Autowired
   @Autowired
-  public ClusterCommands(AmbariClient client, AmbariContext context) {
+  public ClusterCommands(AmbariClient client, AmbariContext context, FlashService flashService) {
     this.client = client;
     this.client = client;
     this.context = context;
     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")
   @CliCommand(value = "cluster build", help = "Starts to build a cluster")
   public String buildCluster(
   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;
     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);
       context.setHint(Hints.ASSIGN_HOSTS);
       message = String.format("%s\n%s",
       message = String.format("%s\n%s",
         renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
         renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
-        renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT"));
+        renderMultiValueMap(client.getBlueprintMap(blueprint), "HOSTGROUP", "COMPONENT"));
       createNewHostGroups();
       createNewHostGroups();
     } else {
     } else {
       message = "Not a valid blueprint id";
       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")
   @CliCommand(value = "cluster assign", help = "Assign host to host group")
   public String assign(
   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) {
     @CliOption(key = "hostGroup", mandatory = true, help = "Host group which to assign the host") String group) {
     String message;
     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);
         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 {
       } else {
         message = String.format("%s is not a valid host group", group);
         message = String.format("%s is not a valid host group", group);
       }
       }
     } else {
     } else {
-      message = String.format("%s is not a valid hostname", host);
+      message = String.format("%s is not a valid hostname", hostName);
     }
     }
     return message;
     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.
    * Checks whether the cluster preview command is available or not.
    *
    *
@@ -162,7 +194,8 @@ public class ClusterCommands implements CommandMarker {
    * @return status message
    * @return status message
    */
    */
   @CliCommand(value = "cluster create", help = "Create a cluster based on current blueprint and assigned hosts")
   @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 message = "Successfully created the cluster";
     String blueprint = context.getFocusValue();
     String blueprint = context.getFocusValue();
     try {
     try {
@@ -170,6 +203,7 @@ public class ClusterCommands implements CommandMarker {
       context.setCluster(blueprint);
       context.setCluster(blueprint);
       context.resetFocus();
       context.resetFocus();
       context.setHint(Hints.PROGRESS);
       context.setHint(Hints.PROGRESS);
+      flashService.showInstallProgress(exit == null ? false : exit);
     } catch (HttpResponseException e) {
     } catch (HttpResponseException e) {
       createNewHostGroups();
       createNewHostGroups();
       message = "Failed to create the cluster: " + e.getMessage();
       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 static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 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.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.apache.ambari.shell.model.FocusType;
 import org.springframework.beans.factory.annotation.Autowired;
 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")
   @CliCommand(value = "host focus", help = "Sets the useHost to the specified host")
   public String focusHost(
   public String focusHost(
-    @CliOption(key = "host", mandatory = true, help = "hostname") String host) {
+    @CliOption(key = "host", mandatory = true, help = "hostname") Host host) {
     String message;
     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 {
     } else {
-      message = host + " is not a valid host name";
+      message = hostName + " is not a valid host name";
     }
     }
     return message;
     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;
 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.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.shell.converters.AvailableCommandsConverter;
 import org.springframework.shell.converters.AvailableCommandsConverter;
@@ -43,6 +47,9 @@ import org.springframework.shell.core.Converter;
 @Configuration
 @Configuration
 public class ConverterConfiguration {
 public class ConverterConfiguration {
 
 
+  @Autowired
+  private AmbariClient client;
+
   @Bean
   @Bean
   Converter simpleFileConverter() {
   Converter simpleFileConverter() {
     return new SimpleFileConverter();
     return new SimpleFileConverter();
@@ -122,4 +129,14 @@ public class ConverterConfiguration {
   Converter staticFieldConverterImpl() {
   Converter staticFieldConverterImpl() {
     return new 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;
 package org.apache.ambari.shell.configuration;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 import org.apache.ambari.groovy.client.AmbariClient;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
 import org.springframework.shell.CommandLine;
 import org.springframework.shell.CommandLine;
 import org.springframework.shell.SimpleShellCommandLineOptions;
 import org.springframework.shell.SimpleShellCommandLineOptions;
 import org.springframework.shell.commands.ExitCommands;
 import org.springframework.shell.commands.ExitCommands;
@@ -80,6 +82,16 @@ public class ShellConfiguration {
     return SimpleShellCommandLineOptions.parseCommandLine(args);
     return SimpleShellCommandLineOptions.parseCommandLine(args);
   }
   }
 
 
+  @Bean
+  ThreadPoolExecutorFactoryBean getThreadPoolExecutorFactoryBean() {
+    return new ThreadPoolExecutorFactoryBean();
+  }
+
+  @Bean
+  ObjectMapper getObjectMapper() {
+    return new ObjectMapper();
+  }
+
   @Bean
   @Bean
   CommandMarker exitCommand() {
   CommandMarker exitCommand() {
     return new ExitCommands();
     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.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 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.groovy.client.AmbariClient;
 import org.apache.ambari.shell.model.AmbariContext;
 import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.junit.Test;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.InjectMocks;
@@ -50,16 +54,25 @@ public class BlueprintCommandsTest {
   private HttpResponseException responseException;
   private HttpResponseException responseException;
   @Mock
   @Mock
   private AmbariContext context;
   private AmbariContext context;
+  @Mock
+  private ObjectMapper objectMapper;
 
 
   @Test
   @Test
   public void testAddBlueprintForFileReadPrecedence() throws IOException {
   public void testAddBlueprintForFileReadPrecedence() throws IOException {
     File file = new File("src/test/resources/testBlueprint.json");
     File file = new File("src/test/resources/testBlueprint.json");
     String json = IOUtils.toString(new FileInputStream(file));
     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);
     String result = blueprintCommands.addBlueprint("url", file);
 
 
     verify(ambariClient).addBlueprint(json);
     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
   @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 java.util.Map;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 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.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
 import org.junit.Test;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.InjectMocks;
@@ -45,7 +49,6 @@ import org.springframework.test.util.ReflectionTestUtils;
 
 
 import groovyx.net.http.HttpResponseException;
 import groovyx.net.http.HttpResponseException;
 
 
-
 @RunWith(MockitoJUnitRunner.class)
 @RunWith(MockitoJUnitRunner.class)
 public class ClusterCommandsTest {
 public class ClusterCommandsTest {
 
 
@@ -58,6 +61,8 @@ public class ClusterCommandsTest {
   private AmbariContext context;
   private AmbariContext context;
   @Mock
   @Mock
   private HttpResponseException responseException;
   private HttpResponseException responseException;
+  @Mock
+  private FlashService flashService;
 
 
   @Test
   @Test
   public void testIsClusterBuildCommandAvailable() {
   public void testIsClusterBuildCommandAvailable() {
@@ -94,11 +99,11 @@ public class ClusterCommandsTest {
 
 
   @Test
   @Test
   public void testBuildClusterForNonExistingBlueprint() {
   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);
     assertEquals("Not a valid blueprint id", result);
   }
   }
 
 
@@ -106,14 +111,14 @@ public class ClusterCommandsTest {
   public void testBuildCluster() {
   public void testBuildCluster() {
     Map<String, String> hostNames = singletonMap("host1", "HEALTHY");
     Map<String, String> hostNames = singletonMap("host1", "HEALTHY");
     Map<String, List<String>> map = singletonMap("group1", asList("comp1", "comp2"));
     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(client.getBlueprintMap("id")).thenReturn(map);
     when(context.getFocusValue()).thenReturn("id");
     when(context.getFocusValue()).thenReturn("id");
     when(client.getHostNames()).thenReturn(hostNames);
     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).getBlueprintMap("id");
     verify(client).getHostGroups("id");
     verify(client).getHostGroups("id");
     assertEquals(String.format("%s\n%s", renderSingleMap(hostNames, "HOSTNAME", "STATE"),
     assertEquals(String.format("%s\n%s", renderSingleMap(hostNames, "HOSTNAME", "STATE"),
@@ -126,7 +131,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
     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);
     assertEquals("group0 is not a valid host group", result);
   }
   }
@@ -138,7 +143,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
     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);
     assertEquals("host3 has been added to group1", result);
   }
   }
@@ -150,7 +155,7 @@ public class ClusterCommandsTest {
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
     when(client.getHostNames()).thenReturn(singletonMap("host2", "HEALTHY"));
     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);
     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).createCluster(blueprint, blueprint, map);
     doThrow(responseException).when(client).deleteCluster(blueprint);
     doThrow(responseException).when(client).deleteCluster(blueprint);
 
 
-    String result = clusterCommands.createCluster();
+    String result = clusterCommands.createCluster(false);
 
 
     verify(client).createCluster(blueprint, blueprint, map);
     verify(client).createCluster(blueprint, blueprint, map);
     verify(client).getHostGroups(blueprint);
     verify(client).getHostGroups(blueprint);
@@ -180,7 +185,7 @@ public class ClusterCommandsTest {
     when(context.getFocusValue()).thenReturn(blueprint);
     when(context.getFocusValue()).thenReturn(blueprint);
     when(client.getClusterName()).thenReturn("cluster");
     when(client.getClusterName()).thenReturn("cluster");
 
 
-    String result = clusterCommands.createCluster();
+    String result = clusterCommands.createCluster(false);
 
 
     verify(client).createCluster(blueprint, blueprint, map);
     verify(client).createCluster(blueprint, blueprint, map);
     verify(context).resetFocus();
     verify(context).resetFocus();
@@ -243,4 +248,32 @@ public class ClusterCommandsTest {
 
 
     assertTrue(result);
     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 static org.mockito.Mockito.when;
 
 
 import org.apache.ambari.groovy.client.AmbariClient;
 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.AmbariContext;
 import org.apache.ambari.shell.model.FocusType;
 import org.apache.ambari.shell.model.FocusType;
 import org.junit.Test;
 import org.junit.Test;
@@ -47,7 +48,7 @@ public class HostCommandsTest {
   public void testFocusHostForValidHost() {
   public void testFocusHostForValidHost() {
     when(client.getHostNames()).thenReturn(singletonMap("host1", "HEALTHY"));
     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);
     verify(context).setFocus("host1", FocusType.HOST);
     assertEquals("Focus set to: host1", result);
     assertEquals("Focus set to: host1", result);
@@ -57,7 +58,7 @@ public class HostCommandsTest {
   public void testFocusHostForInvalidHost() {
   public void testFocusHostForInvalidHost() {
     when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
     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);
     verify(context, times(0)).setFocus("host1", FocusType.HOST);
     assertEquals("host1 is not a valid host name", result);
     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);
     assertEquals("prompt", result);
   }
   }
-}
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä