OOZIE-2296 Add an Oozie diagnostic bundle tool (asasvari)
Project: http://git-wip-us.apache.org/repos/asf/oozie/repo Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/9e8598ea Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/9e8598ea Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/9e8598ea Branch: refs/heads/master Commit: 9e8598ea48d6db78d5feaad60823b55b2998b67d Parents: 567ce9f Author: Attila Sasvari <asasv...@cloudera.com> Authored: Sun Nov 5 00:41:39 2017 +0100 Committer: Attila Sasvari <asasv...@cloudera.com> Committed: Wed Nov 15 14:43:54 2017 +0100 ---------------------------------------------------------------------- docs/src/site/twiki/DG_CommandLineTool.twiki | 54 +++ pom.xml | 6 +- release-log.txt | 1 + tools/pom.xml | 4 + .../src/main/bin/oozie-diag-bundle-collector.sh | 61 +++ .../oozie/tools/diag/AppInfoCollector.java | 443 +++++++++++++++++++ .../org/apache/oozie/tools/diag/ArgParser.java | 176 ++++++++ .../tools/diag/DiagBundleCollectorDriver.java | 156 +++++++ .../oozie/tools/diag/DiagBundleCompressor.java | 43 ++ .../oozie/tools/diag/DiagBundleEntryWriter.java | 118 +++++ .../oozie/tools/diag/DiagOozieClient.java | 52 +++ .../oozie/tools/diag/MetricsCollector.java | 142 ++++++ .../tools/diag/OozieLauncherLogFetcher.java | 135 ++++++ .../oozie/tools/diag/ServerInfoCollector.java | 141 ++++++ .../oozie/tools/diag/TestAppInfoCollector.java | 167 +++++++ .../apache/oozie/tools/diag/TestArgParser.java | 112 +++++ .../oozie/tools/diag/TestMetricsCollector.java | 228 ++++++++++ .../tools/diag/TestServerInfoCollector.java | 125 ++++++ 18 files changed, 2163 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/docs/src/site/twiki/DG_CommandLineTool.twiki ---------------------------------------------------------------------- diff --git a/docs/src/site/twiki/DG_CommandLineTool.twiki b/docs/src/site/twiki/DG_CommandLineTool.twiki index d404767..24fb1c9 100644 --- a/docs/src/site/twiki/DG_CommandLineTool.twiki +++ b/docs/src/site/twiki/DG_CommandLineTool.twiki @@ -1939,6 +1939,60 @@ The properties file must specify the =mapred.mapper.class=, =mapred.reducer.clas The map-reduce job will be created and submitted. All jar files and all other files needed by the mapreduce job need to be uploaded onto HDFS under libpath beforehand. The workflow.xml will be created in Oozie server internally. Users can get the workflow.xml from console or command line(-definition). +---++ Getting Oozie diagnostics bundles + +A tool that collects a diagnostic bundle of information from Oozie. Collected information includes available Oozie ShareLibs; +effective configuration, system properties, environment variables, thread dump of the Oozie server; instrumentation logs; +dump of queued commands; details about workflow(s) (such as workflow xml, logs, current state, job properties), coordinator(s) +and bundle(s). When retrieving coordinator information, the tool will also try to fetch information about related child workflow(s). + +Syntax: + +<verbatim> +$ oozie-diag-bundle-collector.sh [-jobs <id ...>] [-maxchildactions <n>] + [-numbundles <n>] [-numcoordinators <n>] [-numworkflows <n>] -oozie + <url> -output <dir> +</verbatim> + +where + -jobs <id ...> Detailed information on the given job IDs will be + collected (default: none) + -maxchildactions <n> Maximum number of Workflow or Coordinator actions + that will be collected (default: 10) + -numbundles <n> Detailed information on the last n Bundles will be + collected (default: 0) + -numcoordinators <n> Detailed information on the last n Coordinators + will be collected (default: 0) + -numworkflows <n> Detailed information on the last n workflows will + be collected (default: 0) + -oozie <url> Required: Oozie URL (or specify with OOZIE_URL env + var) + -output <dir> Required: Directory to output the zip file + +Example: + +<verbatim> +$ oozie-diag-bundle-collector.sh -jobs 0000001-170918144116149-oozie-test-W -oozie http://localhost:11000/oozie -output diag + +... + +Using Temporary Directory: /var/folders/9q/f8p_r6gj0wbck49_dc092q_m0000gp/T/1505748796767-0 +Getting Sharelib Information...Done +Getting Configuration...Done +Getting OS Environment Variables...Done +Getting Java System Properties...Done +Getting Queue Dump...Done +Getting Thread Dump...Done +Getting Instrumentation...Done +Getting Metrics...Skipping (Metrics are unavailable) +Getting Details for 0000001-170918144116149-oozie-test-W...Done +Creating Zip File: /var/lib/oozie/diag/oozie-diag-bundle-1505748797206.zip...Done +</verbatim> + +Before executing the command, make sure OOZIE_HOME environment variable is set correctly. If Oozie authorization is enabled, then +the user must be an admin user in order to perform admin operations (for example getting a Thread dump of the Oozie server). If the +output directory does not exist, the tool will create it and store generated bundle there. + [[index][::Go back to Oozie Documentation Index::]] </noautolink> http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 0012445..9f58011 100644 --- a/pom.xml +++ b/pom.xml @@ -1537,7 +1537,11 @@ <artifactId>guice</artifactId> <version>3.0</version> </dependency> - + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>annotations</artifactId> + <version>3.0.0</version> + </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/release-log.txt ---------------------------------------------------------------------- diff --git a/release-log.txt b/release-log.txt index 6a6e6d4..8db0dde 100644 --- a/release-log.txt +++ b/release-log.txt @@ -1,5 +1,6 @@ -- Oozie 5.0.0 release (trunk - unreleased) +OOZIE-2296 Add an Oozie diagnostic bundle tool (asasvari) OOZIE-3125 TestDBLoadDump.testImportInvalidDataLeavesTablesEmpty fails (asasvari) OOZIE-3106 upgrade surefire-plugin to 2.20.1 (dbist13 via asasvari) OOZIE-2061 Remove CoordJobDeleteJPAExecutor (dbist13 via gezapeti) http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/pom.xml ---------------------------------------------------------------------- diff --git a/tools/pom.xml b/tools/pom.xml index 111ce53..bdc736d 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -83,6 +83,10 @@ <artifactId>gson</artifactId> <scope>compile</scope> </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>annotations</artifactId> + </dependency> </dependencies> http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/bin/oozie-diag-bundle-collector.sh ---------------------------------------------------------------------- diff --git a/tools/src/main/bin/oozie-diag-bundle-collector.sh b/tools/src/main/bin/oozie-diag-bundle-collector.sh new file mode 100755 index 0000000..68929e1 --- /dev/null +++ b/tools/src/main/bin/oozie-diag-bundle-collector.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# +# 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. +# + +# resolve links - $0 may be a softlink +PRG="${0}" + +while [ -h "${PRG}" ]; do + ls=$(ls -ld "${PRG}") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=$(dirname "${PRG}")/"$link" + fi +done + +BASEDIR=$(dirname "${PRG}") +cd "${BASEDIR}"/.. || (echo "Cannot change directory to ${BASEDIR}" && exit) +BASEDIR=$(pwd) + +OOZIECPPATH="" +for i in "${BASEDIR}/libtools/"*.jar; do + OOZIECPPATH="${OOZIECPPATH}:$i" +done +for i in "${BASEDIR}/lib/"*.jar; do + OOZIECPPATH="${OOZIECPPATH}:$i" +done +for i in "${BASEDIR}/libext/"*.jar; do + OOZIECPPATH="${OOZIECPPATH}:$i" +done + + +if test -z "${JAVA_HOME}" +then + JAVA_BIN=java +else + JAVA_BIN=${JAVA_HOME}/bin/java +fi + +while [[ ${1} =~ ^\-D ]]; do + JAVA_PROPERTIES="${JAVA_PROPERTIES} ${1}" + shift +done + +${JAVA_BIN} ${JAVA_PROPERTIES} -cp "${OOZIECPPATH}" org.apache.oozie.tools.diag.DiagBundleCollectorDriver "${@}" http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java b/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java new file mode 100644 index 0000000..d7e4534 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java @@ -0,0 +1,443 @@ +/** + * 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.oozie.tools.diag; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.util.ConverterUtils; +import org.apache.oozie.client.BundleJob; +import org.apache.oozie.client.CoordinatorAction; +import org.apache.oozie.client.CoordinatorJob; +import org.apache.oozie.client.OozieClientException; +import org.apache.oozie.client.WorkflowAction; +import org.apache.oozie.client.WorkflowJob; +import org.apache.oozie.util.XConfiguration; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Properties; + +@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output directory is specified by user") +class AppInfoCollector { + private final DiagOozieClient client; + private final OozieLauncherLogFetcher oozieLauncherLogFetcher; + + AppInfoCollector(final Configuration hadoopConfig, final DiagOozieClient client) { + this.client = client; + oozieLauncherLogFetcher = new OozieLauncherLogFetcher(hadoopConfig); + } + + private void storeWorkflowJobDetails(final File outputDir, final String jobId, int maxChildActions) { + if (jobId == null || !isWorkflow(jobId)) { + return; + } + + try { + System.out.print("Getting Details for " + jobId + "..."); + final File workflowOutputDir = new File(outputDir, jobId); + if (!createOutputDirectory(workflowOutputDir)) { + return; + } + + final File resolvedActionsDir = new File(workflowOutputDir, "resolved-actions"); + if (!createOutputDirectory(resolvedActionsDir)) { + System.out.println("Workflow details already stored."); + return; + } + + final WorkflowJob job = client.getJobInfo(jobId); + + try (DiagBundleEntryWriter diagBundleEntryWriter = new DiagBundleEntryWriter(workflowOutputDir,"info.txt")) { + persistWorkflowJobInfo(maxChildActions, resolvedActionsDir, job, diagBundleEntryWriter); + } + + storeCommonDetails(workflowOutputDir, jobId, "workflow", job.getConf()); + System.out.println("Done"); + } catch (IOException | OozieClientException e) { + System.err.printf("Exception occurred during the retrieval of workflow information: %s%n", e.getMessage()); + } + } + + private void persistWorkflowJobInfo(int maxChildActions, final File resolvedActionsDir, final WorkflowJob job, + final DiagBundleEntryWriter bundleEntryWriter) throws IOException { + bundleEntryWriter.writeString("WORKFLOW\n") + .writeString("--------\n") + .writeStringValue("Workflow Id : ", job.getId()) + .writeStringValue("Name : ", job.getAppName()) + .writeStringValue("App Path : ", job.getAppPath()) + .writeStringValue("User : ", job.getUser()) + .writeStringValue("ACL : ", job.getAcl()) + .writeStringValue("Status : ", job.getStatus().toString()) + .writeStringValue("Console URL : ", job.getConsoleUrl()) + .writeStringValue("External Id : ", job.getExternalId()) + .writeStringValue("Parent Id : ", job.getParentId()) + .writeDateValue("Created Time : ", job.getCreatedTime()) + .writeDateValue("End Time : ", job.getEndTime()) + .writeDateValue("Last Modified Time : ", job.getLastModifiedTime()) + .writeDateValue("Start Time : ", job.getStartTime()) + .writeIntValue("Run : ", job.getRun()) + .writeIntValue("Action Count : ", job.getActions().size()) + .writeNewLine() + .writeString("ACTIONS\n") + .writeString("------\n") + .flush(); + + final List<WorkflowAction> workflowActions = job.getActions(); + for (int actionCount = 0; actionCount != workflowActions.size() && actionCount < maxChildActions; ++actionCount) { + final WorkflowAction action = workflowActions.get(actionCount); + bundleEntryWriter.writeStringValue("Action Id : ", action.getId()) + .writeStringValue("Name : ", action.getName()) + .writeStringValue("Type : ", action.getType()) + .writeStringValue("Status : ", action.getStatus().toString()) + .writeStringValue("Transition : ", action.getTransition()) + .writeDateValue("Start Time : ", action.getStartTime()) + .writeDateValue("End Time : ", action.getEndTime()) + .writeStringValue("Error Code : ", action.getErrorCode()) + .writeStringValue("Error Message : ", action.getErrorMessage()) + .writeStringValue("Console URL : ", action.getConsoleUrl()) + .writeStringValue("Tracker URI : ", action.getTrackerUri()) + .writeStringValue("External Child Ids : ", action.getExternalChildIDs()) + .writeStringValue("External Id : ", action.getExternalId()) + .writeStringValue("External Status : ", action.getExternalStatus()) + .writeStringValue("Data : ", action.getData()) + .writeStringValue("Stats : ", action.getStats()) + .writeStringValue("Credentials : ", action.getCred()) + .writeIntValue("Retries : ", action.getRetries()) + .writeIntValue("User Retry Int : ", action.getUserRetryInterval()) + .writeIntValue("User Retry Count : ", action.getUserRetryCount()) + .writeIntValue("User Retry Max : ", action.getUserRetryMax()) + .writeNewLine() + .flush(); + + final String actionType = action.getType(); + persistResolvedActionDefinition(action, resolvedActionsDir); + + if (!isControlNode(actionType)) { // skip control nodes + storeOozieLauncherLog(resolvedActionsDir, action, job.getUser()); + } + } + } + + private boolean isControlNode(final String actionType) { + return isNonDecisionControlNode(actionType) || isDecisionNode(actionType); + } + + private boolean isDecisionNode(final String actionType) { + return actionType.contains("switch"); + } + + private boolean isNonDecisionControlNode(final String actionType) { + return actionType.contains(":"); + } + + private void persistResolvedActionDefinition(final WorkflowAction action, final File resolvedActionsDir) throws IOException { + persistWorkflowDefinition(resolvedActionsDir, action.getName(), action.getConf()); + } + + + private void storeOozieLauncherLog(final File outputDir, final WorkflowAction action, final String user) { + try (PrintStream fw = new PrintStream(new File(outputDir, "launcher_" + action.getName() + ".log"), + StandardCharsets.UTF_8.toString())) { + + final ApplicationId appId = ConverterUtils.toApplicationId(action.getExternalId()); + oozieLauncherLogFetcher.dumpAllContainersLogs(appId, user, fw); + } catch (IOException e) { + System.err.printf("Exception occurred during the retrieval of Oozie launcher logs for workflow(s): %s%n", + e.getMessage()); + } + } + + private void getCoordJob(final File outputDir, final String jobId, int maxChildActions) { + if (jobId == null || !isCoordinator(jobId)) { + return; + } + + try { + System.out.print("Getting Details for " + jobId + "..."); + final File coordOutputDir = new File(outputDir, jobId); + + if (!createOutputDirectory(coordOutputDir)) { + return; + } + + final CoordinatorJob job = client.getCoordJobInfo(jobId); + + try (DiagBundleEntryWriter bundleEntryWriter = new DiagBundleEntryWriter(coordOutputDir, "info.txt")) { + persistCoordinatorJobInfo(maxChildActions, job, bundleEntryWriter); + } + + storeCommonDetails(coordOutputDir, jobId, "coordinator", job.getConf()); + System.out.println("Done"); + + final List<CoordinatorAction> coordinatorActions = job.getActions(); + for (int i = 0; i != coordinatorActions.size() && i < maxChildActions; ++i) { + storeWorkflowJobDetails(outputDir, coordinatorActions.get(i).getExternalId(), maxChildActions); + } + } catch (IOException | OozieClientException e) { + System.err.printf(String.format("Exception occurred during the retrieval of coordinator information:%s%n", + e.getMessage())); + } + } + + private void persistCoordinatorJobInfo(int maxChildActions, final CoordinatorJob job, + final DiagBundleEntryWriter bundleEntryWriter) + throws IOException { + bundleEntryWriter.writeString("COORDINATOR\n") + .writeString("-----------\n") + .writeStringValue("Coordinator Id : ", job.getId()) + .writeStringValue("Name : ", job.getAppName()) + .writeStringValue("App Path : ", job.getAppPath()) + .writeStringValue("User : ", job.getUser()) + .writeStringValue("ACL : ", job.getAcl()) + .writeStringValue("Status : ", job.getStatus().toString()) + .writeStringValue("Console URL : ", job.getConsoleUrl()) + .writeStringValue("External Id : ", job.getExternalId()) + .writeStringValue("Bundle Id : ", job.getBundleId()) + .writeStringValue("Frequency : ", job.getFrequency()) + .writeStringValue("Time Unit : ", job.getTimeUnit().toString()) + .writeDateValue("Start Time : ", job.getStartTime()) + .writeDateValue("End Time : ", job.getEndTime()) + .writeDateValue("Last Action Time : ", job.getLastActionTime()) + .writeDateValue("Next Materialized Time : ", job.getNextMaterializedTime()) + .writeDateValue("Pause Time : ", job.getPauseTime()) + .writeStringValue("Timezone : ", job.getTimeZone()) + .writeIntValue("Concurrency : ", job.getConcurrency()) + .writeIntValue("Timeout : ", job.getTimeout()) + .writeStringValue("Execution Order : ", job.getExecutionOrder().toString()) + .writeIntValue("Action Count : ", job.getActions().size()) + .writeNewLine() + .writeString("ACTIONS\n") + .writeString("------\n") + .flush(); + + final List<CoordinatorAction> coordinatorActions = job.getActions(); + for (int i = 0; i < maxChildActions && i != coordinatorActions.size(); ++i) { + final CoordinatorAction action = coordinatorActions.get(i); + bundleEntryWriter.writeStringValue("Action Id : ", action.getId()) + .writeIntValue("Action Number : ", action.getActionNumber()) + .writeStringValue("Job Id : ", action.getJobId()) + .writeStringValue("Status : ", action.getStatus().toString()) + .writeStringValue("External Id : ", action.getExternalId()) + .writeStringValue("External Status : ", action.getExternalStatus()) + .writeStringValue("Console URL : ", action.getConsoleUrl()) + .writeStringValue("Tracker URI : ", action.getTrackerUri()) + .writeDateValue("Created Time : ", action.getCreatedTime()) + .writeDateValue("Nominal Time : ", action.getNominalTime()) + .writeDateValue("Last Modified Time : ", action.getLastModifiedTime()) + .writeStringValue("Error Code : ", action.getErrorCode()) + .writeStringValue("Error Message : ", action.getErrorMessage()) + .writeStringValue("Missing Dependencies : ", action.getMissingDependencies()) + .writeStringValue("Push Missing Dependencies : ", action.getPushMissingDependencies()) + .writeNewLine() + .flush(); + } + } + + private void getBundleJob(final File outputDir, final String jobId, int maxChildActions) { + if (jobId == null || !isBundle(jobId)) { + return; + } + + try { + System.out.print("Getting Details for " + jobId + "..."); + final File bundleOutputDir = new File(outputDir, jobId); + + if (!createOutputDirectory(bundleOutputDir)) { + return; + } + + final BundleJob job = client.getBundleJobInfo(jobId); + + try (DiagBundleEntryWriter bundleEntryWriter = new DiagBundleEntryWriter(bundleOutputDir, "info.txt")) { + persistBundleJobInfo(job, bundleEntryWriter); + } + + storeCommonDetails(bundleOutputDir, jobId, "bundle", job.getConf()); + System.out.println("Done"); + for (CoordinatorJob coordJob : job.getCoordinators()) { + getCoordJob(outputDir, coordJob.getId(), maxChildActions); + } + + + } catch (IOException | OozieClientException e) { + System.err.printf(String.format("Exception occurred during the retrieval of bundle information: %s%n", + e.getMessage())); + } + } + + private boolean createOutputDirectory(final File outputDir) throws IOException { + if (outputDir.isDirectory()) { + System.out.println("(Already) Done"); + return false; + } + if (!outputDir.mkdirs()) { + throw new IOException("Could not create output directory: " + outputDir.getAbsolutePath()); + } + return true; + } + + private void persistBundleJobInfo(final BundleJob job, final DiagBundleEntryWriter bundleEntryWriter) throws IOException { + bundleEntryWriter.writeString("BUNDLE\n") + .writeString("-----------\n") + .writeStringValue("Bundle Id : ", job.getId()) + .writeStringValue("Name : ", job.getAppName()) + .writeStringValue("App Path : ", job.getAppPath()) + .writeStringValue("User : ", job.getUser()) + .writeStringValue("Status : ", job.getStatus().toString()) + .writeDateValue("Created Time : ", job.getCreatedTime()) + .writeDateValue("Start Time : ", job.getStartTime()) + .writeDateValue("End Time : ", job.getEndTime()) + .writeDateValue("KickoffTime : ", job.getKickoffTime()) + .writeDateValue("Pause Time : ", job.getPauseTime()) + .writeIntValue("Timeout : ", job.getTimeout()) + .writeStringValue("Console URL : ", job.getConsoleUrl()) + .writeStringValue( "ACL : ", job.getAcl()) + .flush(); + } + + private void storeCommonDetails(final File outputDir, final String jobId, final String definitionName, + final String jobPropsConfStr) { + try { + final String definition = client.getJobDefinition(jobId); + + if (definition != null) { + persistWorkflowDefinition(outputDir, definitionName, definition); + } + + if (jobPropsConfStr != null) { + persistJobProperties(outputDir, jobPropsConfStr); + } + + persistJobLog(outputDir, jobId); + } catch (OozieClientException | IOException e) { + System.err.printf(String.format("Exception occurred during the retrieval of common job details: %s%n", + e.getMessage())); + } + } + + private void persistJobLog(final File outputDir, final String jobId) throws FileNotFoundException, + UnsupportedEncodingException, OozieClientException { + try (PrintStream ps = new PrintStream(new File(outputDir, "log.txt"), StandardCharsets.UTF_8.toString())) { + client.getJobLog(jobId, null, null, null, ps); + } + } + + private void persistJobProperties(final File outputDir, final String jobPropsConfStr) throws IOException { + final StringReader sr = new StringReader(jobPropsConfStr); + final XConfiguration jobPropsConf = new XConfiguration(sr); + final Properties jobProps = jobPropsConf.toProperties(); + + try (OutputStream outputStream = new FileOutputStream(new File(outputDir, "job.properties"))) { + jobProps.store(outputStream, ""); + } + } + + private void persistWorkflowDefinition(final File outputDir, final String definitionName, String definition) + throws IOException { + try (DiagBundleEntryWriter bundleEntryWriter = new DiagBundleEntryWriter(outputDir, + definitionName + ".xml")) { + bundleEntryWriter.writeString(definition); + } + } + + void storeLastWorkflows(final File outputDir, int numWorkflows, int maxChildActions) { + if (numWorkflows == 0) { + return; + } + + try { + final List<WorkflowJob> jobs = client.getJobsInfo(null, 0, numWorkflows); + for (WorkflowJob job : jobs) { + storeWorkflowJobDetails(outputDir, job.getId(), maxChildActions); + } + } catch (OozieClientException e) { + System.err.printf("Exception occurred during the retrieval of information on the last %d workflow(s): %s.%n", + numWorkflows, e.getMessage()); + } + } + + void storeLastCoordinators(final File outputDir, int numCoordinators, int maxChildActions) { + if (numCoordinators == 0) { + return; + } + + try { + final List<CoordinatorJob> jobs = client.getCoordJobsInfo(null, 0, numCoordinators); + for (CoordinatorJob job : jobs) { + getCoordJob(outputDir, job.getId(), maxChildActions); + } + } catch (OozieClientException e) { + System.err.printf("Exception occurred during the retrieval of information on the last %d coordinator(s): %s.%n", + numCoordinators, e.getMessage()); + } + } + + void storeLastBundles(final File outputDir, int numBundles, int maxChildActions) { + if (numBundles == 0) { + return; + } + + try { + final List<BundleJob> jobs = client.getBundleJobsInfo(null, 0, numBundles); + for (BundleJob job : jobs) { + getBundleJob(outputDir, job.getId(), maxChildActions); + } + } catch (OozieClientException e) { + System.err.printf("Exception occurred during the retrieval of information on the last %d bundle(s): %s.%n", + numBundles, e.getMessage()); + } + } + + void getSpecificJobs(final File outputDir, final String[] jobIds, int maxChildActions) { + if (jobIds == null) { + return; + } + + for (String jobId : jobIds) { + if (isWorkflow(jobId)) { + storeWorkflowJobDetails(outputDir, jobId, maxChildActions); + } else if (isCoordinator(jobId)) { + getCoordJob(outputDir, jobId, maxChildActions); + } else if (isBundle(jobId)) { + getBundleJob(outputDir, jobId, maxChildActions); + } + } + } + + private boolean isBundle(final String jobId) { + return jobId.endsWith("-B"); + } + + private boolean isCoordinator(final String jobId) { + return jobId.endsWith("-C"); + } + + private boolean isWorkflow(final String jobId) { + return jobId.endsWith("-W"); + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java b/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java new file mode 100644 index 0000000..115d0ba --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java @@ -0,0 +1,176 @@ +/** + * 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.oozie.tools.diag; + +import com.google.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.oozie.cli.OozieCLI; + +import java.io.File; +import java.io.IOException; + +@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output directory is specified by user") +class ArgParser { + + private static final String OOZIE_OPTION = "oozie"; + private static final String NUM_WORKFLOWS_OPTION = "numworkflows"; + private static final String NUM_COORDS_OPTION = "numcoordinators"; + private static final String NUM_BUNDLES_OPTION = "numbundles"; + private static final String JOBS_OPTION = "jobs"; + private static final String MAX_CHILD_ACTIONS = "maxchildactions"; + private static final String OUTPUT_DIR_OPTION = "output"; + private final Options options = new Options(); + private CommandLine commandLine; + + public void setCommandLine(CommandLine commandLine) { + this.commandLine = commandLine; + } + + private void addNewOption(String optionName, String argName, String details, boolean required) { + addNewOption(optionName, argName, details, null, required, null, false); + } + + private void addNewOption(String optionName, String argName, String details, Class type, boolean required) { + addNewOption(optionName, argName, details, type, required, null, false); + } + + private void addNewOption(String optionName, String argName, String details, Class type, boolean required, + Character valueSeparator, boolean isUnlimited) { + final Option option = new Option(optionName, true, + details); + option.setRequired(required); + option.setArgName(argName); + if (type != null) { + option.setType(type); + } + if (valueSeparator != null) { + option.setValueSeparator(valueSeparator); + } + if (isUnlimited) { + option.setArgs(Option.UNLIMITED_VALUES); + } + options.addOption(option); + } + + Options setupOptions() { + addNewOption(OOZIE_OPTION, "url", String.format("Required: Oozie URL (or specify with %s env var)", + OozieCLI.ENV_OOZIE_URL), true); + + addNewOption(NUM_WORKFLOWS_OPTION, "n", + "Detailed information on the last n workflows will be collected (default: 0)", Integer.class, false); + + addNewOption(NUM_COORDS_OPTION, "n", + "Detailed information on the last n Coordinators will be collected (default: 0)", Integer.class, + false); + + addNewOption(NUM_BUNDLES_OPTION, "n", + "Detailed information on the last n Bundles will be collected (default: 0)", Integer.class, false); + + addNewOption(JOBS_OPTION, "id ...", + "Detailed information on the given job IDs will be collected (default: none)", null, false, + ',', true); + + addNewOption(MAX_CHILD_ACTIONS, "n", + "Maximum number of Workflow or Coordinator actions that will be collected (default: 10)", Integer.class, + false); + + addNewOption(OUTPUT_DIR_OPTION, "dir", "Required: Directory to output the zip file",true); + return options; + } + + File ensureOutputDir() throws IOException { + final String output = commandLine.getOptionValue(OUTPUT_DIR_OPTION); + Preconditions.checkNotNull(output); + + final File outputDir = new File(output); + if (!outputDir.isDirectory() && !outputDir.mkdirs()) { + throw new IOException("Could not create output directory: " + outputDir.getAbsolutePath()); + } + return outputDir; + } + + Integer getMaxChildActions() { + final Integer maxChildActions = Integer.valueOf(commandLine.getOptionValue(MAX_CHILD_ACTIONS, "10")); + Preconditions.checkArgument(maxChildActions >= 0, + MAX_CHILD_ACTIONS + " cannot be negative"); + return maxChildActions; + } + + String[] getJobIds() { + return commandLine.getOptionValues(JOBS_OPTION); + } + + Integer getNumBundles() { + final Integer numBundles = Integer.valueOf(commandLine.getOptionValue(NUM_BUNDLES_OPTION, "0")); + Preconditions.checkArgument(numBundles >= 0, + NUM_BUNDLES_OPTION + " cannot be negative"); + return numBundles; + } + + Integer getNumCoordinators() { + final Integer numCoords = Integer.valueOf(commandLine.getOptionValue(NUM_COORDS_OPTION, "0")); + Preconditions.checkArgument(numCoords >= 0, + NUM_COORDS_OPTION + " cannot be negative"); + return numCoords; + } + + Integer getNumWorkflows() { + final Integer numWorkflows = Integer.valueOf(commandLine.getOptionValue(NUM_WORKFLOWS_OPTION, "0")); + Preconditions.checkArgument(numWorkflows >= 0, + NUM_WORKFLOWS_OPTION + " cannot be negative"); + return numWorkflows; + } + + String getOozieUrl() { + String url = commandLine.getOptionValue(OOZIE_OPTION); + if (url == null) { + url = System.getenv(OozieCLI.ENV_OOZIE_URL); + if (url == null) { + throw new IllegalArgumentException( + "Oozie URL is not available neither in command option or in the environment"); + } + } + return url; + } + + boolean parseCommandLineArguments(String[] args) { + final CommandLineParser parser = new GnuParser(); + final Options options = setupOptions(); + + try { + commandLine = parser.parse(options, args); + } catch (final ParseException pe) { + System.err.print("Error: " + pe.getMessage()); + System.err.println(); + final HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("DiagBundleCollectorDriver", + "A tool that collects a diagnostic bundle of information from Oozie", + options, "", true); + return false; + } + return true; + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java new file mode 100644 index 0000000..de8e8b1 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java @@ -0,0 +1,156 @@ +/** + * 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.oozie.tools.diag; + +import com.google.common.io.Files; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.time.FastDateFormat; +import org.apache.hadoop.conf.Configuration; +import org.apache.oozie.client.OozieClient; +import org.apache.oozie.client.OozieClientException; +import org.apache.oozie.service.HadoopAccessorService; +import org.apache.oozie.service.ServiceException; +import org.apache.oozie.service.Services; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; +import java.util.TimeZone; + +@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output directory is specified by user") +public class DiagBundleCollectorDriver { + static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss zzz", + TimeZone.getTimeZone("GMT"), Locale.US); + private String oozieURL; + private Integer numWorkflows; + private Integer numCoords; + private Integer numBundles; + private String[] jobIds; + private Integer maxChildActions; + private File outputDir; + private Configuration hadoopConfig; + + public static void main(String[] args) throws Exception { + final DiagBundleCollectorDriver oozieDiagBundleCollector = new DiagBundleCollectorDriver(); + System.exit(oozieDiagBundleCollector.run(args)); + } + + private int run(final String[] args) throws Exception { + if (!parseCommandLineArgs(args) || !setHadoopConfig()) { + return -1; + } + + final DiagOozieClient client = new DiagOozieClient(oozieURL, null); + checkOozieConnection(client); + + final File tempDir = Files.createTempDir(); + System.out.println("Using Temporary Directory: " + tempDir); + + try { + collectDiagInformation(numWorkflows, numCoords, numBundles, jobIds, maxChildActions, client, tempDir); + DiagBundleCompressor.compressDiagInformationToBundle(outputDir, tempDir); + } finally { + FileUtils.deleteDirectory(tempDir); + } + + System.out.println(); + return 0; + } + + private boolean parseCommandLineArgs(final String[] args) throws IOException { + final ArgParser cliParser = new ArgParser(); + if (!cliParser.parseCommandLineArguments(args)) { + return false; + } + + oozieURL = cliParser.getOozieUrl(); + numWorkflows = cliParser.getNumWorkflows(); + numCoords = cliParser.getNumCoordinators(); + numBundles = cliParser.getNumBundles(); + jobIds = cliParser.getJobIds(); + maxChildActions = cliParser.getMaxChildActions(); + outputDir = cliParser.ensureOutputDir(); + + return true; + } + + private void collectDiagInformation(final Integer numWorkflows, + final Integer numCoords, + final Integer numBundles, + final String[] jobIds, + final Integer maxChildActions, + final DiagOozieClient client, + final File tempDir) { + final ServerInfoCollector serverInfoCollector = new ServerInfoCollector(client); + serverInfoCollector.storeShareLibInfo(tempDir); + serverInfoCollector.storeServerConfiguration(tempDir); + serverInfoCollector.storeOsEnv(tempDir); + serverInfoCollector.storeJavaSystemProperties(tempDir); + serverInfoCollector.storeCallableQueueDump(tempDir); + serverInfoCollector.storeThreadDump(tempDir); + + final MetricsCollector metricsCollector = new MetricsCollector(client); + metricsCollector.storeInstrumentationInfo(tempDir); + metricsCollector.storeMetrics(tempDir); + + final AppInfoCollector workflowInfoCollector = new AppInfoCollector(hadoopConfig, client); + workflowInfoCollector.storeLastWorkflows(tempDir, numWorkflows, maxChildActions); + workflowInfoCollector.storeLastCoordinators(tempDir, numCoords, maxChildActions); + workflowInfoCollector.storeLastBundles(tempDir, numBundles, maxChildActions); + workflowInfoCollector.getSpecificJobs(tempDir, jobIds, maxChildActions); + } + + private void checkOozieConnection(final OozieClient client) throws OozieClientException { + System.out.print("Checking Connection..."); + client.validateWSVersion(); + System.out.println("Done"); + } + + private boolean setHadoopConfig() { + final String oozieHome = System.getenv("OOZIE_HOME"); + if (oozieHome == null) { + System.err.println("OOZIE_HOME environment variable is not set. Make sure you've set it to an absolute path."); + return false; + } + + System.setProperty(Services.OOZIE_HOME_DIR, oozieHome); + try { + final Services services = initOozieServices(); + final HadoopAccessorService hadoopAccessorService = services.get(HadoopAccessorService.class); + hadoopConfig = hadoopAccessorService.createConfiguration("*"); + + } catch (ServiceException e) { + System.err.printf("Could not initialize Hadoop configuration: %s%n", e.getMessage()); + return false; + } + return true; + } + + private Services initOozieServices() throws ServiceException { + final Services services = new Services(); + services.getConf() + .set(Services.CONF_SERVICE_CLASSES, "org.apache.oozie.service.LiteWorkflowAppService," + + "org.apache.oozie.service.SchedulerService," + + "org.apache.oozie.service.HadoopAccessorService," + + "org.apache.oozie.service.ShareLibService"); + services.init(); + return services; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java new file mode 100644 index 0000000..7612f0d --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.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.oozie.tools.diag; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.oozie.util.IOUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipOutputStream; + +@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output directory is specified by user") +class DiagBundleCompressor { + private static void zip(final File inputDir, final File outputFile) throws IOException { + System.out.print("Creating Zip File: " + outputFile.getAbsolutePath() + "..."); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFile))) { + IOUtils.zipDir(inputDir, "/", zos); + } + System.out.println("Done"); + } + + static void compressDiagInformationToBundle(final File outputDir, final File tempDir) throws IOException { + final File zipFile = new File(outputDir, "oozie-diag-bundle-" + System.currentTimeMillis() + ".zip"); + zip(tempDir, zipFile); + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java new file mode 100644 index 0000000..e98e0c6 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java @@ -0,0 +1,118 @@ +/** + * 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.oozie.tools.diag; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.oozie.util.IOUtils; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output directory is specified by user") +public class DiagBundleEntryWriter implements Closeable { + private final Writer writer; + + DiagBundleEntryWriter(final File parentDir, final String fileName) throws FileNotFoundException { + this(new FileOutputStream(new File(parentDir, fileName))); + } + + private DiagBundleEntryWriter(final OutputStream ous) { + try { + this.writer = new OutputStreamWriter(ous, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + DiagBundleEntryWriter writeDateValue(final String key, final Date date) throws IOException { + if (date != null) { + writeStringValue(key, DiagBundleCollectorDriver.DATE_FORMAT.format(date)); + } else { + writeStringValue(key, "null"); + } + return this; + } + + DiagBundleEntryWriter writeLongValue(final String key, long value) throws IOException { + writeStringValue(key, Long.toString(value)); + return this; + } + + DiagBundleEntryWriter writeIntValue(final String key, int value) throws IOException { + writeStringValue(key, Integer.toString(value)); + return this; + } + + DiagBundleEntryWriter writeStringValue(final String key, final String value) throws IOException { + writer.write(key); + if (value != null && !value.isEmpty()) { + writer.write(String.format("\"%s\"", value)); + } else if (value == null){ + writeNull(); + } else { + writer.write("\"\""); + } + writer.write("\n"); + return this; + } + + DiagBundleEntryWriter writeString(final String s) throws IOException { + if (s == null) { + writeNull(); + } + else { + writer.write(s); + } + return this; + } + + DiagBundleEntryWriter writeNewLine() throws IOException { + writer.write("\n"); + return this; + } + + DiagBundleEntryWriter writeNull() throws IOException { + writer.write("null"); + return this; + } + + void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() { + try { + writer.flush(); + } catch (IOException e) { + System.err.printf("Could not persist data. Exception: %s%n", e.getMessage()); + } finally { + IOUtils.closeSafely(writer); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java b/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java new file mode 100644 index 0000000..735d0c9 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java @@ -0,0 +1,52 @@ +/** + * 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.oozie.tools.diag; + +import org.apache.oozie.client.AuthOozieClient; +import org.apache.oozie.client.OozieClientException; +import org.apache.oozie.util.IOUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +class DiagOozieClient extends AuthOozieClient { + DiagOozieClient(String oozieUrl, String authOption) { + super(oozieUrl, authOption); + } + + // Oozie has a jsp page with a thread dump, and the Oozie client normally doesn't have a way of getting it, + // so we're doing that here; reusing all the fancy HTTP handling code in AuthOozieClient + void saveThreadDumpPage(File file) throws OozieClientException, IOException { + final URL url = new URL(super.getOozieUrl() + "admin/jvminfo.jsp"); + final HttpURLConnection retryableConnection = createRetryableConnection(url, "GET"); + + if ((retryableConnection.getResponseCode() == HttpURLConnection.HTTP_OK)) { + try (InputStream is = retryableConnection.getInputStream(); + final FileOutputStream os = new FileOutputStream(file)) { + IOUtils.copyStream(is, os); + } + } else { + throw new OozieClientException("HTTP error", retryableConnection.getResponseMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java b/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java new file mode 100644 index 0000000..4703b2c --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java @@ -0,0 +1,142 @@ +/** + * 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.oozie.tools.diag; + +import org.apache.oozie.client.OozieClient; +import org.apache.oozie.client.OozieClientException; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.TreeMap; + +class MetricsCollector { + private final OozieClient client; + + MetricsCollector(OozieClient client) { + this.client = client; + } + + void storeMetrics(final File outputDir) { + try { + System.out.print("Getting Metrics..."); + final OozieClient.Metrics metrics = client.getMetrics(); + + if (metrics == null) { + System.out.println("Skipping (Metrics are unavailable)"); + return; + } + + try (DiagBundleEntryWriter diagEntryWriter = new DiagBundleEntryWriter(outputDir, "metrics.txt")) { + diagEntryWriter.writeString("COUNTERS\n") + .writeString("--------\n"); + + final Map<String, Long> counters = new TreeMap<>(metrics.getCounters()); + for (Map.Entry<String, Long> ent : counters.entrySet()) { + diagEntryWriter.writeLongValue(ent.getKey() + " : ", ent.getValue()).flush(); + } + diagEntryWriter.writeNewLine() + .writeString("GAUGES\n") + .writeString("------\n"); + + final Map<String, Object> gauges = new TreeMap<>(metrics.getGauges()); + for (Map.Entry<String, Object> ent : gauges.entrySet()) { + diagEntryWriter.writeStringValue(ent.getKey() + " : ", ent.getValue().toString()); + } + diagEntryWriter.writeNewLine() + .writeString("TIMERS\n") + .writeString("------\n"); + + final Map<String, OozieClient.Metrics.Timer> timers = new TreeMap<>(metrics.getTimers()); + for (Map.Entry<String, OozieClient.Metrics.Timer> ent : timers.entrySet()) { + diagEntryWriter.writeString(ent.getKey()) + .writeNewLine() + .writeString(ent.getValue().toString()) + .writeNewLine(); + } + diagEntryWriter.writeNewLine() + .writeString("HISTOGRAMS\n") + .writeString("----------\n"); + final Map<String, OozieClient.Metrics.Histogram> histograms = + new TreeMap<>(metrics.getHistograms()); + for (Map.Entry<String, OozieClient.Metrics.Histogram> ent : histograms.entrySet()) { + diagEntryWriter.writeString(ent.getKey()) + .writeNewLine() + .writeString(ent.getValue().toString()) + .writeNewLine(); + } + System.out.println("Done"); + } + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of Oozie metrics information: %s%n", e.getMessage()); + } + } + + void storeInstrumentationInfo(final File outputDir) { + try { + System.out.print("Getting Instrumentation..."); + final OozieClient.Instrumentation instrumentation = client.getInstrumentation(); + if (instrumentation == null) { + System.out.println("Skipping (Instrumentation is unavailable)"); + return; + } + + try (DiagBundleEntryWriter diagEntryWriter = new DiagBundleEntryWriter(outputDir, "instrumentation.txt")) { + diagEntryWriter.writeString("COUNTERS\n"); + diagEntryWriter.writeString("--------\n"); + + final Map<String, Long> counters = new TreeMap<>(instrumentation.getCounters()); + for (Map.Entry<String, Long> ent : counters.entrySet()) { + diagEntryWriter.writeLongValue(ent.getKey() + " : ", ent.getValue()).flush(); + } + diagEntryWriter.writeNewLine(); + diagEntryWriter.writeString("VARIABLES\n"); + diagEntryWriter.writeString("---------\n"); + + final Map<String, Object> variables = new TreeMap<>(instrumentation.getVariables()); + for (Map.Entry<String, Object> ent : variables.entrySet()) { + diagEntryWriter.writeStringValue(ent.getKey() + " : ", ent.getValue().toString()); + } + diagEntryWriter.writeNewLine(); + diagEntryWriter.writeString("SAMPLERS\n"); + diagEntryWriter.writeString("---------\n"); + + final Map<String, Double> samplers = new TreeMap<>(instrumentation.getSamplers()); + for (Map.Entry<String, Double> ent : samplers.entrySet()) { + diagEntryWriter.writeStringValue(ent.getKey() + " : ", ent.getValue().toString()); + } + diagEntryWriter.writeNewLine(); + diagEntryWriter.writeString("TIMERS\n"); + diagEntryWriter.writeString("---------\n"); + + final Map<String, OozieClient.Instrumentation.Timer> timers = + new TreeMap<>(instrumentation.getTimers()); + for (Map.Entry<String, OozieClient.Instrumentation.Timer> ent : timers.entrySet()) { + diagEntryWriter.writeString(ent.getKey()); + diagEntryWriter.writeNewLine(); + diagEntryWriter.writeString(ent.getValue().toString()); + diagEntryWriter.writeNewLine(); + } + System.out.println("Done"); + } + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of Oozie instrumentation information: %s%n", e.getMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java b/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java new file mode 100644 index 0000000..d12e963 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java @@ -0,0 +1,135 @@ +/** + * 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.oozie.tools.diag; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileContext; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.RemoteIterator; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.logaggregation.AggregatedLogFormat; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; + +// TODO: once OOZIE-2983 ("Stream the Launcher AM Logs") is done, remove it. +public class OozieLauncherLogFetcher { + private static final String TMP_FILE_SUFFIX = ".tmp"; + final private Configuration hadoopConfig; + + public OozieLauncherLogFetcher(final Configuration hadoopConfig) { + this.hadoopConfig = hadoopConfig; + } + + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers + private static void logDirNotExist(String remoteAppLogDir) { + System.out.println(remoteAppLogDir + "does not exist."); + System.out.println("Log aggregation has not completed or is not enabled."); + } + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers + private static void emptyLogDir(String remoteAppLogDir) { + System.out.println(remoteAppLogDir + "does not have any log files."); + } + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogAggregationUtils + public static String getRemoteNodeLogDirSuffix(Configuration conf) { + return conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR_SUFFIX, YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX); + } + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogAggregationUtils + public static Path getRemoteLogSuffixedDir(Path remoteRootLogDir, String user, String suffix) { + return suffix != null && !suffix.isEmpty() ? new Path(getRemoteLogUserDir(remoteRootLogDir, user), suffix) : + getRemoteLogUserDir(remoteRootLogDir, user); + } + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogAggregationUtils + public static Path getRemoteLogUserDir(Path remoteRootLogDir, String user) { + return new Path(remoteRootLogDir, user); + } + + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogAggregationUtils + public static Path getRemoteAppLogDir(Path remoteRootLogDir, ApplicationId appId, String user, String suffix) { + return new Path(getRemoteLogSuffixedDir(remoteRootLogDir, user, suffix), appId.toString()); + } + + // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers + public int dumpAllContainersLogs(ApplicationId appId, String appOwner, PrintStream out) throws IOException { + Path remoteRootLogDir = new Path(hadoopConfig.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR, + YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR)); + String logDirSuffix = getRemoteNodeLogDirSuffix(hadoopConfig); + Path remoteAppLogDir = getRemoteAppLogDir(remoteRootLogDir, appId, appOwner, logDirSuffix); + + RemoteIterator nodeFiles; + try { + Path qualifiedLogDir = FileContext.getFileContext(hadoopConfig).makeQualified(remoteAppLogDir); + nodeFiles = FileContext.getFileContext(qualifiedLogDir.toUri(), hadoopConfig).listStatus(remoteAppLogDir); + } catch (FileNotFoundException fileNotFoundException) { + logDirNotExist(remoteAppLogDir.toString()); + return -1; + } + + boolean foundAnyLogs = false; + + while(true) { + FileStatus thisNodeFile; + do { + if (!nodeFiles.hasNext()) { + if (!foundAnyLogs) { + emptyLogDir(remoteAppLogDir.toString()); + return -1; + } + + return 0; + } + + thisNodeFile = (FileStatus)nodeFiles.next(); + } while(thisNodeFile.getPath().getName().endsWith(TMP_FILE_SUFFIX)); + + AggregatedLogFormat.LogReader reader = new AggregatedLogFormat.LogReader(hadoopConfig, thisNodeFile.getPath()); + + try { + AggregatedLogFormat.LogKey key = new AggregatedLogFormat.LogKey(); + DataInputStream valueStream = reader.next(key); + + while(valueStream != null) { + String containerString = "\n\nContainer: " + key + " on " + thisNodeFile.getPath().getName(); + out.println(containerString); + out.println(StringUtils.repeat("=", containerString.length())); + + while(true) { + try { + AggregatedLogFormat.LogReader.readAContainerLogsForALogType(valueStream, out, + thisNodeFile.getModificationTime()); + foundAnyLogs = true; + } catch (EOFException eofException) { + key = new AggregatedLogFormat.LogKey(); + valueStream = reader.next(key); + break; + } + } + } + } finally { + reader.close(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java ---------------------------------------------------------------------- diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java b/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java new file mode 100644 index 0000000..e071a80 --- /dev/null +++ b/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java @@ -0,0 +1,141 @@ +/** + * 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.oozie.tools.diag; + +import org.apache.hadoop.conf.Configuration; +import org.apache.oozie.client.OozieClientException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +class ServerInfoCollector { + + private final DiagOozieClient client; + + ServerInfoCollector(final DiagOozieClient client) { + this.client = client; + } + + void storeShareLibInfo(final File outputDir) { + try { + System.out.print("Getting Sharelib Information..."); + final String[] libs = client.listShareLib(null).split("\n"); + + try (DiagBundleEntryWriter configEntryWriter = new DiagBundleEntryWriter(outputDir, "sharelib.txt")) { + + // Skip i=0 because it's always "[Available Sharelib]" + for (int i = 1; i < libs.length; i++) { + String files = client.listShareLib(libs[i]); + configEntryWriter.writeString(files); + configEntryWriter.writeNewLine(); + } + } + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of ShareLib information: %s%n", e.getMessage()); + } + } + + void storeJavaSystemProperties(final File outputDir) { + try { + System.out.print("Getting Java System Properties..."); + final Map<String, String> javaSysProps = client.getJavaSystemProperties(); + + try (DiagBundleEntryWriter configEntryWriter = new DiagBundleEntryWriter(outputDir, "java-sys-props.txt")) { + for (Map.Entry<String, String> ent : javaSysProps.entrySet()) { + configEntryWriter.writeStringValue(ent.getKey() + " : ", ent.getValue()); + } + } + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of Java system property settings for the Oozie server:" + + " %s%n", e.getMessage()); + } + } + + void storeOsEnv(final File outputDir) { + try { + System.out.print("Getting OS Environment Variables..."); + final Map<String, String> osEnv = client.getOSEnv(); + + try (DiagBundleEntryWriter configEntryWriter = new DiagBundleEntryWriter(outputDir,"os-env-vars.txt")) { + for (Map.Entry<String, String> ent : osEnv.entrySet()) { + configEntryWriter.writeStringValue(ent.getKey() + " : ", ent.getValue()); + } + } + + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of environment variable settings for the Oozie server:%s%n", + e.getMessage()); + } + } + + void storeServerConfiguration(File outputDir) { + try { + System.out.print("Getting Configuration..."); + final Map<String, String> serverConfigMap = client.getServerConfiguration(); + final Configuration serverConfig = new Configuration(false); + for (Map.Entry<String, String> ent : serverConfigMap.entrySet()) { + serverConfig.set(ent.getKey(), ent.getValue()); + } + + try (OutputStream outputStream = new FileOutputStream( + new File(outputDir, "effective-oozie-site.xml"))) { + serverConfig.writeXml(outputStream); + } + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of effective Oozie server configuration " + + "\"oozie-site.xml\": %s%n", e.getMessage()); + } + } + + void storeThreadDump(final File outputDir) { + try { + System.out.print("Getting Thread Dump..."); + client.saveThreadDumpPage(new File(outputDir, "thread-dump.html")); + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.printf("Exception occurred during the retrieval of Oozie server thread dump: %s%n", e.getMessage()); + } + } + + void storeCallableQueueDump(final File outputDir) { + try { + System.out.print("Getting Queue Dump..."); + final List<String> queueDump = client.getQueueDump(); + + try (DiagBundleEntryWriter configEntryWriter = new DiagBundleEntryWriter(outputDir, "queue-dump.txt")) { + for (String d : queueDump) { + configEntryWriter.writeString(d); + configEntryWriter.writeNewLine(); + } + } + + System.out.println("Done"); + } catch (OozieClientException | IOException e) { + System.err.println("Exception occurred during the retrieval of Oozie queue dump: " + e.getMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java ---------------------------------------------------------------------- diff --git a/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java b/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java new file mode 100644 index 0000000..e6ee6b0 --- /dev/null +++ b/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java @@ -0,0 +1,167 @@ +/** + * 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.oozie.tools.diag; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.oozie.client.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; +import static org.apache.oozie.tools.diag.TestServerInfoCollector.assertFileContains; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; + +@RunWith(MockitoJUnitRunner.class) +public class TestAppInfoCollector { + private final Configuration conf= new Configuration(false); + private File testFolder; + private AppInfoCollector appInfoCollector; + + @Rule public final TemporaryFolder folder = new TemporaryFolder(); + @Mock private DiagOozieClient mockOozieClient; + @Mock private WorkflowJob wfJob; + @Mock private WorkflowAction wfAction; + @Mock private CoordinatorJob coordJob; + @Mock private CoordinatorAction coordinatorAction; + @Mock private BundleJob bundleJob; + + @Before + public void setup() throws IOException { + conf.set("yarn.resourcemanager.address", "test:1234"); + testFolder = folder.newFolder(); + appInfoCollector = new AppInfoCollector(conf, mockOozieClient); + } + + @Test + public void testStoreLastWorkflows() throws Exception { + final List<WorkflowJob> wfJobs = Arrays.asList(wfJob); + doReturn(wfJobs).when(mockOozieClient).getJobsInfo(null, 0, 1); + doReturn(wfJob).when(mockOozieClient).getJobInfo(anyString()); + + final String wfName = "0000000-170926142250283-oozie-test-W"; + doReturn(wfName).when(wfJob).getId(); + doReturn("map-reduce-wf").when(wfJob) + .getAppName(); + doReturn("hdfs://localhost:9000/user/test/examples/apps/map-reduce/workflow.xml").when(wfJob) + .getAppPath(); + doReturn("test").when(wfJob).getUser(); + doReturn(null).when(wfJob).getAcl(); + doReturn(WorkflowJob.Status.SUCCEEDED).when(wfJob).getStatus(); + doReturn("").when(wfJob).getConsoleUrl(); + doReturn("http://0.0.0.0:11000/oozie?job=0000000-170926142250283-oozie-asas-W").when(wfJob) + .getExternalId(); + + doReturn(null).when(wfJob).getParentId(); + + final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss zzz"); + final Date startDate = formatter.parse("2017-09-26 12:22:57 GMT"); + doReturn(startDate).when(wfJob).getCreatedTime(); + + final Date endDate = formatter.parse("2017-09-26 12:32:57 GMT"); + doReturn(endDate).when(wfJob).getEndTime(); + doReturn(endDate).when(wfJob).getLastModifiedTime(); + doReturn(startDate).when(wfJob).getStartTime(); + doReturn(0).when(wfJob).getRun(); + + doReturn("0000000-170926142250283-oozie-asas-W@:start:").when(wfAction).getId(); + doReturn(":start:").when(wfAction).getName(); + doReturn(":START:").when(wfAction).getType(); + doReturn(WorkflowAction.Status.OK).when(wfAction).getStatus(); + doReturn("mr-node").when(wfAction).getTransition(); + doReturn(startDate).when(wfAction).getStartTime(); + doReturn(endDate).when(wfAction).getEndTime(); + doReturn(null).when(wfAction).getErrorCode(); + doReturn(null).when(wfAction).getErrorMessage(); + doReturn("").when(wfAction).getConsoleUrl(); + doReturn("").when(wfAction).getTrackerUri(); + doReturn(null).when(wfAction).getExternalChildIDs(); + doReturn("").when(wfAction).getExternalId(); + doReturn("OK").when(wfAction).getExternalStatus(); + doReturn(null).when(wfAction).getData(); + doReturn(null).when(wfAction).getStats(); + doReturn(null).when(wfAction).getCred(); + doReturn(0).when(wfAction).getRetries(); + doReturn(10).when(wfAction).getUserRetryInterval(); + doReturn(0).when(wfAction).getUserRetryCount(); + doReturn(0).when(wfAction).getUserRetryMax(); + + final List<WorkflowAction> wfActions = Arrays.asList(wfAction); + doReturn(wfActions).when(wfJob).getActions(); + + appInfoCollector.storeLastWorkflows(testFolder, 1, 1); + + final File infoOut = new File (testFolder, wfName + Path.SEPARATOR+ "info.txt"); + assertTrue(infoOut.exists()); + assertFileContains(infoOut, wfName); + } + + @Test + public void testStoreCoordinators() throws Exception { + final List<CoordinatorJob> coordJobs = Arrays.asList(coordJob); + doReturn(coordJobs).when(mockOozieClient).getCoordJobsInfo(null, 0, 1); + + final String coordId = "0000000-170926142250283-oozie-test-C"; + doReturn(coordId).when(coordJob).getId(); + doReturn(Job.Status.RUNNING).when(coordJob).getStatus(); + doReturn(CoordinatorJob.Execution.FIFO).when(coordJob).getExecutionOrder(); + + final List<CoordinatorAction> coordinatorActions = Arrays.asList(coordinatorAction); + doReturn(CoordinatorAction.Status.KILLED).when(coordinatorAction).getStatus(); + doReturn(coordinatorActions).when(coordJob).getActions(); + doReturn(coordJob).when(mockOozieClient).getCoordJobInfo(anyString()); + doReturn(CoordinatorJob.Timeunit.MINUTE).when(coordJob).getTimeUnit(); + + appInfoCollector.storeLastCoordinators(testFolder,1,1); + + final File coordInfoOut = new File(testFolder, coordId + Path.SEPARATOR + "info.txt"); + assertTrue(coordInfoOut.exists()); + assertFileContains(coordInfoOut, coordId); + } + + @Test + public void testStoreLastBundles() throws Exception { + final List<BundleJob> bundleJobs = Arrays.asList(bundleJob); + doReturn(bundleJobs).when(mockOozieClient).getBundleJobsInfo(null, 0, 1); + + final String bundleId = "0000027-110322105610515-oozie-chao-B"; + doReturn(bundleId).when(bundleJob).getId(); + doReturn(Job.Status.RUNNING).when(bundleJob).getStatus(); + doReturn(bundleJob).when(mockOozieClient).getBundleJobInfo(anyString()); + + appInfoCollector.storeLastBundles(testFolder,1,1); + + final File bundleInfoOut = new File(testFolder, bundleId + Path.SEPARATOR + "info.txt"); + assertTrue(bundleInfoOut.exists()); + assertFileContains(bundleInfoOut, bundleId); + } +} \ No newline at end of file