浏览代码

AMBARI-5482. Integrate Ambari Shell. (Janos Matyas and Krisztian Horvath via yusaku)

Yusaku Sako 11 年之前
父节点
当前提交
11dd9df85b
共有 100 个文件被更改,包括 3363 次插入157 次删除
  1. 20 0
      ambari-client/assemblies/client.xml
  2. 113 0
      ambari-client/groovy-client/pom.xml
  3. 510 0
      ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy
  4. 27 0
      ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariConnectionException.groovy
  5. 26 0
      ambari-client/groovy-client/src/main/java/org/apache/ambari/groovy/client/DoNotDeleteMe.java
  6. 57 0
      ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn
  7. 48 0
      ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn
  8. 425 0
      ambari-client/groovy-client/src/test/groovy/com.sequenceiq.ambari.client/AmbariClientTest.groovy
  9. 27 0
      ambari-client/groovy-client/src/test/java/org/apache/ambari/groovy/client/NeitherMe.java
  10. 48 0
      ambari-client/groovy-client/src/test/resources/blueprint.json
  11. 21 0
      ambari-client/groovy-client/src/test/resources/blueprints.json
  12. 16 0
      ambari-client/groovy-client/src/test/resources/cluster.json
  13. 63 0
      ambari-client/groovy-client/src/test/resources/hdfsServiceComponents.json
  14. 14 0
      ambari-client/groovy-client/src/test/resources/services.json
  15. 24 0
      ambari-client/groovy-client/src/test/resources/tasks.json
  16. 8 143
      ambari-client/pom.xml
  17. 195 0
      ambari-client/python-client/pom.xml
  18. 0 0
      ambari-client/python-client/src/examples/create.py
  19. 0 0
      ambari-client/python-client/src/examples/delete.py
  20. 0 0
      ambari-client/python-client/src/examples/example.py
  21. 0 0
      ambari-client/python-client/src/examples/main.py
  22. 0 0
      ambari-client/python-client/src/examples/trial.py
  23. 0 0
      ambari-client/python-client/src/main/python/ambari_client/__init__.py
  24. 0 0
      ambari-client/python-client/src/main/python/ambari_client/ambari_api.py
  25. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/__init__.py
  26. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/coreutils.py
  27. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/errors.py
  28. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/http_client.py
  29. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/http_utils.py
  30. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/rest_resource.py
  31. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/__init__.py
  32. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/base_model.py
  33. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/cluster.py
  34. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/component.py
  35. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/configuration.py
  36. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/host.py
  37. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/paths.py
  38. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/service.py
  39. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/stack.py
  40. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/status.py
  41. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/utils.py
  42. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/__init__.py
  43. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/clusters.py
  44. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/hosts.py
  45. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/stacks.py
  46. 0 0
      ambari-client/python-client/src/main/python/setup.py
  47. 0 0
      ambari-client/python-client/src/packages/tarball/all.xml
  48. 0 0
      ambari-client/python-client/src/test/python/TestAmbariClient.py
  49. 0 0
      ambari-client/python-client/src/test/python/TestClusterModel.py
  50. 0 0
      ambari-client/python-client/src/test/python/TestComponentModel.py
  51. 0 0
      ambari-client/python-client/src/test/python/TestHostModel.py
  52. 0 0
      ambari-client/python-client/src/test/python/TestServiceModel.py
  53. 0 0
      ambari-client/python-client/src/test/python/TestStatusModel.py
  54. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_bootstrap_hosts.json
  55. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_all_clusters.json
  56. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_all_hosts.json
  57. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_components.json
  58. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_config.json
  59. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_host.json
  60. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_error_deleting_host.json
  61. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_all_hosts.json
  62. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_all_services.json
  63. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_cluster.json
  64. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_core_site_config.json
  65. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_global_config.json
  66. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_hdfs_site_config.json
  67. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_host.json
  68. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_mapred_site_config.json
  69. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_service.json
  70. 0 0
      ambari-client/python-client/src/test/python/json/componentmodel_get_metrics.json
  71. 0 0
      ambari-client/python-client/src/test/python/json/get_cluster_service.json
  72. 0 0
      ambari-client/python-client/src/test/python/json/get_components_from_stack.json
  73. 0 0
      ambari-client/python-client/src/test/python/json/get_host_component.json
  74. 0 0
      ambari-client/python-client/src/test/python/json/get_host_components.json
  75. 0 0
      ambari-client/python-client/src/test/python/json/get_service_component.json
  76. 0 0
      ambari-client/python-client/src/test/python/json/get_service_components.json
  77. 0 0
      ambari-client/python-client/src/test/python/json/hostmodel_get_host_component.json
  78. 0 0
      ambari-client/python-client/src/test/python/json/hostmodel_get_host_components.json
  79. 0 0
      ambari-client/python-client/src/test/python/json/servicemodel_get_component.json
  80. 0 0
      ambari-client/python-client/src/test/python/json/servicemodel_get_components.json
  81. 0 0
      ambari-client/python-client/src/test/python/json/status_error_with_message.json
  82. 0 0
      ambari-client/python-client/src/test/python/json/status_ok_with_id.json
  83. 0 0
      ambari-client/python-client/src/test/python/unitTests.py
  84. 0 0
      ambari-client/python-client/src/test/python/utils/HttpClientInvoker.py
  85. 58 14
      ambari-project/pom.xml
  86. 120 0
      ambari-shell/pom.xml
  87. 110 0
      ambari-shell/src/main/java/org/apache/ambari/shell/AmbariShell.java
  88. 173 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java
  89. 184 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java
  90. 260 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java
  91. 53 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/ElephantCommand.java
  92. 117 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java
  93. 125 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ConverterConfiguration.java
  94. 102 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ShellConfiguration.java
  95. 50 0
      ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariBanner.java
  96. 43 0
      ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariPrompt.java
  97. 159 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java
  98. 53 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/Focus.java
  99. 55 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java
  100. 59 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/Hints.java

+ 20 - 0
ambari-client/assemblies/client.xml

@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<assembly>
+</assembly>

+ 113 - 0
ambari-client/groovy-client/pom.xml

@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<!-- Licensed 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.
+  See accompanying LICENSE file. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <artifactId>ambari-client</artifactId>
+    <groupId>org.apache.ambari</groupId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../../ambari-client/</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>groovy-client</artifactId>
+  <packaging>jar</packaging>
+  <version>1.3.0-SNAPSHOT</version>
+  <name>Ambari Groovy Client</name>
+  <description>Ambari Groovy client</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.groovy</groupId>
+      <artifactId>groovy-all</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.groovy.modules.http-builder</groupId>
+      <artifactId>http-builder</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>3.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.spockframework</groupId>
+      <artifactId>spock-core</artifactId>
+      <version>0.7-groovy-2.0</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <!-- 2.8.0-01 and later require maven-compiler-plugin 3.1 or higher -->
+        <version>3.1</version>
+        <configuration>
+          <compilerId>groovy-eclipse-compiler</compilerId>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-eclipse-compiler</artifactId>
+            <version>2.8.0-01</version>
+          </dependency>
+          <!-- for 2.8.0-01 and later you must have an explicit dependency on
+            groovy-eclipse-batch -->
+          <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-eclipse-batch</artifactId>
+            <version>2.1.8-01</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/main/resources/blueprints/multi-node-hdfs-yarn</exclude>
+            <exclude>src/main/resources/blueprints/single-node-hdfs-yarn</exclude>
+            <exclude>src/test/resources/blueprint.json</exclude>
+            <exclude>src/test/resources/blueprints.json</exclude>
+            <exclude>src/test/resources/cluster.json</exclude>
+            <exclude>src/test/resources/hdfsServiceComponents.json</exclude>
+            <exclude>src/test/resources/services.json</exclude>
+            <exclude>src/test/resources/tasks.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <skipAssembly>true</skipAssembly>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 510 - 0
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy

