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.CoordinatorActionBean; 018 import org.apache.oozie.client.CoordinatorAction; 019 import org.apache.oozie.service.Services; 020 import org.apache.oozie.service.UUIDService; 021 import org.apache.oozie.util.DateUtils; 022 import org.apache.oozie.client.CoordinatorJob; 023 import org.apache.oozie.client.OozieClient; 024 import org.apache.oozie.CoordinatorJobBean; 025 import org.apache.oozie.ErrorCode; 026 import org.apache.oozie.XException; 027 import org.apache.oozie.command.CommandException; 028 import org.apache.oozie.store.CoordinatorStore; 029 import org.apache.oozie.store.StoreException; 030 import org.apache.oozie.util.ParamChecker; 031 import org.apache.oozie.util.XLog; 032 import java.util.Date; 033 import java.util.HashMap; 034 import java.util.List; 035 import java.util.Map; 036 037 public class CoordChangeCommand extends CoordinatorCommand<Void> { 038 private String jobId; 039 private Date newEndTime = null; 040 private Integer newConcurrency = null; 041 private Date newPauseTime = null; 042 private boolean resetPauseTime = false; 043 private final XLog log = XLog.getLog(getClass()); 044 045 public CoordChangeCommand(String id, String changeValue) throws CommandException { 046 super("coord_change", "coord_change", 0, XLog.STD); 047 this.jobId = ParamChecker.notEmpty(id, "id"); 048 ParamChecker.notEmpty(changeValue, "value"); 049 050 parseChangeValue(changeValue); 051 } 052 053 /** 054 * @param changeValue change value. 055 * @throws CommandException thrown if changeValue cannot be parsed properly. 056 */ 057 private void parseChangeValue(String changeValue) throws CommandException { 058 Map<String, String> map = new HashMap<String, String>(); 059 String[] tokens = changeValue.split(";"); 060 int size = tokens.length; 061 062 if (size < 0 || size > 3) { 063 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 064 } 065 066 for (String token : tokens) { 067 String[] pair = token.split("="); 068 String key = pair[0]; 069 070 if (!key.equals(OozieClient.CHANGE_VALUE_ENDTIME) && !key.equals(OozieClient.CHANGE_VALUE_CONCURRENCY) 071 && !key.equals(OozieClient.CHANGE_VALUE_PAUSETIME)) { 072 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 073 } 074 075 if (!key.equals(OozieClient.CHANGE_VALUE_PAUSETIME) && pair.length != 2) { 076 throw new CommandException(ErrorCode.E1015, changeValue, "elements on " + key + " must be name=value pair"); 077 } 078 079 if (key.equals(OozieClient.CHANGE_VALUE_PAUSETIME) && pair.length != 2 && pair.length != 1) { 080 throw new CommandException(ErrorCode.E1015, changeValue, "elements on " + key + " must be name=value pair or name=(empty string to reset pause time to null)"); 081 } 082 083 if (map.containsKey(key)) { 084 throw new CommandException(ErrorCode.E1015, changeValue, "can not specify repeated change values on " 085 + key); 086 } 087 088 if (pair.length == 2) { 089 map.put(key, pair[1]); 090 } 091 else { 092 map.put(key, ""); 093 } 094 } 095 096 if (map.containsKey(OozieClient.CHANGE_VALUE_ENDTIME)) { 097 String value = map.get(OozieClient.CHANGE_VALUE_ENDTIME); 098 try { 099 newEndTime = DateUtils.parseDateUTC(value); 100 } 101 catch (Exception ex) { 102 throw new CommandException(ErrorCode.E1015, value, "must be a valid date"); 103 } 104 } 105 106 if (map.containsKey(OozieClient.CHANGE_VALUE_CONCURRENCY)) { 107 String value = map.get(OozieClient.CHANGE_VALUE_CONCURRENCY); 108 try { 109 newConcurrency = Integer.parseInt(value); 110 } 111 catch (NumberFormatException ex) { 112 throw new CommandException(ErrorCode.E1015, value, "must be a valid integer"); 113 } 114 } 115 116 if (map.containsKey(OozieClient.CHANGE_VALUE_PAUSETIME)) { 117 String value = map.get(OozieClient.CHANGE_VALUE_PAUSETIME); 118 if (value.equals("")) { // this is to reset pause time to null; 119 resetPauseTime = true; 120 } 121 else { 122 try { 123 newPauseTime = DateUtils.parseDateUTC(value); 124 } 125 catch (Exception ex) { 126 throw new CommandException(ErrorCode.E1015, value, "must be a valid date"); 127 } 128 } 129 } 130 } 131 132 /** 133 * @param store coordinator store. 134 * @param coordJob coordinator job id. 135 * @param newEndTime new end time. 136 * @throws CommandException thrown if new end time is not valid. 137 */ 138 private void checkEndTimeAnUpdateAction(CoordinatorStore store, CoordinatorJobBean coordJob, Date newEndTime) 139 throws StoreException, CommandException { 140 // New endTime cannot be before coordinator job's start time. 141 Date startTime = coordJob.getStartTime(); 142 if (newEndTime.before(startTime)) { 143 throw new CommandException(ErrorCode.E1015, newEndTime, "cannot be before coordinator job's start time [" + startTime + "]"); 144 } 145 146 // New endTime cannot be before coordinator job's last action time that is running or run. 147 Date lastActionTime = coordJob.getLastActionTime(); 148 if (lastActionTime != null) { 149 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 150 if (!newEndTime.after(d)) { 151 int lowestActionNumber = -1; 152 for (CoordinatorActionBean action : store.getActionsOlderThan(coordJob.getId(), newEndTime, true)) { 153 if (action.getStatus() == CoordinatorAction.Status.WAITING || 154 action.getStatus() == CoordinatorAction.Status.READY) { 155 int actionNumber = action.getActionNumber(); 156 if (lowestActionNumber == -1 || actionNumber < lowestActionNumber) { 157 lowestActionNumber = actionNumber; 158 } 159 log.info("Deleting not started action [{0}]", action.getId()); 160 store.deleteAction(action); 161 } 162 else { 163 throw new CommandException(ErrorCode.E1015, newEndTime, 164 "must be after coordinator job's last started action time [" + d + "]"); 165 } 166 } 167 if (lowestActionNumber > -1) { 168 log.info("Setting last materialized action to [{0}]", lowestActionNumber - 1); 169 coordJob.setLastActionNumber(lowestActionNumber - 1); 170 coordJob.setLastActionTime(null); 171 store.updateCoordinatorJob(coordJob); 172 } 173 } 174 } 175 } 176 177 /** 178 * @param coordJob coordinator job id. 179 * @param newPauseTime new pause time. 180 * @param newEndTime new end time, can be null meaning no change on end 181 * time. 182 * @throws CommandException thrown if new pause time is not valid. 183 */ 184 private void checkPauseTime(CoordinatorJobBean coordJob, Date newPauseTime, Date newEndTime) 185 throws CommandException { 186 // New pauseTime cannot be before coordinator job's start time. 187 Date startTime = coordJob.getStartTime(); 188 if (newPauseTime.before(startTime)) { 189 throw new CommandException(ErrorCode.E1015, newPauseTime, "cannot be before coordinator job's start time [" 190 + startTime + "]"); 191 } 192 193 // New pauseTime cannot be before coordinator job's last action time. 194 Date lastActionTime = coordJob.getLastActionTime(); 195 if (lastActionTime != null) { 196 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 197 if (!newPauseTime.after(d)) { 198 throw new CommandException(ErrorCode.E1015, newPauseTime, 199 "must be after coordinator job's last action time [" + d + "]"); 200 } 201 } 202 203 // New pauseTime must be before coordinator job's end time. 204 Date endTime = (newEndTime != null) ? newEndTime : coordJob.getEndTime(); 205 if (!newPauseTime.before(endTime)) { 206 throw new CommandException(ErrorCode.E1015, newPauseTime, "must be before coordinator job's end time [" 207 + endTime + "]"); 208 } 209 } 210 211 /** 212 * @param store coordinator store. 213 * @param coordJob coordinator job id. 214 * @param newEndTime new end time. 215 * @param newConcurrency new concurrency. 216 * @param newPauseTime new pause time. 217 * @throws CommandException thrown if new values are not valid. 218 */ 219 private void checkAnUpdateAction(CoordinatorStore store, CoordinatorJobBean coordJob, Date newEndTime, 220 Integer newConcurrency, Date newPauseTime) 221 throws StoreException, CommandException { 222 if (coordJob.getStatus() == CoordinatorJob.Status.KILLED) { 223 throw new CommandException(ErrorCode.E1016); 224 } 225 226 if (newEndTime != null) { 227 checkEndTimeAnUpdateAction(store, coordJob, newEndTime); 228 } 229 230 if (newPauseTime != null) { 231 checkPauseTime(coordJob, newPauseTime, newEndTime); 232 } 233 } 234 235 @Override 236 protected Void call(CoordinatorStore store) throws StoreException, CommandException { 237 try { 238 CoordinatorJobBean coordJob = store.getCoordinatorJob(jobId, false); 239 setLogInfo(coordJob); 240 241 checkAnUpdateAction(store, coordJob, newEndTime, newConcurrency, newPauseTime); 242 243 if (newEndTime != null) { 244 coordJob.setEndTime(newEndTime); 245 if (coordJob.getStatus() == CoordinatorJob.Status.SUCCEEDED) { 246 coordJob.setStatus(CoordinatorJob.Status.RUNNING); 247 } 248 } 249 250 if (newConcurrency != null) { 251 coordJob.setConcurrency(newConcurrency); 252 } 253 254 if (newPauseTime != null || resetPauseTime == true) { 255 coordJob.setPauseTime(newPauseTime); 256 } 257 258 incrJobCounter(1); 259 store.updateCoordinatorJob(coordJob); 260 261 return null; 262 } 263 catch (XException ex) { 264 throw new CommandException(ex); 265 } 266 } 267 268 @Override 269 protected Void execute(CoordinatorStore store) throws StoreException, CommandException { 270 log.info("STARTED CoordChangeCommand for jobId=" + jobId); 271 try { 272 if (lock(jobId)) { 273 call(store); 274 } 275 else { 276 throw new CommandException(ErrorCode.E0606, "job " + jobId 277 + " has been locked and cannot change value, please retry later"); 278 } 279 } 280 catch (InterruptedException e) { 281 throw new CommandException(ErrorCode.E0606, "acquiring lock for job " + jobId + " failed " 282 + " with exception " + e.getMessage()); 283 } 284 return null; 285 } 286 }