001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import static org.apache.hadoop.hbase.HBaseClusterManager.DEFAULT_RETRY_ATTEMPTS; 021import static org.apache.hadoop.hbase.HBaseClusterManager.DEFAULT_RETRY_SLEEP_INTERVAL; 022import static org.apache.hadoop.hbase.HBaseClusterManager.RETRY_ATTEMPTS_KEY; 023import static org.apache.hadoop.hbase.HBaseClusterManager.RETRY_SLEEP_INTERVAL_KEY; 024 025import java.io.IOException; 026import java.net.URI; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.Locale; 030import java.util.Map; 031import java.util.Objects; 032import java.util.Optional; 033import java.util.concurrent.Callable; 034import javax.xml.ws.http.HTTPException; 035import org.apache.commons.lang3.StringUtils; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.conf.Configured; 038import org.apache.hadoop.hbase.util.RetryCounter; 039import org.apache.hadoop.hbase.util.RetryCounter.RetryConfig; 040import org.apache.hadoop.hbase.util.RetryCounterFactory; 041import org.apache.hadoop.util.ReflectionUtils; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045import org.apache.hbase.thirdparty.com.google.gson.JsonElement; 046import org.apache.hbase.thirdparty.com.google.gson.JsonObject; 047import org.apache.hbase.thirdparty.com.google.gson.JsonParser; 048import org.apache.hbase.thirdparty.javax.ws.rs.client.Client; 049import org.apache.hbase.thirdparty.javax.ws.rs.client.ClientBuilder; 050import org.apache.hbase.thirdparty.javax.ws.rs.client.Entity; 051import org.apache.hbase.thirdparty.javax.ws.rs.client.Invocation; 052import org.apache.hbase.thirdparty.javax.ws.rs.client.WebTarget; 053import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 054import org.apache.hbase.thirdparty.javax.ws.rs.core.Response; 055import org.apache.hbase.thirdparty.javax.ws.rs.core.UriBuilder; 056import org.apache.hbase.thirdparty.org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; 057 058/** 059 * A ClusterManager implementation designed to control Cloudera Manager (http://www.cloudera.com) 060 * clusters via REST API. This API uses HTTP GET requests against the cluster manager server to 061 * retrieve information and POST/PUT requests to perform actions. As a simple example, to retrieve a 062 * list of hosts from a CM server with login credentials admin:admin, a simple curl command would be 063 * curl -X POST -H "Content-Type:application/json" -u admin:admin \ 064 * "http://this.is.my.server.com:7180/api/v8/hosts" This command would return a JSON result, which 065 * would need to be parsed to retrieve relevant information. This action and many others are covered 066 * by this class. A note on nomenclature: while the ClusterManager interface uses a ServiceType enum 067 * when referring to things like RegionServers and DataNodes, cluster managers often use different 068 * terminology. As an example, Cloudera Manager (http://www.cloudera.com) would refer to a 069 * RegionServer as a "role" of the HBase "service." It would further refer to "hbase" as the 070 * "serviceType." Apache Ambari (http://ambari.apache.org) would call the RegionServer a "component" 071 * of the HBase "service." This class will defer to the ClusterManager terminology in methods that 072 * it implements from that interface, but uses Cloudera Manager's terminology when dealing with its 073 * API directly. DEBUG-level logging gives more details of the actions this class takes as they 074 * happen. Log at TRACE-level to see the API requests and responses. TRACE-level logging on 075 * RetryCounter displays wait times, so that can be helpful too. 076 */ 077public class RESTApiClusterManager extends Configured implements ClusterManager { 078 // Properties that need to be in the Configuration object to interact with the REST API cluster 079 // manager. Most easily defined in hbase-site.xml, but can also be passed on the command line. 080 private static final String REST_API_CLUSTER_MANAGER_HOSTNAME = 081 "hbase.it.clustermanager.restapi.hostname"; 082 private static final String REST_API_CLUSTER_MANAGER_USERNAME = 083 "hbase.it.clustermanager.restapi.username"; 084 private static final String REST_API_CLUSTER_MANAGER_PASSWORD = 085 "hbase.it.clustermanager.restapi.password"; 086 private static final String REST_API_CLUSTER_MANAGER_CLUSTER_NAME = 087 "hbase.it.clustermanager.restapi.clustername"; 088 private static final String REST_API_DELEGATE_CLUSTER_MANAGER = 089 "hbase.it.clustermanager.restapi.delegate"; 090 091 private static final JsonParser parser = new JsonParser(); 092 093 // Some default values for the above properties. 094 private static final String DEFAULT_SERVER_HOSTNAME = "http://localhost:7180"; 095 private static final String DEFAULT_SERVER_USERNAME = "admin"; 096 private static final String DEFAULT_SERVER_PASSWORD = "admin"; 097 private static final String DEFAULT_CLUSTER_NAME = "Cluster 1"; 098 099 // Fields for the hostname, username, password, and cluster name of the cluster management server 100 // to be used. 101 private String serverHostname; 102 private String clusterName; 103 104 // Each version of Cloudera Manager supports a particular API versions. Version 6 of this API 105 // provides all the features needed by this class. 106 private static final String API_VERSION = "v6"; 107 108 // Client instances are expensive, so use the same one for all our REST queries. 109 private final Client client = ClientBuilder.newClient(); 110 111 // An instance of HBaseClusterManager is used for methods like the kill, resume, and suspend 112 // because cluster managers don't tend to implement these operations. 113 private ClusterManager hBaseClusterManager; 114 115 private RetryCounterFactory retryCounterFactory; 116 117 private static final Logger LOG = LoggerFactory.getLogger(RESTApiClusterManager.class); 118 119 RESTApiClusterManager() { 120 } 121 122 @Override 123 public void setConf(Configuration conf) { 124 super.setConf(conf); 125 if (conf == null) { 126 // `Configured()` constructor calls `setConf(null)` before calling again with a real value. 127 return; 128 } 129 130 final Class<? extends ClusterManager> clazz = conf.getClass(REST_API_DELEGATE_CLUSTER_MANAGER, 131 HBaseClusterManager.class, ClusterManager.class); 132 hBaseClusterManager = ReflectionUtils.newInstance(clazz, conf); 133 134 serverHostname = conf.get(REST_API_CLUSTER_MANAGER_HOSTNAME, DEFAULT_SERVER_HOSTNAME); 135 clusterName = conf.get(REST_API_CLUSTER_MANAGER_CLUSTER_NAME, DEFAULT_CLUSTER_NAME); 136 137 // Add filter to Client instance to enable server authentication. 138 String serverUsername = conf.get(REST_API_CLUSTER_MANAGER_USERNAME, DEFAULT_SERVER_USERNAME); 139 String serverPassword = conf.get(REST_API_CLUSTER_MANAGER_PASSWORD, DEFAULT_SERVER_PASSWORD); 140 client.register(HttpAuthenticationFeature.basic(serverUsername, serverPassword)); 141 142 this.retryCounterFactory = new RetryCounterFactory( 143 new RetryConfig().setMaxAttempts(conf.getInt(RETRY_ATTEMPTS_KEY, DEFAULT_RETRY_ATTEMPTS)) 144 .setSleepInterval(conf.getLong(RETRY_SLEEP_INTERVAL_KEY, DEFAULT_RETRY_SLEEP_INTERVAL))); 145 } 146 147 @Override 148 public void start(ServiceType service, String hostname, int port) { 149 // With Cloudera Manager (6.3.x), sending a START command to a service role 150 // that is already in the "Started" state is an error. CM will log a message 151 // saying "Role must be stopped". It will complain similarly for other 152 // expected state transitions. 153 // A role process that has been `kill -9`'ed ends up with the service role 154 // retaining the "Started" state but with the process marked as "unhealthy". 155 // Instead of blindly issuing the START command, first send a STOP command 156 // to ensure the START will be accepted. 157 LOG.debug("Performing start of {} on {}:{}", service, hostname, port); 158 final RoleState currentState = getRoleState(service, hostname); 159 switch (currentState) { 160 case NA: 161 case BUSY: 162 case UNKNOWN: 163 case HISTORY_NOT_AVAILABLE: 164 LOG.warn("Unexpected service state detected. Service START requested, but currently in" 165 + " {} state. Attempting to start. {}, {}:{}", currentState, service, hostname, port); 166 performClusterManagerCommand(service, hostname, RoleCommand.START); 167 return; 168 case STOPPING: 169 LOG.warn( 170 "Unexpected service state detected. Service START requested, but currently in" 171 + " {} state. Waiting for stop before attempting start. {}, {}:{}", 172 currentState, service, hostname, port); 173 waitFor(() -> Objects.equals(RoleState.STOPPED, getRoleState(service, hostname))); 174 performClusterManagerCommand(service, hostname, RoleCommand.START); 175 return; 176 case STOPPED: 177 performClusterManagerCommand(service, hostname, RoleCommand.START); 178 return; 179 case STARTING: 180 LOG.warn( 181 "Unexpected service state detected. Service START requested, but already in" 182 + " {} state. Ignoring current request and waiting for start to complete. {}, {}:{}", 183 currentState, service, hostname, port); 184 waitFor(() -> Objects.equals(RoleState.STARTED, getRoleState(service, hostname))); 185 return; 186 case STARTED: 187 LOG.warn("Unexpected service state detected. Service START requested, but already in" 188 + " {} state. Restarting. {}, {}:{}", currentState, service, hostname, port); 189 performClusterManagerCommand(service, hostname, RoleCommand.RESTART); 190 return; 191 } 192 throw new RuntimeException("should not happen."); 193 } 194 195 @Override 196 public void stop(ServiceType service, String hostname, int port) { 197 LOG.debug("Performing stop of {} on {}:{}", service, hostname, port); 198 final RoleState currentState = getRoleState(service, hostname); 199 switch (currentState) { 200 case NA: 201 case BUSY: 202 case UNKNOWN: 203 case HISTORY_NOT_AVAILABLE: 204 LOG.warn("Unexpected service state detected. Service STOP requested, but already in" 205 + " {} state. Attempting to stop. {}, {}:{}", currentState, service, hostname, port); 206 performClusterManagerCommand(service, hostname, RoleCommand.STOP); 207 return; 208 case STOPPING: 209 waitFor(() -> Objects.equals(RoleState.STOPPED, getRoleState(service, hostname))); 210 return; 211 case STOPPED: 212 LOG.warn( 213 "Unexpected service state detected. Service STOP requested, but already in" 214 + " {} state. Ignoring current request. {}, {}:{}", 215 currentState, service, hostname, port); 216 return; 217 case STARTING: 218 LOG.warn( 219 "Unexpected service state detected. Service STOP requested, but already in" 220 + " {} state. Waiting for start to complete. {}, {}:{}", 221 currentState, service, hostname, port); 222 waitFor(() -> Objects.equals(RoleState.STARTED, getRoleState(service, hostname))); 223 performClusterManagerCommand(service, hostname, RoleCommand.STOP); 224 return; 225 case STARTED: 226 performClusterManagerCommand(service, hostname, RoleCommand.STOP); 227 return; 228 } 229 throw new RuntimeException("should not happen."); 230 } 231 232 @Override 233 public void restart(ServiceType service, String hostname, int port) { 234 LOG.debug("Performing stop followed by start of {} on {}:{}", service, hostname, port); 235 stop(service, hostname, port); 236 start(service, hostname, port); 237 } 238 239 @Override 240 public boolean isRunning(ServiceType service, String hostname, int port) { 241 LOG.debug("Issuing isRunning request against {} on {}:{}", service, hostname, port); 242 return executeWithRetries(() -> { 243 String serviceName = getServiceName(roleServiceType.get(service)); 244 String hostId = getHostId(hostname); 245 RoleState roleState = getRoleState(serviceName, service.toString(), hostId); 246 HealthSummary healthSummary = getHealthSummary(serviceName, service.toString(), hostId); 247 return Objects.equals(RoleState.STARTED, roleState) 248 && Objects.equals(HealthSummary.GOOD, healthSummary); 249 }); 250 } 251 252 @Override 253 public void kill(ServiceType service, String hostname, int port) throws IOException { 254 hBaseClusterManager.kill(service, hostname, port); 255 } 256 257 @Override 258 public void suspend(ServiceType service, String hostname, int port) throws IOException { 259 hBaseClusterManager.suspend(service, hostname, port); 260 } 261 262 @Override 263 public void resume(ServiceType service, String hostname, int port) throws IOException { 264 hBaseClusterManager.resume(service, hostname, port); 265 } 266 267 // Convenience method to execute command against role on hostname. Only graceful commands are 268 // supported since cluster management APIs don't tend to let you SIGKILL things. 269 private void performClusterManagerCommand(ServiceType role, String hostname, 270 RoleCommand command) { 271 // retry submitting the command until the submission succeeds. 272 final long commandId = executeWithRetries(() -> { 273 final String serviceName = getServiceName(roleServiceType.get(role)); 274 final String hostId = getHostId(hostname); 275 final String roleName = getRoleName(serviceName, role.toString(), hostId); 276 return doRoleCommand(serviceName, roleName, command); 277 }); 278 LOG.debug("Command {} of {} on {} submitted as commandId {}", command, role, hostname, 279 commandId); 280 281 // assume the submitted command was asynchronous. wait on the commandId to be marked as 282 // successful. 283 waitFor(() -> hasCommandCompleted(commandId)); 284 if (!executeWithRetries(() -> hasCommandCompletedSuccessfully(commandId))) { 285 final String msg = String.format("Command %s of %s on %s submitted as commandId %s failed.", 286 command, role, hostname, commandId); 287 // TODO: this does not interrupt the monkey. should it? 288 throw new RuntimeException(msg); 289 } 290 LOG.debug("Command {} of {} on {} submitted as commandId {} completed successfully.", command, 291 role, hostname, commandId); 292 } 293 294 /** 295 * Issues a command (e.g. starting or stopping a role). 296 * @return the commandId of a successfully submitted asynchronous command. 297 */ 298 private long doRoleCommand(String serviceName, String roleName, RoleCommand roleCommand) { 299 URI uri = UriBuilder.fromUri(serverHostname).path("api").path(API_VERSION).path("clusters") 300 .path(clusterName).path("services").path(serviceName).path("roleCommands") 301 .path(roleCommand.toString()).build(); 302 String body = "{ \"items\": [ \"" + roleName + "\" ] }"; 303 LOG.trace("Executing POST against {} with body {} ...", uri, body); 304 WebTarget webTarget = client.target(uri); 305 Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON); 306 Response response = invocationBuilder.post(Entity.json(body)); 307 final int statusCode = response.getStatus(); 308 final String responseBody = response.readEntity(String.class); 309 if (statusCode != Response.Status.OK.getStatusCode()) { 310 LOG.warn("RoleCommand failed with status code {} and response body {}", statusCode, 311 responseBody); 312 throw new HTTPException(statusCode); 313 } 314 315 LOG.trace("POST against {} completed with status code {} and response body {}", uri, statusCode, 316 responseBody); 317 return parser.parse(responseBody).getAsJsonObject().get("items").getAsJsonArray().get(0) 318 .getAsJsonObject().get("id").getAsLong(); 319 } 320 321 private HealthSummary getHealthSummary(String serviceName, String roleType, String hostId) { 322 return HealthSummary 323 .fromString(getRolePropertyValue(serviceName, roleType, hostId, "healthSummary")); 324 } 325 326 // This API uses a hostId to execute host-specific commands; get one from a hostname. 327 private String getHostId(String hostname) { 328 String hostId = null; 329 URI uri = 330 UriBuilder.fromUri(serverHostname).path("api").path(API_VERSION).path("hosts").build(); 331 JsonElement hosts = parser.parse(getFromURIGet(uri)).getAsJsonObject().get("items"); 332 if (hosts != null) { 333 // Iterate through the list of hosts, stopping once you've reached the requested hostname. 334 for (JsonElement host : hosts.getAsJsonArray()) { 335 if (host.getAsJsonObject().get("hostname").getAsString().equals(hostname)) { 336 hostId = host.getAsJsonObject().get("hostId").getAsString(); 337 break; 338 } 339 } 340 } 341 342 return hostId; 343 } 344 345 private String getFromURIGet(URI uri) { 346 LOG.trace("Executing GET against {} ...", uri); 347 final Response response = client.target(uri).request(MediaType.APPLICATION_JSON_TYPE).get(); 348 int statusCode = response.getStatus(); 349 final String responseBody = response.readEntity(String.class); 350 if (statusCode != Response.Status.OK.getStatusCode()) { 351 LOG.warn("request failed with status code {} and response body {}", statusCode, responseBody); 352 throw new HTTPException(statusCode); 353 } 354 // This API folds information as the value to an "items" attribute. 355 LOG.trace("GET against {} completed with status code {} and response body {}", uri, statusCode, 356 responseBody); 357 return responseBody; 358 } 359 360 // This API assigns a unique role name to each host's instance of a role. 361 private String getRoleName(String serviceName, String roleType, String hostId) { 362 return getRolePropertyValue(serviceName, roleType, hostId, "name"); 363 } 364 365 // Get the value of a property from a role on a particular host. 366 private String getRolePropertyValue(String serviceName, String roleType, String hostId, 367 String property) { 368 String roleValue = null; 369 URI uri = UriBuilder.fromUri(serverHostname).path("api").path(API_VERSION).path("clusters") 370 .path(clusterName).path("services").path(serviceName).path("roles").build(); 371 JsonElement roles = parser.parse(getFromURIGet(uri)).getAsJsonObject().get("items"); 372 if (roles != null) { 373 // Iterate through the list of roles, stopping once the requested one is found. 374 for (JsonElement role : roles.getAsJsonArray()) { 375 JsonObject roleObj = role.getAsJsonObject(); 376 if ( 377 roleObj.get("hostRef").getAsJsonObject().get("hostId").getAsString().equals(hostId) 378 && roleObj.get("type").getAsString().toLowerCase(Locale.ROOT) 379 .equals(roleType.toLowerCase(Locale.ROOT)) 380 ) { 381 roleValue = roleObj.get(property).getAsString(); 382 break; 383 } 384 } 385 } 386 387 return roleValue; 388 } 389 390 private RoleState getRoleState(ServiceType service, String hostname) { 391 return executeWithRetries(() -> { 392 String serviceName = getServiceName(roleServiceType.get(service)); 393 String hostId = getHostId(hostname); 394 RoleState state = getRoleState(serviceName, service.toString(), hostId); 395 // sometimes the response (usually the first) is null. retry those. 396 return Objects.requireNonNull(state); 397 }); 398 } 399 400 private RoleState getRoleState(String serviceName, String roleType, String hostId) { 401 return RoleState.fromString(getRolePropertyValue(serviceName, roleType, hostId, "roleState")); 402 } 403 404 // Convert a service (e.g. "HBASE," "HDFS") into a service name (e.g. "HBASE-1," "HDFS-1"). 405 private String getServiceName(Service service) { 406 String serviceName = null; 407 URI uri = UriBuilder.fromUri(serverHostname).path("api").path(API_VERSION).path("clusters") 408 .path(clusterName).path("services").build(); 409 JsonElement services = parser.parse(getFromURIGet(uri)).getAsJsonObject().get("items"); 410 if (services != null) { 411 // Iterate through the list of services, stopping once the requested one is found. 412 for (JsonElement serviceEntry : services.getAsJsonArray()) { 413 if (serviceEntry.getAsJsonObject().get("type").getAsString().equals(service.toString())) { 414 serviceName = serviceEntry.getAsJsonObject().get("name").getAsString(); 415 break; 416 } 417 } 418 } 419 420 return serviceName; 421 } 422 423 private Optional<JsonObject> getCommand(final long commandId) { 424 final URI uri = UriBuilder.fromUri(serverHostname).path("api").path(API_VERSION) 425 .path("commands").path(Long.toString(commandId)).build(); 426 return Optional.ofNullable(getFromURIGet(uri)).map(parser::parse) 427 .map(JsonElement::getAsJsonObject); 428 } 429 430 /** 431 * Return {@code true} if the {@code commandId} has finished processing. 432 */ 433 private boolean hasCommandCompleted(final long commandId) { 434 return getCommand(commandId).map(val -> { 435 final boolean isActive = val.get("active").getAsBoolean(); 436 if (isActive) { 437 LOG.debug("command {} is still active.", commandId); 438 } 439 return !isActive; 440 }).orElse(false); 441 } 442 443 /** 444 * Return {@code true} if the {@code commandId} has finished successfully. 445 */ 446 private boolean hasCommandCompletedSuccessfully(final long commandId) { 447 return getCommand(commandId).filter(val -> { 448 final boolean isActive = val.get("active").getAsBoolean(); 449 if (isActive) { 450 LOG.debug("command {} is still active.", commandId); 451 } 452 return !isActive; 453 }).map(val -> { 454 final boolean isSuccess = val.get("success").getAsBoolean(); 455 LOG.debug("command {} completed as {}.", commandId, isSuccess); 456 return isSuccess; 457 }).orElse(false); 458 } 459 460 /** 461 * Helper method for executing retryable work. 462 */ 463 private <T> T executeWithRetries(final Callable<T> callable) { 464 final RetryCounter retryCounter = retryCounterFactory.create(); 465 while (true) { 466 try { 467 return callable.call(); 468 } catch (Exception e) { 469 if (retryCounter.shouldRetry()) { 470 LOG.debug("execution failed with exception. Retrying.", e); 471 } else { 472 throw new RuntimeException("retries exhausted", e); 473 } 474 } 475 try { 476 retryCounter.sleepUntilNextRetry(); 477 } catch (InterruptedException e) { 478 throw new RuntimeException(e); 479 } 480 } 481 } 482 483 private void waitFor(final Callable<Boolean> predicate) { 484 final RetryCounter retryCounter = retryCounterFactory.create(); 485 while (true) { 486 try { 487 if (Objects.equals(true, predicate.call())) { 488 return; 489 } 490 } catch (Exception e) { 491 if (retryCounter.shouldRetry()) { 492 LOG.debug("execution failed with exception. Retrying.", e); 493 } else { 494 throw new RuntimeException("retries exhausted", e); 495 } 496 } 497 try { 498 retryCounter.sleepUntilNextRetry(); 499 } catch (InterruptedException e) { 500 throw new RuntimeException(e); 501 } 502 } 503 } 504 505 /* 506 * Some enums to guard against bad calls. 507 */ 508 509 // The RoleCommand enum is used by the doRoleCommand method to guard against non-existent methods 510 // being invoked on a given role. 511 // TODO: Integrate zookeeper and hdfs related failure injections (Ref: HBASE-14261). 512 private enum RoleCommand { 513 START, 514 STOP, 515 RESTART; 516 517 // APIs tend to take commands in lowercase, so convert them to save the trouble later. 518 @Override 519 public String toString() { 520 return name().toLowerCase(Locale.ROOT); 521 } 522 } 523 524 /** 525 * Represents the configured run state of a role. 526 * @see <a href= 527 * "https://archive.cloudera.com/cm6/6.3.0/generic/jar/cm_api/apidocs/json_ApiRoleState.html"> 528 * https://archive.cloudera.com/cm6/6.3.0/generic/jar/cm_api/apidocs/json_ApiRoleState.html</a> 529 */ 530 private enum RoleState { 531 HISTORY_NOT_AVAILABLE, 532 UNKNOWN, 533 STARTING, 534 STARTED, 535 BUSY, 536 STOPPING, 537 STOPPED, 538 NA; 539 540 public static RoleState fromString(final String value) { 541 if (StringUtils.isBlank(value)) { 542 return null; 543 } 544 return RoleState.valueOf(value.toUpperCase()); 545 } 546 } 547 548 /** 549 * Represents of the high-level health status of a subject in the cluster. 550 * @see <a href= 551 * "https://archive.cloudera.com/cm6/6.3.0/generic/jar/cm_api/apidocs/json_ApiHealthSummary.html"> 552 * https://archive.cloudera.com/cm6/6.3.0/generic/jar/cm_api/apidocs/json_ApiHealthSummary.html</a> 553 */ 554 private enum HealthSummary { 555 DISABLED, 556 HISTORY_NOT_AVAILABLE, 557 NOT_AVAILABLE, 558 GOOD, 559 CONCERNING, 560 BAD; 561 562 public static HealthSummary fromString(final String value) { 563 if (StringUtils.isBlank(value)) { 564 return null; 565 } 566 return HealthSummary.valueOf(value.toUpperCase()); 567 } 568 } 569 570 // ClusterManager methods take a "ServiceType" object (e.g. "HBASE_MASTER," "HADOOP_NAMENODE"). 571 // These "service types," which cluster managers call "roles" or "components," need to be mapped 572 // to their corresponding service (e.g. "HBase," "HDFS") in order to be controlled. 573 private static final Map<ServiceType, Service> roleServiceType = buildRoleServiceTypeMap(); 574 575 private static Map<ServiceType, Service> buildRoleServiceTypeMap() { 576 final Map<ServiceType, Service> ret = new HashMap<>(); 577 ret.put(ServiceType.HADOOP_NAMENODE, Service.HDFS); 578 ret.put(ServiceType.HADOOP_DATANODE, Service.HDFS); 579 ret.put(ServiceType.HADOOP_JOBTRACKER, Service.MAPREDUCE); 580 ret.put(ServiceType.HADOOP_TASKTRACKER, Service.MAPREDUCE); 581 ret.put(ServiceType.HBASE_MASTER, Service.HBASE); 582 ret.put(ServiceType.HBASE_REGIONSERVER, Service.HBASE); 583 return Collections.unmodifiableMap(ret); 584 } 585 586 enum Service { 587 HBASE, 588 HDFS, 589 MAPREDUCE 590 } 591}