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.CoordinatorJobBean; 021 import org.apache.oozie.ErrorCode; 022 import org.apache.oozie.XException; 023 import org.apache.oozie.command.CommandException; 024 import org.apache.oozie.store.CoordinatorStore; 025 import org.apache.oozie.store.StoreException; 026 import org.apache.oozie.util.ParamChecker; 027 import org.apache.oozie.util.XLog; 028 import java.util.Date; 029 import java.util.HashMap; 030 import java.util.Map; 031 032 public class CoordChangeCommand extends CoordinatorCommand<Void> { 033 private String jobId; 034 private Date newEndTime = null; 035 private Integer newConcurrency = null; 036 private Date newPauseTime = null; 037 private boolean resetPauseTime = false; 038 private final XLog log = XLog.getLog(getClass()); 039 040 public CoordChangeCommand(String id, String changeValue) throws CommandException { 041 super("coord_change", "coord_change", 0, XLog.STD); 042 this.jobId = ParamChecker.notEmpty(id, "id"); 043 ParamChecker.notEmpty(changeValue, "value"); 044 045 parseChangeValue(changeValue); 046 } 047 048 /** 049 * @param changeValue change value. 050 * @throws CommandException thrown if changeValue cannot be parsed properly. 051 */ 052 private void parseChangeValue(String changeValue) throws CommandException { 053 Map<String, String> map = new HashMap<String, String>(); 054 String[] tokens = changeValue.split(";"); 055 int size = tokens.length; 056 057 if (size < 0 || size > 3) { 058 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 059 } 060 061 for (String token : tokens) { 062 String[] pair = token.split("="); 063 String key = pair[0]; 064 065 if (!key.equals(OozieClient.CHANGE_VALUE_ENDTIME) && !key.equals(OozieClient.CHANGE_VALUE_CONCURRENCY) 066 && !key.equals(OozieClient.CHANGE_VALUE_PAUSETIME)) { 067 throw new CommandException(ErrorCode.E1015, changeValue, "must change endtime|concurrency|pausetime"); 068 } 069 070 if (!key.equals(OozieClient.CHANGE_VALUE_PAUSETIME) && pair.length != 2) { 071 throw new CommandException(ErrorCode.E1015, changeValue, "elements on " + key + " must be name=value pair"); 072 } 073 074 if (key.equals(OozieClient.CHANGE_VALUE_PAUSETIME) && pair.length != 2 && pair.length != 1) { 075 throw new CommandException(ErrorCode.E1015, changeValue, "elements on " + key + " must be name=value pair or name=(empty string to reset pause time to null)"); 076 } 077 078 if (map.containsKey(key)) { 079 throw new CommandException(ErrorCode.E1015, changeValue, "can not specify repeated change values on " 080 + key); 081 } 082 083 if (pair.length == 2) { 084 map.put(key, pair[1]); 085 } 086 else { 087 map.put(key, ""); 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 154 * time. 155 * @throws CommandException thrown if new pause time is not valid. 156 */ 157 private void checkPauseTime(CoordinatorJobBean coordJob, Date newPauseTime, Date newEndTime) 158 throws CommandException { 159 // New pauseTime cannot be before coordinator job's start time. 160 Date startTime = coordJob.getStartTime(); 161 if (newPauseTime.before(startTime)) { 162 throw new CommandException(ErrorCode.E1015, newPauseTime, "cannot be before coordinator job's start time [" 163 + startTime + "]"); 164 } 165 166 // New pauseTime cannot be before coordinator job's last action time. 167 Date lastActionTime = coordJob.getLastActionTime(); 168 if (lastActionTime != null) { 169 Date d = new Date(lastActionTime.getTime() - coordJob.getFrequency() * 60 * 1000); 170 if (!newPauseTime.after(d)) { 171 throw new CommandException(ErrorCode.E1015, newPauseTime, 172 "must be after coordinator job's last action time [" + d + "]"); 173 } 174 } 175 176 // New pauseTime must be before coordinator job's end time. 177 Date endTime = (newEndTime != null) ? newEndTime : coordJob.getEndTime(); 178 if (!newPauseTime.before(endTime)) { 179 throw new CommandException(ErrorCode.E1015, newPauseTime, "must be before coordinator job's end time [" 180 + endTime + "]"); 181 } 182 } 183 184 /** 185 * @param coordJob coordinator job id. 186 * @param newEndTime new end time. 187 * @param newConcurrency new concurrency. 188 * @param newPauseTime new pause time. 189 * @throws CommandException thrown if new values are not valid. 190 */ 191 private void check(CoordinatorJobBean coordJob, Date newEndTime, Integer newConcurrency, Date newPauseTime) 192 throws CommandException { 193 if (coordJob.getStatus() == CoordinatorJob.Status.KILLED) { 194 throw new CommandException(ErrorCode.E1016); 195 } 196 197 if (newEndTime != null) { 198 checkEndTime(coordJob, newEndTime); 199 } 200 201 if (newPauseTime != null) { 202 checkPauseTime(coordJob, newPauseTime, newEndTime); 203 } 204 } 205 206 @Override 207 protected Void call(CoordinatorStore store) throws StoreException, CommandException { 208 try { 209 CoordinatorJobBean coordJob = store.getCoordinatorJob(jobId, false); 210 setLogInfo(coordJob); 211 212 check(coordJob, newEndTime, newConcurrency, newPauseTime); 213 214 if (newEndTime != null) { 215 coordJob.setEndTime(newEndTime); 216 if (coordJob.getStatus() == CoordinatorJob.Status.SUCCEEDED) { 217 coordJob.setStatus(CoordinatorJob.Status.RUNNING); 218 } 219 } 220 221 if (newConcurrency != null) { 222 coordJob.setConcurrency(newConcurrency); 223 } 224 225 if (newPauseTime != null || resetPauseTime == true) { 226 coordJob.setPauseTime(newPauseTime); 227 } 228 229 incrJobCounter(1); 230 store.updateCoordinatorJob(coordJob); 231 232 return null; 233 } 234 catch (XException ex) { 235 throw new CommandException(ex); 236 } 237 } 238 239 @Override 240 protected Void execute(CoordinatorStore store) throws StoreException, CommandException { 241 log.info("STARTED CoordChangeCommand for jobId=" + jobId); 242 try { 243 if (lock(jobId)) { 244 call(store); 245 } 246 else { 247 throw new CommandException(ErrorCode.E0606, "job " + jobId 248 + " has been locked and cannot change value, please retry later"); 249 } 250 } 251 catch (InterruptedException e) { 252 throw new CommandException(ErrorCode.E0606, "acquiring lock for job " + jobId + " failed " 253 + " with exception " + e.getMessage()); 254 } 255 return null; 256 } 257 }