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.text.ParseException; 022import java.util.Calendar; 023import java.util.Date; 024import java.util.Iterator; 025import java.util.List; 026 027import org.apache.oozie.CoordinatorActionBean; 028import org.apache.oozie.CoordinatorJobBean; 029import org.apache.oozie.ErrorCode; 030import org.apache.oozie.client.CoordinatorAction; 031import org.apache.oozie.client.CoordinatorJob; 032import org.apache.oozie.client.Job; 033import org.apache.oozie.command.CommandException; 034import org.apache.oozie.command.PreconditionException; 035import org.apache.oozie.executor.jpa.CoordActionQueryExecutor; 036import org.apache.oozie.executor.jpa.CoordActionQueryExecutor.CoordActionQuery; 037import org.apache.oozie.executor.jpa.CoordJobGetReadyActionsJPAExecutor; 038import org.apache.oozie.executor.jpa.CoordJobGetRunningActionsCountJPAExecutor; 039import org.apache.oozie.executor.jpa.CoordJobQueryExecutor; 040import org.apache.oozie.executor.jpa.CoordJobQueryExecutor.CoordJobQuery; 041import org.apache.oozie.executor.jpa.JPAExecutorException; 042import org.apache.oozie.service.ConfigurationService; 043import org.apache.oozie.service.JPAService; 044import org.apache.oozie.service.Services; 045import org.apache.oozie.util.DateUtils; 046import org.apache.oozie.util.LogUtils; 047import org.apache.oozie.util.XLog; 048import org.jdom.JDOMException; 049 050public class CoordActionReadyXCommand extends CoordinatorXCommand<Void> { 051 private final String jobId; 052 private final XLog log = getLog(); 053 private CoordinatorJobBean coordJob = null; 054 private JPAService jpaService = null; 055 056 public CoordActionReadyXCommand(String id) { 057 super("coord_action_ready", "coord_action_ready", 1); 058 this.jobId = id; 059 } 060 061 @Override 062 protected void setLogInfo() { 063 LogUtils.setLogInfo(jobId); 064 } 065 066 @Override 067 /** 068 * Check for READY actions and change state to SUBMITTED by a command to submit the job to WF engine. 069 * This method checks all the actions associated with a jobId to figure out which actions 070 * to start (based on concurrency and execution order [FIFO, LIFO, LAST_ONLY, NONE]) 071 * 072 */ 073 protected Void execute() throws CommandException { 074 // number of actions to start (-1 means start ALL) 075 int numActionsToStart = -1; 076 077 // get execution setting for this job (FIFO, LIFO, LAST_ONLY) 078 CoordinatorJob.Execution jobExecution = coordJob.getExecutionOrder(); 079 // get concurrency setting for this job 080 int jobConcurrency = coordJob.getConcurrency(); 081 // if less than 0, then UNLIMITED concurrency 082 if (jobConcurrency >= 0) { 083 // count number of actions that are already RUNNING or SUBMITTED 084 // subtract from CONCURRENCY to calculate number of actions to start 085 // in WF engine 086 087 int numRunningJobs; 088 try { 089 numRunningJobs = jpaService.execute(new CoordJobGetRunningActionsCountJPAExecutor(jobId)); 090 } 091 catch (JPAExecutorException je) { 092 throw new CommandException(je); 093 } 094 095 numActionsToStart = jobConcurrency - numRunningJobs; 096 if (numActionsToStart < 0) { 097 numActionsToStart = 0; 098 } 099 log.debug("concurrency=" + jobConcurrency + ", execution=" + jobExecution + ", numRunningJobs=" 100 + numRunningJobs + ", numLeftover=" + numActionsToStart); 101 // no actions to start 102 if (numActionsToStart == 0) { 103 log.info("Not starting any additional actions because max concurrency [{0}]" + 104 " for coordinator [{1}] has been reached.", jobConcurrency, jobId); 105 } 106 } 107 // get list of actions that are READY and fit in the concurrency and execution 108 109 List<CoordinatorActionBean> actions; 110 try { 111 actions = jpaService.execute(new CoordJobGetReadyActionsJPAExecutor(jobId, jobExecution.name())); 112 } 113 catch (JPAExecutorException je) { 114 throw new CommandException(je); 115 } 116 log.debug("Number of READY actions = " + actions.size()); 117 Date now = new Date(); 118 // If we're using LAST_ONLY or NONE, we should check if any of these need to be SKIPPED instead of SUBMITTED 119 if (jobExecution.equals(CoordinatorJobBean.Execution.LAST_ONLY)) { 120 for (Iterator<CoordinatorActionBean> it = actions.iterator(); it.hasNext(); ) { 121 CoordinatorActionBean action = it.next(); 122 try { 123 Date nextNominalTime = CoordCommandUtils.computeNextNominalTime(coordJob, action); 124 if (nextNominalTime != null) { 125 // If the current time is after the next action's nominal time, then we've passed the window where this 126 // action should be started; so set it to SKIPPED 127 if (now.after(nextNominalTime)) { 128 LOG.info("LAST_ONLY execution: Preparing to skip action [{0}] because the current time [{1}] is later " 129 + "than the nominal time [{2}] of the next action]", action.getId(), 130 DateUtils.formatDateOozieTZ(now), DateUtils.formatDateOozieTZ(nextNominalTime)); 131 queue(new CoordActionSkipXCommand(action, coordJob.getUser(), coordJob.getAppName())); 132 it.remove(); 133 } else { 134 LOG.debug("LAST_ONLY execution: Not skipping action [{0}] because the current time [{1}] is earlier " 135 + "than the nominal time [{2}] of the next action]", action.getId(), 136 DateUtils.formatDateOozieTZ(now), DateUtils.formatDateOozieTZ(nextNominalTime)); 137 } 138 } 139 } catch (ParseException e) { 140 LOG.error("Should not happen", e); 141 } catch (JDOMException e) { 142 LOG.error("Should not happen", e); 143 } 144 } 145 } 146 else if (jobExecution.equals(CoordinatorJobBean.Execution.NONE)) { 147 for (Iterator<CoordinatorActionBean> it = actions.iterator(); it.hasNext(); ) { 148 CoordinatorActionBean action = it.next(); 149 // If the current time is after the nominal time of this action plus some tolerance, 150 // then we've passed the window where this action should be started; so set it to SKIPPED 151 Calendar cal = Calendar.getInstance(DateUtils.getTimeZone(coordJob.getTimeZone())); 152 cal.setTime(action.getNominalTime()); 153 int tolerance = ConfigurationService.getInt(CoordActionInputCheckXCommand.COORD_EXECUTION_NONE_TOLERANCE); 154 cal.add(Calendar.MINUTE, tolerance); 155 if (now.after(cal.getTime())) { 156 LOG.info("NONE execution: Preparing to skip action [{0}] because the current time [{1}] is more than [{2}]" 157 + " minutes later than the nominal time [{3}] of the current action]", action.getId(), 158 DateUtils.formatDateOozieTZ(now), tolerance, DateUtils.formatDateOozieTZ(action.getNominalTime())); 159 queue(new CoordActionSkipXCommand(action, coordJob.getUser(), coordJob.getAppName())); 160 it.remove(); 161 } else { 162 LOG.debug("NONE execution: Not skipping action [{0}] because the current time [{1}] is earlier than [{2}]" 163 + " minutes later than the nominal time [{3}] of the current action]", action.getId(), 164 DateUtils.formatDateOozieTZ(now), tolerance, DateUtils.formatDateOozieTZ(action.getNominalTime())); 165 } 166 } 167 } 168 169 int counter = 0; 170 for (CoordinatorActionBean action : actions) { 171 // continue if numActionsToStart is negative (no limit on number of 172 // actions), or if the counter is less than numActionsToStart 173 if ((numActionsToStart < 0) || (counter < numActionsToStart)) { 174 log.debug("Set status to SUBMITTED for id: " + action.getId()); 175 // change state of action to SUBMITTED 176 action.setStatus(CoordinatorAction.Status.SUBMITTED); 177 try { 178 CoordActionQueryExecutor.getInstance().executeUpdate( 179 CoordActionQuery.UPDATE_COORD_ACTION_STATUS_PENDING_TIME, action); 180 } 181 catch (JPAExecutorException je) { 182 throw new CommandException(je); 183 } 184 // start action 185 new CoordActionStartXCommand(action.getId(), coordJob.getUser(), coordJob.getAppName(), 186 action.getJobId()).call(); 187 } 188 else { 189 break; 190 } 191 counter++; 192 193 } 194 return null; 195 } 196 197 @Override 198 public String getEntityKey() { 199 return jobId; 200 } 201 202 @Override 203 public String getKey() { 204 return getName() + "_" + jobId; 205 } 206 207 @Override 208 protected boolean isLockRequired() { 209 return true; 210 } 211 212 @Override 213 protected void loadState() throws CommandException { 214 jpaService = Services.get().get(JPAService.class); 215 if (jpaService == null) { 216 throw new CommandException(ErrorCode.E0610); 217 } 218 try { 219 coordJob = CoordJobQueryExecutor.getInstance().get(CoordJobQuery.GET_COORD_JOB_ACTION_READY, jobId); 220 } 221 catch (JPAExecutorException e) { 222 throw new CommandException(e); 223 } 224 LogUtils.setLogInfo(coordJob); 225 } 226 227 @Override 228 protected void verifyPrecondition() throws CommandException, PreconditionException { 229 if (coordJob.getStatus() != Job.Status.RUNNING && coordJob.getStatus() != Job.Status.RUNNINGWITHERROR 230 && coordJob.getStatus() != Job.Status.SUCCEEDED && coordJob.getStatus() != Job.Status.PAUSED 231 && coordJob.getStatus() != Job.Status.PAUSEDWITHERROR) { 232 throw new PreconditionException(ErrorCode.E1100, "[" + jobId 233 + "]::CoordActionReady:: Ignoring job. Coordinator job is not in RUNNING state, but state=" 234 + coordJob.getStatus()); 235 } 236 } 237}