1 /*
2 * The Kuali Financial System, a comprehensive financial management system for higher education.
3 *
4 * Copyright 2005-2014 The Kuali Foundation
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 package org.kuali.kfs.sys.context;
20
21
22 import java.util.Arrays;
23
24
25 /**
26 * BatchStepTrigger writes .run files containing a job name and step name for BatchContainerStep to read.
27 * It loops and sleeps until either a .success or an .error file is written for the Step. Once a result file is found tt logs the results and exits.
28 *
29 * BatchStepTrigger also checks for BatchContainerStep's .runlock file. If it doesn't find one (indicating the batch container is not running) it exits.
30 * BatchStepTrigger adds a ConsoleAppender to its Logger if one hasn't been configured.
31 *
32 * Note that this class runs without starting the SpringContext. KFS Services and Beans are not available for use.
33 *
34 */
35 public class BatchStepTrigger {
36 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchStepTrigger.class);
37
38 private static BatchStepTriggerParameters batchStepTriggerParameters;
39
40 /**
41 * BatchStepTrigger is instantiated for each step in a brte script.
42 * The value used when exiting the system tells the brte script how to handle failures.
43 * 0: okay, 4: the step returned false, 8: an exception occurred in the execution of the step or the trigger
44 *
45 * - verify that the batch container is running, exit with an 8 if not
46 * - for each step
47 * - write a RUN semaphore file
48 * - wait and listen for a result file (either SUCCESS or ERROR)
49 * - if the result file is null the batch container is not running so remove the run file and exit with an 8
50 * - if the result file is an ERROR
51 * - if the file is EMPTY remove the result file and exit with a 4
52 * - otherwise log the error contained in the result file, remove the result file, and exit with an 8
53 * - otherwise remove the result file and exit with a 0
54 *
55 * @param args - refer to BatchStepTriggerParameters for details
56 */
57 public static void main(String[] args) {
58 try {
59 Log4jConfigurer.configureLogging(false);
60
61 batchStepTriggerParameters = new BatchStepTriggerParameters(args);
62
63 String[] stepNames = getStepNames();
64 String jobName = getJobName();
65 int stepIndex = getStepIndex();
66 long sleepInterval = getSleepInterval();
67 BatchContainerDirectory batchContainerDirectory = getBatchContainerDirectory();
68
69 LOG.info("Executing Job: " + jobName + ", STEP"+ stepIndex +", Step(s): " + Arrays.toString(stepNames));
70
71 if (!batchContainerDirectory.isBatchContainerRunning()) {
72 //an instance of the batch container is not running - exit. Exit status: 8
73 LOG.error("The BatchContainer is not running - exiting without executing the steps: "+ Arrays.toString(stepNames));
74 LOG.info("Exit status: 8");
75 System.exit(8);
76 }
77
78 //Need to humanize 'i'; the index
79 for (int i = (stepIndex-1); i < stepNames.length; i++) {
80 String stepName = stepNames[i];
81 BatchStepFileDescriptor batchStepFile = new BatchStepFileDescriptor(jobName, stepName, BatchStepFileDescriptor.getFileExtensionRun());
82
83 //write step start file
84 batchContainerDirectory.writeBatchStepRunFile(batchStepFile, i);
85
86 //wait for a result file from BatchContainer
87 BatchStepFileDescriptor resultFile = listenForResultFile(batchContainerDirectory, batchStepFile, sleepInterval);
88
89 if (resultFile == null) {
90 //result file is null - something unexpected happened. Exit status: 8
91 batchContainerDirectory.removeBatchStepFileFromSystem(batchStepFile);
92
93 LOG.error("No result files were returned- exiting without knowing whether the Step was executed");
94 LOG.info("Exit status: 8");
95 System.exit(8);
96 }
97
98 if (resultFile.isStepFileAnErrorResultFile()) {
99
100 if (batchContainerDirectory.isFileEmpty(resultFile)) {
101 //do not execute any more steps, but job should succeed. Exit status: 4
102 LOG.error(batchStepFile +" failed");
103 batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
104
105 LOG.info("Exit status: 4");
106 System.exit(4);
107 }
108 else {
109
110 //if file is not empty do not execute any more steps and fail job (write exception to log). Exit status: 8
111 LOG.error(batchStepFile +" failed with the following error message: ");
112 batchContainerDirectory.logFileContents(resultFile, LOG);
113 batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
114
115 LOG.info("Exit status: 8");
116 System.exit(8);
117 }
118 }
119
120 batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
121 LOG.info("Exiting "+ batchStepFile);
122
123 }
124
125 //continue executing steps in the job. Exit status: 0
126 LOG.info("Exit status: 0");
127
128 System.exit(0);
129 }
130 catch (Throwable t) {
131 System.err.println("ERROR: Exception caught: ");
132 t.printStackTrace(System.err);
133 LOG.error(t);
134
135 System.exit(8);
136 }
137 }
138
139 /**
140 * Loop - look for a result file in the directory, if none is found then sleep.
141 * If the batch container is not running then write an error result file and return it.
142 *
143 * @param batchContainerDirectory the directory in which the semaphore files are located
144 * @param batchStepFile the step descriptor for the current step
145 * @param sleepInterval the amount of time to sleep while waiting for a result file
146 * @return the step descriptor of the result file
147 */
148 private static BatchStepFileDescriptor listenForResultFile(BatchContainerDirectory batchContainerDirectory, BatchStepFileDescriptor batchStepFile, long sleepInterval) {
149 if (LOG.isDebugEnabled()) {
150 LOG.debug("Waiting for result file for "+ batchStepFile);
151 }
152
153 while (true) {
154 //look for a result file in file-system
155 BatchStepFileDescriptor resultFile = batchContainerDirectory.getResultFile(batchStepFile);
156 if (resultFile != null) {
157 LOG.info("Found result file: "+ resultFile.getName());
158
159 return resultFile;
160 }
161
162 if (batchContainerDirectory.isBatchContainerRunning()) {
163 sleep(sleepInterval);
164 }
165 else {
166 //the batch container is not running - return an error file with an exception
167 batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile, new RuntimeException("The BatchContainer is not running - exiting without knowing whether the Step executed"));
168
169 resultFile = batchContainerDirectory.getResultFile(batchStepFile);
170 return resultFile;
171 }
172 }
173 }
174
175 /**
176 * Sleep for the specified amount of time
177 *
178 * @param sleepInterval the amount of time (in milliseconds) to wait before looking for a result file
179 */
180 private static void sleep(long sleepInterval) {
181 if (LOG.isDebugEnabled()) {
182 LOG.debug("Sleeping...");
183 }
184 try {
185 Thread.sleep(sleepInterval);
186 }
187 catch (InterruptedException e) {
188 throw new RuntimeException("BatchStepTrigger encountered interrupt exception while trying to wait for the specified batch step semaphore processing interval", e);
189 }
190 }
191
192 /**
193 * @return the names of the steps to be executed
194 */
195 private static String[] getStepNames() {
196 return batchStepTriggerParameters.getStepNames();
197 }
198
199 /**
200 * @return the name of the job in which the steps are running
201 */
202 private static String getJobName() {
203 return batchStepTriggerParameters.getJobName();
204 }
205
206 /**
207 * @return the index of the step in the job
208 */
209 private static int getStepIndex() {
210 return batchStepTriggerParameters.getStepIndex();
211 }
212
213 /**
214 * @return the amount of time to sleep (in milliseconds) while waiting for a result file
215 */
216 private static long getSleepInterval() {
217 return batchStepTriggerParameters.getSleepInterval();
218 }
219
220 /**
221 * @return the directory in which the semaphore files are located
222 */
223 private static BatchContainerDirectory getBatchContainerDirectory() {
224 return batchStepTriggerParameters.getBatchContainerDirectory();
225 }
226 }