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 }