@@ -0,0 +1,510 @@
+/**
+ * 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.JsonBuilder
+import groovy.json.JsonSlurper
+import groovyx.net.http.HttpResponseException
+import groovyx.net.http.RESTClient
+import org.apache.http.NoHttpResponseException
+import org.apache.http.client.ClientProtocolException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * Basic client to send requests to the Ambari server.
+ */
+class AmbariClient {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(AmbariClient.class)
+  private static final int PAD = 30
+  private static final int OK_RESPONSE = 200
+  boolean debugEnabled = false;
+  def RESTClient ambari
+  def slurper = new JsonSlurper()
+  def clusterName
+
+  /**
+   * Connects to the ambari server.
+   *
+   * @param host host of the Ambari server; default value is localhost
+   * @param port port of the Ambari server; default value is 8080
+   * @param user username of the Ambari server; default is admin
+   * @param password password fom the Ambari server; default is admin
+   */
+  AmbariClient(host = 'localhost', port = '8080', user = 'admin', password = 'admin') {
+    ambari = new RESTClient("http://${host}:${port}/api/v1/" as String)
+    ambari.headers['Authorization'] = 'Basic ' + "$user:$password".getBytes('iso-8859-1').encodeBase64()
+    ambari.headers['X-Requested-By'] = 'ambari'
+  }
+
+  /**
+   * Connects to the ambari server.
+   *
+   * @param restClient underlying client
+   * @param slurper slurper to parse responses
+   */
+  AmbariClient(RESTClient restClient, JsonSlurper slurper) {
+    this.ambari = restClient
+    this.slurper = slurper
+  }
+
+  /**
+   * Sets the debug variable. Used by printing the API calls for the Ambari Shell.
+   *
+   * @param enabled enable or disable
+   */
+  def setDebugEnabled(boolean enabled) {
+    debugEnabled = enabled;
+  }
+
+  /**
+   * Returns the name of the cluster.
+   *
+   * @return the name of the cluster of null if no cluster yet
+   */
+  def String getClusterName() {
+    if (!clusterName) {
+      def clusters = getClusters();
+      if (clusters) {
+        clusterName = clusters.items[0]?.Clusters?.cluster_name
+      }
+    }
+    return clusterName
+  }
+
+  /**
+   * Checks whether the blueprint exists or not.
+   *
+   * @param id id of the blueprint
+   * @return true if exists false otherwise
+   */
+  def boolean doesBlueprintExists(String id) {
+    def result = false
+    try {
+      result = ambari.get(path: "blueprints/$id", query: ['fields': "Blueprints"]).status == OK_RESPONSE
+    } catch (e) {
+      LOGGER.info("Blueprint does not exist", e)
+    }
+    return result
+  }
+
+  /**
+   * Checks whether there are available blueprints or not.
+   *
+   * @return true if blueprints are available false otherwise
+   */
+  def boolean isBlueprintAvailable() {
+    return getBlueprints().items?.size > 0
+  }
+
+  /**
+   * Returns a pre-formatted String of the blueprint.
+   *
+   * @param id id of the blueprint
+   * @return formatted String
+   */
+  def String showBlueprint(String id) {
+    def resp = getBlueprint(id)
+    if (resp) {
+      def groups = resp.host_groups.collect {
+        def name = it.name
+        def comps = it.components.collect { it.name.padRight(PAD).padLeft(PAD + 10) }.join("\n")
+        return "HOSTGROUP: $name\n$comps"
+      }.join("\n")
+      return "[${resp.Blueprints.stack_name}:${resp.Blueprints.stack_version}]\n$groups"
+    }
+    return "Not found"
+  }
+
+  /**
+   * Returns the host group - components mapping of the blueprint.
+   *
+   * @param id id of the blueprint
+   * @return Map where the key is the host group and the value is the list of components
+   */
+  def Map<String, List<String>> getBlueprintMap(String id) {
+    def result = getBlueprint(id).host_groups?.collectEntries { [(it.name): it.components.collect { it.name }] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted String of the blueprints.
+   *
+   * @return formatted blueprint list
+   */
+  def String showBlueprints() {
+    getBlueprints().items.collect { "${it.Blueprints.blueprint_name.padRight(PAD)} [${it.Blueprints.stack_name}:${it.Blueprints.stack_version}]" }.join("\n")
+  }
+
+  /**
+   * Returns a Map containing the blueprint name - stack association.
+   *
+   * @return Map where the key is the blueprint's name value is the used stack
+   */
+  def Map<String, String> getBlueprintsMap() {
+    def result = getBlueprints().items?.collectEntries { [(it.Blueprints.blueprint_name): it.Blueprints.stack_name + ":" + it.Blueprints.stack_version] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the name of the host groups for a given blueprint.
+   *
+   * @param blueprint id of the blueprint
+   * @return host group list or empty list
+   */
+  def List<String> getHostGroups(String blueprint) {
+    def result = getBlueprint(blueprint)
+    result ? result.host_groups.collect { it.name } : new ArrayList<String>()
+  }
+
+  /**
+   * Returns a pre-formatted String of a blueprint used by the cluster.
+   *
+   * @return formatted String
+   */
+  def String showClusterBlueprint() {
+    ambari.get(path: "clusters/${getClusterName()}", query: ['format': "blueprint"]).data.text
+  }
+
+  /**
+   * Adds a blueprint to the Ambari server. Exception is thrown if fails.
+   *
+   * @param json blueprint as json
+   * @throws HttpResponseException in case of error
+   */
+  def void addBlueprint(String json) throws HttpResponseException {
+    if (json) {
+      postBlueprint(json)
+    }
+  }
+
+  /**
+   * Adds 2 default blueprints.
+   *
+   * @throws HttpResponseException in case of error
+   */
+  def void addDefaultBlueprints() throws HttpResponseException {
+    addBlueprint(getResourceContent("blueprints/multi-node-hdfs-yarn"))
+    addBlueprint(getResourceContent("blueprints/single-node-hdfs-yarn"))
+  }
+
+  /**
+   * Creates a cluster with the given blueprint and host group - host association.
+   *
+   * @param clusterName name of the cluster
+   * @param blueprintName blueprint id used to create this cluster
+   * @param hostGroups Map<String, List<String> key - host group, value - host list
+   * @return true if the creation was successful false otherwise
+   * @throws HttpResponseException in case of error
+   */
+  def void createCluster(String clusterName, String blueprintName, Map<String, List<String>> hostGroups) throws HttpResponseException {
+    ambari.post(path: "clusters/$clusterName", body: createClusterJson(blueprintName, hostGroups), { it })
+  }
+
+  /**
+   * Deletes the cluster.
+   *
+   * @param clusterName name of the cluster
+   * @throws HttpResponseException in case of error
+   */
+  def void deleteCluster(String clusterName) throws HttpResponseException {
+    ambari.delete(path: "clusters/$clusterName")
+  }
+
+  /**
+   * Returns the active cluster as json
+   *
+   * @return cluster as json String
+   * @throws HttpResponseException in case of error
+   */
+  def String getClusterAsJson() throws HttpResponseException {
+    getRequest("clusters/${getClusterName()}")
+  }
+
+  /**
+   * Returns all clusters as json
+   *
+   * @return json String
+   * @throws HttpResponseException in case of error
+   */
+  def getClustersAsJson() throws HttpResponseException {
+    getRequest("clusters")
+  }
+
+  /**
+   * Returns a pre-formatted String of the clusters.
+   *
+   * @return pre-formatted cluster list
+   */
+  def String showClusterList() {
+    getClusters().items.collect { "[$it.Clusters.cluster_id] $it.Clusters.cluster_name:$it.Clusters.version" }.join("\n")
+  }
+
+  /**
+   * Returns the task properties as Map.
+   *
+   * @param request request id; default is 1
+   * @return property Map or empty Map
+   */
+  def getTasks(request = 1) {
+    getAllResources("requests/$request", "tasks/Tasks")
+  }
+
+  /**
+   * Returns a pre-formatted task list.
+   *
+   * @param request request id; default is 1
+   * @return pre-formatted task list
+   */
+  def String showTaskList(request = 1) {
+    getTasks(request)?.tasks.collect { "${it.Tasks.command_detail.padRight(PAD)} [${it.Tasks.status}]" }.join("\n")
+  }
+
+  /**
+   * Returns a Map containing the task's command detail as key and the task's status as value.
+   *
+   * @param request request id; default is 1
+   * @return key task command detail; task value status
+   */
+  def Map<String, String> getTaskMap(request = 1) {
+    def result = getTasks(request).tasks?.collectEntries { [(it.Tasks.command_detail): it.Tasks.status] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the available host names and its states.
+   *
+   * @return hostname state association
+   */
+  def Map<String, String> getHostNames() {
+    getHosts().items.collectEntries { [(it.Hosts.host_name): it.Hosts.host_status] }
+  }
+
+  /**
+   * Returns a pre-formatted list of the hosts.
+   *
+   * @return pre-formatted String
+   */
+  def String showHostList() {
+    getHosts().items.collect { "$it.Hosts.host_name [$it.Hosts.host_status] $it.Hosts.ip $it.Hosts.os_type:$it.Hosts.os_arch" }.join("\n")
+  }
+
+  /**
+   * Returns a pre-formatted list of the service components.
+   *
+   * @return pre-formatted String
+   */
+  def String showServiceComponents() {
+    getServices().items.collect {
+      def name = it.ServiceInfo.service_name
+      def state = it.ServiceInfo.state
+      def componentList = getServiceComponents(name).items.collect {
+        "    ${it.ServiceComponentInfo.component_name.padRight(PAD)}  [$it.ServiceComponentInfo.state]"
+      }.join("\n")
+      "${name.padRight(PAD)} [$state]\n$componentList"
+    }.join("\n")
+  }
+
+  /**
+   * Returns the service components properties as Map where the key is the service name and
+   * value is a component - state association.
+   *
+   * @return service name - [component name - status]
+   */
+  def Map<String, Map<String, String>> getServiceComponentsMap() {
+    def result = getServices().items.collectEntries {
+      def name = it.ServiceInfo.service_name
+      def componentList = getServiceComponents(name).items.collectEntries { [(it.ServiceComponentInfo.component_name): it.ServiceComponentInfo.state] }
+      [(name): componentList]
+    }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted service list.
+   *
+   * @return formatted String
+   */
+  def String showServiceList() {
+    getServices().items.collect { "${it.ServiceInfo.service_name.padRight(PAD)} [$it.ServiceInfo.state]" }.join("\n")
+  }
+
+  /**
+   * Returns the services properties as Map where the key is the service's name and the values is the service's state.
+   *
+   * @return service name - service state association as Map
+   */
+  def Map<String, String> getServicesMap() {
+    def result = getServices().items.collectEntries { [(it.ServiceInfo.service_name): it.ServiceInfo.state] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted component list of a host.
+   *
+   * @param host which host's components are requested
+   * @return formatted String
+   */
+  def String showHostComponentList(host) {
+    getHostComponents(host).items.collect { "${it.HostRoles.component_name.padRight(PAD)} [$it.HostRoles.state]" }.join("\n")
+  }
+
+  /**
+   * Returns the host's components as Map where the key is the component's name and values is its state.
+   *
+   * @param host which host's components are requested
+   * @return component name - state association
+   */
+  def Map<String, String> getHostComponentsMap(host) {
+    def result = getHostComponents(host).items?.collectEntries { [(it.HostRoles.component_name): it.HostRoles.state] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the blueprint json as String.
+   *
+   * @param id id of the blueprint
+   * @return json as String, exception if thrown is it fails
+   */
+  def String getBlueprintAsJson(id) {
+    return getRequest("blueprints/$id", "host_groups,Blueprints")
+  }
+
+  private def getAllResources(resourceName, fields) {
+    slurp("clusters/${getClusterName()}/$resourceName", "$fields/*")
+  }
+
+  /**
+   * Posts the blueprint JSON to Ambari with name 'bp' in the URL
+   * because it does not matter here. The blueprint's name is
+   * provided in the JSON.
+   *
+   * @param blueprint json
+   * @return response message
+   */
+  private void postBlueprint(String blueprint) {
+    ambari.post(path: "blueprints/bp", body: blueprint, { it })
+  }
+
+  private def createClusterJson(String name, Map hostGroups) {
+    def builder = new JsonBuilder()
+    def groups = hostGroups.collect {
+      def hostList = it.value.collect { ['fqdn': it] }
+      [name: it.key, hosts: hostList]
+    }
+    builder { "blueprint" name; "host-groups" groups }
+    builder.toPrettyString()
+  }
+
+  private def slurp(path, fields = "") {
+    def baseUri = ambari.getUri();
+    if (debugEnabled) {
+      println "[DEBUG] ${baseUri}${path}?fields=$fields"
+    }
+    def result = new HashMap()
+    try {
+      result = slurper.parseText(getRequest(path, fields))
+    } catch (e) {
+      if (e instanceof NoHttpResponseException || e instanceof ConnectException || e instanceof ClientProtocolException ||
+        e instanceof UnknownHostException || (e instanceof HttpResponseException && e.message == "Bad credentials")) {
+        throw new AmbariConnectionException("Cannot connect to Ambari $baseUri")
+      }
+      LOGGER.error("Error occurred during GET request to $baseUri/$path", e)
+    }
+    return result
+  }
+
+  /**
+   * Return the blueprint's properties as a Map.
+   *
+   * @param id id of the blueprint
+   * @return properties as Map
+   */
+  private def getBlueprint(id) {
+    slurp("blueprints/$id", "host_groups,Blueprints")
+  }
+
+  /**
+   * Returns a Map containing the blueprint's properties parsed from the Ambari response json.
+   *
+   * @return blueprint's properties as Map or empty Map
+   */
+  private def getBlueprints() {
+    slurp("blueprints", "Blueprints")
+  }
+
+  /**
+   * Returns a Map containing the cluster's properties parsed from the Ambari response json.
+   *
+   * @return cluster's properties as Map or empty Map
+   */
+  private def getClusters() {
+    slurp("clusters", "Clusters")
+  }
+
+  /**
+   * Returns the available hosts properties as a Map.
+   *
+   * @return Map containing the hosts properties
+   */
+  private def getHosts() {
+    slurp("hosts", "Hosts")
+  }
+
+  /**
+   * Returns the service components properties as Map.
+   *
+   * @param service id of the service
+   * @return service component properties as Map
+   */
+  private def getServiceComponents(service) {
+    getAllResources("services/$service/components", "ServiceComponentInfo")
+  }
+
+  /**
+   * Returns the services properties as Map parsed from Ambari response json.
+   *
+   * @return service properties as Map
+   */
+  private def getServices() {
+    getAllResources("services", "ServiceInfo")
+  }
+
+  /**
+   * Returns the properties of the host components as a Map parsed from the Ambari response json.
+   *
+   * @param host which host's components are requested
+   * @return component properties as Map
+   */
+  private def getHostComponents(host) {
+    getAllResources("hosts/$host/host_components", "HostRoles")
+  }
+
+  private String getRequest(path, fields = "") {
+    if (fields) {
+      ambari.get(path: "$path", query: ['fields': "$fields"]).data.text
+    } else {
+      ambari.get(path: "$path").data.text
+    }
+  }
+
+  private String getResourceContent(name) {
+    getClass().getClassLoader().getResourceAsStream(name)?.text
+  }
+}

+ 27 - 0
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariConnectionException.groovy

@@ -0,0 +1,27 @@
+/**
+ * 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
+
+/**
+ * Exception used to indicate that there are connection issues.
+ */
+class AmbariConnectionException extends RuntimeException {
+  public AmbariConnectionException(String s) {
+    super(s)
+  }
+}

+ 26 - 0
ambari-client/groovy-client/src/main/java/org/apache/ambari/groovy/client/DoNotDeleteMe.java

@@ -0,0 +1,26 @@
+/**
+ * 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;
+
+/**
+ * The maven plugin will only compile the groovy classes into the jar
+ * if it contains at least one java class, otherwise it will tell you that
+ * there are no source classes found.
+ */
+public class DoNotDeleteMe {
+}

+ 57 - 0
ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn

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

+ 48 - 0
ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn

@@ -0,0 +1,48 @@
+{
+  "host_groups" : [
+    {
+      "name" : "host_group_1",
+      "components" : [
+      {
+        "name" : "NAMENODE"
+      },
+      {
+        "name" : "SECONDARY_NAMENODE"
+      },       
+      {
+        "name" : "DATANODE"
+      },
+      {
+        "name" : "HDFS_CLIENT"
+      },
+      {
+        "name" : "RESOURCEMANAGER"
+      },
+      {
+        "name" : "NODEMANAGER"
+      },
+      {
+        "name" : "YARN_CLIENT"
+      },
+      {
+        "name" : "HISTORYSERVER"
+      },
+      {
+        "name" : "MAPREDUCE2_CLIENT"
+      },
+      {
+        "name" : "ZOOKEEPER_SERVER"
+      },
+      {
+        "name" : "ZOOKEEPER_CLIENT"
+      }
+      ],
+      "cardinality" : "1"
+    }
+  ],
+  "Blueprints" : {
+    "blueprint_name" : "single-node-hdfs-yarn",
+    "stack_name" : "HDP",
+    "stack_version" : "2.0"
+  }
+}

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

@@ -0,0 +1,425 @@
+/**
+ * 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
+  }
+}

+ 27 - 0
ambari-client/groovy-client/src/test/java/org/apache/ambari/groovy/client/NeitherMe.java

@@ -0,0 +1,27 @@
+/**
+ * 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;
+
+/**
+ * The maven plugin will only run the tests if it contains
+ * at least one java class.
+ *
+ * @see org.apache.ambari.groovy.client.DoNotDeleteMe
+ */
+public class NeitherMe {
+}

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

@@ -0,0 +1,48 @@
+{
+  "host_groups" : [
+    {
+      "name" : "host_group_1",
+      "components" : [
+      {
+        "name" : "NAMENODE"
+      },
+      {
+        "name" : "SECONDARY_NAMENODE"
+      },       
+      {
+        "name" : "DATANODE"
+      },
+      {
+        "name" : "HDFS_CLIENT"
+      },
+      {
+        "name" : "RESOURCEMANAGER"
+      },
+      {
+        "name" : "NODEMANAGER"
+      },
+      {
+        "name" : "YARN_CLIENT"
+      },
+      {
+        "name" : "HISTORYSERVER"
+      },
+      {
+        "name" : "MAPREDUCE2_CLIENT"
+      },
+      {
+        "name" : "ZOOKEEPER_SERVER"
+      },
+      {
+        "name" : "ZOOKEEPER_CLIENT"
+      }
+      ],
+      "cardinality" : "1"
+    }
+  ],
+  "Blueprints" : {
+    "blueprint_name" : "single-node-hdfs-yarn",
+    "stack_name" : "HDP",
+    "stack_version" : "2.0"
+  }
+}

+ 21 - 0
ambari-client/groovy-client/src/test/resources/blueprints.json

@@ -0,0 +1,21 @@
+{
+  "href" : "http://localhost:49233/api/v1/blueprints?fields=Blueprints",
+  "items" : [
+    {
+      "href" : "http://localhost:49233/api/v1/blueprints/multi-node-hdfs-yarn",
+      "Blueprints" : {
+        "blueprint_name" : "multi-node-hdfs-yarn",
+        "stack_name" : "HDP",
+        "stack_version" : "2.0"
+      }
+    },
+    {
+      "href" : "http://localhost:49233/api/v1/blueprints/single-node-hdfs-yarn",
+      "Blueprints" : {
+        "blueprint_name" : "single-node-hdfs-yarn",
+        "stack_name" : "HDP",
+        "stack_version" : "2.0"
+      }
+    }
+  ]
+}

+ 16 - 0
ambari-client/groovy-client/src/test/resources/cluster.json

@@ -0,0 +1,16 @@
+{
+    "blueprint": "c1",
+    "host-groups": [
+        {
+            "name": "host_group_1",
+            "hosts": [
+                {
+                    "fqdn": "server.ambari.com"
+                },
+                {
+                    "fqdn": "server2.ambari.com"
+                }
+            ]
+        }
+    ]
+}

+ 63 - 0
ambari-client/groovy-client/src/test/resources/hdfsServiceComponents.json

@@ -0,0 +1,63 @@
+{
+  "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components?fields=ServiceComponentInfo/*",
+  "items" : [
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/DATANODE",
+      "ServiceComponentInfo" : {
+        "category" : "SLAVE",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "DATANODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/HDFS_CLIENT",
+      "ServiceComponentInfo" : {
+        "category" : "CLIENT",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "HDFS_CLIENT",
+        "service_name" : "HDFS",
+        "state" : "INSTALLED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/NAMENODE",
+      "ServiceComponentInfo" : {
+        "CapacityRemaining" : 31924920320,
+        "CapacityTotal" : 39078707200,
+        "CapacityUsed" : 24576,
+        "DeadNodes" : "{}",
+        "DecomNodes" : "{}",
+        "HeapMemoryMax" : 1052770304,
+        "HeapMemoryUsed" : 100962448,
+        "LiveNodes" : "{\"server.ambari.com\":{\"infoAddr\":\"172.17.0.2:50075\",\"infoSecureAddr\":\"172.17.0.2:0\",\"xferaddr\":\"172.17.0.2:50010\",\"lastContact\":0,\"usedSpace\":24576,\"adminState\":\"In Service\",\"nonDfsUsedSpace\":7153762304,\"capacity\":39078707200,\"numBlocks\":0,\"version\":\"2.4.0.2.1.1.0-385\",\"used\":24576,\"remaining\":31924920320,\"blockScheduled\":0,\"blockPoolUsed\":24576,\"blockPoolUsedPercent\":6.2888466E-5,\"volfails\":0}}",
+        "NonDfsUsedSpace" : 7153762304,
+        "NonHeapMemoryMax" : 136314880,
+        "NonHeapMemoryUsed" : 33860904,
+        "PercentRemaining" : 81.6939,
+        "PercentUsed" : 6.2888466E-5,
+        "Safemode" : "",
+        "StartTime" : 1400572832732,
+        "TotalFiles" : 10,
+        "UpgradeFinalized" : true,
+        "Version" : "2.4.0.2.1.1.0-385, r68ceccf06a4441273e81a5ec856d41fc7e11c792",
+        "category" : "MASTER",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/SECONDARY_NAMENODE",
+      "ServiceComponentInfo" : {
+        "category" : "MASTER",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "SECONDARY_NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    }
+  ]
+}

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

@@ -0,0 +1,14 @@
+{
+  "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services?fields=ServiceInfo/*",
+  "items" : [
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS",
+      "ServiceInfo" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    }
+  ]
+}

文件差异内容过多而无法显示
+ 24 - 0
ambari-client/groovy-client/src/test/resources/tasks.json


+ 8 - 143
ambari-client/pom.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <!--
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -28,31 +29,19 @@
   <packaging>pom</packaging>
   <version>1.3.0-SNAPSHOT</version>
   <name>Ambari Client</name>
-  <description>Ambari Python client</description>
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <final.name>${project.artifactId}-${project.version}</final.name>
-    <package.release>1</package.release>
-    <package.prefix>/usr</package.prefix>
-    <package.log.dir>/var/log/ambari-client</package.log.dir>
-    <package.pid.dir>/var/run/ambari-client</package.pid.dir>
-    <skipTests>false</skipTests>
-    <install.dir>/usr/lib/python2.6/site-packages/ambari_client</install.dir>
-    <lib.dir>/usr/lib/ambari-client/lib</lib.dir>
-    <python.ver>python &gt;= 2.6</python.ver>
-  </properties>
+  <description>Ambari client</description>
+  <modules>
+    <module>python-client</module>
+    <module>groovy-client</module>
+  </modules>
   <build>
     <plugins>
-      <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.0</version>
-      </plugin>
       <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
           <tarLongFileMode>gnu</tarLongFileMode>
           <descriptors>
-            <descriptor>src/packages/tarball/all.xml</descriptor>
+            <descriptor>assemblies/client.xml</descriptor>
           </descriptors>
         </configuration>
         <executions>
@@ -65,130 +54,6 @@
           </execution>
         </executions>
       </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>exec-maven-plugin</artifactId>
-        <version>1.2</version>
-        <executions>
-          <execution>
-            <configuration>
-              <executable>${project.basedir}/../ambari-common/src/main/unix/ambari-python-wrap</executable>
-              <workingDirectory>src/test/python</workingDirectory>
-              <arguments>
-                <argument>unitTests.py</argument>
-              </arguments>
-              <environmentVariables>
-                <PYTHONPATH>${project.basedir}/../ambari-common/src/test/python:${project.basedir}/src/main/python/ambari_client:${project.basedir}/src/test/python/utils:$PYTHONPATH</PYTHONPATH>
-              </environmentVariables>
-              <skip>${skipTests}</skip>
-            </configuration>
-            <id>python-test</id>
-            <phase>test</phase>
-            <goals>
-              <goal>exec</goal>
-            </goals>
-          </execution>
-          <execution>
-            <configuration>
-              <executable>${project.basedir}/../ambari-common/src/main/unix/ambari-python-wrap</executable>
-              <workingDirectory>target/ambari-client-${project.version}</workingDirectory>
-              <arguments>
-                <argument>${project.basedir}/src/main/python/setup.py</argument>
-                <argument>clean</argument>
-                <argument>bdist_dumb</argument>
-              </arguments>
-              <environmentVariables>
-                <PYTHONPATH>target/ambari-client-${project.version}:$PYTHONPATH</PYTHONPATH>
-              </environmentVariables>
-            </configuration>
-            <id>python-package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>exec</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>rpm-maven-plugin</artifactId>
-        <version>2.1-alpha-2</version>
-        <executions>
-          <execution>
-            <phase>none</phase>
-            <goals>
-              <goal>rpm</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-          <copyright>2012, Apache Software Foundation</copyright>
-          <group>Development</group>
-          <description>Maven Recipe: RPM Package.</description>
-          <requires>
-            <require>openssl</require>
-            <require>zlib</require>
-            <require>${python.ver}</require>
-          </requires>
-          <needarch>x86_64</needarch>
-          <autoRequires>false</autoRequires>
-          <mappings>
-          <mapping>
-              <directory>${install.dir}</directory>
-              <sources>
-                <source>
-                  <location>${project.build.directory}/${project.artifactId}-${project.version}/ambari_client</location>
-                </source>
-              </sources>
-            </mapping>
-            <!-- -->
-          </mappings>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.rat</groupId>
-        <artifactId>apache-rat-plugin</artifactId>
-        <configuration>
-          <excludes>
-            <exclude>src/examples/*</exclude>
-            <exclude>src/test/python/dummy*.txt</exclude>
-            <exclude>src/main/python/ambari_client/imports.txt</exclude>
-            <exclude>**/*.erb</exclude>
-            <exclude>**/*.json</exclude>
-          </excludes>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>test</phase>
-            <goals>
-              <goal>check</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.vafer</groupId>
-        <artifactId>jdeb</artifactId>
-        <version>1.0.1</version>
-        <executions>
-          <execution>
-            <phase>none</phase>
-            <goals>
-              <goal>jdeb</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-           <skip>true</skip>
-           <submodules>false</submodules>
-         </configuration>
-      </plugin>
     </plugins>
-    <extensions>
-      <extension>
-        <groupId>org.apache.maven.wagon</groupId>
-        <artifactId>wagon-ssh-external</artifactId>
-      </extension>
-    </extensions>
   </build>
 </project>

+ 195 - 0
ambari-client/python-client/pom.xml

@@ -0,0 +1,195 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+   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 Licenseam for the specific language governing permissions and
+   limitations under the License.
+-->
+  <parent>
+    <groupId>org.apache.ambari</groupId>
+    <artifactId>ambari-client</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../../ambari-client</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>python-client</artifactId>
+  <packaging>pom</packaging>
+  <version>1.3.0-SNAPSHOT</version>
+  <name>Ambari Python Client</name>
+  <description>Ambari Python client</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <final.name>${project.artifactId}-${project.version}</final.name>
+    <package.release>1</package.release>
+    <package.prefix>/usr</package.prefix>
+    <package.log.dir>/var/log/python-client</package.log.dir>
+    <package.pid.dir>/var/run/python-client</package.pid.dir>
+    <skipTests>false</skipTests>
+    <install.dir>/usr/lib/python2.6/site-packages/ambari_client</install.dir>
+    <lib.dir>/usr/lib/ambari-client/lib</lib.dir>
+    <python.ver>python &gt;= 2.6</python.ver>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.0</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <tarLongFileMode>gnu</tarLongFileMode>
+          <descriptors>
+            <descriptor>src/packages/tarball/all.xml</descriptor>
+          </descriptors>
+        </configuration>
+        <executions>
+          <execution>
+            <id>build-tarball</id>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.2</version>
+        <executions>
+          <execution>
+            <configuration>
+              <executable>python2.6</executable>
+              <workingDirectory>src/test/python</workingDirectory>
+              <arguments>
+                <argument>unitTests.py</argument>
+              </arguments>
+              <environmentVariables>
+                <PYTHONPATH>${project.basedir}/../../ambari-common/src/test/python:${project.basedir}/src/main/python/ambari_client:${project.basedir}/src/test/python/utils:$PYTHONPATH</PYTHONPATH>
+              </environmentVariables>
+              <skip>${skipTests}</skip>
+            </configuration>
+            <id>python-test</id>
+            <phase>test</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>
+          <execution>
+            <configuration>
+              <executable>python2.6</executable>
+              <workingDirectory>target/python-client-${project.version}</workingDirectory>
+              <arguments>
+                <argument>${project.basedir}/src/main/python/setup.py</argument>
+                <argument>clean</argument>
+                <argument>bdist_dumb</argument>
+              </arguments>
+              <environmentVariables>
+                <PYTHONPATH>target/python-client-${project.version}:$PYTHONPATH</PYTHONPATH>
+              </environmentVariables>
+            </configuration>
+            <id>python-package</id>
+            <phase>package</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rpm-maven-plugin</artifactId>
+        <version>2.1-alpha-2</version>
+        <executions>
+          <execution>
+            <phase>none</phase>
+            <goals>
+              <goal>rpm</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <copyright>2012, Apache Software Foundation</copyright>
+          <group>Development</group>
+          <description>Maven Recipe: RPM Package.</description>
+          <requires>
+            <require>openssl</require>
+            <require>zlib</require>
+            <require>${python.ver}</require>
+          </requires>
+          <needarch>x86_64</needarch>
+          <autoRequires>false</autoRequires>
+          <mappings>
+            <mapping>
+              <directory>${install.dir}</directory>
+              <sources>
+                <source>
+                  <location>${project.build.directory}/${project.artifactId}-${project.version}/ambari_client</location>
+                </source>
+              </sources>
+            </mapping>
+            <!-- -->
+          </mappings>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/examples/*</exclude>
+            <exclude>src/test/python/dummy*.txt</exclude>
+            <exclude>src/main/python/ambari_client/imports.txt</exclude>
+            <exclude>src/main/puppet/modules/stdlib/**</exclude>
+            <exclude>**/*.erb</exclude>
+            <exclude>**/*.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.vafer</groupId>
+        <artifactId>jdeb</artifactId>
+        <version>1.0.1</version>
+        <executions>
+          <execution>
+            <phase>none</phase>
+            <goals>
+              <goal>jdeb</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <skip>true</skip>
+          <submodules>false</submodules>
+        </configuration>
+      </plugin>
+    </plugins>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+        <artifactId>wagon-ssh-external</artifactId>
+      </extension>
+    </extensions>
+  </build>
+</project>

+ 0 - 0
ambari-client/src/examples/create.py → ambari-client/python-client/src/examples/create.py


+ 0 - 0
ambari-client/src/examples/delete.py → ambari-client/python-client/src/examples/delete.py


+ 0 - 0
ambari-client/src/examples/example.py → ambari-client/python-client/src/examples/example.py


+ 0 - 0
ambari-client/src/examples/main.py → ambari-client/python-client/src/examples/main.py


+ 0 - 0
ambari-client/src/examples/trial.py → ambari-client/python-client/src/examples/trial.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/__init__.py → ambari-client/python-client/src/main/python/ambari_client/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/ambari_api.py → ambari-client/python-client/src/main/python/ambari_client/ambari_api.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/__init__.py → ambari-client/python-client/src/main/python/ambari_client/core/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/coreutils.py → ambari-client/python-client/src/main/python/ambari_client/core/coreutils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/errors.py → ambari-client/python-client/src/main/python/ambari_client/core/errors.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/http_client.py → ambari-client/python-client/src/main/python/ambari_client/core/http_client.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/http_utils.py → ambari-client/python-client/src/main/python/ambari_client/core/http_utils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/rest_resource.py → ambari-client/python-client/src/main/python/ambari_client/core/rest_resource.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/__init__.py → ambari-client/python-client/src/main/python/ambari_client/model/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/base_model.py → ambari-client/python-client/src/main/python/ambari_client/model/base_model.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/cluster.py → ambari-client/python-client/src/main/python/ambari_client/model/cluster.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/component.py → ambari-client/python-client/src/main/python/ambari_client/model/component.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/configuration.py → ambari-client/python-client/src/main/python/ambari_client/model/configuration.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/host.py → ambari-client/python-client/src/main/python/ambari_client/model/host.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/paths.py → ambari-client/python-client/src/main/python/ambari_client/model/paths.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/service.py → ambari-client/python-client/src/main/python/ambari_client/model/service.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/stack.py → ambari-client/python-client/src/main/python/ambari_client/model/stack.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/status.py → ambari-client/python-client/src/main/python/ambari_client/model/status.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/utils.py → ambari-client/python-client/src/main/python/ambari_client/model/utils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/__init__.py → ambari-client/python-client/src/main/python/ambari_client/resources/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/clusters.py → ambari-client/python-client/src/main/python/ambari_client/resources/clusters.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/hosts.py → ambari-client/python-client/src/main/python/ambari_client/resources/hosts.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/stacks.py → ambari-client/python-client/src/main/python/ambari_client/resources/stacks.py


+ 0 - 0
ambari-client/src/main/python/setup.py → ambari-client/python-client/src/main/python/setup.py


+ 0 - 0
ambari-client/src/packages/tarball/all.xml → ambari-client/python-client/src/packages/tarball/all.xml


+ 0 - 0
ambari-client/src/test/python/TestAmbariClient.py → ambari-client/python-client/src/test/python/TestAmbariClient.py


+ 0 - 0
ambari-client/src/test/python/TestClusterModel.py → ambari-client/python-client/src/test/python/TestClusterModel.py


+ 0 - 0
ambari-client/src/test/python/TestComponentModel.py → ambari-client/python-client/src/test/python/TestComponentModel.py


+ 0 - 0
ambari-client/src/test/python/TestHostModel.py → ambari-client/python-client/src/test/python/TestHostModel.py


+ 0 - 0
ambari-client/src/test/python/TestServiceModel.py → ambari-client/python-client/src/test/python/TestServiceModel.py


+ 0 - 0
ambari-client/src/test/python/TestStatusModel.py → ambari-client/python-client/src/test/python/TestStatusModel.py


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_bootstrap_hosts.json → ambari-client/python-client/src/test/python/json/ambariclient_bootstrap_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_all_clusters.json → ambari-client/python-client/src/test/python/json/ambariclient_get_all_clusters.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_all_hosts.json → ambari-client/python-client/src/test/python/json/ambariclient_get_all_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_components.json → ambari-client/python-client/src/test/python/json/ambariclient_get_components.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_config.json → ambari-client/python-client/src/test/python/json/ambariclient_get_config.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_host.json → ambari-client/python-client/src/test/python/json/ambariclient_get_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_error_deleting_host.json → ambari-client/python-client/src/test/python/json/clustermodel_error_deleting_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_all_hosts.json → ambari-client/python-client/src/test/python/json/clustermodel_get_all_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_all_services.json → ambari-client/python-client/src/test/python/json/clustermodel_get_all_services.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_cluster.json → ambari-client/python-client/src/test/python/json/clustermodel_get_cluster.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_core_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_core_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_global_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_global_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_hdfs_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_hdfs_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_host.json → ambari-client/python-client/src/test/python/json/clustermodel_get_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_mapred_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_mapred_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_service.json → ambari-client/python-client/src/test/python/json/clustermodel_get_service.json


+ 0 - 0
ambari-client/src/test/python/json/componentmodel_get_metrics.json → ambari-client/python-client/src/test/python/json/componentmodel_get_metrics.json


+ 0 - 0
ambari-client/src/test/python/json/get_cluster_service.json → ambari-client/python-client/src/test/python/json/get_cluster_service.json


+ 0 - 0
ambari-client/src/test/python/json/get_components_from_stack.json → ambari-client/python-client/src/test/python/json/get_components_from_stack.json


+ 0 - 0
ambari-client/src/test/python/json/get_host_component.json → ambari-client/python-client/src/test/python/json/get_host_component.json


+ 0 - 0
ambari-client/src/test/python/json/get_host_components.json → ambari-client/python-client/src/test/python/json/get_host_components.json


+ 0 - 0
ambari-client/src/test/python/json/get_service_component.json → ambari-client/python-client/src/test/python/json/get_service_component.json


+ 0 - 0
ambari-client/src/test/python/json/get_service_components.json → ambari-client/python-client/src/test/python/json/get_service_components.json


+ 0 - 0
ambari-client/src/test/python/json/hostmodel_get_host_component.json → ambari-client/python-client/src/test/python/json/hostmodel_get_host_component.json


+ 0 - 0
ambari-client/src/test/python/json/hostmodel_get_host_components.json → ambari-client/python-client/src/test/python/json/hostmodel_get_host_components.json


+ 0 - 0
ambari-client/src/test/python/json/servicemodel_get_component.json → ambari-client/python-client/src/test/python/json/servicemodel_get_component.json


+ 0 - 0
ambari-client/src/test/python/json/servicemodel_get_components.json → ambari-client/python-client/src/test/python/json/servicemodel_get_components.json


+ 0 - 0
ambari-client/src/test/python/json/status_error_with_message.json → ambari-client/python-client/src/test/python/json/status_error_with_message.json


+ 0 - 0
ambari-client/src/test/python/json/status_ok_with_id.json → ambari-client/python-client/src/test/python/json/status_ok_with_id.json


+ 0 - 0
ambari-client/src/test/python/unitTests.py → ambari-client/python-client/src/test/python/unitTests.py


+ 0 - 0
ambari-client/src/test/python/utils/HttpClientInvoker.py → ambari-client/python-client/src/test/python/utils/HttpClientInvoker.py


+ 58 - 14
ambari-project/pom.xml

@@ -12,7 +12,8 @@
   See the License for the specific language governing permissions and
   limitations under the License. See accompanying LICENSE file.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0                       http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0                       http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>org.apache.ambari</groupId>
@@ -46,6 +47,14 @@
       <id>EclipseLink</id>
       <url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
     </repository>
+    <repository>
+      <id>spring-milestones</id>
+      <name>Spring Milestones</name>
+      <url>http://repo.spring.io/milestone</url>
+      <snapshots>
+        <enabled>false</enabled>
+      </snapshots>
+    </repository>
   </repositories>
   <profiles>
     <profile>
@@ -102,7 +111,7 @@
         <artifactId>guice-servlet</artifactId>
         <version>3.0</version>
       </dependency>
-       <dependency>
+      <dependency>
         <groupId>org.codehaus.jettison</groupId>
         <artifactId>jettison</artifactId>
         <version>1.1</version>
@@ -164,19 +173,19 @@
         <version>1.3.1.RELEASE</version>
       </dependency>
       <dependency>
-      <groupId>org.apache.directory.server</groupId>
-      <artifactId>apacheds-core</artifactId>
-      <version>1.5.5</version>
+        <groupId>org.apache.directory.server</groupId>
+        <artifactId>apacheds-core</artifactId>
+        <version>1.5.5</version>
       </dependency>
       <dependency>
-      <groupId>org.apache.directory.server</groupId>
-      <artifactId>apacheds-protocol-ldap</artifactId>
-      <version>1.5.5</version>
+        <groupId>org.apache.directory.server</groupId>
+        <artifactId>apacheds-protocol-ldap</artifactId>
+        <version>1.5.5</version>
       </dependency>
       <dependency>
-      <groupId>org.apache.directory.shared</groupId>
-      <artifactId>shared-ldap</artifactId>
-      <version>0.9.17</version>
+        <groupId>org.apache.directory.shared</groupId>
+        <artifactId>shared-ldap</artifactId>
+        <version>0.9.17</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
@@ -213,7 +222,7 @@
         <artifactId>mockito-core</artifactId>
         <version>1.8.5</version>
       </dependency>
-       <dependency>
+      <dependency>
         <groupId>org.eclipse.jetty</groupId>
         <artifactId>jetty-server</artifactId>
         <version>7.6.7.v20120910</version>
@@ -366,6 +375,41 @@
         <artifactId>junit</artifactId>
         <version>4.10</version>
       </dependency>
+      <dependency>
+        <groupId>org.springframework.shell</groupId>
+        <artifactId>spring-shell</artifactId>
+        <version>1.1.0.RC3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter</artifactId>
+        <version>1.0.2.RELEASE</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-test</artifactId>
+        <version>1.0.2.RELEASE</version>
+      </dependency>
+      <dependency>
+        <groupId>com.github.lalyos</groupId>
+        <artifactId>jfiglet</artifactId>
+        <version>0.0.3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.codehaus.groovy</groupId>
+        <artifactId>groovy-all</artifactId>
+        <version>2.1.8</version>
+      </dependency>
+      <dependency>
+        <groupId>org.codehaus.groovy.modules.http-builder</groupId>
+        <artifactId>http-builder</artifactId>
+        <version>0.7.1</version>
+      </dependency>
+      <dependency>
+        <groupId>jline</groupId>
+        <artifactId>jline</artifactId>
+        <version>2.11</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <build>
@@ -418,9 +462,9 @@
           </execution>
         </executions>
         <configuration>
-            <controlDir>${basedir}/src/main/package/deb/control</controlDir>
+          <controlDir>${basedir}/src/main/package/deb/control</controlDir>
         </configuration>
-        </plugin>
+      </plugin>
     </plugins>
   </build>
 </project>

+ 120 - 0
ambari-shell/pom.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!-- Licensed 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.
+  See accompanying LICENSE file. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.apache.ambari</groupId>
+    <artifactId>ambari-project</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../ambari-project</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>ambari-shell</artifactId>
+  <packaging>jar</packaging>
+  <name>Ambari Shell</name>
+  <version>1.3.0-SNAPSHOT</version>
+  <description>Ambari Shell</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <start-class>org.apache.ambari.shell.AmbariShell</start-class>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.shell</groupId>
+      <artifactId>spring-shell</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.github.lalyos</groupId>
+      <artifactId>jfiglet</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ambari</groupId>
+      <artifactId>groovy-client</artifactId>
+      <version>1.3.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>jline</groupId>
+      <artifactId>jline</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.0</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/main/resources/elephant.txt</exclude>
+            <exclude>src/test/resources/2columns</exclude>
+            <exclude>src/test/resources/3columns</exclude>
+            <exclude>src/test/resources/testBlueprint.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <skipAssembly>true</skipAssembly>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>1.0.2.RELEASE</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <mainClass>${start-class}</mainClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

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

@@ -0,0 +1,110 @@
+/**
+ * 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;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell.CommandLine;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.shell.event.ShellStatus;
+import org.springframework.shell.event.ShellStatusListener;
+
+/**
+ * Shell bootstrap.
+ */
+@Configuration
+@ComponentScan(basePackageClasses = {AmbariShell.class})
+public class AmbariShell implements CommandLineRunner, ShellStatusListener {
+
+  @Autowired
+  private CommandLine commandLine;
+  @Autowired
+  private JLineShellComponent shell;
+  @Autowired
+  private AmbariContext context;
+  @Autowired
+  private AmbariClient client;
+
+  @Override
+  public void run(String... arg) throws Exception {
+    String[] shellCommandsToExecute = commandLine.getShellCommandsToExecute();
+    if (shellCommandsToExecute != null) {
+      for (String cmd : shellCommandsToExecute) {
+        if (!shell.executeScriptLine(cmd)) {
+          break;
+        }
+      }
+    } else {
+      shell.addShellStatusListener(this);
+      shell.start();
+      shell.promptLoop();
+      shell.waitForComplete();
+    }
+  }
+
+  @Override
+  public void onShellStatusChange(ShellStatus oldStatus, ShellStatus newStatus) {
+    if (newStatus.getStatus() == ShellStatus.Status.STARTED) {
+      try {
+        String cluster = client.getClusterName();
+        boolean available = client.isBlueprintAvailable();
+        if (cluster == null) {
+          if (available) {
+            context.setHint(Hints.BUILD_CLUSTER);
+          } else {
+            context.setHint(Hints.ADD_BLUEPRINT);
+          }
+        } else {
+          context.setHint(Hints.PROGRESS);
+        }
+        context.setCluster(cluster);
+        context.setBlueprintsAvailable(available);
+      } catch (Exception e) {
+        System.out.println(e.getMessage());
+        shell.executeCommand("quit");
+      }
+    }
+  }
+
+
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.out.println(
+        "\nAmbari Shell: Interactive command line tool for managing Apache Ambari.\n\n" +
+          "Usage:\n" +
+          "  java -jar ambari-shell.jar                  : Starts Ambari Shell in interactive mode.\n" +
+          "  java -jar ambari-shell.jar --cmdfile=<FILE> : Ambari Shell executes commands read from the file.\n\n" +
+          "Options:\n" +
+          "  --ambari.host=<HOSTNAME>       Hostname of the Ambari Server [default: localhost].\n" +
+          "  --ambari.port=<PORT>           Port of the Ambari Server [default: 8080].\n" +
+          "  --ambari.user=<USER>           Username of the Ambari admin [default: admin].\n" +
+          "  --ambari.password=<PASSWORD>   Password of the Ambari admin [default: admin].\n\n" +
+          "Note:\n" +
+          "  At least one option is mandatory."
+      );
+      System.exit(1);
+    }
+    new SpringApplicationBuilder(AmbariShell.class).showBanner(false).run(args);
+  }
+}

+ 173 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java

@@ -0,0 +1,173 @@
+/**
+ * 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.commands;
+
+import static org.apache.ambari.shell.support.TableRenderer.renderMapValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Basic commands used in the shell. Delegating the commands
+ * to the Ambari Server via a Groovy based client.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class BasicCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public BasicCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the tasks command is available or not.
+   *
+   * @return true if its available false otherwise
+   */
+  @CliAvailabilityIndicator("tasks")
+  public boolean isTasksCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the tasks of the Ambari Server.
+   *
+   * @param id id of the request
+   * @return task list
+   */
+  @CliCommand(value = "tasks", help = "Lists the Ambari tasks")
+  public String tasks(
+    @CliOption(key = "id", mandatory = false, help = "Id of the request; default is: 1", unspecifiedDefaultValue = "1") String id) {
+    return renderSingleMap(client.getTaskMap(id), "TASK", "STATUS");
+  }
+
+  /**
+   * Checks whether the service list command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("service list")
+  public boolean isServiceListCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the available service list of the Ambari Server.
+   *
+   * @return service list
+   */
+  @CliCommand(value = "service list", help = "Lists the available services")
+  public String servicesList() {
+    return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
+  }
+
+  /**
+   * Checks whether the service components command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("service components")
+  public boolean isServiceComponentsCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the service components of the Ambari Server.
+   *
+   * @return service component list
+   */
+  @CliCommand(value = "service components", help = "Lists all services with their components")
+  public String serviceComponents() {
+    return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
+  }
+
+  /**
+   * Checks whether the debug on command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("debug on")
+  public boolean isDebugOnCommandAvailable() {
+    return !client.isDebugEnabled();
+  }
+
+  /**
+   * Turns the debug on. From now on users will see the URLs of the API calls.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "debug on", help = "Shows the URL of the API calls")
+  public String debugOn() {
+    client.setDebugEnabled(true);
+    return "debug enabled";
+  }
+
+  /**
+   * Checks whether the debug off command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("debug off")
+  public boolean isDebugOffCommandAvailable() {
+    return client.isDebugEnabled();
+  }
+
+  /**
+   * Turns the debug off. URLs are not visible anymore.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "debug off", help = "Stops showing the URL of the API calls")
+  public String debugOff() {
+    client.setDebugEnabled(false);
+    return "debug disabled";
+  }
+
+  /**
+   * Checks whether the hint command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("hint")
+  public boolean isHintCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Provides some hints what you can do in the current context.
+   *
+   * @return hint message
+   */
+  @CliCommand(value = "hint", help = "Shows some hints")
+  public String hint() {
+    return context.getHint();
+  }
+}

+ 184 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java

@@ -0,0 +1,184 @@
+/**
+ * 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.commands;
+
+import static org.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Blueprint related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class BlueprintCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public BlueprintCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the blueprints command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("blueprint list")
+  public boolean isBlueprintListCommandAvailable() {
+    return context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Prints all the blueprints.
+   *
+   * @return list of blueprints
+   */
+  @CliCommand(value = "blueprint list", help = "Lists all known blueprints")
+  public String listBlueprints() {
+    return renderSingleMap(client.getBlueprintsMap(), "BLUEPRINT", "STACK");
+  }
+
+  /**
+   * Checks whether the blueprint show command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint show")
+  public boolean isBlueprintShowCommandAvailable() {
+    return context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Shows the requested blueprint's details.
+   *
+   * @param id id of the blueprint
+   * @return blueprint as formatted table
+   */
+  @CliCommand(value = "blueprint show", help = "Shows the blueprint by its id")
+  public String showBlueprint(
+    @CliOption(key = "id", mandatory = true, help = "Id of the blueprint") String id) {
+    return renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT");
+  }
+
+  /**
+   * Checks whether the blueprint add command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint add")
+  public boolean isBlueprintAddCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Adds a blueprint to the Ambari server either through an URL or from a file.
+   * If both specified the file takes precedence.
+   *
+   * @param url  -optional, URL containing the blueprint json
+   * @param file - optional, file containing the blueprint json
+   * @return status message
+   */
+  @CliCommand(value = "blueprint add", help = "Add a new blueprint with either --url or --file")
+  public String addBlueprint(
+    @CliOption(key = "url", mandatory = false, help = "URL of the blueprint to download from") String url,
+    @CliOption(key = "file", mandatory = false, help = "File which contains the blueprint") File file) {
+    String message;
+    try {
+      String json = file == null ? readContent(url) : readContent(file);
+      if (json != null) {
+        client.addBlueprint(json);
+        message = "Blueprint added";
+        context.setHint(Hints.BUILD_CLUSTER);
+        context.setBlueprintsAvailable(true);
+      } else {
+        message = "No blueprint specified";
+      }
+    } catch (Exception e) {
+      message = "Cannot add blueprint: " + e.getMessage();
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the blueprint defaults command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint defaults")
+  public boolean isBlueprintDefaultsAddCommandAvailable() {
+    return !context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Adds two default blueprints to the Ambari server.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "blueprint defaults", help = "Adds the default blueprints to Ambari")
+  public String addBlueprint() {
+    String message = "Default blueprints added";
+    try {
+      client.addDefaultBlueprints();
+      context.setHint(Hints.BUILD_CLUSTER);
+      context.setBlueprintsAvailable(true);
+    } catch (Exception e) {
+      message = "Failed to add the default blueprints: " + e.getMessage();
+    }
+    return message;
+  }
+
+  private String readContent(File file) {
+    String content = null;
+    try {
+      content = IOUtils.toString(new FileInputStream(file));
+    } catch (IOException e) {
+      // not important
+    }
+    return content;
+  }
+
+  private String readContent(String url) {
+    String content = null;
+    try {
+      content = IOUtils.toString(new URL(url));
+    } catch (IOException e) {
+      // not important
+    }
+    return content;
+  }
+}

+ 260 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java

@@ -0,0 +1,260 @@
+/**
+ * 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.commands;
+
+import static org.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.FocusType;
+import org.apache.ambari.shell.model.Hints;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+import groovyx.net.http.HttpResponseException;
+
+/**
+ * Cluster related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class ClusterCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+  private Map<String, List<String>> hostGroups;
+
+  @Autowired
+  public ClusterCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the cluster build command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster build")
+  public boolean isClusterBuildCommandAvailable() {
+    return !context.isConnectedToCluster() && !context.isFocusOnClusterBuild() && context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Sets the focus on cluster building. Takes a blueprint id, if it does not exists it wont focus.
+   * After focus the users are able to assign hosts to host groups.
+   *
+   * @param id id of the blueprint
+   * @return prints the blueprint as formatted table if exists, otherwise error message
+   */
+  @CliCommand(value = "cluster build", help = "Starts to build a cluster")
+  public String buildCluster(
+    @CliOption(key = "blueprint", mandatory = true, help = "Id of the blueprint, use 'blueprints' command to see the list") String id) {
+    String message;
+    if (client.doesBlueprintExists(id)) {
+      context.setFocus(id, FocusType.CLUSTER_BUILD);
+      context.setHint(Hints.ASSIGN_HOSTS);
+      message = String.format("%s\n%s",
+        renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
+        renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT"));
+      createNewHostGroups();
+    } else {
+      message = "Not a valid blueprint id";
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster assign command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster assign")
+  public boolean isAssignCommandAvailable() {
+    return context.isFocusOnClusterBuild();
+  }
+
+  /**
+   * Assign hosts to host groups provided in the blueprint.
+   *
+   * @param host  host to assign
+   * @param group which host group to
+   * @return status message
+   */
+  @CliCommand(value = "cluster assign", help = "Assign host to host group")
+  public String assign(
+    @CliOption(key = "host", mandatory = true, help = "Fully qualified host name") String host,
+    @CliOption(key = "hostGroup", mandatory = true, help = "Host group which to assign the host") String group) {
+    String message;
+    if (client.getHostNames().keySet().contains(host)) {
+      if (addHostToGroup(host, group)) {
+        context.setHint(Hints.CREATE_CLUSTER);
+        message = String.format("%s has been added to %s", host, group);
+      } else {
+        message = String.format("%s is not a valid host group", group);
+      }
+    } else {
+      message = String.format("%s is not a valid hostname", host);
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster preview command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster preview")
+  public boolean isClusterPreviewCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  /**
+   * Shows the currently assigned hosts.
+   *
+   * @return formatted host - host group table
+   */
+  @CliCommand(value = "cluster preview", help = "Shows the currently assigned hosts")
+  public String showAssignments() {
+    return renderMultiValueMap(hostGroups, "HOSTGROUP", "HOST");
+  }
+
+  /**
+   * Checks whether the cluster create command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster create")
+  public boolean isCreateClusterCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  /**
+   * Creates a new cluster based on the provided host - host group associations and the selected blueprint.
+   * If the cluster creation fails, deletes the cluster.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "cluster create", help = "Create a cluster based on current blueprint and assigned hosts")
+  public String createCluster() {
+    String message = "Successfully created the cluster";
+    String blueprint = context.getFocusValue();
+    try {
+      client.createCluster(blueprint, blueprint, hostGroups);
+      context.setCluster(blueprint);
+      context.resetFocus();
+      context.setHint(Hints.PROGRESS);
+    } catch (HttpResponseException e) {
+      createNewHostGroups();
+      message = "Failed to create the cluster: " + e.getMessage();
+      try {
+        deleteCluster(blueprint);
+      } catch (HttpResponseException e1) {
+        message += ". Failed to cleanup cluster creation: " + e1.getMessage();
+      }
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster delete command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster delete")
+  public boolean isDeleteClusterCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Deletes the cluster.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "cluster delete", help = "Delete the cluster")
+  public String deleteCluster() {
+    String message = "Successfully deleted the cluster";
+    try {
+      deleteCluster(context.getCluster());
+    } catch (HttpResponseException e) {
+      message = "Could not delete the cluster: " + e.getMessage();
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster reset command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "cluster reset")
+  public boolean isClusterResetCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  @CliCommand(value = "cluster reset", help = "Clears the host - host group assignments")
+  public void reset() {
+    context.setHint(Hints.ASSIGN_HOSTS);
+    createNewHostGroups();
+  }
+
+  private void deleteCluster(String id) throws HttpResponseException {
+    client.deleteCluster(id);
+  }
+
+  private void createNewHostGroups() {
+    Map<String, List<String>> groups = new HashMap<String, List<String>>();
+    for (String hostGroup : client.getHostGroups(context.getFocusValue())) {
+      groups.put(hostGroup, new ArrayList<String>());
+    }
+    this.hostGroups = groups;
+  }
+
+  private boolean addHostToGroup(String host, String group) {
+    boolean result = true;
+    List<String> hosts = hostGroups.get(group);
+    if (hosts == null) {
+      result = false;
+    } else {
+      hosts.add(host);
+    }
+    return result;
+  }
+
+  private boolean isHostAssigned() {
+    boolean result = false;
+    for (String group : hostGroups.keySet()) {
+      if (!hostGroups.get(group).isEmpty()) {
+        result = true;
+        break;
+      }
+    }
+    return result;
+  }
+}

+ 53 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/ElephantCommand.java

@@ -0,0 +1,53 @@
+/**
+ * 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.commands;
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.stereotype.Component;
+
+/**
+ * Draws an elephant to the console.
+ */
+@Component
+public class ElephantCommand implements CommandMarker {
+
+  /**
+   * Checks whether the hello command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("hello")
+  public boolean isCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Prints an elephant to the console.
+   *
+   * @return elephant
+   */
+  @CliCommand(value = "hello", help = "Prints a simple elephant to the console")
+  public String elephant() throws IOException {
+    return IOUtils.toString(getClass().getResourceAsStream("/elephant.txt"));
+  }
+}

+ 117 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java

@@ -0,0 +1,117 @@
+/**
+ * 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.commands;
+
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.FocusType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Host related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class HostCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public HostCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the host list command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host list")
+  public boolean isHostsCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Prints the available hosts of the Ambari Server.
+   *
+   * @return host list
+   */
+  @CliCommand(value = "host list", help = "Lists the available hosts")
+  public String hosts() {
+    return client.showHostList();
+  }
+
+  /**
+   * Checks whether the host focus command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host focus")
+  public boolean isFocusHostCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Sets the focus to the specified host.
+   *
+   * @param host the host to set the focus to
+   * @return status message
+   */
+  @CliCommand(value = "host focus", help = "Sets the useHost to the specified host")
+  public String focusHost(
+    @CliOption(key = "host", mandatory = true, help = "hostname") String host) {
+    String message;
+    if (client.getHostNames().keySet().contains(host)) {
+      context.setFocus(host, FocusType.HOST);
+      message = "Focus set to: " + host;
+    } else {
+      message = host + " is not a valid host name";
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the host components command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host components")
+  public boolean isHostComponentsCommandAvailable() {
+    return context.isFocusOnHost();
+  }
+
+  /**
+   * Prints the components which belongs to the host being focused on.
+   *
+   * @return list of host components
+   */
+  @CliCommand(value = "host components", help = "Lists the components assigned to the selected host")
+  public String hostComponents() {
+    return renderSingleMap(client.getHostComponentsMap(context.getFocusValue()), "COMPONENT", "STATE");
+  }
+}

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

@@ -0,0 +1,125 @@
+/**
+ * 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.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell.converters.AvailableCommandsConverter;
+import org.springframework.shell.converters.BigDecimalConverter;
+import org.springframework.shell.converters.BigIntegerConverter;
+import org.springframework.shell.converters.BooleanConverter;
+import org.springframework.shell.converters.CharacterConverter;
+import org.springframework.shell.converters.DateConverter;
+import org.springframework.shell.converters.DoubleConverter;
+import org.springframework.shell.converters.EnumConverter;
+import org.springframework.shell.converters.FloatConverter;
+import org.springframework.shell.converters.IntegerConverter;
+import org.springframework.shell.converters.LocaleConverter;
+import org.springframework.shell.converters.LongConverter;
+import org.springframework.shell.converters.ShortConverter;
+import org.springframework.shell.converters.SimpleFileConverter;
+import org.springframework.shell.converters.StaticFieldConverterImpl;
+import org.springframework.shell.converters.StringConverter;
+import org.springframework.shell.core.Converter;
+
+/**
+ * Configures the converters used by the shell.
+ */
+@Configuration
+public class ConverterConfiguration {
+
+  @Bean
+  Converter simpleFileConverter() {
+    return new SimpleFileConverter();
+  }
+
+  @Bean
+  Converter stringConverter() {
+    return new StringConverter();
+  }
+
+  @Bean
+  Converter availableCommandsConverter() {
+    return new AvailableCommandsConverter();
+  }
+
+  @Bean
+  Converter bigDecimalConverter() {
+    return new BigDecimalConverter();
+  }
+
+  @Bean
+  Converter bigIntegerConverter() {
+    return new BigIntegerConverter();
+  }
+
+  @Bean
+  Converter booleanConverter() {
+    return new BooleanConverter();
+  }
+
+  @Bean
+  Converter characterConverter() {
+    return new CharacterConverter();
+  }
+
+  @Bean
+  Converter dateConverter() {
+    return new DateConverter();
+  }
+
+  @Bean
+  Converter doubleConverter() {
+    return new DoubleConverter();
+  }
+
+  @Bean
+  Converter enumConverter() {
+    return new EnumConverter();
+  }
+
+  @Bean
+  Converter floatConverter() {
+    return new FloatConverter();
+  }
+
+  @Bean
+  Converter integerConverter() {
+    return new IntegerConverter();
+  }
+
+  @Bean
+  Converter localeConverter() {
+    return new LocaleConverter();
+  }
+
+  @Bean
+  Converter longConverter() {
+    return new LongConverter();
+  }
+
+  @Bean
+  Converter shortConverter() {
+    return new ShortConverter();
+  }
+
+  @Bean
+  Converter staticFieldConverterImpl() {
+    return new StaticFieldConverterImpl();
+  }
+}

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

@@ -0,0 +1,102 @@
+/**
+ * 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.configuration;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.shell.CommandLine;
+import org.springframework.shell.SimpleShellCommandLineOptions;
+import org.springframework.shell.commands.ExitCommands;
+import org.springframework.shell.commands.HelpCommands;
+import org.springframework.shell.commands.ScriptCommands;
+import org.springframework.shell.commands.VersionCommands;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.shell.plugin.HistoryFileNameProvider;
+import org.springframework.shell.plugin.support.DefaultHistoryFileNameProvider;
+
+/**
+ * Spring bean definitions.
+ */
+@Configuration
+public class ShellConfiguration {
+
+  @Value("${ambari.host:localhost}")
+  private String host;
+
+  @Value("${ambari.port:8080}")
+  private String port;
+
+  @Value("${ambari.user:admin}")
+  private String user;
+
+  @Value("${ambari.password:admin}")
+  private String password;
+
+  @Value("${cmdfile:}")
+  private String cmdFile;
+
+  @Bean
+  AmbariClient createAmbariClient() {
+    return new AmbariClient(host, port, user, password);
+  }
+
+  @Bean
+  static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
+    return new PropertySourcesPlaceholderConfigurer();
+  }
+
+  @Bean
+  HistoryFileNameProvider defaultHistoryFileNameProvider() {
+    return new DefaultHistoryFileNameProvider();
+  }
+
+  @Bean(name = "shell")
+  JLineShellComponent shell() {
+    return new JLineShellComponent();
+  }
+
+  @Bean
+  CommandLine commandLine() throws Exception {
+    String[] args = cmdFile.length() > 0 ? new String[]{"--cmdfile", cmdFile} : new String[0];
+    return SimpleShellCommandLineOptions.parseCommandLine(args);
+  }
+
+  @Bean
+  CommandMarker exitCommand() {
+    return new ExitCommands();
+  }
+
+  @Bean
+  CommandMarker versionCommands() {
+    return new VersionCommands();
+  }
+
+  @Bean
+  CommandMarker helpCommands() {
+    return new HelpCommands();
+  }
+
+  @Bean
+  CommandMarker scriptCommands() {
+    return new ScriptCommands();
+  }
+}

+ 50 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariBanner.java

@@ -0,0 +1,50 @@
+/**
+ * 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.BannerProvider;
+import org.springframework.stereotype.Component;
+
+import com.github.lalyos.jfiglet.FigletFont;
+
+/**
+ * Prints the banner when the user starts the shell.
+ */
+@Component
+public class AmbariBanner implements BannerProvider {
+
+  @Override
+  public String getProviderName() {
+    return "AmbariShell";
+  }
+
+  @Override
+  public String getBanner() {
+    return FigletFont.convertOneLine("AmbariShell");
+  }
+
+  @Override
+  public String getVersion() {
+    return getClass().getPackage().getImplementationVersion();
+  }
+
+  @Override
+  public String getWelcomeMessage() {
+    return "Welcome to Ambari Shell. For command and param completion press TAB, for assistance type 'hint'.";
+  }
+}

+ 43 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariPrompt.java

@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.shell.customization;
+
+import org.apache.ambari.shell.model.AmbariContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.plugin.PromptProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * Manages the text of the shell's prompt.
+ */
+@Component
+public class AmbariPrompt implements PromptProvider {
+
+  @Autowired
+  private AmbariContext context;
+
+  @Override
+  public String getProviderName() {
+    return AmbariPrompt.class.getSimpleName();
+  }
+
+  @Override
+  public String getPrompt() {
+    return context.getPrompt();
+  }
+}

+ 159 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java

@@ -0,0 +1,159 @@
+/**
+ * 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.model;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Holds information about the connected Ambari Server.
+ */
+@Component
+public class AmbariContext {
+
+  private String cluster;
+  private boolean blueprintsAvailable;
+  private Focus focus;
+  private Hints hint;
+
+  public AmbariContext() {
+    this.focus = getRootFocus();
+  }
+
+  /**
+   * Sets the name of the cluster.
+   *
+   * @param cluster
+   */
+  public void setCluster(String cluster) {
+    this.cluster = cluster;
+  }
+
+  /**
+   * Sets the focus to the root.
+   */
+  public void resetFocus() {
+    this.focus = getRootFocus();
+  }
+
+  /**
+   * Sets the focus.
+   *
+   * @param id   target of the focus, can be anything (blueprint id, host id..)
+   * @param type type of the focus
+   */
+  public void setFocus(String id, FocusType type) {
+    this.focus = new Focus(id, type);
+  }
+
+  /**
+   * Returns the target of the focus.
+   *
+   * @return target
+   */
+  public String getFocusValue() {
+    return focus.getValue();
+  }
+
+  /**
+   * Checks whether blueprints are available or not.
+   */
+  public boolean areBlueprintsAvailable() {
+    return blueprintsAvailable;
+  }
+
+  /**
+   * Sets what should be the next hint message.
+   *
+   * @param hint the new message
+   */
+  public void setHint(Hints hint) {
+    this.hint = hint;
+  }
+
+  /**
+   * Returns the context sensitive prompt.
+   *
+   * @return text of the prompt
+   */
+  public String getPrompt() {
+    return focus.isType(FocusType.ROOT) ?
+      isConnectedToCluster() ? formatPrompt(focus.getPrefix(), cluster) : "ambari-shell>" :
+      formatPrompt(focus.getPrefix(), focus.getValue());
+  }
+
+  public boolean isConnectedToCluster() {
+    return cluster != null;
+  }
+
+  /**
+   * Checks whether the focus is on the host or not.
+   *
+   * @return true if the focus is on a host false otherwise
+   */
+  public boolean isFocusOnHost() {
+    return isFocusOn(FocusType.HOST);
+  }
+
+  /**
+   * Checks whether the focus is on the cluster build or not.
+   *
+   * @return true if the focus is on a cluster build false otherwise
+   */
+  public boolean isFocusOnClusterBuild() {
+    return isFocusOn(FocusType.CLUSTER_BUILD);
+  }
+
+  /**
+   * Returns some context sensitive hint.
+   *
+   * @return hint
+   */
+  public String getHint() {
+    return "Hint: " + hint.message();
+  }
+
+  /**
+   * Returns the name of the cluster.
+   *
+   * @return cluster's name
+   */
+  public String getCluster() {
+    return cluster;
+  }
+
+  /**
+   * Sets whether there are blueprints available or not.
+   *
+   * @param blueprintsAvailable
+   */
+  public void setBlueprintsAvailable(boolean blueprintsAvailable) {
+    this.blueprintsAvailable = blueprintsAvailable;
+  }
+
+  private boolean isFocusOn(FocusType type) {
+    return focus.isType(type);
+  }
+
+  private Focus getRootFocus() {
+    return new Focus("root", FocusType.ROOT);
+  }
+
+  private String formatPrompt(String prefix, String postfix) {
+    return String.format("%s:%s>", prefix, postfix);
+  }
+}

+ 53 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/model/Focus.java

@@ -0,0 +1,53 @@
+/**
+ * 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.model;
+
+/**
+ * Holds information about the focus. Focus give you the ability to
+ * provide context sensitive commands.
+ *
+ * @see org.apache.ambari.shell.model.FocusType
+ */
+public class Focus {
+
+  private final String value;
+  private final FocusType type;
+
+  public Focus(String value, FocusType type) {
+    this.value = value;
+    this.type = type;
+  }
+
+  public String getPrefix() {
+    return type.prefix();
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the current focus exists with the provided one.
+   *
+   * @param type type to check with the current
+   * @return true if they match false otherwise
+   */
+  public boolean isType(FocusType type) {
+    return this.type == type;
+  }
+}

+ 55 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java

@@ -0,0 +1,55 @@
+/**
+ * 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.model;
+
+/**
+ * Types for different focuses. Its purpose to give the command availability
+ * checkers a chance in decision making.
+ */
+public enum FocusType {
+
+  /**
+   * The focus is on a selected host.
+   */
+  HOST("HOST"),
+
+  /**
+   * The focus is on the cluster building phase.
+   */
+  CLUSTER_BUILD("CLUSTER_BUILD"),
+
+  /**
+   * No focus at all.
+   */
+  ROOT("CLUSTER");
+
+  private final String prefix;
+
+  private FocusType(String prefix) {
+    this.prefix = prefix;
+  }
+
+  /**
+   * Prefix provided for the prompt.
+   *
+   * @return focus prefix
+   */
+  public String prefix() {
+    return prefix;
+  }
+}

+ 59 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/model/Hints.java

@@ -0,0 +1,59 @@
+/**
+ * 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.model;
+
+/**
+ * Provides some guidance's to the user, what he/she can follow.
+ */
+public enum Hints {
+
+  /**
+   * Hint for adding blueprints.
+   */
+  ADD_BLUEPRINT("Add a blueprint with the 'blueprint add' or add the default blueprints with the 'blueprint defaults' command."),
+
+  /**
+   * Hint for start building a cluster.
+   */
+  BUILD_CLUSTER("Start building a cluster with the 'cluster build' command using a previously added blueprint."),
+
+  /**
+   * Hint for start assigning hosts to host groups in cluster build phase.
+   */
+  ASSIGN_HOSTS("Assign hosts to different host groups with the 'cluster assign' command."),
+
+  /**
+   * Hint for create a cluster from the assigned hosts.
+   */
+  CREATE_CLUSTER("Create the cluster with the 'cluster create' command or use the 'cluster reset' command and start over."),
+
+  /**
+   * Hint for check the cluster creation result.
+   */
+  PROGRESS("See the install progress with the 'tasks' command.");
+
+  private final String message;
+
+  private Hints(String message) {
+    this.message = message;
+  }
+
+  public String message() {
+    return message;
+  }
+}

部分文件因为文件数量过多而无法显示