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*/ 018 019package org.apache.hadoop.yarn.client.cli; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.List; 026 027import javax.ws.rs.core.MediaType; 028 029import org.apache.commons.cli.CommandLine; 030import org.apache.commons.cli.CommandLineParser; 031import org.apache.commons.cli.GnuParser; 032import org.apache.commons.cli.HelpFormatter; 033import org.apache.commons.cli.Option; 034import org.apache.commons.cli.Options; 035import org.apache.commons.cli.ParseException; 036import org.apache.commons.lang.StringUtils; 037import org.apache.hadoop.classification.InterfaceAudience.Public; 038import org.apache.hadoop.classification.InterfaceStability.Evolving; 039import org.apache.hadoop.conf.Configuration; 040import org.apache.hadoop.conf.Configured; 041import org.apache.hadoop.security.UserGroupInformation; 042import org.apache.hadoop.util.Tool; 043import org.apache.hadoop.yarn.api.records.ApplicationId; 044import org.apache.hadoop.yarn.api.records.ApplicationReport; 045import org.apache.hadoop.yarn.api.records.ContainerReport; 046import org.apache.hadoop.yarn.api.records.YarnApplicationState; 047import org.apache.hadoop.yarn.client.api.YarnClient; 048import org.apache.hadoop.yarn.conf.YarnConfiguration; 049import org.apache.hadoop.yarn.exceptions.YarnException; 050import org.apache.hadoop.yarn.logaggregation.LogCLIHelpers; 051import org.apache.hadoop.yarn.util.ConverterUtils; 052import org.apache.hadoop.yarn.util.Times; 053import org.apache.hadoop.yarn.webapp.util.WebAppUtils; 054import org.codehaus.jettison.json.JSONArray; 055import org.codehaus.jettison.json.JSONException; 056import org.codehaus.jettison.json.JSONObject; 057 058import com.google.common.annotations.VisibleForTesting; 059import com.sun.jersey.api.client.Client; 060import com.sun.jersey.api.client.ClientHandlerException; 061import com.sun.jersey.api.client.ClientResponse; 062import com.sun.jersey.api.client.UniformInterfaceException; 063import com.sun.jersey.api.client.WebResource; 064 065@Public 066@Evolving 067public class LogsCLI extends Configured implements Tool { 068 069 private static final String CONTAINER_ID_OPTION = "containerId"; 070 private static final String APPLICATION_ID_OPTION = "applicationId"; 071 private static final String NODE_ADDRESS_OPTION = "nodeAddress"; 072 private static final String APP_OWNER_OPTION = "appOwner"; 073 private static final String AM_CONTAINER_OPTION = "am"; 074 private static final String CONTAINER_LOG_FILES = "logFiles"; 075 public static final String HELP_CMD = "help"; 076 077 @Override 078 public int run(String[] args) throws Exception { 079 080 Options opts = new Options(); 081 opts.addOption(HELP_CMD, false, "Displays help for all commands."); 082 Option appIdOpt = 083 new Option(APPLICATION_ID_OPTION, true, "ApplicationId (required)"); 084 appIdOpt.setRequired(true); 085 opts.addOption(appIdOpt); 086 opts.addOption(CONTAINER_ID_OPTION, true, "ContainerId. " 087 + "By default, it will only print syslog if the application is runing." 088 + " Work with -logFiles to get other logs."); 089 opts.addOption(NODE_ADDRESS_OPTION, true, "NodeAddress in the format " 090 + "nodename:port"); 091 opts.addOption(APP_OWNER_OPTION, true, 092 "AppOwner (assumed to be current user if not specified)"); 093 Option amOption = new Option(AM_CONTAINER_OPTION, true, 094 "Prints the AM Container logs for this application. " 095 + "Specify comma-separated value to get logs for related AM Container. " 096 + "For example, If we specify -am 1,2, we will get the logs for " 097 + "the first AM Container as well as the second AM Container. " 098 + "To get logs for all AM Containers, use -am ALL. " 099 + "To get logs for the latest AM Container, use -am -1. " 100 + "By default, it will only print out syslog. Work with -logFiles " 101 + "to get other logs"); 102 amOption.setValueSeparator(','); 103 amOption.setArgs(Option.UNLIMITED_VALUES); 104 amOption.setArgName("AM Containers"); 105 opts.addOption(amOption); 106 Option logFileOpt = new Option(CONTAINER_LOG_FILES, true, 107 "Work with -am/-containerId and specify comma-separated value " 108 + "to get specified Container log files"); 109 logFileOpt.setValueSeparator(','); 110 logFileOpt.setArgs(Option.UNLIMITED_VALUES); 111 logFileOpt.setArgName("Log File Name"); 112 opts.addOption(logFileOpt); 113 114 opts.getOption(APPLICATION_ID_OPTION).setArgName("Application ID"); 115 opts.getOption(CONTAINER_ID_OPTION).setArgName("Container ID"); 116 opts.getOption(NODE_ADDRESS_OPTION).setArgName("Node Address"); 117 opts.getOption(APP_OWNER_OPTION).setArgName("Application Owner"); 118 opts.getOption(AM_CONTAINER_OPTION).setArgName("AM Containers"); 119 120 Options printOpts = new Options(); 121 printOpts.addOption(opts.getOption(HELP_CMD)); 122 printOpts.addOption(opts.getOption(CONTAINER_ID_OPTION)); 123 printOpts.addOption(opts.getOption(NODE_ADDRESS_OPTION)); 124 printOpts.addOption(opts.getOption(APP_OWNER_OPTION)); 125 printOpts.addOption(opts.getOption(AM_CONTAINER_OPTION)); 126 printOpts.addOption(opts.getOption(CONTAINER_LOG_FILES)); 127 128 if (args.length < 1) { 129 printHelpMessage(printOpts); 130 return -1; 131 } 132 if (args[0].equals("-help")) { 133 printHelpMessage(printOpts); 134 return 0; 135 } 136 CommandLineParser parser = new GnuParser(); 137 String appIdStr = null; 138 String containerIdStr = null; 139 String nodeAddress = null; 140 String appOwner = null; 141 boolean getAMContainerLogs = false; 142 String[] logFiles = null; 143 List<String> amContainersList = new ArrayList<String>(); 144 try { 145 CommandLine commandLine = parser.parse(opts, args, true); 146 appIdStr = commandLine.getOptionValue(APPLICATION_ID_OPTION); 147 containerIdStr = commandLine.getOptionValue(CONTAINER_ID_OPTION); 148 nodeAddress = commandLine.getOptionValue(NODE_ADDRESS_OPTION); 149 appOwner = commandLine.getOptionValue(APP_OWNER_OPTION); 150 getAMContainerLogs = commandLine.hasOption(AM_CONTAINER_OPTION); 151 if (getAMContainerLogs) { 152 String[] amContainers = commandLine.getOptionValues(AM_CONTAINER_OPTION); 153 for (String am : amContainers) { 154 boolean errorInput = false; 155 if (!am.trim().equalsIgnoreCase("ALL")) { 156 try { 157 int id = Integer.parseInt(am.trim()); 158 if (id != -1 && id <= 0) { 159 errorInput = true; 160 } 161 } catch (NumberFormatException ex) { 162 errorInput = true; 163 } 164 if (errorInput) { 165 System.err.println( 166 "Invalid input for option -am. Valid inputs are 'ALL', -1 " 167 + "and any other integer which is larger than 0."); 168 printHelpMessage(printOpts); 169 return -1; 170 } 171 amContainersList.add(am.trim()); 172 } else { 173 amContainersList.add("ALL"); 174 break; 175 } 176 } 177 } 178 if (commandLine.hasOption(CONTAINER_LOG_FILES)) { 179 logFiles = commandLine.getOptionValues(CONTAINER_LOG_FILES); 180 } 181 } catch (ParseException e) { 182 System.err.println("options parsing failed: " + e.getMessage()); 183 printHelpMessage(printOpts); 184 return -1; 185 } 186 187 if (appIdStr == null) { 188 System.err.println("ApplicationId cannot be null!"); 189 printHelpMessage(printOpts); 190 return -1; 191 } 192 193 ApplicationId appId = null; 194 try { 195 appId = ConverterUtils.toApplicationId(appIdStr); 196 } catch (Exception e) { 197 System.err.println("Invalid ApplicationId specified"); 198 return -1; 199 } 200 201 LogCLIHelpers logCliHelper = new LogCLIHelpers(); 202 logCliHelper.setConf(getConf()); 203 204 if (appOwner == null || appOwner.isEmpty()) { 205 appOwner = UserGroupInformation.getCurrentUser().getShortUserName(); 206 } 207 208 YarnApplicationState appState = YarnApplicationState.NEW; 209 try { 210 appState = getApplicationState(appId); 211 if (appState == YarnApplicationState.NEW 212 || appState == YarnApplicationState.NEW_SAVING 213 || appState == YarnApplicationState.SUBMITTED) { 214 System.out.println("Logs are not avaiable right now."); 215 return -1; 216 } 217 } catch (IOException | YarnException e) { 218 System.err.println("Unable to get ApplicationState." 219 + " Attempting to fetch logs directly from the filesystem."); 220 } 221 222 // To get am logs 223 if (getAMContainerLogs) { 224 // if we do not specify the value for CONTAINER_LOG_FILES option, 225 // we will only output syslog 226 if (logFiles == null || logFiles.length == 0) { 227 logFiles = new String[] { "syslog" }; 228 } 229 // If the application is running, we will call the RM WebService 230 // to get the AppAttempts which includes the nodeHttpAddress 231 // and containerId for all the AM Containers. 232 // After that, we will call NodeManager webService to get the 233 // related logs 234 if (appState == YarnApplicationState.ACCEPTED 235 || appState == YarnApplicationState.RUNNING) { 236 return printAMContainerLogs(getConf(), appIdStr, amContainersList, 237 logFiles, logCliHelper, appOwner, false); 238 } else { 239 // If the application is in the final state, we will call RM webservice 240 // to get all AppAttempts information first. If we get nothing, 241 // we will try to call AHS webservice to get related AppAttempts 242 // which includes nodeAddress for the AM Containers. 243 // After that, we will use nodeAddress and containerId 244 // to get logs from HDFS directly. 245 if (getConf().getBoolean(YarnConfiguration.APPLICATION_HISTORY_ENABLED, 246 YarnConfiguration.DEFAULT_APPLICATION_HISTORY_ENABLED)) { 247 return printAMContainerLogs(getConf(), appIdStr, amContainersList, 248 logFiles, logCliHelper, appOwner, true); 249 } else { 250 System.out 251 .println("Can not get AMContainers logs for the application:" 252 + appId); 253 System.out.println("This application:" + appId + " is finished." 254 + " Please enable the application history service. Or Using " 255 + "yarn logs -applicationId <appId> -containerId <containerId> " 256 + "--nodeAddress <nodeHttpAddress> to get the container logs"); 257 return -1; 258 } 259 } 260 } 261 262 int resultCode = 0; 263 if (containerIdStr != null) { 264 // if we provide the node address and the application is in the final 265 // state, we could directly get logs from HDFS. 266 if (nodeAddress != null && isApplicationFinished(appState)) { 267 return logCliHelper.dumpAContainersLogsForALogType(appIdStr, 268 containerIdStr, nodeAddress, appOwner, logFiles == null ? null 269 : Arrays.asList(logFiles)); 270 } 271 try { 272 // If the nodeAddress is not provided, we will try to get 273 // the ContainerReport. In the containerReport, we could get 274 // nodeAddress and nodeHttpAddress 275 ContainerReport report = getContainerReport(containerIdStr); 276 String nodeHttpAddress = 277 report.getNodeHttpAddress().replaceFirst( 278 WebAppUtils.getHttpSchemePrefix(getConf()), ""); 279 String nodeId = report.getAssignedNode().toString(); 280 // If the application is not in the final state, 281 // we will provide the NodeHttpAddress and get the container logs 282 // by calling NodeManager webservice. 283 if (!isApplicationFinished(appState)) { 284 if (logFiles == null || logFiles.length == 0) { 285 logFiles = new String[] { "syslog" }; 286 } 287 printContainerLogsFromRunningApplication(getConf(), appIdStr, 288 containerIdStr, nodeHttpAddress, nodeId, logFiles, logCliHelper, 289 appOwner); 290 } else { 291 // If the application is in the final state, we will directly 292 // get the container logs from HDFS. 293 printContainerLogsForFinishedApplication(appIdStr, containerIdStr, 294 nodeId, logFiles, logCliHelper, appOwner); 295 } 296 return resultCode; 297 } catch (IOException | YarnException ex) { 298 System.err.println("Unable to get logs for this container:" 299 + containerIdStr + "for the application:" + appId); 300 if (!getConf().getBoolean(YarnConfiguration.APPLICATION_HISTORY_ENABLED, 301 YarnConfiguration.DEFAULT_APPLICATION_HISTORY_ENABLED)) { 302 System.out.println("Please enable the application history service. Or "); 303 } 304 System.out.println("Using " 305 + "yarn logs -applicationId <appId> -containerId <containerId> " 306 + "--nodeAddress <nodeHttpAddress> to get the container logs"); 307 return -1; 308 } 309 } else { 310 if (nodeAddress == null) { 311 resultCode = 312 logCliHelper.dumpAllContainersLogs(appId, appOwner, System.out); 313 } else { 314 System.out.println("Should at least provide ContainerId!"); 315 printHelpMessage(printOpts); 316 resultCode = -1; 317 } 318 } 319 return resultCode; 320 } 321 322 private YarnApplicationState getApplicationState(ApplicationId appId) 323 throws IOException, YarnException { 324 YarnClient yarnClient = createYarnClient(); 325 326 try { 327 ApplicationReport appReport = yarnClient.getApplicationReport(appId); 328 return appReport.getYarnApplicationState(); 329 } finally { 330 yarnClient.close(); 331 } 332 } 333 334 @VisibleForTesting 335 protected YarnClient createYarnClient() { 336 YarnClient yarnClient = YarnClient.createYarnClient(); 337 yarnClient.init(getConf()); 338 yarnClient.start(); 339 return yarnClient; 340 } 341 342 public static void main(String[] args) throws Exception { 343 Configuration conf = new YarnConfiguration(); 344 LogsCLI logDumper = new LogsCLI(); 345 logDumper.setConf(conf); 346 int exitCode = logDumper.run(args); 347 System.exit(exitCode); 348 } 349 350 private void printHelpMessage(Options options) { 351 System.out.println("Retrieve logs for completed YARN applications."); 352 HelpFormatter formatter = new HelpFormatter(); 353 formatter.printHelp("yarn logs -applicationId <application ID> [OPTIONS]", new Options()); 354 formatter.setSyntaxPrefix(""); 355 formatter.printHelp("general options are:", options); 356 } 357 358 private List<JSONObject> getAMContainerInfoForRMWebService( 359 Configuration conf, String appId) throws ClientHandlerException, 360 UniformInterfaceException, JSONException { 361 Client webServiceClient = Client.create(); 362 String webAppAddress = 363 WebAppUtils.getWebAppBindURL(conf, YarnConfiguration.RM_BIND_HOST, 364 WebAppUtils.getRMWebAppURLWithScheme(conf)); 365 WebResource webResource = webServiceClient.resource(webAppAddress); 366 367 ClientResponse response = 368 webResource.path("ws").path("v1").path("cluster").path("apps") 369 .path(appId).path("appattempts").accept(MediaType.APPLICATION_JSON) 370 .get(ClientResponse.class); 371 JSONObject json = 372 response.getEntity(JSONObject.class).getJSONObject("appAttempts"); 373 JSONArray requests = json.getJSONArray("appAttempt"); 374 List<JSONObject> amContainersList = new ArrayList<JSONObject>(); 375 for (int i = 0; i < requests.length(); i++) { 376 amContainersList.add(requests.getJSONObject(i)); 377 } 378 return amContainersList; 379 } 380 381 private List<JSONObject> getAMContainerInfoForAHSWebService(Configuration conf, 382 String appId) throws ClientHandlerException, UniformInterfaceException, 383 JSONException { 384 Client webServiceClient = Client.create(); 385 String webAppAddress = 386 WebAppUtils.getHttpSchemePrefix(conf) 387 + WebAppUtils.getAHSWebAppURLWithoutScheme(conf); 388 WebResource webResource = webServiceClient.resource(webAppAddress); 389 390 ClientResponse response = 391 webResource.path("ws").path("v1").path("applicationhistory").path("apps") 392 .path(appId).path("appattempts").accept(MediaType.APPLICATION_JSON) 393 .get(ClientResponse.class); 394 JSONObject json = response.getEntity(JSONObject.class); 395 JSONArray requests = json.getJSONArray("appAttempt"); 396 List<JSONObject> amContainersList = new ArrayList<JSONObject>(); 397 for (int i = 0; i < requests.length(); i++) { 398 amContainersList.add(requests.getJSONObject(i)); 399 } 400 Collections.reverse(amContainersList); 401 return amContainersList; 402 } 403 404 private void printContainerLogsFromRunningApplication(Configuration conf, 405 String appId, String containerIdStr, String nodeHttpAddress, 406 String nodeId, String[] logFiles, LogCLIHelpers logCliHelper, 407 String appOwner) throws IOException { 408 Client webServiceClient = Client.create(); 409 String containerString = "\n\nContainer: " + containerIdStr; 410 System.out.println(containerString); 411 System.out.println(StringUtils.repeat("=", containerString.length())); 412 for (String logFile : logFiles) { 413 System.out.println("LogType:" + logFile); 414 System.out.println("Log Upload Time:" 415 + Times.format(System.currentTimeMillis())); 416 System.out.println("Log Contents:"); 417 try { 418 WebResource webResource = 419 webServiceClient.resource(WebAppUtils.getHttpSchemePrefix(conf) 420 + nodeHttpAddress); 421 ClientResponse response = 422 webResource.path("ws").path("v1").path("node") 423 .path("containerlogs").path(containerIdStr).path(logFile) 424 .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class); 425 System.out.println(response.getEntity(String.class)); 426 System.out.println("End of LogType:" + logFile); 427 } catch (ClientHandlerException | UniformInterfaceException ex) { 428 System.out.println("Can not find the log file:" + logFile 429 + " for the container:" + containerIdStr + " in NodeManager:" 430 + nodeId); 431 } 432 } 433 // for the case, we have already uploaded partial logs in HDFS 434 logCliHelper.dumpAContainersLogsForALogType(appId, containerIdStr, nodeId, 435 appOwner, Arrays.asList(logFiles)); 436 } 437 438 private void printContainerLogsForFinishedApplication(String appId, 439 String containerId, String nodeAddress, String[] logFiles, 440 LogCLIHelpers logCliHelper, String appOwner) throws IOException { 441 String containerString = "\n\nContainer: " + containerId; 442 System.out.println(containerString); 443 System.out.println(StringUtils.repeat("=", containerString.length())); 444 logCliHelper.dumpAContainersLogsForALogType(appId, containerId, 445 nodeAddress, appOwner, logFiles != null ? Arrays.asList(logFiles) : null); 446 } 447 448 private ContainerReport getContainerReport(String containerIdStr) 449 throws YarnException, IOException { 450 YarnClient yarnClient = createYarnClient(); 451 try { 452 return yarnClient.getContainerReport(ConverterUtils 453 .toContainerId(containerIdStr)); 454 } finally { 455 yarnClient.close(); 456 } 457 } 458 459 private boolean isApplicationFinished(YarnApplicationState appState) { 460 return appState == YarnApplicationState.FINISHED 461 || appState == YarnApplicationState.FAILED 462 || appState == YarnApplicationState.KILLED; 463 } 464 465 private int printAMContainerLogs(Configuration conf, String appId, 466 List<String> amContainers, String[] logFiles, LogCLIHelpers logCliHelper, 467 String appOwner, boolean applicationFinished) throws Exception { 468 List<JSONObject> amContainersList = null; 469 List<AMLogsRequest> requests = new ArrayList<AMLogsRequest>(); 470 boolean getAMContainerLists = false; 471 String errorMessage = ""; 472 try { 473 amContainersList = getAMContainerInfoForRMWebService(conf, appId); 474 if (amContainersList != null && !amContainersList.isEmpty()) { 475 getAMContainerLists = true; 476 for (JSONObject amContainer : amContainersList) { 477 AMLogsRequest request = new AMLogsRequest(applicationFinished); 478 request.setAmContainerId(amContainer.getString("containerId")); 479 request.setNodeHttpAddress(amContainer.getString("nodeHttpAddress")); 480 request.setNodeId(amContainer.getString("nodeId")); 481 requests.add(request); 482 } 483 } 484 } catch (Exception ex) { 485 errorMessage = ex.getMessage(); 486 if (applicationFinished) { 487 try { 488 amContainersList = getAMContainerInfoForAHSWebService(conf, appId); 489 if (amContainersList != null && !amContainersList.isEmpty()) { 490 getAMContainerLists = true; 491 for (JSONObject amContainer : amContainersList) { 492 AMLogsRequest request = new AMLogsRequest(applicationFinished); 493 request.setAmContainerId(amContainer.getString("amContainerId")); 494 requests.add(request); 495 } 496 } 497 } catch (Exception e) { 498 errorMessage = e.getMessage(); 499 } 500 } 501 } 502 503 if (!getAMContainerLists) { 504 System.err.println("Unable to get AM container informations " 505 + "for the application:" + appId); 506 System.err.println(errorMessage); 507 return -1; 508 } 509 510 if (amContainers.contains("ALL")) { 511 for (AMLogsRequest request : requests) { 512 outputAMContainerLogs(request, conf, appId, logFiles, logCliHelper, 513 appOwner); 514 } 515 System.out.println(); 516 System.out.println("Specified ALL for -am option. " 517 + "Printed logs for all am containers."); 518 } else { 519 for (String amContainer : amContainers) { 520 int amContainerId = Integer.parseInt(amContainer.trim()); 521 if (amContainerId == -1) { 522 outputAMContainerLogs(requests.get(requests.size() - 1), conf, appId, 523 logFiles, logCliHelper, appOwner); 524 } else { 525 if (amContainerId <= requests.size()) { 526 outputAMContainerLogs(requests.get(amContainerId - 1), conf, appId, 527 logFiles, logCliHelper, appOwner); 528 } 529 } 530 } 531 } 532 return 0; 533 } 534 535 private void outputAMContainerLogs(AMLogsRequest request, Configuration conf, 536 String appId, String[] logFiles, LogCLIHelpers logCliHelper, 537 String appOwner) throws Exception { 538 String nodeHttpAddress = request.getNodeHttpAddress(); 539 String containerId = request.getAmContainerId(); 540 String nodeId = request.getNodeId(); 541 542 if (request.isAppFinished()) { 543 if (containerId != null && !containerId.isEmpty()) { 544 if (nodeId == null || nodeId.isEmpty()) { 545 try { 546 nodeId = 547 getContainerReport(containerId).getAssignedNode().toString(); 548 } catch (Exception ex) { 549 System.err.println(ex); 550 nodeId = null; 551 } 552 } 553 if (nodeId != null && !nodeId.isEmpty()) { 554 printContainerLogsForFinishedApplication(appId, containerId, nodeId, 555 logFiles, logCliHelper, appOwner); 556 } 557 } 558 } else { 559 if (nodeHttpAddress != null && containerId != null 560 && !nodeHttpAddress.isEmpty() && !containerId.isEmpty()) { 561 printContainerLogsFromRunningApplication(conf, appId, containerId, 562 nodeHttpAddress, nodeId, logFiles, logCliHelper, appOwner); 563 } 564 } 565 } 566 567 private static class AMLogsRequest { 568 private String amContainerId; 569 private String nodeId; 570 private String nodeHttpAddress; 571 private final boolean isAppFinished; 572 573 AMLogsRequest(boolean isAppFinished) { 574 this.isAppFinished = isAppFinished; 575 this.setAmContainerId(""); 576 this.setNodeId(""); 577 this.setNodeHttpAddress(""); 578 } 579 580 public String getAmContainerId() { 581 return amContainerId; 582 } 583 584 public void setAmContainerId(String amContainerId) { 585 this.amContainerId = amContainerId; 586 } 587 588 public String getNodeId() { 589 return nodeId; 590 } 591 592 public void setNodeId(String nodeId) { 593 this.nodeId = nodeId; 594 } 595 596 public String getNodeHttpAddress() { 597 return nodeHttpAddress; 598 } 599 600 public void setNodeHttpAddress(String nodeHttpAddress) { 601 this.nodeHttpAddress = nodeHttpAddress; 602 } 603 604 public boolean isAppFinished() { 605 return isAppFinished; 606 } 607 } 608}