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.sql.Timestamp;
018    import java.util.Date;
019    
020    import org.apache.oozie.ErrorCode;
021    import org.apache.oozie.WorkflowActionBean;
022    import org.apache.oozie.WorkflowJobBean;
023    import org.apache.oozie.XException;
024    import org.apache.oozie.action.ActionExecutor;
025    import org.apache.oozie.action.ActionExecutorException;
026    import org.apache.oozie.client.WorkflowJob;
027    import org.apache.oozie.client.WorkflowAction.Status;
028    import org.apache.oozie.command.CommandException;
029    import org.apache.oozie.command.PreconditionException;
030    import org.apache.oozie.executor.jpa.JPAExecutorException;
031    import org.apache.oozie.executor.jpa.WorkflowActionGetJPAExecutor;
032    import org.apache.oozie.executor.jpa.WorkflowActionUpdateJPAExecutor;
033    import org.apache.oozie.executor.jpa.WorkflowJobGetJPAExecutor;
034    import org.apache.oozie.executor.jpa.WorkflowJobUpdateJPAExecutor;
035    import org.apache.oozie.service.ActionService;
036    import org.apache.oozie.service.JPAService;
037    import org.apache.oozie.service.Services;
038    import org.apache.oozie.service.UUIDService;
039    import org.apache.oozie.util.InstrumentUtils;
040    import org.apache.oozie.util.Instrumentation;
041    import org.apache.oozie.util.LogUtils;
042    import org.apache.oozie.util.XLog;
043    
044    /**
045     * Executes the check command for ActionHandlers. </p> Ensures the action is in
046     * RUNNING state before executing
047     * {@link ActionExecutor#check(org.apache.oozie.action.ActionExecutor.Context, org.apache.oozie.client.WorkflowAction)}
048     */
049    public class ActionCheckXCommand extends ActionXCommand<Void> {
050        public static final String EXEC_DATA_MISSING = "EXEC_DATA_MISSING";
051        private String actionId;
052        private String jobId;
053        private int actionCheckDelay;
054        private WorkflowJobBean wfJob = null;
055        private WorkflowActionBean wfAction = null;
056        private JPAService jpaService = null;
057        private ActionExecutor executor = null;
058    
059        public ActionCheckXCommand(String actionId) {
060            this(actionId, -1);
061        }
062    
063        public ActionCheckXCommand(String actionId, int priority, int checkDelay) {
064            super("action.check", "action.check", priority);
065            this.actionId = actionId;
066            this.actionCheckDelay = checkDelay;
067            this.jobId = Services.get().get(UUIDService.class).getId(actionId);
068        }
069    
070        public ActionCheckXCommand(String actionId, int checkDelay) {
071            this(actionId, 0, checkDelay);
072        }
073    
074        @Override
075        protected void eagerLoadState() throws CommandException {
076            try {
077                jpaService = Services.get().get(JPAService.class);
078                if (jpaService != null) {
079                    this.wfJob = jpaService.execute(new WorkflowJobGetJPAExecutor(jobId));
080                    this.wfAction = jpaService.execute(new WorkflowActionGetJPAExecutor(actionId));
081                    LogUtils.setLogInfo(wfJob, logInfo);
082                    LogUtils.setLogInfo(wfAction, logInfo);
083                }
084                else {
085                    throw new CommandException(ErrorCode.E0610);
086                }
087            }
088            catch (XException ex) {
089                throw new CommandException(ex);
090            }
091        }
092    
093        @Override
094        protected void eagerVerifyPrecondition() throws CommandException, PreconditionException {
095            if (wfJob == null) {
096                throw new PreconditionException(ErrorCode.E0604, jobId);
097            }
098            if (wfAction == null) {
099                throw new PreconditionException(ErrorCode.E0605, actionId);
100            }
101            // if the action has been updated, quit this command
102            if (actionCheckDelay > 0) {
103                Timestamp actionCheckTs = new Timestamp(System.currentTimeMillis() - actionCheckDelay * 1000);
104                Timestamp actionLmt = wfAction.getLastCheckTimestamp();
105                if (actionLmt.after(actionCheckTs)) {
106                    throw new PreconditionException(ErrorCode.E0817, actionId);
107                }
108            }
109    
110            executor = Services.get().get(ActionService.class).getExecutor(wfAction.getType());
111            if (executor == null) {
112                throw new CommandException(ErrorCode.E0802, wfAction.getType());
113            }
114        }
115    
116        @Override
117        protected boolean isLockRequired() {
118            return true;
119        }
120    
121        @Override
122        protected String getEntityKey() {
123            return this.jobId;
124        }
125    
126        @Override
127        protected void loadState() throws CommandException {
128        }
129    
130        @Override
131        protected void verifyPrecondition() throws CommandException, PreconditionException {
132            if (!wfAction.isPending() || wfAction.getStatus() != WorkflowActionBean.Status.RUNNING) {
133                throw new PreconditionException(ErrorCode.E0815, wfAction.getPending(), wfAction.getStatusStr());
134            }
135            if (wfJob.getStatus() != WorkflowJob.Status.RUNNING) {
136                wfAction.setLastCheckTime(new Date());
137                try {
138                    jpaService.execute(new WorkflowActionUpdateJPAExecutor(wfAction));
139                }
140                catch (JPAExecutorException e) {
141                    throw new CommandException(e);
142                }
143                throw new PreconditionException(ErrorCode.E0818, wfAction.getId(), wfJob.getId(), wfJob.getStatus());
144            }
145        }
146    
147        @Override
148        protected Void execute() throws CommandException {
149            LOG.debug("STARTED ActionCheckXCommand for wf actionId=" + actionId + " priority =" + getPriority());
150    
151            ActionExecutorContext context = null;
152            try {
153                boolean isRetry = false;
154                context = new ActionXCommand.ActionExecutorContext(wfJob, wfAction, isRetry);
155                incrActionCounter(wfAction.getType(), 1);
156    
157                Instrumentation.Cron cron = new Instrumentation.Cron();
158                cron.start();
159                executor.check(context, wfAction);
160                cron.stop();
161                addActionCron(wfAction.getType(), cron);
162    
163                if (wfAction.isExecutionComplete()) {
164                    if (!context.isExecuted()) {
165                        LOG.warn(XLog.OPS, "Action Completed, ActionExecutor [{0}] must call setExecutionData()", executor
166                                .getType());
167                        wfAction.setErrorInfo(EXEC_DATA_MISSING,
168                                "Execution Complete, but Execution Data Missing from Action");
169                        failJob(context);
170                        wfAction.setLastCheckTime(new Date());
171                        jpaService.execute(new WorkflowActionUpdateJPAExecutor(wfAction));
172                        jpaService.execute(new WorkflowJobUpdateJPAExecutor(wfJob));
173                        return null;
174                    }
175                    wfAction.setPending();
176                    queue(new ActionEndXCommand(wfAction.getId(), wfAction.getType()));
177                }
178                wfAction.setLastCheckTime(new Date());
179                jpaService.execute(new WorkflowActionUpdateJPAExecutor(wfAction));
180                jpaService.execute(new WorkflowJobUpdateJPAExecutor(wfJob));
181            }
182            catch (ActionExecutorException ex) {
183                LOG.warn("Exception while executing check(). Error Code [{0}], Message[{1}]", ex.getErrorCode(), ex
184                        .getMessage(), ex);
185    
186                switch (ex.getErrorType()) {
187                    case FAILED:
188                        failAction(wfJob, wfAction);
189                        break;
190                }
191                wfAction.setLastCheckTime(new Date());
192                try {
193                    jpaService.execute(new WorkflowActionUpdateJPAExecutor(wfAction));
194                    jpaService.execute(new WorkflowJobUpdateJPAExecutor(wfJob));
195                }
196                catch (JPAExecutorException e) {
197                    throw new CommandException(e);
198                }
199                return null;
200            }
201            catch (JPAExecutorException e) {
202                throw new CommandException(e);
203            }
204    
205            LOG.debug("ENDED ActionCheckXCommand for wf actionId=" + actionId + ", jobId=" + jobId);
206            return null;
207        }
208    
209        private void failAction(WorkflowJobBean workflow, WorkflowActionBean action) throws CommandException {
210            LOG.warn("Failing Job [{0}] due to failed action [{1}]", workflow.getId(), action.getId());
211            action.resetPending();
212            action.setStatus(Status.FAILED);
213            workflow.setStatus(WorkflowJob.Status.FAILED);
214            InstrumentUtils.incrJobCounter(INSTR_FAILED_JOBS_COUNTER, 1, getInstrumentation());
215        }
216    }