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.coord; 020 021import java.io.IOException; 022import java.io.StringReader; 023import java.util.Date; 024import java.util.List; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.Path; 027import org.apache.oozie.CoordinatorActionBean; 028import org.apache.oozie.CoordinatorActionInfo; 029import org.apache.oozie.CoordinatorJobBean; 030import org.apache.oozie.ErrorCode; 031import org.apache.oozie.SLAEventBean; 032import org.apache.oozie.XException; 033import org.apache.oozie.action.ActionExecutorException; 034import org.apache.oozie.action.hadoop.FsActionExecutor; 035import org.apache.oozie.client.CoordinatorAction; 036import org.apache.oozie.client.CoordinatorJob; 037import org.apache.oozie.client.Job; 038import org.apache.oozie.client.SLAEvent.SlaAppType; 039import org.apache.oozie.client.rest.RestConstants; 040import org.apache.oozie.command.CommandException; 041import org.apache.oozie.command.PreconditionException; 042import org.apache.oozie.command.RerunTransitionXCommand; 043import org.apache.oozie.command.bundle.BundleStatusUpdateXCommand; 044import org.apache.oozie.coord.CoordELFunctions; 045import org.apache.oozie.coord.CoordUtils; 046import org.apache.oozie.executor.jpa.BatchQueryExecutor; 047import org.apache.oozie.executor.jpa.BatchQueryExecutor.UpdateEntry; 048import org.apache.oozie.executor.jpa.CoordActionQueryExecutor.CoordActionQuery; 049import org.apache.oozie.executor.jpa.CoordJobQueryExecutor; 050import org.apache.oozie.executor.jpa.CoordJobQueryExecutor.CoordJobQuery; 051import org.apache.oozie.executor.jpa.JPAExecutorException; 052import org.apache.oozie.service.EventHandlerService; 053import org.apache.oozie.sla.SLAOperations; 054import org.apache.oozie.sla.service.SLAService; 055import org.apache.oozie.util.InstrumentUtils; 056import org.apache.oozie.util.LogUtils; 057import org.apache.oozie.util.ParamChecker; 058import org.apache.oozie.util.StatusUtils; 059import org.apache.oozie.util.XConfiguration; 060import org.apache.oozie.util.XLog; 061import org.apache.oozie.util.XmlUtils; 062import org.apache.oozie.util.db.SLADbOperations; 063import org.jdom.Element; 064import org.jdom.JDOMException; 065 066/** 067 * Rerun coordinator actions by a list of dates or ids. User can specify if refresh or noCleanup. 068 * <p/> 069 * The "rerunType" can be set as {@link RestConstants.JOB_COORD_RERUN_DATE} or 070 * {@link RestConstants.JOB_COORD_RERUN_ACTION}. 071 * <p/> 072 * The "refresh" is used to indicate if user wants to refresh an action's input and output events. 073 * <p/> 074 * The "noCleanup" is used to indicate if user wants to cleanup output events for given rerun actions 075 */ 076@SuppressWarnings("deprecation") 077public class CoordRerunXCommand extends RerunTransitionXCommand<CoordinatorActionInfo> { 078 079 private String rerunType; 080 private String scope; 081 private boolean refresh; 082 private boolean noCleanup; 083 private CoordinatorJobBean coordJob = null; 084 protected boolean prevPending; 085 private boolean failed; 086 087 /** 088 * The constructor for class {@link CoordRerunXCommand} 089 * 090 * @param jobId the job id 091 * @param rerunType rerun type {@link RestConstants.JOB_COORD_RERUN_DATE} or {@link RestConstants.JOB_COORD_RERUN_ACTION} 092 * @param scope the rerun scope for given rerunType separated by "," 093 * @param refresh true if user wants to refresh input/output dataset urls 094 * @param noCleanup false if user wants to cleanup output events for given rerun actions 095 */ 096 public CoordRerunXCommand(String jobId, String rerunType, String scope, boolean refresh, boolean noCleanup, 097 boolean failed) { 098 super("coord_rerun", "coord_rerun", 1); 099 this.jobId = ParamChecker.notEmpty(jobId, "jobId"); 100 this.rerunType = ParamChecker.notEmpty(rerunType, "rerunType"); 101 this.scope = ParamChecker.notEmpty(scope, "scope"); 102 this.refresh = refresh; 103 this.noCleanup = noCleanup; 104 this.failed = failed; 105 } 106 107 /** 108 * Check if all given actions are eligible to rerun. 109 * 110 * @param actions list of CoordinatorActionBean 111 * @return true if all actions are eligible to rerun 112 */ 113 private static boolean checkAllActionsRunnable(List<CoordinatorActionBean> coordActions) { 114 ParamChecker.notNull(coordActions, "Coord actions to be rerun"); 115 boolean ret = false; 116 for (CoordinatorActionBean coordAction : coordActions) { 117 ret = true; 118 if (!coordAction.isTerminalStatus()) { 119 ret = false; 120 break; 121 } 122 } 123 return ret; 124 } 125 126 /** 127 * Cleanup output-events directories 128 * 129 * @param eAction coordinator action xml 130 * @param user user name 131 * @param group group name 132 */ 133 @SuppressWarnings("unchecked") 134 private void cleanupOutputEvents(Element eAction, String user, String group) { 135 Element outputList = eAction.getChild("output-events", eAction.getNamespace()); 136 if (outputList != null) { 137 for (Element data : (List<Element>) outputList.getChildren("data-out", eAction.getNamespace())) { 138 String nocleanup = data.getAttributeValue("nocleanup"); 139 if (data.getChild("uris", data.getNamespace()) != null 140 && (nocleanup == null || !nocleanup.equals("true"))) { 141 String uris = data.getChild("uris", data.getNamespace()).getTextTrim(); 142 if (uris != null) { 143 String[] uriArr = uris.split(CoordELFunctions.INSTANCE_SEPARATOR); 144 FsActionExecutor fsAe = new FsActionExecutor(); 145 for (String uri : uriArr) { 146 Path path = new Path(uri); 147 try { 148 fsAe.delete(user, group, path); 149 LOG.debug("Cleanup the output dir " + path); 150 } 151 catch (ActionExecutorException ae) { 152 LOG.warn("Failed to cleanup the output dir " + uri, ae); 153 } 154 } 155 } 156 157 } 158 } 159 } 160 else { 161 LOG.info("No output-events defined in coordinator xml. Therefore nothing to cleanup"); 162 } 163 } 164 165 /** 166 * Refresh an action's input and ouput events. 167 * 168 * @param coordJob coordinator job bean 169 * @param coordAction coordinator action bean 170 * @throws Exception thrown if failed to materialize coordinator action 171 */ 172 private void refreshAction(CoordinatorJobBean coordJob, CoordinatorActionBean coordAction) throws Exception { 173 Configuration jobConf = null; 174 try { 175 jobConf = new XConfiguration(new StringReader(coordJob.getConf())); 176 } 177 catch (IOException ioe) { 178 LOG.warn("Configuration parse error. read from DB :" + coordJob.getConf(), ioe); 179 throw new CommandException(ErrorCode.E1005, ioe.getMessage(), ioe); 180 } 181 String jobXml = coordJob.getJobXml(); 182 Element eJob = XmlUtils.parseXml(jobXml); 183 Date actualTime = new Date(); 184 String actionXml = CoordCommandUtils.materializeOneInstance(jobId, dryrun, (Element) eJob.clone(), coordAction 185 .getNominalTime(), actualTime, coordAction.getActionNumber(), jobConf, coordAction); 186 LOG.debug("Refresh Action actionId=" + coordAction.getId() + ", actionXml=" 187 + XmlUtils.prettyPrint(actionXml).toString()); 188 coordAction.setActionXml(actionXml); 189 } 190 191 /** 192 * Update an action into database table 193 * 194 * @param coordJob coordinator job bean 195 * @param coordAction coordinator action bean 196 * @throws Exception thrown failed to update coordinator action bean or unable to write sla registration event 197 */ 198 private void updateAction(CoordinatorJobBean coordJob, CoordinatorActionBean coordAction) 199 throws Exception { 200 LOG.debug("updateAction for actionId=" + coordAction.getId()); 201 if (coordAction.getStatus() == CoordinatorAction.Status.TIMEDOUT) { 202 LOG.debug("Updating created time for TIMEDOUT action id =" + coordAction.getId()); 203 coordAction.setCreatedTime(new Date()); 204 } 205 coordAction.setStatus(CoordinatorAction.Status.WAITING); 206 if(!failed) { 207 coordAction.setExternalId(null); 208 } 209 coordAction.setExternalStatus(null); 210 coordAction.setRerunTime(new Date()); 211 coordAction.setLastModifiedTime(new Date()); 212 coordAction.setErrorCode(""); 213 coordAction.setErrorMessage(""); 214 updateList.add(new UpdateEntry<CoordActionQuery>(CoordActionQuery.UPDATE_COORD_ACTION_RERUN, coordAction)); 215 writeActionRegistration(coordAction.getActionXml(), coordAction, coordJob.getUser(), coordJob.getGroup()); 216 } 217 218 /** 219 * Create SLA RegistrationEvent 220 * 221 * @param actionXml action xml 222 * @param actionBean coordinator action bean 223 * @param user user name 224 * @param group group name 225 * @throws Exception thrown if unable to write sla registration event 226 */ 227 private void writeActionRegistration(String actionXml, CoordinatorActionBean actionBean, String user, String group) 228 throws Exception { 229 Element eAction = XmlUtils.parseXml(actionXml); 230 Element eSla = eAction.getChild("action", eAction.getNamespace()).getChild("info", eAction.getNamespace("sla")); 231 SLAEventBean slaEvent = SLADbOperations.createSlaRegistrationEvent(eSla, actionBean.getId(), 232 SlaAppType.COORDINATOR_ACTION, user, group, LOG); 233 if(slaEvent != null) { 234 insertList.add(slaEvent); 235 } 236 } 237 238 /* (non-Javadoc) 239 * @see org.apache.oozie.command.XCommand#getEntityKey() 240 */ 241 @Override 242 public String getEntityKey() { 243 return jobId; 244 } 245 246 /* (non-Javadoc) 247 * @see org.apache.oozie.command.XCommand#isLockRequired() 248 */ 249 @Override 250 protected boolean isLockRequired() { 251 return true; 252 } 253 254 /* (non-Javadoc) 255 * @see org.apache.oozie.command.XCommand#loadState() 256 */ 257 @Override 258 protected void loadState() throws CommandException { 259 try { 260 coordJob = CoordJobQueryExecutor.getInstance().get(CoordJobQuery.GET_COORD_JOB, jobId); 261 prevPending = coordJob.isPending(); 262 } 263 catch (JPAExecutorException je) { 264 throw new CommandException(je); 265 } 266 LogUtils.setLogInfo(coordJob); 267 } 268 269 /* (non-Javadoc) 270 * @see org.apache.oozie.command.XCommand#verifyPrecondition() 271 */ 272 @Override 273 protected void verifyPrecondition() throws CommandException, PreconditionException { 274 BundleStatusUpdateXCommand bundleStatusUpdate = new BundleStatusUpdateXCommand(coordJob, coordJob.getStatus()); 275 276 // no actions have been created for PREP job 277 if (coordJob.getStatus() == CoordinatorJob.Status.PREP || coordJob.getStatus() == CoordinatorJob.Status.IGNORED) { 278 LOG.info("CoordRerunXCommand is not able to run, job status=" + coordJob.getStatus() + ", jobid=" + jobId); 279 // Call the parent so the pending flag is reset and state transition 280 // of bundle can happen 281 if (coordJob.getBundleId() != null) { 282 bundleStatusUpdate.call(); 283 } 284 if (coordJob.getStatus() == CoordinatorJob.Status.PREP) { 285 throw new CommandException(ErrorCode.E1018, 286 "coordinator job is PREP so no actions are materialized to rerun!"); 287 } 288 else { 289 throw new CommandException(ErrorCode.E1018, 290 "coordinator job is IGNORED, please change it to RUNNING before rerunning actions"); 291 } 292 } 293 } 294 295 @Override 296 protected void eagerLoadState() throws CommandException { 297 try { 298 coordJob = CoordJobQueryExecutor.getInstance().get(CoordJobQuery.GET_COORD_JOB, jobId); 299 } 300 catch (JPAExecutorException e) { 301 throw new CommandException(e); 302 } 303 } 304 305 @Override 306 protected void eagerVerifyPrecondition() throws CommandException, PreconditionException { 307 verifyPrecondition(); 308 } 309 310 @Override 311 public void rerunChildren() throws CommandException { 312 boolean isError = false; 313 try { 314 CoordinatorActionInfo coordInfo = null; 315 InstrumentUtils.incrJobCounter(getName(), 1, getInstrumentation()); 316 List<CoordinatorActionBean> coordActions = CoordUtils.getCoordActions(rerunType, jobId, scope, false); 317 if (checkAllActionsRunnable(coordActions)) { 318 for (CoordinatorActionBean coordAction : coordActions) { 319 String actionXml = coordAction.getActionXml(); 320 if (!noCleanup) { 321 Element eAction = XmlUtils.parseXml(actionXml); 322 cleanupOutputEvents(eAction, coordJob.getUser(), coordJob.getGroup()); 323 } 324 if (refresh) { 325 refreshAction(coordJob, coordAction); 326 } 327 updateAction(coordJob, coordAction); 328 if (SLAService.isEnabled()) { 329 SLAOperations.updateRegistrationEvent(coordAction.getId()); 330 } 331 queue(new CoordActionNotificationXCommand(coordAction), 100); 332 queue(new CoordActionInputCheckXCommand(coordAction.getId(), coordAction.getJobId()), 100); 333 } 334 } 335 else { 336 isError = true; 337 throw new CommandException(ErrorCode.E1018, "part or all actions are not eligible to rerun!"); 338 } 339 coordInfo = new CoordinatorActionInfo(coordActions); 340 341 ret = coordInfo; 342 } 343 catch (XException xex) { 344 isError = true; 345 throw new CommandException(xex); 346 } 347 catch (JDOMException jex) { 348 isError = true; 349 throw new CommandException(ErrorCode.E0700, jex.getMessage(), jex); 350 } 351 catch (Exception ex) { 352 isError = true; 353 throw new CommandException(ErrorCode.E1018, ex.getMessage(), ex); 354 } 355 finally{ 356 if(isError){ 357 transitToPrevious(); 358 } 359 } 360 } 361 362 /* 363 * (non-Javadoc) 364 * @see org.apache.oozie.command.TransitionXCommand#getJob() 365 */ 366 @Override 367 public Job getJob() { 368 return coordJob; 369 } 370 371 @Override 372 public void notifyParent() throws CommandException { 373 //update bundle action 374 if (getPrevStatus() != null && coordJob.getBundleId() != null) { 375 BundleStatusUpdateXCommand bundleStatusUpdate = new BundleStatusUpdateXCommand(coordJob, getPrevStatus()); 376 bundleStatusUpdate.call(); 377 } 378 } 379 380 @Override 381 public void updateJob() { 382 if (getPrevStatus()!= null){ 383 Job.Status coordJobStatus = getPrevStatus(); 384 if(coordJobStatus.equals(Job.Status.PAUSED) || coordJobStatus.equals(Job.Status.PAUSEDWITHERROR)) { 385 coordJob.setStatus(coordJobStatus); 386 } 387 if (prevPending) { 388 coordJob.setPending(); 389 } else { 390 coordJob.resetPending(); 391 } 392 } 393 updateList.add(new UpdateEntry<CoordJobQuery>(CoordJobQuery.UPDATE_COORD_JOB_STATUS_PENDING, coordJob)); 394 } 395 396 /* (non-Javadoc) 397 * @see org.apache.oozie.command.RerunTransitionXCommand#performWrites() 398 */ 399 @Override 400 public void performWrites() throws CommandException { 401 try { 402 BatchQueryExecutor.getInstance().executeBatchInsertUpdateDelete(insertList, updateList, null); 403 if (EventHandlerService.isEnabled()) { 404 generateEvents(coordJob, null); 405 } 406 } 407 catch (JPAExecutorException e) { 408 throw new CommandException(e); 409 } 410 } 411 412 /* (non-Javadoc) 413 * @see org.apache.oozie.command.RerunTransitionXCommand#getLog() 414 */ 415 @Override 416 public XLog getLog() { 417 return LOG; 418 } 419 420 @Override 421 public final void transitToNext() { 422 prevStatus = coordJob.getStatus(); 423 if (prevStatus == CoordinatorJob.Status.SUCCEEDED || prevStatus == CoordinatorJob.Status.PAUSED 424 || prevStatus == CoordinatorJob.Status.SUSPENDED || prevStatus == CoordinatorJob.Status.RUNNING) { 425 coordJob.setStatus(Job.Status.RUNNING); 426 } 427 else { 428 // Check for backward compatibility for Oozie versions (3.2 and before) 429 // when RUNNINGWITHERROR, SUSPENDEDWITHERROR and 430 // PAUSEDWITHERROR is not supported 431 coordJob.setStatus(StatusUtils.getStatusIfBackwardSupportTrue(CoordinatorJob.Status.RUNNINGWITHERROR)); 432 } 433 // used for backward support of coordinator 0.1 schema 434 coordJob.setStatus(StatusUtils.getStatusForCoordRerun(coordJob, prevStatus)); 435 coordJob.setPending(); 436 } 437 438 private final void transitToPrevious() throws CommandException { 439 coordJob.setStatus(getPrevStatus()); 440 if (!prevPending) { 441 coordJob.resetPending(); 442 } 443 else { 444 coordJob.setPending(); 445 } 446 } 447}