001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.command.coord; 016 017 import org.apache.oozie.util.DateUtils; 018 import org.apache.oozie.client.CoordinatorJob; 019 import org.apache.oozie.client.OozieClient; 020 import org.apache.oozie.CoordinatorActionBean; 021 import org.apache.oozie.CoordinatorJobBean; 022 import org.apache.oozie.ErrorCode; 023 import org.apache.oozie.XException; 024 import org.apache.oozie.command.CommandException; 025 import org.apache.oozie.executor.jpa.CoordActionRemoveJPAExecutor; 026 import org.apache.oozie.executor.jpa.CoordJobGetActionByActionNumberJPAExecutor; 027 import org.apache.oozie.executor.jpa.JPAExecutorException; 028 import org.apache.oozie.service.JPAService; 029 import org.apache.oozie.service.Services; 030 import org.apache.oozie.store.CoordinatorStore; 031 import org.apache.oozie.store.StoreException; 032 import org.apache.oozie.util.JobUtils; 033 import org.apache.oozie.util.ParamChecker; 034 import org.apache.oozie.util.XLog; 035 036 import java.util.Date; 037 import java.util.HashSet; 038 import java.util.Map; 039 import java.util.Set; 040 import java.util.Map.Entry; 041 042 public class CoordChangeCommand extends CoordinatorCommand<Void> { 043 private String jobId; 044 private Date newEndTime = null; 045 private Integer newConcurrency = null; 046 private Date newPauseTime = null; 047 private boolean resetPauseTime = false; 048 private static final XLog LOG = XLog.getLog(CoordChangeCommand.class); 049 050 private static final Set<String> ALLOWED_CHANGE_OPTIONS = new HashSet<String>(); 051 static { 052 ALLOWED_CHANGE_OPTIONS.add("endtime"); 053 ALLOWED_CHANGE_OPTIONS.add("concurrency"); 054 ALLOWED_CHANGE_OPTIONS.add("pausetime"); 055 } 056 057 public CoordChangeCommand(String id, String changeValue) throws CommandException { 058 super("coord_change", "coord_change", 0, XLog.STD); 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 * @param coordJob coordinator job id. 129 * @param newEndTime new end time. 130 * @throws CommandException thrown if new end time is not valid. 131 */ 132 private void checkEndTime(CoordinatorJobBean coordJob, Date newEndTime) throws CommandException { 133 // New endTime cannot be before coordinator job's start time. 134 Date startTime = coordJob.getStartTime(); 135 if (newEndTime.before(startTime)) { 136 throw new CommandException(ErrorCode.E1015, newEndTime, "cannot be before coordinator job's start time [" + startTime + "]"); 137 } 138 139 // New endTime cannot be before coordinator job's last action time. 140 Date lastActionTime = coordJob.getLastActionTime(); 141 if (lastActionTime != null) { 142 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 143 if (!newEndTime.after(d)) { 144 throw new CommandException(ErrorCode.E1015, newEndTime, 145 "must be after coordinator job's last action time [" + d + "]"); 146 } 147 } 148 } 149 150 /** 151 * @param coordJob coordinator job id. 152 * @param newPauseTime new pause time. 153 * @param newEndTime new end time, can be null meaning no change on end time. 154 * @throws CommandException thrown if new pause time is not valid. 155 */ 156 private void checkPauseTime(CoordinatorJobBean coordJob, Date newPauseTime) 157 throws CommandException { 158 // New pauseTime has to be a non-past time. 159 Date d = new Date(); 160 if (newPauseTime.before(d)) { 161 throw new CommandException(ErrorCode.E1015, newPauseTime, "must be a non-past time"); 162 } 163 } 164 165 /** 166 * Process lookahead created actions that become invalid because of the new pause time, 167 * These actions will be deleted from DB, also the coordinator job will be updated accordingly 168 * 169 * @param coordJob coordinator job 170 * @param newPauseTime new pause time 171 */ 172 private void processLookaheadActions(CoordinatorJobBean coordJob, Date newPauseTime) throws CommandException { 173 Date lastActionTime = coordJob.getLastActionTime(); 174 if (lastActionTime != null) { 175 // d is the real last action time. 176 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 177 int lastActionNumber = coordJob.getLastActionNumber(); 178 179 boolean hasChanged = false; 180 while (true) { 181 if (!newPauseTime.after(d)) { 182 deleteAction(coordJob.getId(), lastActionNumber); 183 d = new Date(d.getTime() - coordJob.getFrequency() * 60 * 1000); 184 lastActionNumber = lastActionNumber - 1; 185 186 hasChanged = true; 187 } 188 else { 189 break; 190 } 191 } 192 193 if (hasChanged == true) { 194 coordJob.setLastActionNumber(lastActionNumber); 195 Date d1 = new Date(d.getTime() + coordJob.getFrequency() * 60 * 1000); 196 coordJob.setLastActionTime(d1); 197 coordJob.setNextMaterializedTime(d1); 198 199 if (coordJob.getStatus() == CoordinatorJob.Status.SUCCEEDED) { 200 coordJob.setStatus(CoordinatorJob.Status.RUNNING); 201 } 202 } 203 } 204 } 205 206 /** 207 * delete last action for a coordinator job 208 * @param coordJob coordinator job 209 * @param lastActionNum last action number of the coordinator job 210 */ 211 private void deleteAction(String jobId, int lastActionNum) throws CommandException { 212 JPAService jpaService = Services.get().get(JPAService.class); 213 if (jpaService == null) { 214 throw new CommandException(ErrorCode.E0610); 215 } 216 217 try { 218 CoordinatorActionBean actionBean = jpaService.execute(new CoordJobGetActionByActionNumberJPAExecutor(jobId, lastActionNum)); 219 jpaService.execute(new CoordActionRemoveJPAExecutor(actionBean.getId())); 220 } 221 catch (JPAExecutorException e) { 222 throw new CommandException(e); 223 } 224 } 225 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 @Override 249 protected Void call(CoordinatorStore store) throws StoreException, CommandException { 250 try { 251 CoordinatorJobBean coordJob = store.getCoordinatorJob(jobId, false); 252 setLogInfo(coordJob); 253 254 check(coordJob, newEndTime, newConcurrency, newPauseTime); 255 256 if (newEndTime != null) { 257 coordJob.setEndTime(newEndTime); 258 if (coordJob.getStatus() == CoordinatorJob.Status.SUCCEEDED) { 259 coordJob.setStatus(CoordinatorJob.Status.RUNNING); 260 } 261 } 262 263 if (newConcurrency != null) { 264 coordJob.setConcurrency(newConcurrency); 265 } 266 267 if (newPauseTime != null || resetPauseTime == true) { 268 coordJob.setPauseTime(newPauseTime); 269 if (!resetPauseTime) { 270 processLookaheadActions(coordJob, newPauseTime); 271 } 272 } 273 274 store.updateCoordinatorJob(coordJob); 275 276 return null; 277 } 278 catch (XException ex) { 279 throw new CommandException(ex); 280 } 281 } 282 283 @Override 284 protected Void execute(CoordinatorStore store) throws StoreException, CommandException { 285 LOG.info("STARTED CoordChangeCommand for jobId=" + jobId); 286 try { 287 if (lock(jobId)) { 288 call(store); 289 } 290 else { 291 throw new CommandException(ErrorCode.E0606, "job " + jobId 292 + " has been locked and cannot change value, please retry later"); 293 } 294 } 295 catch (InterruptedException e) { 296 throw new CommandException(ErrorCode.E0606, "acquiring lock for job " + jobId + " failed " 297 + " with exception " + e.getMessage()); 298 } 299 finally { 300 LOG.info("ENDED CoordChangeCommand for jobId=" + jobId); 301 } 302 return null; 303 } 304 }