001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.command.wf;
020
021import java.util.ArrayList;
022import java.util.Date;
023import java.util.List;
024
025import org.apache.hadoop.conf.Configuration;
026import org.apache.oozie.DagELFunctions;
027import org.apache.oozie.ErrorCode;
028import org.apache.oozie.SLAEventBean;
029import org.apache.oozie.WorkflowActionBean;
030import org.apache.oozie.WorkflowJobBean;
031import org.apache.oozie.XException;
032import org.apache.oozie.action.ActionExecutor;
033import org.apache.oozie.action.ActionExecutorException;
034import org.apache.oozie.action.control.ControlNodeActionExecutor;
035import org.apache.oozie.client.OozieClient;
036import org.apache.oozie.client.WorkflowAction;
037import org.apache.oozie.client.WorkflowJob;
038import org.apache.oozie.client.SLAEvent.SlaAppType;
039import org.apache.oozie.client.SLAEvent.Status;
040import org.apache.oozie.client.rest.JsonBean;
041import org.apache.oozie.command.CommandException;
042import org.apache.oozie.command.PreconditionException;
043import org.apache.oozie.executor.jpa.BatchQueryExecutor.UpdateEntry;
044import org.apache.oozie.executor.jpa.BatchQueryExecutor;
045import org.apache.oozie.executor.jpa.JPAExecutorException;
046import org.apache.oozie.executor.jpa.WorkflowActionQueryExecutor;
047import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor;
048import org.apache.oozie.executor.jpa.WorkflowActionQueryExecutor.WorkflowActionQuery;
049import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor.WorkflowJobQuery;
050import org.apache.oozie.service.ActionService;
051import org.apache.oozie.service.EventHandlerService;
052import org.apache.oozie.service.JPAService;
053import org.apache.oozie.service.Services;
054import org.apache.oozie.service.UUIDService;
055import org.apache.oozie.util.Instrumentation;
056import org.apache.oozie.util.LogUtils;
057import org.apache.oozie.util.XLog;
058import org.apache.oozie.util.db.SLADbXOperations;
059import org.apache.oozie.workflow.WorkflowInstance;
060
061@SuppressWarnings("deprecation")
062public class ActionEndXCommand extends ActionXCommand<Void> {
063    public static final String COULD_NOT_END = "COULD_NOT_END";
064    public static final String END_DATA_MISSING = "END_DATA_MISSING";
065
066    private String jobId = null;
067    private String actionId = null;
068    private WorkflowJobBean wfJob = null;
069    private WorkflowActionBean wfAction = null;
070    private JPAService jpaService = null;
071    private ActionExecutor executor = null;
072    private List<UpdateEntry> updateList = new ArrayList<UpdateEntry>();
073    private List<JsonBean> insertList = new ArrayList<JsonBean>();
074
075    public ActionEndXCommand(String actionId, String type) {
076        super("action.end", type, 0);
077        this.actionId = actionId;
078        this.jobId = Services.get().get(UUIDService.class).getId(actionId);
079    }
080
081    @Override
082    protected void setLogInfo() {
083        LogUtils.setLogInfo(actionId);
084    }
085
086    @Override
087    protected boolean isLockRequired() {
088        return true;
089    }
090
091    @Override
092    public String getEntityKey() {
093        return this.jobId;
094    }
095
096    @Override
097    public String getKey() {
098        return getName() + "_" + actionId;
099    }
100
101    @Override
102    protected void loadState() throws CommandException {
103        try {
104            jpaService = Services.get().get(JPAService.class);
105            if (jpaService != null) {
106                this.wfJob = WorkflowJobQueryExecutor.getInstance().get(WorkflowJobQuery.GET_WORKFLOW_ACTION_OP,
107                        jobId);
108                this.wfAction = WorkflowActionQueryExecutor.getInstance().get(WorkflowActionQuery.GET_ACTION_END,
109                        actionId);
110                LogUtils.setLogInfo(wfJob);
111                LogUtils.setLogInfo(wfAction);
112            }
113            else {
114                throw new CommandException(ErrorCode.E0610);
115            }
116        }
117        catch (XException ex) {
118            throw new CommandException(ex);
119        }
120    }
121
122    @Override
123    protected void verifyPrecondition() throws CommandException, PreconditionException {
124        if (wfJob == null) {
125            throw new PreconditionException(ErrorCode.E0604, jobId);
126        }
127        if (wfAction == null) {
128            throw new PreconditionException(ErrorCode.E0605, actionId);
129        }
130        if (wfAction.isPending()
131                && (wfAction.getStatus() == WorkflowActionBean.Status.DONE
132                        || wfAction.getStatus() == WorkflowActionBean.Status.END_RETRY || wfAction.getStatus()
133                        == WorkflowActionBean.Status.END_MANUAL)) {
134
135            if (wfJob.getStatus() != WorkflowJob.Status.RUNNING) {
136                throw new PreconditionException(ErrorCode.E0811,  WorkflowJob.Status.RUNNING.toString());
137            }
138        }
139        else {
140            throw new PreconditionException(ErrorCode.E0812, wfAction.isPending(), wfAction.getStatusStr());
141        }
142
143        executor = Services.get().get(ActionService.class).getExecutor(wfAction.getType());
144        if (executor == null) {
145            throw new CommandException(ErrorCode.E0802, wfAction.getType());
146        }
147    }
148
149    @Override
150    protected Void execute() throws CommandException {
151        LOG.debug("STARTED ActionEndXCommand for action " + actionId);
152
153        Configuration conf = wfJob.getWorkflowInstance().getConf();
154
155        int maxRetries = 0;
156        long retryInterval = 0;
157
158        if (!(executor instanceof ControlNodeActionExecutor)) {
159            maxRetries = conf.getInt(OozieClient.ACTION_MAX_RETRIES, executor.getMaxRetries());
160            retryInterval = conf.getLong(OozieClient.ACTION_RETRY_INTERVAL, executor.getRetryInterval());
161        }
162
163        executor.setMaxRetries(maxRetries);
164        executor.setRetryInterval(retryInterval);
165
166        boolean isRetry = false;
167        if (wfAction.getStatus() == WorkflowActionBean.Status.END_RETRY
168                || wfAction.getStatus() == WorkflowActionBean.Status.END_MANUAL) {
169            isRetry = true;
170        }
171        boolean isUserRetry = false;
172        ActionExecutorContext context = new ActionXCommand.ActionExecutorContext(wfJob, wfAction, isRetry, isUserRetry);
173        try {
174
175            LOG.debug(
176                    "End, name [{0}] type [{1}] status[{2}] external status [{3}] signal value [{4}]",
177                    wfAction.getName(), wfAction.getType(), wfAction.getStatus(), wfAction.getExternalStatus(),
178                    wfAction.getSignalValue());
179
180            Instrumentation.Cron cron = new Instrumentation.Cron();
181            cron.start();
182            executor.end(context, wfAction);
183            cron.stop();
184            addActionCron(wfAction.getType(), cron);
185
186            incrActionCounter(wfAction.getType(), 1);
187
188            if (!context.isEnded()) {
189                LOG.warn(XLog.OPS, "Action Ended, ActionExecutor [{0}] must call setEndData()",
190                        executor.getType());
191                wfAction.setErrorInfo(END_DATA_MISSING, "Execution Ended, but End Data Missing from Action");
192                failJob(context);
193            } else {
194                wfAction.setRetries(0);
195                wfAction.setEndTime(new Date());
196
197                boolean shouldHandleUserRetry = false;
198                Status slaStatus = null;
199                switch (wfAction.getStatus()) {
200                    case OK:
201                        slaStatus = Status.SUCCEEDED;
202                        break;
203                    case KILLED:
204                        slaStatus = Status.KILLED;
205                        break;
206                    case FAILED:
207                        slaStatus = Status.FAILED;
208                        shouldHandleUserRetry = true;
209                        break;
210                    case ERROR:
211                        LOG.info("ERROR is considered as FAILED for SLA");
212                        slaStatus = Status.KILLED;
213                        shouldHandleUserRetry = true;
214                        break;
215                    default:
216                        slaStatus = Status.FAILED;
217                        shouldHandleUserRetry = true;
218                        break;
219                }
220                if (!shouldHandleUserRetry || !handleUserRetry(context, wfAction)) {
221                    SLAEventBean slaEvent = SLADbXOperations.createStatusEvent(wfAction.getSlaXml(), wfAction.getId(), slaStatus,
222                            SlaAppType.WORKFLOW_ACTION);
223                    if(slaEvent != null) {
224                        insertList.add(slaEvent);
225                    }
226                }
227            }
228            WorkflowInstance wfInstance = wfJob.getWorkflowInstance();
229            DagELFunctions.setActionInfo(wfInstance, wfAction);
230            wfJob.setWorkflowInstance(wfInstance);
231
232            updateList.add(new UpdateEntry<WorkflowActionQuery>(WorkflowActionQuery.UPDATE_ACTION_END,wfAction));
233            wfJob.setLastModifiedTime(new Date());
234            updateList.add(new UpdateEntry<WorkflowJobQuery>(WorkflowJobQuery.UPDATE_WORKFLOW_STATUS_INSTANCE_MODIFIED, wfJob));
235        }
236        catch (ActionExecutorException ex) {
237            LOG.warn(
238                    "Error ending action [{0}]. ErrorType [{1}], ErrorCode [{2}], Message [{3}]",
239                    wfAction.getName(), ex.getErrorType(), ex.getErrorCode(), ex.getMessage());
240            wfAction.setErrorInfo(ex.getErrorCode(), ex.getMessage());
241            wfAction.setEndTime(null);
242
243            switch (ex.getErrorType()) {
244                case TRANSIENT:
245                    if (!handleTransient(context, executor, WorkflowAction.Status.END_RETRY)) {
246                        handleNonTransient(context, executor, WorkflowAction.Status.END_MANUAL);
247                        wfAction.setPendingAge(new Date());
248                        wfAction.setRetries(0);
249                    }
250                    wfAction.setEndTime(null);
251                    break;
252                case NON_TRANSIENT:
253                    handleNonTransient(context, executor, WorkflowAction.Status.END_MANUAL);
254                    wfAction.setEndTime(null);
255                    break;
256                case ERROR:
257                    handleError(context, executor, COULD_NOT_END, false, WorkflowAction.Status.ERROR);
258                    break;
259                case FAILED:
260                    failJob(context);
261                    break;
262            }
263
264            WorkflowInstance wfInstance = wfJob.getWorkflowInstance();
265            DagELFunctions.setActionInfo(wfInstance, wfAction);
266            wfJob.setWorkflowInstance(wfInstance);
267
268            updateList.add(new UpdateEntry<WorkflowActionQuery>(WorkflowActionQuery.UPDATE_ACTION_END,wfAction));
269            wfJob.setLastModifiedTime(new Date());
270            updateList.add(new UpdateEntry<WorkflowJobQuery>(WorkflowJobQuery.UPDATE_WORKFLOW_STATUS_INSTANCE_MODIFIED, wfJob));
271        }
272        finally {
273            try {
274                BatchQueryExecutor.getInstance().executeBatchInsertUpdateDelete(insertList, updateList, null);
275            }
276            catch (JPAExecutorException e) {
277                throw new CommandException(e);
278            }
279            if (!(executor instanceof ControlNodeActionExecutor) && EventHandlerService.isEnabled()) {
280                generateEvent(wfAction, wfJob.getUser());
281            }
282            new SignalXCommand(jobId, actionId).call();
283        }
284
285        LOG.debug("ENDED ActionEndXCommand for action " + actionId);
286        return null;
287    }
288
289}