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