OOZIE-2691 Show workflow action retry information in UI
Project: http://git-wip-us.apache.org/repos/asf/oozie/repo Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/bf2802e4 Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/bf2802e4 Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/bf2802e4 Branch: refs/heads/oya Commit: bf2802e4052ad9612bb63b41671f32fe9237f496 Parents: c58f545 Author: puru <puru.s...@gmail.com> Authored: Sun Jan 22 16:13:59 2017 -0800 Committer: puru <puru.s...@gmail.com> Committed: Sun Jan 22 16:13:59 2017 -0800 ---------------------------------------------------------------------- .../java/org/apache/oozie/cli/OozieCLI.java | 35 ++- .../org/apache/oozie/client/OozieClient.java | 39 ++- .../org/apache/oozie/client/rest/JsonTags.java | 2 +- .../apache/oozie/client/rest/RestConstants.java | 2 + .../main/java/org/apache/oozie/DagEngine.java | 17 ++ .../oozie/command/wf/ActionCheckXCommand.java | 2 +- .../oozie/command/wf/ActionEndXCommand.java | 2 +- .../oozie/command/wf/ActionStartXCommand.java | 9 +- .../apache/oozie/command/wf/ActionXCommand.java | 39 ++- .../wf/WorkflowActionRetryInfoXCommand.java | 115 ++++++++ .../apache/oozie/servlet/BaseJobServlet.java | 22 +- .../org/apache/oozie/servlet/V0JobServlet.java | 7 + .../org/apache/oozie/servlet/V1JobServlet.java | 8 +- .../org/apache/oozie/servlet/V2JobServlet.java | 20 +- .../java/org/apache/oozie/util/JobUtils.java | 10 + .../apache/oozie/ForTestingActionExecutor.java | 16 +- .../org/apache/oozie/client/TestOozieCLI.java | 15 ++ .../wf/TestWorkflowActionRetryInfoXCommand.java | 138 ++++++++++ .../oozie/servlet/MockDagEngineService.java | 6 +- core/src/test/resources/wf-ext-schema.xsd | 1 + docs/src/site/twiki/DG_CommandLineTool.twiki | 29 ++ docs/src/site/twiki/WebServicesAPI.twiki | 31 +++ release-log.txt | 1 + webapp/src/main/webapp/oozie-console.js | 270 ++++++++++++++++--- 24 files changed, 768 insertions(+), 68 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/client/src/main/java/org/apache/oozie/cli/OozieCLI.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/cli/OozieCLI.java b/client/src/main/java/org/apache/oozie/cli/OozieCLI.java index e67fae9..6e30d7e 100644 --- a/client/src/main/java/org/apache/oozie/cli/OozieCLI.java +++ b/client/src/main/java/org/apache/oozie/cli/OozieCLI.java @@ -179,6 +179,8 @@ public class OozieCLI { public static final String ALL_WORKFLOWS_FOR_COORD_ACTION = "allruns"; + public static final String WORKFLOW_ACTIONS_RETRIES = "retries"; + private static final String[] OOZIE_HELP = { "the env variable '" + ENV_OOZIE_URL + "' is used as default value for the '-" + OOZIE_OPTION + "' option", "the env variable '" + ENV_OOZIE_TIME_ZONE + "' is used as default value for the '-" + TIME_ZONE_OPTION + "' option", @@ -381,9 +383,11 @@ public class OozieCLI { Option slaChange = new Option(SLA_CHANGE, true, "Update sla param for jobs, supported param are should-start, should-end, nominal-time and max-duration"); - Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user"); + Option workflowActionRetries = new Option(WORKFLOW_ACTIONS_RETRIES, true, + "Get information of the retry attempts for a given workflow action"); + OptionGroup actions = new OptionGroup(); actions.addOption(submit); actions.addOption(start); @@ -406,7 +410,7 @@ public class OozieCLI { actions.addOption(slaDisableAlert); actions.addOption(slaEnableAlert); actions.addOption(slaChange); - + actions.addOption(workflowActionRetries); actions.setRequired(true); Options jobOptions = new Options(); jobOptions.addOption(oozie); @@ -1184,6 +1188,7 @@ public class OozieCLI { } printWorkflowAction(wc.getWorkflowActionInfo(optionValue), timeZoneId, options.contains(VERBOSE_OPTION)); + } else { String filter = commandLine.getOptionValue(FILTER_OPTION); @@ -1319,6 +1324,12 @@ public class OozieCLI { else if (options.contains(SLA_CHANGE)) { slaAlertCommand(commandLine.getOptionValue(SLA_CHANGE), wc, commandLine, options); } + else if (options.contains(WORKFLOW_ACTIONS_RETRIES)) { + printWorkflowActionRetries( + wc.getWorkflowActionRetriesInfo(commandLine.getOptionValue(WORKFLOW_ACTIONS_RETRIES)), + commandLine.getOptionValue(WORKFLOW_ACTIONS_RETRIES)); + } + } catch (OozieClientException ex) { throw new OozieCLIException(ex.toString(), ex); @@ -1484,6 +1495,26 @@ public class OozieCLI { System.out.println(RULER); } + void printWorkflowActionRetries(List<Map<String, String>> retries, String actionId) { + System.out.println("ID : " + maskIfNull(actionId)); + if (retries.isEmpty()) { + System.out.println("No Retries"); + } + for (Map<String, String> retry: retries) { + System.out.println(RULER); + System.out.println("Attempt : " + retry.get(JsonTags.ACTION_ATTEMPT)); + System.out.println("Start Time : " + retry.get(JsonTags.WORKFLOW_ACTION_START_TIME)); + System.out.println("End Time : " + retry.get(JsonTags.WORKFLOW_ACTION_END_TIME)); + if (null != retry.get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL)) { + System.out.println("Console URL : " + retry.get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL)); + } + if (null != retry.get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS)) { + System.out.println("Child URL : " + retry.get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS)); + } + } + System.out.println(RULER); + } + private static final String WORKFLOW_JOBS_FORMATTER = "%-41s%-13s%-10s%-10s%-10s%-24s%-24s"; private static final String COORD_JOBS_FORMATTER = "%-41s%-15s%-10s%-5s%-13s%-24s%-24s"; private static final String BUNDLE_JOBS_FORMATTER = "%-41s%-15s%-10s%-20s%-20s%-13s%-13s"; http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/client/src/main/java/org/apache/oozie/client/OozieClient.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/client/OozieClient.java b/client/src/main/java/org/apache/oozie/client/OozieClient.java index 12c80cb..a107c4a 100644 --- a/client/src/main/java/org/apache/oozie/client/OozieClient.java +++ b/client/src/main/java/org/apache/oozie/client/OozieClient.java @@ -47,7 +47,6 @@ import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -59,6 +58,9 @@ import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + /** * Client API to submit and manage Oozie workflow jobs against an Oozie intance. @@ -1025,6 +1027,29 @@ public class OozieClient { } } + private class WorkflowActionRetriesInfo extends ClientCallable<List<Map<String, String>>> { + WorkflowActionRetriesInfo(String actionId) { + super("GET", RestConstants.JOB, notEmpty(actionId, "id"), + prepareParams(RestConstants.JOB_SHOW_PARAM, RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM)); + } + + @Override + protected List<Map<String, String>> call(HttpURLConnection conn) + throws IOException, OozieClientException { + if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) { + Reader reader = new InputStreamReader(conn.getInputStream()); + JSONObject json = (JSONObject) JSONValue.parse(reader); + return new ObjectMapper().readValue(json.get(JsonTags.WORKFLOW_ACTION_RETRIES).toString(), + new TypeReference<List<Map<String, String>>>() { + }); + } + else { + handleError(conn); + } + return null; + } + } + /** * Get the info of a workflow job. * @@ -1069,6 +1094,18 @@ public class OozieClient { return new WorkflowActionInfo(actionId).call(); } + + /** + * Get the info of a workflow action. + * + * @param actionId Id. + * @return the workflow action retries info. + * @throws OozieClientException thrown if the job info could not be retrieved. + */ + public List<Map<String, String>> getWorkflowActionRetriesInfo(String actionId) throws OozieClientException { + return new WorkflowActionRetriesInfo(actionId).call(); + } + /** * Get the log of a workflow job. * http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java b/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java index 397e9ed..d670142 100644 --- a/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java +++ b/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java @@ -244,7 +244,7 @@ public interface JsonTags { String COORD_UPDATE_DIFF = "diff"; String STATUS = "status"; - + String ACTION_ATTEMPT = "attempt"; String VALIDATE = "validate"; } http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java index 4129364..9a3be97 100644 --- a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java +++ b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java @@ -81,6 +81,8 @@ public interface RestConstants { String JOB_SHOW_PARAM = "show"; + String JOB_SHOW_ACTION_RETRIES_PARAM = "retries"; + String JOB_SHOW_CONFIG = "config"; String JOB_SHOW_INFO = "info"; http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/DagEngine.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/DagEngine.java b/core/src/main/java/org/apache/oozie/DagEngine.java index 7597142..57d2761 100644 --- a/core/src/main/java/org/apache/oozie/DagEngine.java +++ b/core/src/main/java/org/apache/oozie/DagEngine.java @@ -55,6 +55,7 @@ import org.apache.oozie.command.wf.SubmitSqoopXCommand; import org.apache.oozie.command.wf.SubmitXCommand; import org.apache.oozie.command.wf.SuspendXCommand; import org.apache.oozie.command.wf.WorkflowActionInfoXCommand; +import org.apache.oozie.command.wf.WorkflowActionRetryInfoXCommand; import org.apache.oozie.executor.jpa.JPAExecutorException; import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor; import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor.WorkflowJobQuery; @@ -583,6 +584,22 @@ public class DagEngine extends BaseEngine { } } + /** + * Gets the workflow action retries. + * + * @param actionId the action id + * @return the workflow action retries + * @throws BaseEngineException the base engine exception + */ + public List<Map<String, String>> getWorkflowActionRetries(String actionId) throws BaseEngineException { + try { + return new WorkflowActionRetryInfoXCommand(actionId).call(); + } + catch (CommandException ex) { + throw new BaseEngineException(ex); + } + } + /* (non-Javadoc) * @see org.apache.oozie.BaseEngine#dryRunSubmit(org.apache.hadoop.conf.Configuration) */ http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/command/wf/ActionCheckXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/command/wf/ActionCheckXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/ActionCheckXCommand.java index d0551ff..335527d 100644 --- a/core/src/main/java/org/apache/oozie/command/wf/ActionCheckXCommand.java +++ b/core/src/main/java/org/apache/oozie/command/wf/ActionCheckXCommand.java @@ -210,7 +210,7 @@ public class ActionCheckXCommand extends ActionXCommand<Void> { switch (ex.getErrorType()) { case ERROR: // If allowed to retry, this will handle it; otherwise, we should fall through to FAILED - if (handleUserRetry(wfAction, wfJob)) { + if (handleUserRetry(context, wfAction)) { break; } case FAILED: http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/command/wf/ActionEndXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/command/wf/ActionEndXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/ActionEndXCommand.java index 740b8d3..86ee1cc 100644 --- a/core/src/main/java/org/apache/oozie/command/wf/ActionEndXCommand.java +++ b/core/src/main/java/org/apache/oozie/command/wf/ActionEndXCommand.java @@ -216,7 +216,7 @@ public class ActionEndXCommand extends ActionXCommand<Void> { shouldHandleUserRetry = true; break; } - if (!shouldHandleUserRetry || !handleUserRetry(wfAction, wfJob)) { + if (!shouldHandleUserRetry || !handleUserRetry(context, wfAction)) { SLAEventBean slaEvent = SLADbXOperations.createStatusEvent(wfAction.getSlaXml(), wfAction.getId(), slaStatus, SlaAppType.WORKFLOW_ACTION); if(slaEvent != null) { insertList.add(slaEvent); http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/command/wf/ActionStartXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/command/wf/ActionStartXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/ActionStartXCommand.java index edfac48..5bc0eab 100644 --- a/core/src/main/java/org/apache/oozie/command/wf/ActionStartXCommand.java +++ b/core/src/main/java/org/apache/oozie/command/wf/ActionStartXCommand.java @@ -40,6 +40,7 @@ import org.apache.oozie.client.WorkflowJob; import org.apache.oozie.client.SLAEvent.SlaAppType; import org.apache.oozie.client.SLAEvent.Status; import org.apache.oozie.client.rest.JsonBean; +import org.apache.oozie.client.rest.JsonTags; import org.apache.oozie.command.CommandException; import org.apache.oozie.command.PreconditionException; import org.apache.oozie.command.XCommand; @@ -55,8 +56,10 @@ import org.apache.oozie.service.EventHandlerService; import org.apache.oozie.service.JPAService; import org.apache.oozie.service.Services; import org.apache.oozie.service.UUIDService; +import org.apache.oozie.util.DateUtils; import org.apache.oozie.util.ELEvaluationException; import org.apache.oozie.util.Instrumentation; +import org.apache.oozie.util.JobUtils; import org.apache.oozie.util.LogUtils; import org.apache.oozie.util.XLog; import org.apache.oozie.util.XmlUtils; @@ -230,7 +233,11 @@ public class ActionStartXCommand extends ActionXCommand<org.apache.oozie.command Instrumentation.Cron cron = new Instrumentation.Cron(); cron.start(); - context.setStartTime(); + // do not override starttime for retries + if (wfAction.getStartTime() == null) { + context.setStartTime(); + } + context.setVar(JobUtils.getRetryKey(wfAction, JsonTags.WORKFLOW_ACTION_START_TIME), String.valueOf(new Date().getTime())); executor.start(context, wfAction); cron.stop(); FaultInjection.activate("org.apache.oozie.command.SkipCommitFaultInjection"); http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/command/wf/ActionXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/command/wf/ActionXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/ActionXCommand.java index 836e5d4..4f127c0 100644 --- a/core/src/main/java/org/apache/oozie/command/wf/ActionXCommand.java +++ b/core/src/main/java/org/apache/oozie/command/wf/ActionXCommand.java @@ -39,6 +39,7 @@ import org.apache.oozie.action.ActionExecutor; import org.apache.oozie.client.Job; import org.apache.oozie.client.WorkflowAction; import org.apache.oozie.client.WorkflowJob; +import org.apache.oozie.client.rest.JsonTags; import org.apache.oozie.command.CommandException; import org.apache.oozie.service.CallbackService; import org.apache.oozie.service.ConfigurationService; @@ -51,6 +52,7 @@ import org.apache.oozie.service.Services; import org.apache.oozie.util.ELEvaluator; import org.apache.oozie.util.InstrumentUtils; import org.apache.oozie.util.Instrumentation; +import org.apache.oozie.util.JobUtils; import org.apache.oozie.util.XConfiguration; import org.apache.oozie.workflow.WorkflowException; import org.apache.oozie.workflow.WorkflowInstance; @@ -64,6 +66,7 @@ import org.apache.oozie.workflow.lite.NodeDef; */ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { private static final String INSTRUMENTATION_GROUP = "action.executors"; + public static final String RETRY = "retry."; protected static final String RECOVERY_ID_SEPARATOR = "@"; @@ -157,8 +160,7 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { LOG.warn("Setting Action Status to [{0}]", status); ActionExecutorContext aContext = (ActionExecutorContext) context; WorkflowActionBean action = (WorkflowActionBean) aContext.getAction(); - WorkflowJobBean wfJob = (WorkflowJobBean) context.getWorkflow(); - if (!handleUserRetry(action, wfJob)) { + if (!handleUserRetry(context, action)) { incrActionErrorCounter(action.getType(), "error", 1); action.setPending(); if (isStart) { @@ -192,7 +194,7 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { */ public void failJob(ActionExecutor.Context context, WorkflowActionBean action) throws CommandException { WorkflowJobBean workflow = (WorkflowJobBean) context.getWorkflow(); - if (!handleUserRetry(action, workflow)) { + if (!handleUserRetry(context, action)) { incrActionErrorCounter(action.getType(), "failed", 1); LOG.warn("Failing Job due to failed action [{0}]", action.getName()); try { @@ -220,7 +222,8 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { * @return true if user-retry has to be handled for this action * @throws CommandException thrown if unable to fail job */ - public boolean handleUserRetry(WorkflowActionBean action, WorkflowJobBean wfJob) throws CommandException { + public boolean handleUserRetry(ActionExecutor.Context context, WorkflowActionBean action) throws CommandException { + WorkflowJobBean wfJob = (WorkflowJobBean) context.getWorkflow(); String errorCode = action.getErrorCode(); Set<String> allowedRetryCode = LiteWorkflowStoreService.getUserRetryErrorCode(); @@ -232,6 +235,7 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { ActionExecutor.RETRYPOLICY retryPolicy = getUserRetryPolicy(action, wfJob); long interval = getRetryDelay(action.getUserRetryCount(), action.getUserRetryInterval() * 60, retryPolicy); action.setStatus(WorkflowAction.Status.USER_RETRY); + context.setVar(JobUtils.getRetryKey(action, JsonTags.WORKFLOW_ACTION_END_TIME), String.valueOf(new Date().getTime())); action.incrmentUserRetryCount(); action.setPending(); queue(new ActionStartXCommand(action.getId(), action.getType()), interval); @@ -295,22 +299,29 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { private Job.Status jobStatus; /** - * Constructing the ActionExecutorContext, setting the private members - * and constructing the proto configuration - */ - public ActionExecutorContext(WorkflowJobBean workflow, WorkflowActionBean action, boolean isRetry, boolean isUserRetry) { + * Constructing the ActionExecutorContext, setting the private members + * and constructing the proto configuration + */ + public ActionExecutorContext(WorkflowJobBean workflow, WorkflowActionBean action, boolean isRetry, + boolean isUserRetry) { this.workflow = workflow; this.action = action; this.isRetry = isRetry; this.isUserRetry = isUserRetry; - try { - protoConf = new XConfiguration(new StringReader(workflow.getProtoActionConf())); - } - catch (IOException ex) { - throw new RuntimeException("It should not happen", ex); + if (null != workflow.getProtoActionConf()) { + try { + protoConf = new XConfiguration(new StringReader(workflow.getProtoActionConf())); + } + catch (IOException ex) { + throw new RuntimeException("It should not happen", ex); + } } } + public ActionExecutorContext(WorkflowJobBean workflow, WorkflowActionBean action) { + this(workflow, action, false, false); + } + /* * (non-Javadoc) * @see org.apache.oozie.action.ActionExecutor.Context#getCallbackUrl(java.lang.String) @@ -388,6 +399,7 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { * @see org.apache.oozie.action.ActionExecutor.Context#setStartData(java.lang.String, java.lang.String, java.lang.String) */ public void setStartData(String externalId, String trackerUri, String consoleUrl) { + setVar(JobUtils.getRetryKey(action, JsonTags.WORKFLOW_ACTION_CONSOLE_URL), consoleUrl); action.setStartData(externalId, trackerUri, consoleUrl); started = true; } @@ -423,6 +435,7 @@ public abstract class ActionXCommand<T> extends WorkflowXCommand<T> { * @see org.apache.oozie.action.ActionExecutor.Context#setExternalChildIDs(java.lang.String) */ public void setExternalChildIDs(String externalChildIDs) { + setVar(JobUtils.getRetryKey(action, JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS), externalChildIDs); action.setExternalChildIDs(externalChildIDs); executed = true; } http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/command/wf/WorkflowActionRetryInfoXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/command/wf/WorkflowActionRetryInfoXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/WorkflowActionRetryInfoXCommand.java new file mode 100644 index 0000000..fac0989 --- /dev/null +++ b/core/src/main/java/org/apache/oozie/command/wf/WorkflowActionRetryInfoXCommand.java @@ -0,0 +1,115 @@ +/** + * 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.command.wf; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.oozie.ErrorCode; +import org.apache.oozie.WorkflowActionBean; +import org.apache.oozie.WorkflowJobBean; +import org.apache.oozie.client.rest.JsonTags; +import org.apache.oozie.client.rest.JsonUtils; +import org.apache.oozie.command.CommandException; +import org.apache.oozie.command.wf.ActionXCommand.ActionExecutorContext; +import org.apache.oozie.executor.jpa.JPAExecutorException; +import org.apache.oozie.executor.jpa.WorkflowActionQueryExecutor; +import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor; +import org.apache.oozie.executor.jpa.WorkflowActionQueryExecutor.WorkflowActionQuery; +import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor.WorkflowJobQuery; +import org.apache.oozie.util.JobUtils; +import org.apache.oozie.util.LogUtils; + +public class WorkflowActionRetryInfoXCommand extends WorkflowXCommand<List<Map<String, String>>> { + private String actionId; + private WorkflowJobBean wfJob; + protected WorkflowActionBean wfAction = null; + + public WorkflowActionRetryInfoXCommand(String id) { + super("action.retries.info", "action.retries.info", 1); + this.actionId = id; + } + + @Override + protected List<Map<String, String>> execute() throws CommandException { + List<Map<String, String>> retriesList = new ArrayList<Map<String, String>>(); + ActionExecutorContext context = new ActionXCommand.ActionExecutorContext(wfJob, wfAction); + for (int i = 0; i < wfAction.getUserRetryCount(); i++) { + Map<String, String> retries = new HashMap<String, String>(); + String value = context.getVar(JobUtils.getRetryKey(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS, i)); + if (value != null) { + retries.put(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS, value); + } + value = context.getVar(JobUtils.getRetryKey(JsonTags.WORKFLOW_ACTION_CONSOLE_URL, i)); + if (value != null) { + retries.put(JsonTags.WORKFLOW_ACTION_CONSOLE_URL, value); + } + value = context.getVar(JobUtils.getRetryKey(JsonTags.WORKFLOW_ACTION_START_TIME, i)); + if (value != null) { + retries.put(JsonTags.WORKFLOW_ACTION_START_TIME, + JsonUtils.formatDateRfc822(new Date(Long.parseLong(value)))); + } + value = context.getVar(JobUtils.getRetryKey(JsonTags.WORKFLOW_ACTION_END_TIME, i)); + if (value != null) { + retries.put(JsonTags.WORKFLOW_ACTION_END_TIME, + JsonUtils.formatDateRfc822(new Date(Long.parseLong(value)))); + } + retries.put(JsonTags.ACTION_ATTEMPT, String.valueOf(i + 1)); + retriesList.add(retries); + } + return retriesList; + } + + @Override + public String getEntityKey() { + return null; + } + + @Override + protected void loadState() throws CommandException { + try { + this.wfAction = WorkflowActionQueryExecutor.getInstance().get(WorkflowActionQuery.GET_ACTION_CHECK, + actionId); + this.wfJob = WorkflowJobQueryExecutor.getInstance().get(WorkflowJobQuery.GET_WORKFLOW_DEFINITION, + actionId.substring(0, actionId.indexOf("@"))); + LogUtils.setLogInfo(wfAction); + } + catch (JPAExecutorException ex) { + if (ex.getErrorCode() == ErrorCode.E0605) { + throw new CommandException(ErrorCode.E0605, actionId); + } + else { + throw new CommandException(ex); + } + } + } + + @Override + protected void verifyPrecondition() throws CommandException { + } + + @Override + protected boolean isLockRequired() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/servlet/BaseJobServlet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/servlet/BaseJobServlet.java b/core/src/main/java/org/apache/oozie/servlet/BaseJobServlet.java index 2110522..87a2b42 100644 --- a/core/src/main/java/org/apache/oozie/servlet/BaseJobServlet.java +++ b/core/src/main/java/org/apache/oozie/servlet/BaseJobServlet.java @@ -41,6 +41,7 @@ import org.apache.oozie.util.ConfigUtils; import org.apache.oozie.util.JobUtils; import org.apache.oozie.util.XConfiguration; import org.apache.oozie.util.XLog; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; public abstract class BaseJobServlet extends JsonRestServlet { @@ -353,8 +354,14 @@ public abstract class BaseJobServlet extends JsonRestServlet { json.put(JsonTags.STATUS, status); startCron(); sendJsonResponse(response, HttpServletResponse.SC_OK, json); - } - else { + } else if (show.equals(RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM)) { + stopCron(); + JSONArray retries = getActionRetries(request, response); + JSONObject json = new JSONObject(); + json.put(JsonTags.WORKFLOW_ACTION_RETRIES, retries); + startCron(); + sendJsonResponse(response, HttpServletResponse.SC_OK, json); + } else { throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0303, RestConstants.JOB_SHOW_PARAM, show); } @@ -570,4 +577,15 @@ public abstract class BaseJobServlet extends JsonRestServlet { abstract void slaChange(HttpServletRequest request, HttpServletResponse response) throws XServletException, IOException; + /** + * Gets the action retries. + * + * @param request the request + * @param response the response + * @return the action retries + * @throws XServletException the x servlet exception + * @throws IOException Signals that an I/O exception has occurred. + */ + abstract JSONArray getActionRetries(HttpServletRequest request, HttpServletResponse response) + throws XServletException, IOException; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/servlet/V0JobServlet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/servlet/V0JobServlet.java b/core/src/main/java/org/apache/oozie/servlet/V0JobServlet.java index 86ff278..0c42128 100644 --- a/core/src/main/java/org/apache/oozie/servlet/V0JobServlet.java +++ b/core/src/main/java/org/apache/oozie/servlet/V0JobServlet.java @@ -29,6 +29,7 @@ import org.apache.oozie.DagEngineException; import org.apache.oozie.client.rest.JsonBean; import org.apache.oozie.service.DagEngineService; import org.apache.oozie.service.Services; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.apache.oozie.ErrorCode; @@ -260,4 +261,10 @@ public class V0JobServlet extends BaseJobServlet { void slaChange(HttpServletRequest request, HttpServletResponse response) throws XServletException, IOException { throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v0"); } + + @Override + JSONArray getActionRetries(HttpServletRequest request, HttpServletResponse response) throws XServletException, + IOException { + throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v0"); + } } http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/servlet/V1JobServlet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/servlet/V1JobServlet.java b/core/src/main/java/org/apache/oozie/servlet/V1JobServlet.java index 60f1029..95dcca6 100644 --- a/core/src/main/java/org/apache/oozie/servlet/V1JobServlet.java +++ b/core/src/main/java/org/apache/oozie/servlet/V1JobServlet.java @@ -1123,4 +1123,10 @@ public class V1JobServlet extends BaseJobServlet { void slaChange(HttpServletRequest request, HttpServletResponse response) throws XServletException, IOException { throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v1"); } -} + + @Override + JSONArray getActionRetries(HttpServletRequest request, HttpServletResponse response) throws XServletException, + IOException { + throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v1"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/servlet/V2JobServlet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/servlet/V2JobServlet.java b/core/src/main/java/org/apache/oozie/servlet/V2JobServlet.java index 662a7ff..3a0ffb0 100644 --- a/core/src/main/java/org/apache/oozie/servlet/V2JobServlet.java +++ b/core/src/main/java/org/apache/oozie/servlet/V2JobServlet.java @@ -21,6 +21,7 @@ package org.apache.oozie.servlet; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,7 +30,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.oozie.BaseEngine; import org.apache.oozie.BaseEngineException; -import org.apache.oozie.BundleEngine; import org.apache.oozie.CoordinatorActionBean; import org.apache.oozie.CoordinatorActionInfo; import org.apache.oozie.CoordinatorEngine; @@ -42,10 +42,12 @@ import org.apache.oozie.client.rest.JsonBean; import org.apache.oozie.client.rest.JsonTags; import org.apache.oozie.client.rest.RestConstants; import org.apache.oozie.command.CommandException; +import org.apache.oozie.command.wf.ActionXCommand; import org.apache.oozie.service.BundleEngineService; import org.apache.oozie.service.CoordinatorEngineService; import org.apache.oozie.service.DagEngineService; import org.apache.oozie.service.Services; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; @SuppressWarnings("serial") @@ -295,9 +297,23 @@ public class V2JobServlet extends V1JobServlet { catch (BaseEngineException e) { throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, e); } - } + @SuppressWarnings("unchecked") + @Override + JSONArray getActionRetries(HttpServletRequest request, HttpServletResponse response) + throws XServletException, IOException { + JSONArray jsonArray = new JSONArray(); + String jobId = getResourceName(request); + try { + jsonArray.addAll(Services.get().get(DagEngineService.class).getDagEngine(getUser(request)) + .getWorkflowActionRetries(jobId)); + return jsonArray; + } + catch (BaseEngineException ex) { + throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ex); + } + } /** * Gets the base engine based on jobId. http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/main/java/org/apache/oozie/util/JobUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/util/JobUtils.java b/core/src/main/java/org/apache/oozie/util/JobUtils.java index a7a53b3..63f88ac 100644 --- a/core/src/main/java/org/apache/oozie/util/JobUtils.java +++ b/core/src/main/java/org/apache/oozie/util/JobUtils.java @@ -28,10 +28,12 @@ import org.apache.hadoop.filecache.DistributedCache; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.oozie.ErrorCode; +import org.apache.oozie.WorkflowActionBean; import org.apache.oozie.action.hadoop.JavaActionExecutor; import org.apache.oozie.client.OozieClient; import org.apache.oozie.client.XOozieClient; import org.apache.oozie.command.CommandException; +import org.apache.oozie.command.wf.ActionXCommand; import org.apache.oozie.service.HadoopAccessorException; import org.apache.oozie.service.HadoopAccessorService; import org.apache.oozie.service.Services; @@ -166,4 +168,12 @@ public class JobUtils { DistributedCache.addFileToClassPath(file, conf, fs); } } + + public static String getRetryKey(WorkflowActionBean wfAction, String key) { + return ActionXCommand.RETRY + wfAction.getUserRetryCount() + "." + key; + } + + public static String getRetryKey(String key, int retry) { + return ActionXCommand.RETRY + retry + "." + key; + } } http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/test/java/org/apache/oozie/ForTestingActionExecutor.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/ForTestingActionExecutor.java b/core/src/test/java/org/apache/oozie/ForTestingActionExecutor.java index a70dc02..c9d0c98 100644 --- a/core/src/test/java/org/apache/oozie/ForTestingActionExecutor.java +++ b/core/src/test/java/org/apache/oozie/ForTestingActionExecutor.java @@ -60,19 +60,26 @@ public class ForTestingActionExecutor extends ActionExecutor { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, TEST_ERROR, "start"); } String externalStatus = eConf.getChild("external-status", ns).getText().trim(); + Element externalChildIds = eConf.getChild("external-childIds", ns); String runningMode = "sync"; Element runningModeElement = eConf.getChild("running-mode", ns); if (null != runningModeElement) { - if (runningModeElement.getText().trim().equals("async")) { - runningMode = "async"; - } + runningMode = runningModeElement.getText().trim(); } if (runningMode.equals("async")) { context.setStartData("blah", "blah", "blah"); return; } + if (runningMode.equals("async-error")) { + context.setStartData("blah", "blah", "blah"); + context.setExecutionData(externalStatus, null); + if (null != externalChildIds) { + context.setExternalChildIDs(externalChildIds.getText()); + } + throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, TEST_ERROR, "start"); + } boolean callSetExecutionData = true; Element setStartData = eConf.getChild("avoid-set-execution-data", ns); if (null != setStartData) { @@ -83,6 +90,9 @@ public class ForTestingActionExecutor extends ActionExecutor { if (callSetExecutionData) { context.setExecutionData(externalStatus, null); } + if (null != externalChildIds) { + context.setExternalChildIDs(externalChildIds.getText()); + } } public void end(Context context, WorkflowAction action) throws ActionExecutorException { http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java b/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java index ce95ff3..8ec38e4 100644 --- a/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java +++ b/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java @@ -1154,6 +1154,21 @@ public class TestOozieCLI extends DagServletTestCase { }); } + public void testWfActionRetries() throws Exception { + runTest(END_POINTS, SERVLET_CLASSES, IS_SECURITY_ENABLED, new Callable<Void>() { + @Override + public Void call() throws Exception { + String oozieUrl = getContextURL(); + MockDagEngineService.reset(); + String[] args = new String[] { "job", "-oozie", oozieUrl, "-retries", + MockDagEngineService.JOB_ID + "0" + MockDagEngineService.JOB_ID_END + "@a"}; + assertEquals(0, new OozieCLI().run(args)); + assertEquals(RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM, MockDagEngineService.did); + return null; + } + }); + } + public void testAdminQueueDump() throws Exception { runTest(END_POINTS, SERVLET_CLASSES, IS_SECURITY_ENABLED, new Callable<Void>() { @Override http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/test/java/org/apache/oozie/command/wf/TestWorkflowActionRetryInfoXCommand.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/command/wf/TestWorkflowActionRetryInfoXCommand.java b/core/src/test/java/org/apache/oozie/command/wf/TestWorkflowActionRetryInfoXCommand.java new file mode 100644 index 0000000..1a5f354 --- /dev/null +++ b/core/src/test/java/org/apache/oozie/command/wf/TestWorkflowActionRetryInfoXCommand.java @@ -0,0 +1,138 @@ +/** + * 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.command.wf; + +import java.io.File; +import java.util.List; +import java.util.Map; +import org.apache.hadoop.conf.Configuration; +import org.apache.oozie.ForTestingActionExecutor; +import org.apache.oozie.WorkflowActionBean; +import org.apache.oozie.client.OozieClient; +import org.apache.oozie.client.rest.JsonTags; +import org.apache.oozie.client.rest.JsonUtils; +import org.apache.oozie.executor.jpa.WorkflowActionsGetForJobJPAExecutor; +import org.apache.oozie.service.ActionService; +import org.apache.oozie.service.ExtendedCallableQueueService; +import org.apache.oozie.service.JPAService; +import org.apache.oozie.service.LiteWorkflowStoreService; +import org.apache.oozie.service.SchemaService; +import org.apache.oozie.service.Services; +import org.apache.oozie.test.XDataTestCase; +import org.apache.oozie.util.XConfiguration; + +public class TestWorkflowActionRetryInfoXCommand extends XDataTestCase { + private Services services; + + @Override + protected void setUp() throws Exception { + super.setUp(); + setSystemProperty(SchemaService.WF_CONF_EXT_SCHEMAS, "wf-ext-schema.xsd"); + setSystemProperty(LiteWorkflowStoreService.CONF_USER_RETRY_ERROR_CODE_EXT, ForTestingActionExecutor.TEST_ERROR); + setSystemProperty(Services.CONF_SERVICE_EXT_CLASSES, ExtendedCallableQueueService.class.getName()); + services = new Services(); + services.init(); + services.get(ActionService.class).registerAndInitExecutor(ForTestingActionExecutor.class); + } + + @Override + protected void tearDown() throws Exception { + services.destroy(); + super.tearDown(); + } + + public void testRetryConsoleUrl() throws Exception { + Configuration conf = new XConfiguration(); + File workflowUri = new File(getTestCaseDir(), "workflow.xml"); + + //@formatter:off + String appXml = "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"wf-fork\">" + + "<start to=\"action1\"/>" + +"<action name=\"action1\" retry-max=\"2\" retry-interval=\"0\">" + + "<test xmlns=\"uri:test\">" + + "<signal-value>${wf:conf('signal-value')}</signal-value>" + + "<external-status>${wf:conf('external-status')}</external-status> " + + "<external-childIds>${wf:conf('external-status')}</external-childIds> " + + "<error>${wf:conf('error')}</error>" + + "<avoid-set-execution-data>${wf:conf('avoid-set-execution-data')}</avoid-set-execution-data>" + + "<avoid-set-end-data>${wf:conf('avoid-set-end-data')}</avoid-set-end-data>" + + "<running-mode>async-error</running-mode>" + + "</test>" + + "<ok to=\"end\"/>" + + "<error to=\"kill\"/>" + + "</action>" + + "<kill name=\"kill\"><message>killed</message></kill>" + + "<end name=\"end\"/>" + + "</workflow-app>"; + //@Formatter:on + writeToFile(appXml, workflowUri); + conf.set(OozieClient.APP_PATH, workflowUri.toURI().toString()); + conf.set(OozieClient.USER_NAME, getTestUser()); + conf.set("external-status", "error"); + conf.set("signal-value", "based_on_action_status"); + conf.set("external-childIds", "1"); + + SubmitXCommand sc = new SubmitXCommand(conf); + final String jobId = sc.call(); + new StartXCommand(jobId).call(); + final WorkflowActionsGetForJobJPAExecutor actionsGetExecutor = new WorkflowActionsGetForJobJPAExecutor(jobId); + final JPAService jpaService = Services.get().get(JPAService.class); + + waitFor(20 * 1000, new Predicate() { + public boolean evaluate() throws Exception { + List<WorkflowActionBean> actions = jpaService.execute(actionsGetExecutor); + WorkflowActionBean action = null; + for (WorkflowActionBean bean : actions) { + if (bean.getType().equals("test")) { + action = bean; + break; + } + } + return (action != null && action.getUserRetryCount() == 2); + } + }); + + List<WorkflowActionBean> actions = jpaService.execute(actionsGetExecutor); + WorkflowActionBean action = null; + for (WorkflowActionBean bean : actions) { + if (bean.getType().equals("test")) { + action = bean; + break; + } + } + WorkflowActionRetryInfoXCommand command = new WorkflowActionRetryInfoXCommand(action.getId()); + List<Map<String, String>> retriesList = command.call(); + assertEquals(2, retriesList.size()); + assertEquals(2, action.getUserRetryCount()); + + assertEquals(retriesList.get(0).get(JsonTags.ACTION_ATTEMPT), "1"); + assertEquals(retriesList.get(0).get(JsonTags.WORKFLOW_ACTION_START_TIME), + JsonUtils.formatDateRfc822(action.getStartTime())); + + assertNotNull(retriesList.get(0).get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL)); + assertNotNull(retriesList.get(0).get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS)); + + assertNotNull(retriesList.get(1).get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL)); + assertNotNull(retriesList.get(1).get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS)); + assertEquals(retriesList.get(1).get(JsonTags.WORKFLOW_ACTION_END_TIME), + JsonUtils.formatDateRfc822(action.getEndTime())); + + } + +} http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/test/java/org/apache/oozie/servlet/MockDagEngineService.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/servlet/MockDagEngineService.java b/core/src/test/java/org/apache/oozie/servlet/MockDagEngineService.java index 60b7735..c76e6d8 100644 --- a/core/src/test/java/org/apache/oozie/servlet/MockDagEngineService.java +++ b/core/src/test/java/org/apache/oozie/servlet/MockDagEngineService.java @@ -38,7 +38,6 @@ import org.apache.oozie.client.rest.JMSConnectionInfoBean; import org.apache.oozie.client.rest.RestConstants; import org.apache.oozie.service.DagEngineService; import org.apache.oozie.util.XmlUtils; -import org.json.simple.JSONValue; public class MockDagEngineService extends DagEngineService { public static final String JOB_ID = "job-"; @@ -235,6 +234,11 @@ public class MockDagEngineService extends DagEngineService { return (externalId.equals("external-valid")) ? "id-valid" : null; } + public List<Map<String, String>> getWorkflowActionRetries(String actionId) throws DagEngineException { + did = RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM; + return new ArrayList<Map<String, String>>(); + } + private int validateWorkflowIdx(String jobId) throws DagEngineException { int idx = -1; try { http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/core/src/test/resources/wf-ext-schema.xsd ---------------------------------------------------------------------- diff --git a/core/src/test/resources/wf-ext-schema.xsd b/core/src/test/resources/wf-ext-schema.xsd index 48aecf5..3e2ab3e 100644 --- a/core/src/test/resources/wf-ext-schema.xsd +++ b/core/src/test/resources/wf-ext-schema.xsd @@ -26,6 +26,7 @@ <xs:sequence> <xs:element name="signal-value" type="xs:string" minOccurs="1" maxOccurs="unbounded"/> <xs:element name="external-status" type="xs:string" minOccurs="1" maxOccurs="unbounded"/> + <xs:element name="external-childIds" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="error" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="avoid-set-execution-data" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="avoid-set-end-data" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/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 35eee40..2dbbd4c 100644 --- a/docs/src/site/twiki/DG_CommandLineTool.twiki +++ b/docs/src/site/twiki/DG_CommandLineTool.twiki @@ -105,6 +105,7 @@ oozie job <OPTIONS> : job operations -sladisable disables sla alerts for the job and its children -slaenable enables sla alerts for the job and its children -slachange Update sla param for jobs, supported param are should-start, should-end and max-duration + -retries Get information of the retry attempts for a given workflow action. </verbatim> @@ -625,6 +626,34 @@ Job ID Status Started Ended .---------------------------------------------------------------------------------------------------- </verbatim> +---+++ Listing all retry attempts of a workflow action + +When retry-max is specified for an action in the workflow definition, and there is a failure, it will be retried till it succeeds or retry-max attempts are exhausted. To get information on all the retry attempts, =-retries= command can be used. + +<verbatim> +$ oozie job -retries 0000000-161212175234862-oozie-puru-W@pig-node -oozie http://localhost:11000/oozie + +ID : 0000000-161212175234862-oozie-puru-W@pig-node +------------------------------------------------------------------------------------------------------------------------------------ +Attempt : 1 +Start Time : Tue, 13 Dec 2016 01:56:24 GMT +End Time : Tue, 13 Dec 2016 01:56:27 GMT +Console URL : http://localhost:50030/jobdetails.jsp?jobid=job_201612051339_2650 +------------------------------------------------------------------------------------------------------------------------------------ +Attempt : 2 +Start Time : Tue, 13 Dec 2016 01:56:24 GMT +End Time : Tue, 13 Dec 2016 01:56:27 GMT +Console URL : http://localhost:50030/jobdetails.jsp?jobid=job_201612051339_2650 +------------------------------------------------------------------------------------------------------------------------------------ +Attempt : 3 +Start Time : Tue, 13 Dec 2016 01:56:24 GMT +End Time : Tue, 13 Dec 2016 01:56:27 GMT +Console URL : http://localhost:50030/jobdetails.jsp?jobid=job_201612051339_2650 +------------------------------------------------------------------------------------------------------------------------------------ +$ +</verbatim> + + ---+++ Checking the xml definition of a Workflow, Coordinator or Bundle Job Example: http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/docs/src/site/twiki/WebServicesAPI.twiki ---------------------------------------------------------------------- diff --git a/docs/src/site/twiki/WebServicesAPI.twiki b/docs/src/site/twiki/WebServicesAPI.twiki index b76e934..8406da6 100644 --- a/docs/src/site/twiki/WebServicesAPI.twiki +++ b/docs/src/site/twiki/WebServicesAPI.twiki @@ -1353,6 +1353,37 @@ GET /oozie/v1/job/0000001-111219170928042-oozie-joe-C?show=info&filter=status%21 </verbatim> This retrieves coordinator actions except for SUCCEEDED status, which is useful for debugging. +*Retrive information of the retry attempts of the workflow action:* + +<verbatim> +GET oozie/v2/job/0000000-161212175234862-oozie-puru-W@pig-node?show=retries +</verbatim> + +*Response* + +<verbatim> +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +. +{ + "retries": + [ + { + "startTime": "Tue, 13 Dec 2016 01:54:13 GMT", + "consoleUrl": "http://localhost:50030/jobdetails.jsp?jobid=job_201612051339_2648", + "endTime": "Tue, 13 Dec 2016 01:54:20 GMT", + "attempt": "1" + }, + { + "startTime": "Tue, 13 Dec 2016 01:55:20 GMT", + "consoleUrl": "http://localhost:50030/jobdetails.jsp?jobid=job_201612051339_2649", + "endTime": "Tue, 13 Dec 2016 01:55:24 GMT", + "attempt": "2" + } + ] +} +</verbatim> + ---++++ Job Application Definition A HTTP GET request retrieves the workflow or a coordinator job definition file. http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/release-log.txt ---------------------------------------------------------------------- diff --git a/release-log.txt b/release-log.txt index 8a4aad0..ca9cd43 100644 --- a/release-log.txt +++ b/release-log.txt @@ -1,5 +1,6 @@ -- Oozie 4.4.0 release (trunk - unreleased) +OOZIE-2691 Show workflow action retry information in UI (puru) OOZIE-2709 Log the substituted pig script by expanding macros (satishsaley) OOZIE-2732 Remove login server example (rkanter via abhishekbafna) OOZIE-2756 Extend HTTPS configuration settings for embedded Jetty (asasvari via abhishekbafna) http://git-wip-us.apache.org/repos/asf/oozie/blob/bf2802e4/webapp/src/main/webapp/oozie-console.js ---------------------------------------------------------------------- diff --git a/webapp/src/main/webapp/oozie-console.js b/webapp/src/main/webapp/oozie-console.js index 11c6940..7b20e91 100644 --- a/webapp/src/main/webapp/oozie-console.js +++ b/webapp/src/main/webapp/oozie-console.js @@ -453,7 +453,8 @@ function jobDetailsPopup(response, request) { var appName = jobDetails["appName"]; var jobActionStatus = new Ext.data.JsonStore({ data: jobDetails["actions"], - fields: ['id', 'name', 'type', 'startTime', 'retries', 'consoleUrl', 'endTime', 'externalId', 'status', 'trackerUri', 'workflowId', 'errorCode', 'errorMessage', 'conf', 'transition', 'externalStatus', 'externalChildIDs'] + fields: ['id', 'name', 'type', 'startTime', 'consoleUrl', 'endTime', 'externalId', 'status', 'userRetryCount' ,'trackerUri', + 'workflowId', 'errorCode', 'errorMessage', 'conf', 'transition', 'externalStatus', 'externalChildIDs'] }); var formFieldSet = new Ext.form.FieldSet({ @@ -623,6 +624,7 @@ function jobDetailsPopup(response, request) { function showActionContextMenu(thisGrid, rowIndex, cellIndex, e) { var actionStatus = thisGrid.store.data.items[rowIndex].data; actionDetailsGridWindow(actionStatus); + function actionDetailsGridWindow(actionStatus) { var formFieldSet = new Ext.form.FieldSet({ title: actionStatus.actionName, @@ -666,6 +668,12 @@ function jobDetailsPopup(response, request) { width: 400, value: actionStatus["status"] }, { + fieldLabel: 'Retries', + editable: false, + width: 400, + name: 'userRetryCount', + value: actionStatus["userRetryCount"] + }, { fieldLabel: 'Error Code', editable: false, name: 'errorCode', @@ -716,66 +724,244 @@ function jobDetailsPopup(response, request) { //width: 540, items: [formFieldSet] }); + var actionInfoPanel = new Ext.TabPanel({ + activeTab: 0, + autoHeight: true, + deferredRender: false, + items: [ { + title: 'Action Info', + items: detail + }, { + title: 'Action Configuration', + items: new Ext.form.TextArea({ + fieldLabel: 'Configuration', + editable: false, + name: 'config', + height: 350, + width: 540, + autoScroll: true, + value: actionStatus["conf"] + }) + + }], + tbar: [{ + text: " ", + icon: 'ext-2.2/resources/images/default/grid/refresh.gif', + handler: function() { + var a = win.items.get(0).getActiveTab(); + if (a.title == "Action retries") { + fetchActionRetries(workflowId + "@" + actionStatus["name"]); + } + else { + refreshActionDetails(workflowId + "@" + actionStatus["name"], detail, urlUnit); + } + } + }] + }); + var actionRetries; var urlUnit = new Ext.FormPanel(); - populateUrlUnit(actionStatus, urlUnit); + populateUrlUnit(actionStatus["consoleUrl"], actionStatus["externalChildIDs"], urlUnit); var win = new Ext.Window({ title: 'Action (Name: ' + actionStatus["name"] + '/JobId: ' + workflowId + ')', closable: true, width: 560, autoHeight: true, plain: true, - items: [new Ext.TabPanel({ - activeTab: 0, - autoHeight: true, - deferredRender: false, - items: [ { - title: 'Action Info', - items: detail - }, { - title: 'Action Configuration', - items: new Ext.form.TextArea({ - fieldLabel: 'Configuration', - editable: false, - name: 'config', - height: 350, - width: 540, - autoScroll: true, - value: actionStatus["conf"] + items: [actionInfoPanel] + }); + + var isLoadedActionRetries = false; + + actionInfoPanel.addListener("tabchange", function(panel, selectedTab) { + if (selectedTab.title == "Action retries") { + if ( !isLoadedActionRetries ) { + fetchActionRetries(workflowId + "@" + actionStatus["name"]); + isLoadedActionRetries = true; + } + } + }); + + var reTriesPanel = new Ext.TabPanel(); + + function fetchActionRetries(actionId) { + Ext.Ajax.request({ + url: getOozieBase() + 'job/' + actionId + "?show=retries", + timeout: 300000, + success: function(response, request) { + actionRetries = JSON.parse(response.responseText); + var currentTabs = []; + reTriesPanel.items.each(function(item) { + currentTabs.push(item); + }); + populateRetries(actionStatus, actionRetries, reTriesPanel); + currentTabs.forEach(function(item) { + reTriesPanel.remove(item); }) - }], - tbar: [{ - text: " ", - icon: 'ext-2.2/resources/images/default/grid/refresh.gif', - handler: function() { - refreshActionDetails(workflowId+"@"+actionStatus["name"], detail, urlUnit); + } + }); + } + populateRetries(actionStatus, actionRetries, reTriesPanel); + + function populateRetries(actionStatus, actionRetries, reTriesPanel) { + var retryCount = getRetryCount(actionStatus, actionRetries); + for (i = 0; i < retryCount; i++) { + var attemptNumber = ' Attempt - ' + (i + 1); + if (actionRetries) { + var retriesJson = actionRetries['retries']; + attemptNumber = ' Attempt - ' + retriesJson[i]["attempt"]; + } + var form = new Ext.FormPanel({ + title: attemptNumber, + width: 500, + height: 400, + style: 'background-color: #eaf1fb' + }); + if (actionRetries) { + var retriesJson = actionRetries['retries']; + addTextField(form, "Start Time", "startTime", retriesJson[i]); + addTextField(form, "End Time", "endTime", retriesJson[i]); + populateRetriesUrl(form, "Console URL", "consoleUrl", retriesJson[i]); + populateRetriesChildUrl(form, retriesJson[i]); + } + reTriesPanel.add(form); + form.show(); + } + reTriesPanel.doLayout(); + } + + function getRetryCount(actionStatus, actionRetries) { + if (actionRetries) { + if (actionRetries['retries']) { + return actionRetries['retries'].length; + } + } else { + return actionStatus["userRetryCount"]; + } + } + function populateRetriesUrl(form, label, key, retriesObj) { + if ( retriesObj[key] ) { + var textUrl = new Ext.form.TriggerField({ + fieldLabel: label, + editable: false, + width: 400, + height: 100, + value: retriesObj[key], + triggerClass: 'x-form-search-trigger', + onTriggerClick: function() { + window.open(retriesObj[text]); } - }] - })] - }); + }); + form.add(textUrl); + } + else { + addTextField(form, label, key, retriesObj); + } + } + + function addTextField(form, label, key, retriesObj) { + if (retriesObj[key]) { + var textUrl = new Ext.form.TextField({ + fieldLabel: label, + width: 400, + value: retriesObj[key] + }); + form.add(textUrl); + } else { + var textUrl = new Ext.form.TextField({ + fieldLabel: label, + width: 400, + value: 'n/a' + }); + form.add(textUrl); + } + } + + function populateRetriesChildUrl(form, retriesObj) { + populateUrlUnit(retriesObj["consoleUrl"], retriesObj["externalChildIDs"], form); + } // Tab to show list of child Job URLs var childJobsItem = { - title : 'Child Job URLs', - autoScroll : true, - frame : true, - labelAlign : 'right', - labelWidth : 70, + title: 'Child Job URLs', + autoScroll: true, + frame: true, + labelAlign: 'right', + labelWidth: 70, height: 350, width: 540, - items : urlUnit + items: urlUnit }; - if (actionStatus.type == "pig" || actionStatus.type == "hive" || actionStatus.type == "map-reduce" - || actionStatus.type == "hive2" || actionStatus.type == "sqoop" || actionStatus.type == "distcp" - || actionStatus.type == "spark") { + if (actionStatus.type == "pig" || actionStatus.type == "hive" || actionStatus.type == "map-reduce" || + actionStatus.type == "hive2" || actionStatus.type == "sqoop" || actionStatus.type == "distcp" || + actionStatus.type == "spark") { var tabPanel = win.items.get(0); tabPanel.add(childJobsItem); } + // Tab to show list of Retries + var retriesActionItem = { + title: 'Action retries', + autoScroll: true, + frame: true, + labelAlign: 'right', + labelWidth: 70, + height: 350, + width: 540, + items: reTriesPanel + }; + if (actionStatus["userRetryCount"] > 0) { + var tabPanel = win.items.get(0); + tabPanel.add(retriesActionItem); + } win.setPosition(50, 50); win.show(); } } + function populateUrlUnit(consoleUrl, externalChildIDs, urlUnit) { + if (consoleUrl && externalChildIDs && externalChildIDs != "null") { + var urlPrefix = consoleUrl.trim().split(/_/)[0]; + // externalChildIds is a comma-separated string of each child job + // ID. + // Create URL list by appending jobID portion after stripping "job" + var jobIds = externalChildIDs.split(/,/); + var count = 1; + jobIds.forEach(function(jobId) { + jobId = jobId.trim().split(/job/); + if (jobId.length > 1) { + jobId = jobId[1]; + var jobUrl = new Ext.form.TriggerField({ + fieldLabel : 'Child Job ' + count, + editable : false, + name : 'childJobURLs', + width : 400, + value : urlPrefix + jobId, + triggerClass : 'x-form-search-trigger', + onTriggerClick : function() { + window.open(urlPrefix + jobId); + } + }); + if (jobId != undefined) { + urlUnit.add(jobUrl); + count++; + } + } + }); + if (count == 1) { + var note = new Ext.form.TextField({ + fieldLabel : 'Child Job', + value : 'n/a' + }); + urlUnit.add(note); + } + } else { + var note = new Ext.form.TextField({ + fieldLabel : 'Child Job', + value : 'n/a' + }); + urlUnit.add(note); + } + } - function populateUrlUnit(actionStatus, urlUnit) { + function populateRetries(actionStatus, urlUnit) { var consoleUrl = actionStatus["consoleUrl"]; var externalChildIDs = actionStatus["externalChildIDs"]; if (consoleUrl && externalChildIDs && externalChildIDs != "null") { @@ -830,7 +1016,7 @@ function jobDetailsPopup(response, request) { var results = JSON.parse(response.responseText); detail.getForm().setValues(results); urlUnit.getForm().setValues(results); - populateUrlUnit(results, urlUnit); + populateUrlUnit(results["consoleUrl"], results["externalChildIDs"], urlUnit); } }); } @@ -1354,6 +1540,12 @@ function coordJobDetailsPopup(response, request) { width: 400, value: actionStatus["status"] }, { + fieldLabel: 'Retries', + editable: false, + width: 400, + name: 'userRetryCount', + value: actionStatus["userRetryCount"] + }, { fieldLabel: 'Error Code', editable: false, name: 'errorCode',