001 package org.apache.oozie.command.coord; 002 003 import java.util.Date; 004 import java.util.HashSet; 005 import java.util.Map; 006 import java.util.Set; 007 import java.util.Map.Entry; 008 009 import org.apache.oozie.CoordinatorActionBean; 010 import org.apache.oozie.CoordinatorJobBean; 011 import org.apache.oozie.ErrorCode; 012 import org.apache.oozie.XException; 013 import org.apache.oozie.client.CoordinatorJob; 014 import org.apache.oozie.client.Job; 015 import org.apache.oozie.client.OozieClient; 016 import org.apache.oozie.command.CommandException; 017 import org.apache.oozie.command.PreconditionException; 018 import org.apache.oozie.command.bundle.BundleStatusUpdateXCommand; 019 import org.apache.oozie.executor.jpa.CoordActionRemoveJPAExecutor; 020 import org.apache.oozie.executor.jpa.CoordJobGetActionByActionNumberJPAExecutor; 021 import org.apache.oozie.executor.jpa.CoordJobGetJPAExecutor; 022 import org.apache.oozie.executor.jpa.CoordJobUpdateJPAExecutor; 023 import org.apache.oozie.executor.jpa.JPAExecutorException; 024 import org.apache.oozie.service.JPAService; 025 import org.apache.oozie.service.Services; 026 import org.apache.oozie.util.DateUtils; 027 import org.apache.oozie.util.JobUtils; 028 import org.apache.oozie.util.LogUtils; 029 import org.apache.oozie.util.ParamChecker; 030 031 public class CoordChangeXCommand extends CoordinatorXCommand<Void> { 032 private final String jobId; 033 private Date newEndTime = null; 034 private Integer newConcurrency = null; 035 private Date newPauseTime = null; 036 private Date oldPauseTime = null; 037 private boolean resetPauseTime = false; 038 private CoordinatorJobBean coordJob; 039 private JPAService jpaService = null; 040 private Job.Status prevStatus; 041 042 private static final Set<String> ALLOWED_CHANGE_OPTIONS = new HashSet<String>(); 043 static { 044 ALLOWED_CHANGE_OPTIONS.add("endtime"); 045 ALLOWED_CHANGE_OPTIONS.add("concurrency"); 046 ALLOWED_CHANGE_OPTIONS.add("pausetime"); 047 } 048 049 /** 050 * This command is used to update the Coordinator job with the new values Update the coordinator job bean and update 051 * that to database. 052 * 053 * @param id Coordinator job id. 054 * @param changeValue This the changed value in the form key=value. 055 * @throws CommandException thrown if changeValue cannot be parsed properly. 056 */ 057 public CoordChangeXCommand(String id, String changeValue) throws CommandException { 058 super("coord_change", "coord_change", 0); 059 this.jobId = ParamChecker.notEmpty(id, "id"); 060 ParamChecker.notEmpty(changeValue, "value"); 061 062 validateChangeValue(changeValue); 063 } 064 065 /** 066 * @param changeValue change value. 067 * @throws CommandException thrown if changeValue cannot be parsed properly. 068 */ 069 private void validateChangeValue(String changeValue) throws CommandException { 070 Map<String, String> map = JobUtils.parseChangeValue(changeValue); 071 072 if (map.size() > ALLOWED_CHANGE_OPTIONS.size()) { 073 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 074 } 075 076 java.util.Iterator<Entry<String, String>> iter = map.entrySet().iterator(); 077 while (iter.hasNext()) { 078 Entry<String, String> entry = iter.next(); 079 String key = entry.getKey(); 080 String value = entry.getValue(); 081 082 if (!ALLOWED_CHANGE_OPTIONS.contains(key)) { 083 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 084 } 085 086 if (!key.equals(OozieClient.CHANGE_VALUE_PAUSETIME) && value.equalsIgnoreCase("")) { 087 throw new CommandException(ErrorCode.E1015, changeValue, "value on " + key + " can not be empty"); 088 } 089 } 090 091 if (map.containsKey(OozieClient.CHANGE_VALUE_ENDTIME)) { 092 String value = map.get(OozieClient.CHANGE_VALUE_ENDTIME); 093 try { 094 newEndTime = DateUtils.parseDateUTC(value); 095 } 096 catch (Exception ex) { 097 throw new CommandException(ErrorCode.E1015, value, "must be a valid date"); 098 } 099 } 100 101 if (map.containsKey(OozieClient.CHANGE_VALUE_CONCURRENCY)) { 102 String value = map.get(OozieClient.CHANGE_VALUE_CONCURRENCY); 103 try { 104 newConcurrency = Integer.parseInt(value); 105 } 106 catch (NumberFormatException ex) { 107 throw new CommandException(ErrorCode.E1015, value, "must be a valid integer"); 108 } 109 } 110 111 if (map.containsKey(OozieClient.CHANGE_VALUE_PAUSETIME)) { 112 String value = map.get(OozieClient.CHANGE_VALUE_PAUSETIME); 113 if (value.equals("")) { // this is to reset pause time to null; 114 resetPauseTime = true; 115 } 116 else { 117 try { 118 newPauseTime = DateUtils.parseDateUTC(value); 119 } 120 catch (Exception ex) { 121 throw new CommandException(ErrorCode.E1015, value, "must be a valid date"); 122 } 123 } 124 } 125 } 126 127 /** 128 * Check if new end time is valid. 129 * 130 * @param coordJob coordinator job id. 131 * @param newEndTime new end time. 132 * @throws CommandException thrown if new end time is not valid. 133 */ 134 private void checkEndTime(CoordinatorJobBean coordJob, Date newEndTime) throws CommandException { 135 // New endTime cannot be before coordinator job's start time. 136 Date startTime = coordJob.getStartTime(); 137 if (newEndTime.before(startTime)) { 138 throw new CommandException(ErrorCode.E1015, newEndTime, "cannot be before coordinator job's start time [" 139 + startTime + "]"); 140 } 141 142 // New endTime cannot be before coordinator job's last action time. 143 Date lastActionTime = coordJob.getLastActionTime(); 144 if (lastActionTime != null) { 145 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 146 if (!newEndTime.after(d)) { 147 throw new CommandException(ErrorCode.E1015, newEndTime, 148 "must be after coordinator job's last action time [" + d + "]"); 149 } 150 } 151 } 152 153 /** 154 * Check if new pause time is valid. 155 * 156 * @param coordJob coordinator job id. 157 * @param newPauseTime new pause time. 158 * @param newEndTime new end time, can be null meaning no change on end time. 159 * @throws CommandException thrown if new pause time is not valid. 160 */ 161 private void checkPauseTime(CoordinatorJobBean coordJob, Date newPauseTime) 162 throws CommandException { 163 // New pauseTime has to be a non-past time. 164 Date d = new Date(); 165 if (newPauseTime.before(d)) { 166 throw new CommandException(ErrorCode.E1015, newPauseTime, "must be a non-past time"); 167 } 168 } 169 170 /** 171 * Process lookahead created actions that become invalid because of the new pause time, 172 * These actions will be deleted from DB, also the coordinator job will be updated accordingly 173 * 174 * @param coordJob coordinator job 175 * @param newPauseTime new pause time 176 */ 177 private void processLookaheadActions(CoordinatorJobBean coordJob, Date newPauseTime) throws CommandException { 178 Date lastActionTime = coordJob.getLastActionTime(); 179 if (lastActionTime != null) { 180 // d is the real last action time. 181 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 182 int lastActionNumber = coordJob.getLastActionNumber(); 183 184 boolean hasChanged = false; 185 while (true) { 186 if (!newPauseTime.after(d)) { 187 deleteAction(coordJob.getId(), lastActionNumber); 188 d = new Date(d.getTime() - coordJob.getFrequency() * 60 * 1000); 189 lastActionNumber = lastActionNumber - 1; 190 191 hasChanged = true; 192 } 193 else { 194 break; 195 } 196 } 197 198 if (hasChanged == true) { 199 coordJob.setLastActionNumber(lastActionNumber); 200 Date d1 = new Date(d.getTime() + coordJob.getFrequency() * 60 * 1000); 201 coordJob.setLastActionTime(d1); 202 coordJob.setNextMaterializedTime(d1); 203 coordJob.resetDoneMaterialization(); 204 } 205 } 206 } 207 208 /** 209 * Delete last action for a coordinator job 210 * 211 * @param coordJob coordinator job 212 * @param lastActionNum last action number of the coordinator job 213 */ 214 private void deleteAction(String jobId, int lastActionNum) throws CommandException { 215 try { 216 CoordinatorActionBean actionBean = jpaService.execute(new CoordJobGetActionByActionNumberJPAExecutor(jobId, lastActionNum)); 217 jpaService.execute(new CoordActionRemoveJPAExecutor(actionBean.getId())); 218 } 219 catch (JPAExecutorException e) { 220 throw new CommandException(e); 221 } 222 } 223 224 /** 225 * Check if new end time, new concurrency, new pause time are valid. 226 * 227 * @param coordJob coordinator job id. 228 * @param newEndTime new end time. 229 * @param newConcurrency new concurrency. 230 * @param newPauseTime new pause time. 231 * @throws CommandException thrown if new values are not valid. 232 */ 233 private void check(CoordinatorJobBean coordJob, Date newEndTime, Integer newConcurrency, Date newPauseTime) 234 throws CommandException { 235 if (coordJob.getStatus() == CoordinatorJob.Status.KILLED) { 236 throw new CommandException(ErrorCode.E1016); 237 } 238 239 if (newEndTime != null) { 240 checkEndTime(coordJob, newEndTime); 241 } 242 243 if (newPauseTime != null) { 244 checkPauseTime(coordJob, newPauseTime); 245 } 246 } 247 248 /* (non-Javadoc) 249 * @see org.apache.oozie.command.XCommand#execute() 250 */ 251 @Override 252 protected Void execute() throws CommandException { 253 LOG.info("STARTED CoordChangeXCommand for jobId=" + jobId); 254 255 try { 256 if (newEndTime != null) { 257 coordJob.setEndTime(newEndTime); 258 if (coordJob.getStatus() == CoordinatorJob.Status.SUCCEEDED 259 || coordJob.getStatus() == CoordinatorJob.Status.RUNNING 260 || coordJob.getStatus() == CoordinatorJob.Status.DONEWITHERROR 261 || coordJob.getStatus() == CoordinatorJob.Status.FAILED) { 262 coordJob.setStatus(CoordinatorJob.Status.RUNNING); 263 coordJob.setPending(); 264 coordJob.resetDoneMaterialization(); 265 } 266 } 267 268 if (newConcurrency != null) { 269 this.coordJob.setConcurrency(newConcurrency); 270 } 271 272 if (newPauseTime != null || resetPauseTime == true) { 273 this.coordJob.setPauseTime(newPauseTime); 274 if (oldPauseTime != null && newPauseTime != null) { 275 if (oldPauseTime.before(newPauseTime) && this.coordJob.getStatus() == Job.Status.PAUSED) { 276 this.coordJob.setStatus(Job.Status.RUNNING); 277 } 278 } 279 else if (oldPauseTime != null && newPauseTime == null && this.coordJob.getStatus() == Job.Status.PAUSED) { 280 this.coordJob.setStatus(Job.Status.RUNNING); 281 } 282 283 if (!resetPauseTime) { 284 processLookaheadActions(coordJob, newPauseTime); 285 } 286 } 287 288 jpaService.execute(new CoordJobUpdateJPAExecutor(this.coordJob)); 289 290 return null; 291 } 292 catch (XException ex) { 293 throw new CommandException(ex); 294 } 295 finally { 296 LOG.info("ENDED CoordChangeXCommand for jobId=" + jobId); 297 // update bundle action 298 if (coordJob.getBundleId() != null) { 299 BundleStatusUpdateXCommand bundleStatusUpdate = new BundleStatusUpdateXCommand(coordJob, prevStatus); 300 bundleStatusUpdate.call(); 301 } 302 } 303 } 304 305 /* (non-Javadoc) 306 * @see org.apache.oozie.command.XCommand#getEntityKey() 307 */ 308 @Override 309 protected String getEntityKey() { 310 return this.jobId; 311 } 312 313 /* (non-Javadoc) 314 * @see org.apache.oozie.command.XCommand#loadState() 315 */ 316 @Override 317 protected void loadState() throws CommandException{ 318 jpaService = Services.get().get(JPAService.class); 319 320 if (jpaService == null) { 321 throw new CommandException(ErrorCode.E0610); 322 } 323 324 try { 325 this.coordJob = jpaService.execute(new CoordJobGetJPAExecutor(jobId)); 326 oldPauseTime = coordJob.getPauseTime(); 327 prevStatus = coordJob.getStatus(); 328 } 329 catch (JPAExecutorException e) { 330 throw new CommandException(e); 331 } 332 333 LogUtils.setLogInfo(this.coordJob, logInfo); 334 } 335 336 /* (non-Javadoc) 337 * @see org.apache.oozie.command.XCommand#verifyPrecondition() 338 */ 339 @Override 340 protected void verifyPrecondition() throws CommandException,PreconditionException { 341 check(this.coordJob, newEndTime, newConcurrency, newPauseTime); 342 } 343 344 /* (non-Javadoc) 345 * @see org.apache.oozie.command.XCommand#isLockRequired() 346 */ 347 @Override 348 protected boolean isLockRequired() { 349 return true; 350 } 351 }