瀏覽代碼

AMBARI-6027. Add the capability of starting and stoping services with Ambari Shell. (Janos Matyas and Krisztian Horvath via yusaku)

Yusaku Sako 11 年之前
父節點
當前提交
5ceeeb52b9

+ 28 - 4
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy

@@ -781,7 +781,7 @@ class AmbariClient {
    */
   def int startAllServices() {
     log.debug("Starting all services ...")
-    manageAllServices("Start All Services", "STARTED")
+    manageService("Start All Services", "STARTED")
   }
 
   /**
@@ -791,7 +791,27 @@ class AmbariClient {
    */
   def int stopAllServices() {
     log.debug("Stopping all services ...")
-    manageAllServices("Stop All Services", "INSTALLED")
+    manageService("Stop All Services", "INSTALLED")
+  }
+
+  /**
+   * Starts the given service.
+   *
+   * @param service name of the service
+   * @return id of the request
+   */
+  def int startService(String service) {
+    manageService("Starting $service", "STARTED", service)
+  }
+
+  /**
+   * Stops the given service.
+   *
+   * @param service name of the service
+   * @return id of the request
+   */
+  def int stopService(String service) {
+    manageService("Stopping $service", "INSTALLED", service)
   }
 
   def boolean servicesStarted() {
@@ -868,15 +888,19 @@ class AmbariClient {
     return allInState;
   }
 
-  def private manageAllServices(String context, String state) {
+  def private manageService(String context, String state, String service = "") {
     Map bodyMap = [
       RequestInfo: [context: context],
       ServiceInfo: [state: state]
     ]
     JsonBuilder builder = new JsonBuilder(bodyMap)
+    def path = "${ambari.getUri()}clusters/${getClusterName()}/services"
+    if (service) {
+      path += "/$service"
+    }
     def Map<String, ?> putRequestMap = [:]
     putRequestMap.put('requestContentType', ContentType.URLENC)
-    putRequestMap.put('path', "${ambari.getUri()}" + "clusters/${getClusterName()}/services")
+    putRequestMap.put('path', path)
     putRequestMap.put('query', ['params/run_smoke_test': 'false'])
     putRequestMap.put('body', builder.toPrettyString());
 

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

@@ -17,11 +17,14 @@
  */
 package org.apache.ambari.groovy.client
 
+import groovy.json.JsonSlurper
 import groovy.util.logging.Slf4j
 
 @Slf4j
 class AmbariServicesTest extends AbstractAmbariClientTest {
 
+  def slurper = new JsonSlurper()
+
   private enum Scenario {
     SERVICES, NO_SERVICES, NO_SERVICE_COMPONENTS
   }
@@ -114,6 +117,66 @@ class AmbariServicesTest extends AbstractAmbariClientTest {
     !result
   }
 
+  def "test stop all services"() {
+    given:
+    def context
+    ambari.metaClass.getClusterName = { return "cluster" }
+    ambari.getAmbari().metaClass.put = { Map request ->
+      context = request
+    }
+    ambari.getSlurper().metaClass.parseText { String text -> return ["Requests": ["id": 1]] }
+
+    when:
+    def id = ambari.stopAllServices()
+
+    then:
+    1 == id
+    context.path == "http://localhost:8080/api/v1/clusters/cluster/services"
+    def body = slurper.parseText(context.body)
+    body.RequestInfo.context == "Stop All Services"
+    body.ServiceInfo.state == "INSTALLED"
+  }
+
+  def "test start service ZOOKEEPER"() {
+    given:
+    def context
+    ambari.metaClass.getClusterName = { return "cluster" }
+    ambari.getAmbari().metaClass.put = { Map request ->
+      context = request
+    }
+    ambari.getSlurper().metaClass.parseText { String text -> return ["Requests": ["id": 1]] }
+
+    when:
+    def id = ambari.startService("ZOOKEEPER")
+
+    then:
+    1 == id
+    context.path == "http://localhost:8080/api/v1/clusters/cluster/services/ZOOKEEPER"
+    def body = slurper.parseText(context.body)
+    body.RequestInfo.context == "Starting ZOOKEEPER"
+    body.ServiceInfo.state == "STARTED"
+  }
+
+  def "test stop service ZOOKEEPER"() {
+    given:
+    def context
+    ambari.metaClass.getClusterName = { return "cluster" }
+    ambari.getAmbari().metaClass.put = { Map request ->
+      context = request
+    }
+    ambari.getSlurper().metaClass.parseText { String text -> return ["Requests": ["id": 1]] }
+
+    when:
+    def id = ambari.stopService("ZOOKEEPER")
+
+    then:
+    1 == id
+    context.path == "http://localhost:8080/api/v1/clusters/cluster/services/ZOOKEEPER"
+    def body = slurper.parseText(context.body)
+    body.RequestInfo.context == "Stopping ZOOKEEPER"
+    body.ServiceInfo.state == "INSTALLED"
+  }
+
   def private String selectResponseJson(Map resourceRequestMap, String scenarioStr) {
     def thePath = resourceRequestMap.get("path");
     def Scenario scenario = Scenario.valueOf(scenarioStr)

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

@@ -17,7 +17,6 @@
  */
 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;
@@ -69,46 +68,6 @@ public class BasicCommands implements CommandMarker {
     return renderSingleMap(client.getTaskMap(id), "TASK", "STATUS");
   }
 
-  /**
-   * Checks whether the service list command is available or not.
-   *
-   * @return true if available false otherwise
-   */
-  @CliAvailabilityIndicator("services list")
-  public boolean isServiceListCommandAvailable() {
-    return context.isConnectedToCluster();
-  }
-
-  /**
-   * Prints the available service list of the Ambari Server.
-   *
-   * @return service list
-   */
-  @CliCommand(value = "services 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("services components")
-  public boolean isServiceComponentsCommandAvailable() {
-    return context.isConnectedToCluster();
-  }
-
-  /**
-   * Prints the service components of the Ambari Server.
-   *
-   * @return service component list
-   */
-  @CliCommand(value = "services 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.
    *
@@ -171,37 +130,4 @@ public class BasicCommands implements CommandMarker {
     return context.getHint();
   }
 
-  @CliAvailabilityIndicator("services stop")
-  public boolean isServiceStopCommandAvailable() {
-    return context.isConnectedToCluster();
-  }
-
-  @CliCommand(value = "services stop", help = "Stops all the running services")
-  public String stopServices() {
-    String message;
-    try {
-      client.stopAllServices();
-      message = "Stopping all services..";
-    } catch (Exception e) {
-      message = "Cannot stop services";
-    }
-    return String.format("%s\n\n%s", message, servicesList());
-  }
-
-  @CliAvailabilityIndicator("services start")
-  public boolean isServiceStartCommandAvailable() {
-    return context.isConnectedToCluster();
-  }
-
-  @CliCommand(value = "services start", help = "Starts all the services")
-  public String startServices() {
-    String message;
-    try {
-      client.startAllServices();
-      message = "Starting all services..";
-    } catch (Exception e) {
-      message = "Cannot start services";
-    }
-    return String.format("%s\n\n%s", message, servicesList());
-  }
 }

+ 155 - 0
ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/commands/ServiceCommands.java

@@ -0,0 +1,155 @@
+/**
+ * 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.completion.Service;
+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;
+
+/**
+ * Service related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class ServiceCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public ServiceCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the services list command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("services list")
+  public boolean isServiceListCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the available services list of the Ambari Server.
+   *
+   * @return service list
+   */
+  @CliCommand(value = "services list", help = "Lists the available services")
+  public String servicesList() {
+    return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
+  }
+
+  /**
+   * Checks whether the services components command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("services components")
+  public boolean isServiceComponentsCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the services components of the Ambari Server.
+   *
+   * @return service component list
+   */
+  @CliCommand(value = "services components", help = "Lists all services with their components")
+  public String serviceComponents() {
+    return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
+  }
+
+  /**
+   * Checks whether the services stop command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("services stop")
+  public boolean isServiceStopCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Stops a service or all services if no service name is provided.
+   *
+   * @return service list
+   */
+  @CliCommand(value = "services stop", help = "Stops a service/all the running services")
+  public String stopServices(@CliOption(key = "service", mandatory = false, help = "Name of the service to stop") Service service) {
+    String message;
+    try {
+      if (service != null) {
+        String serviceName = service.getName();
+        message = "Stopping " + serviceName;
+        client.stopService(serviceName);
+      } else {
+        message = "Stopping all services..";
+        client.stopAllServices();
+      }
+    } catch (Exception e) {
+      message = "Cannot stop services";
+    }
+    return String.format("%s\n\n%s", message, servicesList());
+  }
+
+  /**
+   * Checks whether the services start command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("services start")
+  public boolean isServiceStartCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Starts a service or all services if no service name is provided.
+   *
+   * @return service list
+   */
+  @CliCommand(value = "services start", help = "Starts a service/all the services")
+  public String startServices(@CliOption(key = "service", mandatory = false, help = "Name of the service to start") Service service) {
+    String message;
+    try {
+      if (service != null) {
+        String serviceName = service.getName();
+        message = "Starting " + serviceName;
+        client.startService(serviceName);
+      } else {
+        client.startAllServices();
+        message = "Starting all services..";
+      }
+    } catch (Exception e) {
+      message = "Cannot start services";
+    }
+    return String.format("%s\n\n%s", message, servicesList());
+  }
+}

+ 31 - 0
ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/completion/Service.java

@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.shell.completion;
+
+public class Service {
+
+  private final String name;
+
+  public Service(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+}

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

@@ -20,6 +20,7 @@ package org.apache.ambari.shell.configuration;
 import org.apache.ambari.groovy.client.AmbariClient;
 import org.apache.ambari.shell.converter.BlueprintConverter;
 import org.apache.ambari.shell.converter.HostConverter;
+import org.apache.ambari.shell.converter.ServiceConverter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -139,4 +140,9 @@ public class ConverterConfiguration {
   Converter hostConverter() {
     return new HostConverter(client);
   }
+
+  @Bean
+  Converter serviceConverter() {
+    return new ServiceConverter(client);
+  }
 }

+ 55 - 0
ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/converter/ServiceConverter.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.converter;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Service;
+import org.springframework.shell.core.Completion;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell.core.MethodTarget;
+
+public class ServiceConverter implements Converter<Service> {
+
+  private AmbariClient client;
+
+  public ServiceConverter(AmbariClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public boolean supports(Class<?> type, String optionContext) {
+    return Service.class.isAssignableFrom(type);
+  }
+
+  @Override
+  public Service convertFromText(String value, Class<?> targetType, String optionContext) {
+    return new Service(value);
+  }
+
+  @Override
+  public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData, String optionContext, MethodTarget target) {
+    Set<String> services = client.getServicesMap().keySet();
+    for (String service : services) {
+      completions.add(new Completion(service));
+    }
+    return true;
+  }
+}

+ 70 - 0
ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/ServiceCommandsTest.java

@@ -0,0 +1,70 @@
+/**
+ * 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.mockito.Mockito.verify;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Service;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ServiceCommandsTest {
+
+  @InjectMocks
+  private ServiceCommands serviceCommands;
+
+  @Mock
+  private AmbariClient client;
+  @Mock
+  private AmbariContext context;
+
+  @Test
+  public void testStopAllServices() {
+    serviceCommands.stopServices(null);
+
+    verify(client).stopAllServices();
+  }
+
+  @Test
+  public void testStopService() {
+    serviceCommands.stopServices(new Service("ZOOKEEPER"));
+
+    verify(client).stopService("ZOOKEEPER");
+  }
+
+  @Test
+  public void testStartAllServices() {
+    serviceCommands.startServices(null);
+
+    verify(client).startAllServices();
+  }
+
+  @Test
+  public void testStartService() {
+    serviceCommands.startServices(new Service("ZOOKEEPER"));
+
+    verify(client).startService("ZOOKEEPER");
+  }
+
+}