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.net.URI; 024import java.util.Date; 025import java.util.List; 026 027import org.apache.commons.lang.StringUtils; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.oozie.CoordinatorActionBean; 030import org.apache.oozie.CoordinatorJobBean; 031import org.apache.oozie.ErrorCode; 032import org.apache.oozie.client.CoordinatorAction; 033import org.apache.oozie.client.Job; 034import org.apache.oozie.client.OozieClient; 035import org.apache.oozie.command.CommandException; 036import org.apache.oozie.command.PreconditionException; 037import org.apache.oozie.coord.ElException; 038import org.apache.oozie.coord.input.dependency.CoordInputDependency; 039import org.apache.oozie.dependency.ActionDependency; 040import org.apache.oozie.dependency.DependencyChecker; 041import org.apache.oozie.dependency.URIHandler; 042import org.apache.oozie.executor.jpa.CoordActionGetForInputCheckJPAExecutor; 043import org.apache.oozie.executor.jpa.CoordActionQueryExecutor; 044import org.apache.oozie.executor.jpa.CoordActionQueryExecutor.CoordActionQuery; 045import org.apache.oozie.executor.jpa.CoordJobGetJPAExecutor; 046import org.apache.oozie.executor.jpa.JPAExecutorException; 047import org.apache.oozie.service.CallableQueueService; 048import org.apache.oozie.service.ConfigurationService; 049import org.apache.oozie.service.EventHandlerService; 050import org.apache.oozie.service.JPAService; 051import org.apache.oozie.service.PartitionDependencyManagerService; 052import org.apache.oozie.service.RecoveryService; 053import org.apache.oozie.service.Service; 054import org.apache.oozie.service.Services; 055import org.apache.oozie.service.URIHandlerService; 056import org.apache.oozie.util.LogUtils; 057import org.apache.oozie.util.StatusUtils; 058import org.apache.oozie.util.XConfiguration; 059import org.apache.oozie.util.XLog; 060import org.apache.oozie.util.DateUtils; 061 062public class CoordPushDependencyCheckXCommand extends CoordinatorXCommand<Void> { 063 protected String actionId; 064 protected JPAService jpaService = null; 065 protected CoordinatorActionBean coordAction = null; 066 protected CoordinatorJobBean coordJob = null; 067 068 /** 069 * Property name of command re-queue interval for coordinator push check in 070 * milliseconds. 071 */ 072 public static final String CONF_COORD_PUSH_CHECK_REQUEUE_INTERVAL = Service.CONF_PREFIX 073 + "coord.push.check.requeue.interval"; 074 private boolean registerForNotification; 075 private boolean removeAvailDependencies; 076 077 public CoordPushDependencyCheckXCommand(String actionId) { 078 this(actionId, false, true); 079 } 080 081 public CoordPushDependencyCheckXCommand(String actionId, boolean registerForNotification) { 082 this(actionId, registerForNotification, !registerForNotification); 083 } 084 085 public CoordPushDependencyCheckXCommand(String actionId, boolean registerForNotification, 086 boolean removeAvailDependencies) { 087 super("coord_push_dep_check", "coord_push_dep_check", 0); 088 this.actionId = actionId; 089 this.registerForNotification = registerForNotification; 090 this.removeAvailDependencies = removeAvailDependencies; 091 } 092 093 protected CoordPushDependencyCheckXCommand(String actionName, String actionId) { 094 super(actionName, actionName, 0); 095 this.actionId = actionId; 096 } 097 098 @Override 099 protected void setLogInfo() { 100 LogUtils.setLogInfo(actionId); 101 } 102 103 @Override 104 protected Void execute() throws CommandException { 105 // this action should only get processed if current time > nominal time; 106 // otherwise, requeue this action for delay execution; 107 Date nominalTime = coordAction.getNominalTime(); 108 Date currentTime = new Date(); 109 if (nominalTime.compareTo(currentTime) > 0) { 110 queue(new CoordPushDependencyCheckXCommand(coordAction.getId(), true), nominalTime.getTime() - currentTime.getTime()); 111 updateCoordAction(coordAction, false); 112 LOG.info("[" + actionId 113 + "]::CoordPushDependency:: nominal Time is newer than current time, so requeue and wait. Current=" 114 + DateUtils.formatDateOozieTZ(currentTime) + ", nominal=" + DateUtils.formatDateOozieTZ(nominalTime)); 115 return null; 116 } 117 118 CoordInputDependency coordPushInputDependency = coordAction.getPushInputDependencies(); 119 CoordInputDependency coordPullInputDependency = coordAction.getPullInputDependencies(); 120 if (coordPushInputDependency.getMissingDependenciesAsList().size() == 0) { 121 LOG.info("Nothing to check. Empty push missing dependency"); 122 } 123 else { 124 List<String> missingDependenciesArray = coordPushInputDependency.getMissingDependenciesAsList(); 125 LOG.info("First Push missing dependency is [{0}] ", missingDependenciesArray.get(0)); 126 LOG.trace("Push missing dependencies are [{0}] ", missingDependenciesArray); 127 if (registerForNotification) { 128 LOG.debug("Register for notifications is true"); 129 } 130 131 try { 132 Configuration actionConf = null; 133 try { 134 actionConf = new XConfiguration(new StringReader(coordAction.getRunConf())); 135 } 136 catch (IOException e) { 137 throw new CommandException(ErrorCode.E1307, e.getMessage(), e); 138 } 139 140 141 boolean isChangeInDependency = true; 142 boolean timeout = false; 143 ActionDependency actionDependency = coordPushInputDependency.checkPushMissingDependencies(coordAction, 144 registerForNotification); 145 // Check all dependencies during materialization to avoid registering in the cache. 146 // But check only first missing one afterwards similar to 147 // CoordActionInputCheckXCommand for efficiency. listPartitions is costly. 148 if (actionDependency.getMissingDependencies().size() == missingDependenciesArray.size()) { 149 isChangeInDependency = false; 150 } 151 else { 152 String stillMissingDeps = DependencyChecker.dependenciesAsString(actionDependency.getMissingDependencies()); 153 coordPushInputDependency.setMissingDependencies(stillMissingDeps); 154 } 155 156 if (coordPushInputDependency.isDependencyMet()) { 157 // All push-based dependencies are available 158 onAllPushDependenciesAvailable(coordPullInputDependency.isDependencyMet()); 159 } 160 else { 161 // Checking for timeout 162 timeout = isTimeout(); 163 if (timeout) { 164 queue(new CoordActionTimeOutXCommand(coordAction, coordJob.getUser(), coordJob.getAppName())); 165 } 166 else { 167 queue(new CoordPushDependencyCheckXCommand(coordAction.getId()), 168 getCoordPushCheckRequeueInterval()); 169 } 170 } 171 172 updateCoordAction(coordAction, isChangeInDependency || coordPushInputDependency.isDependencyMet()); 173 if (registerForNotification) { 174 registerForNotification(coordPushInputDependency.getMissingDependenciesAsList(), actionConf); 175 } 176 if (removeAvailDependencies) { 177 unregisterAvailableDependencies(actionDependency.getAvailableDependencies()); 178 } 179 if (timeout) { 180 unregisterMissingDependencies(coordPushInputDependency.getMissingDependenciesAsList(), actionId); 181 } 182 } 183 catch (Exception e) { 184 final CallableQueueService callableQueueService = Services.get().get(CallableQueueService.class); 185 if (isTimeout()) { 186 LOG.debug("Queueing timeout command"); 187 // XCommand.queue() will not work when there is a Exception 188 callableQueueService.queue(new CoordActionTimeOutXCommand(coordAction, coordJob.getUser(), 189 coordJob.getAppName())); 190 unregisterMissingDependencies(missingDependenciesArray, actionId); 191 } 192 else if (coordPullInputDependency.getMissingDependenciesAsList().size() > 0) { 193 // Queue again on exception as RecoveryService will not queue this again with 194 // the action being updated regularly by CoordActionInputCheckXCommand 195 callableQueueService.queue(new CoordPushDependencyCheckXCommand(coordAction.getId(), 196 registerForNotification, removeAvailDependencies), 197 Services.get().getConf().getInt(RecoveryService.CONF_COORD_OLDER_THAN, 600) * 1000); 198 } 199 throw new CommandException(ErrorCode.E1021, e.getMessage(), e); 200 } 201 } 202 return null; 203 } 204 205 /** 206 * Return the re-queue interval for coord push dependency check 207 * @return requeueInterval returns the requeue interval for coord push dependency check 208 */ 209 public long getCoordPushCheckRequeueInterval() { 210 long requeueInterval = ConfigurationService.getLong(CONF_COORD_PUSH_CHECK_REQUEUE_INTERVAL); 211 return requeueInterval; 212 } 213 214 /** 215 * Returns true if timeout period has been reached 216 * 217 * @return true if it is time for timeout else false 218 */ 219 protected boolean isTimeout() { 220 long waitingTime = (new Date().getTime() - Math.max(coordAction.getNominalTime().getTime(), coordAction 221 .getCreatedTime().getTime())) 222 / (60 * 1000); 223 int timeOut = coordAction.getTimeOut(); 224 return (timeOut >= 0) && (waitingTime > timeOut); 225 } 226 227 protected void onAllPushDependenciesAvailable(boolean isPullDependencyMeet) throws CommandException { 228 Services.get().get(PartitionDependencyManagerService.class) 229 .removeCoordActionWithDependenciesAvailable(coordAction.getId()); 230 if (isPullDependencyMeet) { 231 Date nominalTime = coordAction.getNominalTime(); 232 Date currentTime = new Date(); 233 // The action should become READY only if current time > nominal time; 234 // CoordActionInputCheckXCommand will take care of moving it to READY when it is nominal time. 235 if (nominalTime.compareTo(currentTime) > 0) { 236 LOG.info("[" + actionId + "]::ActionInputCheck:: nominal Time is newer than current time. Current=" 237 + DateUtils.formatDateOozieTZ(currentTime) + ", nominal=" 238 + DateUtils.formatDateOozieTZ(nominalTime)); 239 } 240 else { 241 String actionXml = resolveCoordConfiguration(); 242 coordAction.setActionXml(actionXml); 243 coordAction.setStatus(CoordinatorAction.Status.READY); 244 // pass jobID to the CoordActionReadyXCommand 245 queue(new CoordActionReadyXCommand(coordAction.getJobId()), 100); 246 } 247 } 248 else if (isTimeout()) { 249 // If it is timeout and all push dependencies are available but still some unresolved 250 // missing dependencies queue CoordActionInputCheckXCommand now. Else it will have to 251 // wait till RecoveryService kicks in 252 queue(new CoordActionInputCheckXCommand(coordAction.getId(), coordAction.getJobId())); 253 } 254 coordAction.getPushInputDependencies().setDependencyMet(true); 255 256 } 257 258 private String resolveCoordConfiguration() throws CommandException { 259 try { 260 Configuration actionConf = new XConfiguration(new StringReader(coordAction.getRunConf())); 261 StringBuilder actionXml = new StringBuilder(coordAction.getActionXml()); 262 String newActionXml = CoordActionInputCheckXCommand.resolveCoordConfiguration(actionXml, actionConf, 263 actionId, coordAction.getPullInputDependencies(), coordAction 264 .getPushInputDependencies()); 265 actionXml.replace(0, actionXml.length(), newActionXml); 266 return actionXml.toString(); 267 } 268 catch (ElException e) { 269 coordAction.setStatus(CoordinatorAction.Status.FAILED); 270 updateCoordAction(coordAction, true); 271 throw new CommandException(ErrorCode.E1021, e.getMessage(), e); 272 } 273 catch (Exception e) { 274 throw new CommandException(ErrorCode.E1021, e.getMessage(), e); 275 } 276 } 277 278 protected void updateCoordAction(CoordinatorActionBean coordAction, boolean isChangeInDependency) 279 throws CommandException { 280 coordAction.setLastModifiedTime(new Date()); 281 if (jpaService != null) { 282 try { 283 if (isChangeInDependency) { 284 coordAction.setPushMissingDependencies(coordAction.getPushInputDependencies().serialize()); 285 CoordActionQueryExecutor.getInstance().executeUpdate( 286 CoordActionQuery.UPDATE_COORD_ACTION_FOR_PUSH_INPUTCHECK, coordAction); 287 if (EventHandlerService.isEnabled() && coordAction.getStatus() != CoordinatorAction.Status.READY) { 288 // since event is not to be generated unless action 289 // RUNNING via StartX 290 generateEvent(coordAction, coordJob.getUser(), coordJob.getAppName(), null); 291 } 292 } 293 else { 294 CoordActionQueryExecutor.getInstance().executeUpdate( 295 CoordActionQuery.UPDATE_COORD_ACTION_FOR_MODIFIED_DATE, coordAction); 296 } 297 } 298 catch (JPAExecutorException jex) { 299 throw new CommandException(ErrorCode.E1021, jex.getMessage(), jex); 300 } 301 catch (IOException ioe) { 302 throw new CommandException(ErrorCode.E1021, ioe.getMessage(), ioe); 303 } 304 } 305 } 306 307 private void registerForNotification(List<String> missingDeps, Configuration actionConf) { 308 URIHandlerService uriService = Services.get().get(URIHandlerService.class); 309 String user = actionConf.get(OozieClient.USER_NAME, OozieClient.USER_NAME); 310 for (String missingDep : missingDeps) { 311 try { 312 URI missingURI = new URI(missingDep); 313 URIHandler handler = uriService.getURIHandler(missingURI); 314 handler.registerForNotification(missingURI, actionConf, user, actionId); 315 LOG.debug("Registered uri [{0}] for notifications", missingURI); 316 } 317 catch (Exception e) { 318 LOG.warn("Exception while registering uri [{0}] for notifications", missingDep, e); 319 } 320 } 321 } 322 323 private void unregisterAvailableDependencies(List<String> availableDeps) { 324 URIHandlerService uriService = Services.get().get(URIHandlerService.class); 325 for (String availableDep : availableDeps) { 326 try { 327 URI availableURI = new URI(availableDep); 328 URIHandler handler = uriService.getURIHandler(availableURI); 329 if (handler.unregisterFromNotification(availableURI, actionId)) { 330 LOG.debug("Successfully unregistered uri [{0}] from notifications", availableURI); 331 } 332 else { 333 LOG.warn("Unable to unregister uri [{0}] from notifications", availableURI); 334 } 335 } 336 catch (Exception e) { 337 LOG.warn("Exception while unregistering uri [{0}] from notifications", availableDep, e); 338 } 339 } 340 } 341 342 public static void unregisterMissingDependencies(List<String> missingDeps, String actionId) { 343 final XLog LOG = XLog.getLog(CoordPushDependencyCheckXCommand.class); 344 URIHandlerService uriService = Services.get().get(URIHandlerService.class); 345 for (String missingDep : missingDeps) { 346 try { 347 URI missingURI = new URI(missingDep); 348 URIHandler handler = uriService.getURIHandler(missingURI); 349 if (handler.unregisterFromNotification(missingURI, actionId)) { 350 LOG.debug("Successfully unregistered uri [{0}] from notifications", missingURI); 351 } 352 else { 353 LOG.warn("Unable to unregister uri [{0}] from notifications", missingURI); 354 } 355 } 356 catch (Exception e) { 357 LOG.warn("Exception while unregistering uri [{0}] from notifications", missingDep, e); 358 } 359 } 360 } 361 362 @Override 363 public String getEntityKey() { 364 return actionId.substring(0, actionId.indexOf("@")); 365 } 366 367 @Override 368 public String getKey(){ 369 return getName() + "_" + actionId; 370 } 371 372 @Override 373 protected boolean isLockRequired() { 374 return true; 375 } 376 377 @Override 378 protected void loadState() throws CommandException { 379 jpaService = Services.get().get(JPAService.class); 380 try { 381 coordAction = jpaService.execute(new CoordActionGetForInputCheckJPAExecutor(actionId)); 382 if (coordAction != null) { 383 coordJob = jpaService.execute(new CoordJobGetJPAExecutor(coordAction.getJobId())); 384 LogUtils.setLogInfo(coordAction); 385 } 386 else { 387 throw new CommandException(ErrorCode.E0605, actionId); 388 } 389 } 390 catch (JPAExecutorException je) { 391 throw new CommandException(je); 392 } 393 } 394 395 @Override 396 protected void verifyPrecondition() throws CommandException, PreconditionException { 397 if (coordAction.getStatus() != CoordinatorActionBean.Status.WAITING) { 398 throw new PreconditionException(ErrorCode.E1100, "[" + actionId 399 + "]::CoordPushDependencyCheck:: Ignoring action. Should be in WAITING state, but state=" 400 + coordAction.getStatus()); 401 } 402 403 // if eligible to do action input check when running with backward 404 // support is true 405 if (StatusUtils.getStatusForCoordActionInputCheck(coordJob)) { 406 return; 407 } 408 409 if (coordJob.getStatus() != Job.Status.RUNNING && coordJob.getStatus() != Job.Status.RUNNINGWITHERROR 410 && coordJob.getStatus() != Job.Status.PAUSED && coordJob.getStatus() != Job.Status.PAUSEDWITHERROR) { 411 throw new PreconditionException(ErrorCode.E1100, "[" + actionId 412 + "]::CoordPushDependencyCheck:: Ignoring action." 413 + " Coordinator job is not in RUNNING/RUNNINGWITHERROR/PAUSED/PAUSEDWITHERROR state, but state=" 414 + coordJob.getStatus()); 415 } 416 } 417 418}