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.wf;
016    
017    import org.apache.hadoop.conf.Configuration;
018    import org.apache.oozie.client.WorkflowJob;
019    import org.apache.oozie.client.SLAEvent.SlaAppType;
020    import org.apache.oozie.client.SLAEvent.Status;
021    import org.apache.oozie.WorkflowActionBean;
022    import org.apache.oozie.WorkflowJobBean;
023    import org.apache.oozie.ErrorCode;
024    import org.apache.oozie.XException;
025    import org.apache.oozie.command.CommandException;
026    import org.apache.oozie.command.PreconditionException;
027    import org.apache.oozie.command.coord.CoordActionUpdateXCommand;
028    import org.apache.oozie.executor.jpa.JPAExecutorException;
029    import org.apache.oozie.executor.jpa.WorkflowActionGetJPAExecutor;
030    import org.apache.oozie.executor.jpa.WorkflowActionInsertJPAExecutor;
031    import org.apache.oozie.executor.jpa.WorkflowActionUpdateJPAExecutor;
032    import org.apache.oozie.executor.jpa.WorkflowJobGetJPAExecutor;
033    import org.apache.oozie.executor.jpa.WorkflowJobUpdateJPAExecutor;
034    import org.apache.oozie.service.ELService;
035    import org.apache.oozie.service.JPAService;
036    import org.apache.oozie.service.SchemaService;
037    import org.apache.oozie.service.Services;
038    import org.apache.oozie.service.UUIDService;
039    import org.apache.oozie.service.WorkflowStoreService;
040    import org.apache.oozie.workflow.WorkflowException;
041    import org.apache.oozie.workflow.WorkflowInstance;
042    import org.apache.oozie.util.ELEvaluator;
043    import org.apache.oozie.util.InstrumentUtils;
044    import org.apache.oozie.util.LogUtils;
045    import org.apache.oozie.util.XConfiguration;
046    import org.apache.oozie.util.ParamChecker;
047    import org.apache.oozie.util.XmlUtils;
048    import org.apache.oozie.util.db.SLADbXOperations;
049    import org.jdom.Element;
050    import org.jdom.Namespace;
051    
052    import java.io.StringReader;
053    import java.util.Date;
054    import java.util.List;
055    import java.util.Map;
056    
057    public class SignalXCommand extends WorkflowXCommand<Void> {
058    
059        protected static final String INSTR_SUCCEEDED_JOBS_COUNTER_NAME = "succeeded";
060    
061        private JPAService jpaService = null;
062        private String jobId;
063        private String actionId;
064        private WorkflowJobBean wfJob;
065        private WorkflowActionBean wfAction;
066    
067        public SignalXCommand(String name, int priority, String jobId) {
068            super(name, name, priority);
069            this.jobId = ParamChecker.notEmpty(jobId, "jobId");
070        }
071    
072        public SignalXCommand(String jobId, String actionId) {
073            this("signal", 1, jobId);
074            this.actionId = ParamChecker.notEmpty(actionId, "actionId");
075        }
076    
077        @Override
078        protected boolean isLockRequired() {
079            return true;
080        }
081    
082        @Override
083        protected String getEntityKey() {
084            return this.jobId;
085        }
086    
087        @Override
088        protected void loadState() throws CommandException {
089            try {
090                jpaService = Services.get().get(JPAService.class);
091                if (jpaService != null) {
092                    this.wfJob = jpaService.execute(new WorkflowJobGetJPAExecutor(jobId));
093                    LogUtils.setLogInfo(wfJob, logInfo);
094                    if (actionId != null) {
095                        this.wfAction = jpaService.execute(new WorkflowActionGetJPAExecutor(actionId));
096                        LogUtils.setLogInfo(wfAction, logInfo);
097                    }
098                }
099                else {
100                    throw new CommandException(ErrorCode.E0610);
101                }
102            }
103            catch (XException ex) {
104                throw new CommandException(ex);
105            }
106        }
107    
108        @Override
109        protected void verifyPrecondition() throws CommandException, PreconditionException {
110            if ((wfAction == null) || (wfAction.isComplete() && wfAction.isPending())) {
111                if (wfJob.getStatus() != WorkflowJob.Status.RUNNING && wfJob.getStatus() != WorkflowJob.Status.PREP) {
112                    throw new PreconditionException(ErrorCode.E0813, wfJob.getStatusStr());
113                }
114            }
115            else {
116                throw new PreconditionException(ErrorCode.E0814, actionId, wfAction.getStatusStr(), wfAction.isPending());
117            }
118        }
119    
120        @Override
121        protected Void execute() throws CommandException {
122            LOG.debug("STARTED SignalCommand for jobid=" + jobId + ", actionId=" + actionId);
123            WorkflowInstance workflowInstance = wfJob.getWorkflowInstance();
124            workflowInstance.setTransientVar(WorkflowStoreService.WORKFLOW_BEAN, wfJob);
125            boolean completed = false;
126            boolean skipAction = false;
127            if (wfAction == null) {
128                if (wfJob.getStatus() == WorkflowJob.Status.PREP) {
129                    try {
130                        completed = workflowInstance.start();
131                    }
132                    catch (WorkflowException e) {
133                        throw new CommandException(e);
134                    }
135                    wfJob.setStatus(WorkflowJob.Status.RUNNING);
136                    wfJob.setStartTime(new Date());
137                    wfJob.setWorkflowInstance(workflowInstance);
138                    // 1. Add SLA status event for WF-JOB with status STARTED
139                    // 2. Add SLA registration events for all WF_ACTIONS
140                    SLADbXOperations.writeStausEvent(wfJob.getSlaXml(), jobId, Status.STARTED, SlaAppType.WORKFLOW_JOB);
141                    writeSLARegistrationForAllActions(workflowInstance.getApp().getDefinition(), wfJob.getUser(), wfJob
142                            .getGroup(), wfJob.getConf());
143                    queue(new NotificationXCommand(wfJob));
144                }
145                else {
146                    throw new CommandException(ErrorCode.E0801, wfJob.getId());
147                }
148            }
149            else {
150                String skipVar = workflowInstance.getVar(wfAction.getName() + WorkflowInstance.NODE_VAR_SEPARATOR
151                        + ReRunCommand.TO_SKIP);
152                if (skipVar != null) {
153                    skipAction = skipVar.equals("true");
154                }
155                try {
156                    completed = workflowInstance.signal(wfAction.getExecutionPath(), wfAction.getSignalValue());
157                }
158                catch (WorkflowException e) {
159                    throw new CommandException(e);
160                }
161                wfJob.setWorkflowInstance(workflowInstance);
162                wfAction.resetPending();
163                if (!skipAction) {
164                    wfAction.setTransition(workflowInstance.getTransition(wfAction.getName()));
165                }
166                try {
167                    jpaService.execute(new WorkflowActionUpdateJPAExecutor(wfAction));
168                }
169                catch (JPAExecutorException je) {
170                    throw new CommandException(je);
171                }
172            }
173    
174            if (completed) {
175                try {
176                    for (String actionToKillId : WorkflowStoreService.getActionsToKill(workflowInstance)) {
177                        WorkflowActionBean actionToKill;
178    
179                        actionToKill = jpaService.execute(new WorkflowActionGetJPAExecutor(actionToKillId));
180    
181                        actionToKill.setPending();
182                        actionToKill.setStatus(WorkflowActionBean.Status.KILLED);
183                        jpaService.execute(new WorkflowActionUpdateJPAExecutor(actionToKill));
184                        queue(new ActionKillXCommand(actionToKill.getId(), actionToKill.getType()));
185                    }
186    
187                    for (String actionToFailId : WorkflowStoreService.getActionsToFail(workflowInstance)) {
188                        WorkflowActionBean actionToFail = jpaService.execute(new WorkflowActionGetJPAExecutor(
189                                actionToFailId));
190                        actionToFail.resetPending();
191                        actionToFail.setStatus(WorkflowActionBean.Status.FAILED);
192                        SLADbXOperations.writeStausEvent(wfAction.getSlaXml(), wfAction.getId(), Status.FAILED,
193                                SlaAppType.WORKFLOW_ACTION);
194                        jpaService.execute(new WorkflowActionUpdateJPAExecutor(actionToFail));
195                    }
196                }
197                catch (JPAExecutorException je) {
198                    throw new CommandException(je);
199                }
200    
201                wfJob.setStatus(WorkflowJob.Status.valueOf(workflowInstance.getStatus().toString()));
202                wfJob.setEndTime(new Date());
203                wfJob.setWorkflowInstance(workflowInstance);
204                Status slaStatus = Status.SUCCEEDED;
205                switch (wfJob.getStatus()) {
206                    case SUCCEEDED:
207                        slaStatus = Status.SUCCEEDED;
208                        break;
209                    case KILLED:
210                        slaStatus = Status.KILLED;
211                        break;
212                    case FAILED:
213                        slaStatus = Status.FAILED;
214                        break;
215                    default: // TODO SUSPENDED
216                        break;
217                }
218                SLADbXOperations.writeStausEvent(wfJob.getSlaXml(), jobId, slaStatus, SlaAppType.WORKFLOW_JOB);
219                queue(new NotificationXCommand(wfJob));
220                if (wfJob.getStatus() == WorkflowJob.Status.SUCCEEDED) {
221                    InstrumentUtils.incrJobCounter(INSTR_SUCCEEDED_JOBS_COUNTER_NAME, 1, getInstrumentation());
222                }
223            }
224            else {
225                for (WorkflowActionBean newAction : WorkflowStoreService.getStartedActions(workflowInstance)) {
226                    String skipVar = workflowInstance.getVar(newAction.getName() + WorkflowInstance.NODE_VAR_SEPARATOR
227                            + ReRunCommand.TO_SKIP);
228                    boolean skipNewAction = false;
229                    if (skipVar != null) {
230                        skipNewAction = skipVar.equals("true");
231                    }
232                    try {
233                        if (skipNewAction) {
234                            WorkflowActionBean oldAction;
235    
236                            oldAction = jpaService.execute(new WorkflowActionGetJPAExecutor(newAction.getId()));
237    
238                            oldAction.setPending();
239                            jpaService.execute(new WorkflowActionUpdateJPAExecutor(oldAction));
240    
241                            queue(new SignalXCommand(jobId, oldAction.getId()));
242                        }
243                        else {
244                            newAction.setPending();
245                            String actionSlaXml = getActionSLAXml(newAction.getName(), workflowInstance.getApp()
246                                    .getDefinition(), wfJob.getConf());
247                            newAction.setSlaXml(actionSlaXml);
248                            jpaService.execute(new WorkflowActionInsertJPAExecutor(newAction));
249                            LOG.debug("SignalXCommand: Name: "+ newAction.getName() + ", Id: " +newAction.getId() + ", Authcode:" + newAction.getCred());
250                            queue(new ActionStartXCommand(newAction.getId(), newAction.getType()));
251                        }
252                    }
253                    catch (JPAExecutorException je) {
254                        throw new CommandException(je);
255                    }
256                }
257            }
258    
259            try {
260                jpaService.execute(new WorkflowJobUpdateJPAExecutor(wfJob));
261            }
262            catch (JPAExecutorException je) {
263                throw new CommandException(je);
264            }
265            LOG.debug(
266                    "Updated the workflow status to " + wfJob.getId() + "  status =" + wfJob.getStatusStr());
267            if (wfJob.getStatus() != WorkflowJob.Status.RUNNING && wfJob.getStatus() != WorkflowJob.Status.SUSPENDED) {
268                // update coordinator action
269                new CoordActionUpdateXCommand(wfJob).call();
270                new WfEndXCommand(wfJob).call(); //To delete the WF temp dir
271            }
272            LOG.debug("ENDED SignalCommand for jobid=" + jobId + ", actionId=" + actionId);
273            return null;
274        }
275    
276        public static ELEvaluator createELEvaluatorForGroup(Configuration conf, String group) {
277            ELEvaluator eval = Services.get().get(ELService.class).createEvaluator(group);
278            for (Map.Entry<String, String> entry : conf) {
279                eval.setVariable(entry.getKey(), entry.getValue());
280            }
281            return eval;
282        }
283    
284        @SuppressWarnings("unchecked")
285        private String getActionSLAXml(String actionName, String wfXml, String wfConf) throws CommandException {
286            String slaXml = null;
287            try {
288                Element eWfJob = XmlUtils.parseXml(wfXml);
289                for (Element action : (List<Element>) eWfJob.getChildren("action", eWfJob.getNamespace())) {
290                    if (action.getAttributeValue("name").equals(actionName) == false) {
291                        continue;
292                    }
293                    Element eSla = action.getChild("info", Namespace.getNamespace(SchemaService.SLA_NAME_SPACE_URI));
294                    if (eSla != null) {
295                        slaXml = XmlUtils.prettyPrint(eSla).toString();
296                        break;
297                    }
298                }
299            }
300            catch (Exception e) {
301                throw new CommandException(ErrorCode.E1004, e.getMessage(), e);
302            }
303            return slaXml;
304        }
305    
306        private String resolveSla(Element eSla, Configuration conf) throws CommandException {
307            String slaXml = null;
308            try {
309                ELEvaluator evalSla = SubmitCommand.createELEvaluatorForGroup(conf, "wf-sla-submit");
310                slaXml = SubmitCommand.resolveSla(eSla, evalSla);
311            }
312            catch (Exception e) {
313                throw new CommandException(ErrorCode.E1004, e.getMessage(), e);
314            }
315            return slaXml;
316        }
317    
318        @SuppressWarnings("unchecked")
319        private void writeSLARegistrationForAllActions(String wfXml, String user, String group, String strConf)
320                throws CommandException {
321            try {
322                Element eWfJob = XmlUtils.parseXml(wfXml);
323                Configuration conf = new XConfiguration(new StringReader(strConf));
324                for (Element action : (List<Element>) eWfJob.getChildren("action", eWfJob.getNamespace())) {
325                    Element eSla = action.getChild("info", Namespace.getNamespace(SchemaService.SLA_NAME_SPACE_URI));
326                    if (eSla != null) {
327                        String slaXml = resolveSla(eSla, conf);
328                        eSla = XmlUtils.parseXml(slaXml);
329                        String actionId = Services.get().get(UUIDService.class).generateChildId(jobId,
330                                action.getAttributeValue("name") + "");
331                        SLADbXOperations.writeSlaRegistrationEvent(eSla, actionId, SlaAppType.WORKFLOW_ACTION, user, group);
332                    }
333                }
334            }
335            catch (Exception e) {
336                throw new CommandException(ErrorCode.E1007, "workflow:Actions " + jobId, e);
337            }
338    
339        }
340    
341    }