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.bundle;
016    
017    import java.util.Date;
018    import java.util.HashSet;
019    import java.util.List;
020    import java.util.Map;
021    import java.util.Set;
022    
023    import org.apache.oozie.BundleActionBean;
024    import org.apache.oozie.BundleJobBean;
025    import org.apache.oozie.ErrorCode;
026    import org.apache.oozie.XException;
027    import org.apache.oozie.client.Job;
028    import org.apache.oozie.client.OozieClient;
029    import org.apache.oozie.command.CommandException;
030    import org.apache.oozie.command.PreconditionException;
031    import org.apache.oozie.command.XCommand;
032    import org.apache.oozie.command.coord.CoordChangeXCommand;
033    import org.apache.oozie.executor.jpa.BundleActionUpdateJPAExecutor;
034    import org.apache.oozie.executor.jpa.BundleActionsGetJPAExecutor;
035    import org.apache.oozie.executor.jpa.BundleJobGetJPAExecutor;
036    import org.apache.oozie.executor.jpa.BundleJobUpdateJPAExecutor;
037    import org.apache.oozie.service.JPAService;
038    import org.apache.oozie.service.Services;
039    import org.apache.oozie.util.DateUtils;
040    import org.apache.oozie.util.JobUtils;
041    import org.apache.oozie.util.LogUtils;
042    import org.apache.oozie.util.ParamChecker;
043    
044    public class BundleJobChangeXCommand extends XCommand<Void> {
045        private String jobId;
046        private String changeValue;
047        private JPAService jpaService;
048        private List<BundleActionBean> bundleActions;
049        private BundleJobBean bundleJob;
050        private Date newPauseTime = null;
051        private Date newEndTime = null;
052        boolean isChangePauseTime = false;
053        boolean isChangeEndTime = false;
054    
055        private static final Set<String> ALLOWED_CHANGE_OPTIONS = new HashSet<String>();
056        static {
057            ALLOWED_CHANGE_OPTIONS.add("pausetime");
058            ALLOWED_CHANGE_OPTIONS.add("endtime");
059        }
060    
061        /**
062         * @param id bundle job id
063         * @param changeValue change value
064         *
065         * @throws CommandException thrown if failed to change bundle
066         */
067        public BundleJobChangeXCommand(String id, String changeValue) throws CommandException {
068            super("bundle_change", "bundle_change", 1);
069            this.jobId = ParamChecker.notEmpty(id, "id");
070            this.changeValue = ParamChecker.notEmpty(changeValue, "changeValue");
071        }
072    
073        /**
074         * Check if new pause time is future time.
075         *
076         * @param newPauseTime new pause time.
077         * @throws CommandException thrown if new pause time is not valid.
078         */
079        private void checkPauseTime(Date newPauseTime) throws CommandException {
080            // New pauseTime has to be a non-past time.
081            Date d = new Date();
082            if (newPauseTime.before(d)) {
083                throw new CommandException(ErrorCode.E1317, newPauseTime, "must be a non-past time");
084            }
085        }
086    
087        /**
088         * Check if new pause time is future time.
089         *
090         * @param newEndTime new end time, can be null meaning no change on end time.
091         * @throws CommandException thrown if new end time is not valid.
092         */
093        private void checkEndTime(Date newEndTime) throws CommandException {
094            // New endTime has to be a non-past start time.
095            Date startTime = bundleJob.getKickoffTime();
096            if (startTime != null && newEndTime.before(startTime)) {
097                throw new CommandException(ErrorCode.E1317, newEndTime, "must be greater then kickoff time");
098            }
099        }
100    
101        /**
102         * validate if change value is valid.
103         *
104         * @param changeValue change value.
105         * @throws CommandException thrown if changeValue cannot be parsed properly.
106         */
107        private void validateChangeValue(String changeValue) throws CommandException {
108            Map<String, String> map = JobUtils.parseChangeValue(changeValue);
109    
110            if (map.size() > ALLOWED_CHANGE_OPTIONS.size() || !(map.containsKey(OozieClient.CHANGE_VALUE_PAUSETIME) || map.containsKey(OozieClient.CHANGE_VALUE_ENDTIME))) {
111                throw new CommandException(ErrorCode.E1317, changeValue, "can only change pausetime or end time");
112            }
113    
114            if (map.containsKey(OozieClient.CHANGE_VALUE_PAUSETIME)) {
115                isChangePauseTime = true;
116            }
117            else if(map.containsKey(OozieClient.CHANGE_VALUE_ENDTIME)){
118                isChangeEndTime = true;
119            }
120            else {
121                throw new CommandException(ErrorCode.E1317, changeValue, "should change pausetime or endtime");
122            }
123    
124            if(isChangePauseTime){
125                String value = map.get(OozieClient.CHANGE_VALUE_PAUSETIME);
126                if (!value.equals(""))   {
127                    try {
128                        newPauseTime = DateUtils.parseDateUTC(value);
129                    }
130                    catch (Exception ex) {
131                        throw new CommandException(ErrorCode.E1317, value, "is not a valid date");
132                    }
133    
134                    checkPauseTime(newPauseTime);
135                }
136            }
137            else if (isChangeEndTime){
138                String value = map.get(OozieClient.CHANGE_VALUE_ENDTIME);
139                if (!value.equals(""))   {
140                    try {
141                        newEndTime = DateUtils.parseDateUTC(value);
142                    }
143                    catch (Exception ex) {
144                        throw new CommandException(ErrorCode.E1317, value, "is not a valid date");
145                    }
146    
147                    checkEndTime(newEndTime);
148                }
149            }
150        }
151    
152        /* (non-Javadoc)
153         * @see org.apache.oozie.command.XCommand#execute()
154         */
155        @Override
156        protected Void execute() throws CommandException {
157            try {
158                if (isChangePauseTime || isChangeEndTime) {
159                    if (isChangePauseTime) {
160                        bundleJob.setPauseTime(newPauseTime);
161                    }
162                    else if (isChangeEndTime) {
163                        bundleJob.setEndTime(newEndTime);
164                        bundleJob.setStatus(Job.Status.RUNNING);
165                    }
166                    for (BundleActionBean action : this.bundleActions) {
167                        // queue coord change commands;
168                        if (action.getStatus() != Job.Status.KILLED && action.getCoordId() != null) {
169                            queue(new CoordChangeXCommand(action.getCoordId(), changeValue));
170                            LOG.info("Queuing CoordChangeXCommand coord job = " + action.getCoordId() + " to change "
171                                    + changeValue);
172                            action.setPending(action.getPending() + 1);
173                            jpaService.execute(new BundleActionUpdateJPAExecutor(action));
174                        }
175                    }
176                    jpaService.execute(new BundleJobUpdateJPAExecutor(bundleJob));
177                }
178                return null;
179            }
180            catch (XException ex) {
181                throw new CommandException(ex);
182            }
183        }
184    
185        /* (non-Javadoc)
186         * @see org.apache.oozie.command.XCommand#getEntityKey()
187         */
188        @Override
189        protected String getEntityKey() {
190            return this.jobId;
191        }
192    
193        /* (non-Javadoc)
194         * @see org.apache.oozie.command.XCommand#isLockRequired()
195         */
196        @Override
197        protected boolean isLockRequired() {
198            return true;
199        }
200    
201        /* (non-Javadoc)
202         * @see org.apache.oozie.command.XCommand#loadState()
203         */
204        @Override
205        protected void loadState() throws CommandException {
206            try{
207                eagerLoadState();
208                this.bundleActions = jpaService.execute(new BundleActionsGetJPAExecutor(jobId));
209            }
210            catch(Exception Ex){
211                throw new CommandException(ErrorCode.E1311,this.jobId);
212            }
213        }
214    
215        /* (non-Javadoc)
216         * @see org.apache.oozie.command.XCommand#verifyPrecondition()
217         */
218        @Override
219        protected void verifyPrecondition() throws CommandException, PreconditionException {
220        }
221    
222        /* (non-Javadoc)
223         * @see org.apache.oozie.command.XCommand#eagerLoadState()
224         */
225        @Override
226        protected void eagerLoadState() throws CommandException {
227            try {
228                jpaService = Services.get().get(JPAService.class);
229    
230                if (jpaService != null) {
231                    this.bundleJob = jpaService.execute(new BundleJobGetJPAExecutor(jobId));
232                    LogUtils.setLogInfo(bundleJob, logInfo);
233                }
234                else {
235                    throw new CommandException(ErrorCode.E0610);
236                }
237            }
238            catch (XException ex) {
239                throw new CommandException(ex);
240            }
241        }
242    
243        /* (non-Javadoc)
244         * @see org.apache.oozie.command.XCommand#eagerVerifyPrecondition()
245         */
246        @Override
247        protected void eagerVerifyPrecondition() throws CommandException, PreconditionException {
248            validateChangeValue(changeValue);
249    
250            if (bundleJob == null) {
251                LOG.info("BundleChangeCommand not succeeded - " + "job " + jobId + " does not exist");
252                throw new PreconditionException(ErrorCode.E1314, jobId);
253            }
254            if (isChangePauseTime) {
255                if (bundleJob.getStatus() == Job.Status.SUCCEEDED || bundleJob.getStatus() == Job.Status.FAILED
256                        || bundleJob.getStatus() == Job.Status.KILLED || bundleJob.getStatus() == Job.Status.DONEWITHERROR
257                        || bundleJob == null) {
258                    LOG.info("BundleChangeCommand not succeeded for changing pausetime- " + "job " + jobId + " finished, status is "
259                            + bundleJob.getStatusStr());
260                    throw new PreconditionException(ErrorCode.E1312, jobId, bundleJob.getStatus().toString());
261                }
262            }
263            else if(isChangeEndTime){
264                if (bundleJob.getStatus() == Job.Status.KILLED || bundleJob == null) {
265                    LOG.info("BundleChangeCommand not succeeded for changing endtime- " + "job " + jobId + " finished, status is "
266                            + bundleJob.getStatusStr());
267                    throw new PreconditionException(ErrorCode.E1312, jobId, bundleJob.getStatus().toString());
268                }
269            }
270        }
271    }