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}