This repository has been archived by the owner on Mar 27, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 55
/
Command.java
463 lines (399 loc) · 15.6 KB
/
Command.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/**
* /*******************************************************************************
* Copyright (c) 2019- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation - Jay Jay Billings,
* Joe Osborn
*******************************************************************************/
package org.eclipse.ice.commands;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the instantiation class of the CommandFactory class and thus is
* responsible for executing particular commands. It is the super class for
* local and remote commands, and thus delegates the creation of a LocalCommand
* or RemoteCommand, depending on the hostname.
*
* @author Joe Osborn
*
*/
public abstract class Command {
/**
* The current status of the command
*/
protected CommandStatus status;
/**
* The configuration parameters of the command - contains information about what
* the command is actually intended to do (e.g. the executable filename).
*/
protected CommandConfiguration commandConfig;
/**
* The connection configuration parameters of the command - this will contain
* information about whether or not the command should be run locally or
* remotely. If remote, it contains all of the necessary ssh information for
* opening the remote connection. This is the configuration to connect to the
* host that will ultimately perform the job.
*/
protected ConnectionConfiguration connectionConfig;
/**
* Logger for handling event messages and other information.
*/
protected static final Logger logger = LoggerFactory.getLogger(Command.class);
/**
* An exit value that is determined when the job is processing. The convention
* is that anything other than 0 indicates a failure. Set by default to -1 to
* indicate that the job is not running (or hasn't finished).
*/
protected int exitValue = -1;
/**
* Have a connection manager for commands that defaults to the static object
* from the factory method. Users can override this if they want to through a
* specific constructor which sets the manager.
*/
protected ConnectionManager manager = ConnectionManagerFactory.getConnectionManager();
/**
* An optional update handler that can provide status updates for the command
*/
ICommandUpdateHandler updater = null;
/**
* Default constructor
*/
public Command() {
}
/**
* This function executes the command based on the information provided in the
* dictionary which is stored in the CommandConfiguration.
*
* @return CommandStatus - indicating whether or not the Command was properly
* executed
*/
public CommandStatus execute() {
// Check that the commandConfig and connectionConfig(s) file was properly
// instantiated in the constructor
if (!checkStatus(status))
return CommandStatus.INFOERROR;
// Configure the command to be ready to run.
status = setConfiguration();
// Ensure that the command was properly configured
if (!checkStatus(status))
return CommandStatus.INFOERROR;
// Now that all of the prerequisites have been set, start the job running
status = run();
// Clean up any remaining items from the job, in preparation for a (potential)
// next job to be run
status = cleanUp();
// Confirm the job finished with some status
logger.info("The job finished with status: " + status);
if(updater != null) {
String message = "Job number " + commandConfig.getCommandId()
+ " finished with status " + status +"\n "
+ "Check your log/out/error files for more details";
updater.setMessage(message);
try {
updater.postUpdate();
} catch (IOException e) {
// Just warn since it is not indicative of a job failing
logger.warn("Couldn't post update. Check stack trace" , e);
}
}
return status;
}
/**
* This function actually runs the particular command in question. It is called
* in execute() after all of the setup for the job execution is finished. It
* processes the several steps required to run the job, e.g. setting it up,
* monitoring it, etc.
*
* @return - CommandStatus indicating the result of the function.
*/
protected abstract CommandStatus run();
/**
* This function runs the job through the relevant API and performs the process.
*
* @return - CommandStatus indicating the result of the function.
*/
protected abstract CommandStatus processJob();
/**
* This operation is responsible for monitoring the exit value of the running
* job. If it does not finish after some time then the function will print a
* message to the error output file. If the job has failed then it stops
* monitoring and returns that the exit value of the job was unsuccessful. The
* function also writes to the output logfile what the actual final job exit
* value is, so the user can always see if their job finished successfully.
*
* @return - CommandStatus indicating the result of the function.
*/
protected abstract CommandStatus monitorJob();
/**
* This function is responsible for cleaning up any remaining things from the
* job after the process builder or JSch API has submitted the job to be
* processed. These are things related to, for example, logging output.
*
* @return - CommandStatus indicating the result of the function.
*/
protected abstract CommandStatus finishJob();
/**
* This function cancels the already submitted command, if possible.
*
* @return CommandStatus - indicates whether or not the Command was properly
* cancelled.
*/
public CommandStatus cancel() {
status = CommandStatus.CANCELED;
return status;
}
/**
* This function is intended to clean up any (possible) remaining loose ends
* after the job is finished processing.
*
* @return CommandStatus - indicating that the configuration completed clean up
*/
protected CommandStatus cleanUp() {
// Clear the command list, so as to not inadvertently concatenate more commands
// if the same instance of Command is used again.
// Same for input file list
commandConfig.getSplitCommand().clear();
commandConfig.getInputFileList().clear();
// Return the already set status once the job was finished processing
return status;
}
/**
* This function sets up the configuration in preparation for the job running.
* It checks to make sure the necessary strings are set and then constructs the
* executable to be run. It also creates the output files which contain
* log/error information.
*
* @return - CommandStatus indicating that configuration completed and job can
* start running
*/
protected CommandStatus setConfiguration() {
// Check the info and return failure if something was not set
if (commandConfig.getExecutable() == null || commandConfig.getOutFileName() == null
|| commandConfig.getErrFileName() == null || commandConfig.getNumProcs() == null) {
logger.error("An important piece of information is missing from the CommandConfiguration. Exiting.");
return CommandStatus.INFOERROR;
}
// Get a string of the executable to manipulate
String exec = commandConfig.getExecutable();
// If the executable contains a prefix, remove it
if (exec.contains("./"))
exec = exec.substring(2, exec.length());
String separator = "/";
if (commandConfig.getOS().toLowerCase().contains("win"))
separator = "\\";
// Check if the working directory exists
String workingDir = commandConfig.getWorkingDirectory();
boolean exists = false, execExists = false;
if(workingDir != null) {
if (!workingDir.endsWith(separator))
workingDir += separator;
// Check that the directory exists
// Get the file handler factory
FileHandlerFactory factory = new FileHandlerFactory();
try {
// Get the handler for this particular connection, whether local or remote
FileHandler handler = factory.getFileHandler(connectionConfig);
// If the working directory was set, check that it exists. If it wasn't set,
// then the paths should have been explicitly identified
exists = handler.exists(workingDir);
// Check if the executable exists in the working directory
execExists = handler.exists(workingDir + exec);
} catch (IOException e) {
// If we can't get the file handler, then there was an error in the connection
// configuration
logger.error("Unable to connect to filehandler and check file existence. Exiting.", e);
return CommandStatus.INFOERROR;
}
}
// If the working directory doesn't exist, we won't be able to continue the job
// processing unless the full paths were specified. Warn the user
if (!exists) {
logger.warn("Directory containing files does not exist!");
logger.warn("If you did not specify absolute paths for all your files, the job will fail!");
}
// If the executable does not exist but the working directory was set, warn the
// user again that the executable is unavailable
if (!execExists && exists) {
// If the executable doesn't exist, we shouldn't cancel the job because it is
// possible that the command is a simple shell command. So warn the user
logger.warn("Warning: Executable file could not be found");
logger.warn(
"If you are running a simple shell command, or specified the full path to the executable, ignore this warning");
logger.warn("Otherwise, the job will fail");
}
// Set the command to actually run and execute
commandConfig.setFullCommand(commandConfig.getExecutableName());
// Create the output files associated to the job for logging
commandConfig.createOutputFiles();
// All setup completed, return that the job will now run
return CommandStatus.RUNNING;
}
/**
* This function takes the given streams as parameters and logs them into an
* output file. The function returns a boolean on whether or not the function
* completed successfully (and thus the streams were correctly written out).
*
* @param output - Output stream from the job
* @param errors - Error stream from the job
* @return - boolean - true if output was logged, false otherwise
*/
protected boolean logOutput(final InputStream output, final InputStream errors) {
InputStreamReader stdOutStreamReader = null, stdErrStreamReader = null;
BufferedReader stdOutReader = null, stdErrReader = null;
String nextLine = null;
// Setup the BufferedReader that will get stdout from the process.
stdOutStreamReader = new InputStreamReader(output);
stdOutReader = new BufferedReader(stdOutStreamReader);
// Setup the BufferedReader that will get stderr from the process.
stdErrStreamReader = new InputStreamReader(errors);
stdErrReader = new BufferedReader(stdErrStreamReader);
// Set the objects to the updated readers
commandConfig.setStdErr(commandConfig.getBufferedWriter(commandConfig.getErrFileName()));
commandConfig.setStdOut(commandConfig.getBufferedWriter(commandConfig.getOutFileName()));
// Make a new line for writing out the output
String newLine = "\n";
// Add a \r for Windows systems.
if (commandConfig.getOS().contains("windows"))
newLine = "\r\n";
// Catch the stdout and stderr output
try {
// Write to the stdOut file
while ((nextLine = stdOutReader.readLine()) != null) {
commandConfig.getStdOut().write(nextLine);
// Only add to the string if it is not a commented out line
if (!nextLine.startsWith("#")) {
commandConfig.addToStdOutputString(nextLine);
}
// MUST put a new line for this type of writer
commandConfig.getStdOut().write(newLine);
commandConfig.getStdOut().flush();
}
// Write to the stdErr file
while ((nextLine = stdErrReader.readLine()) != null) {
commandConfig.getStdErr().write(nextLine);
// If the next line isn't commented out, add it to the error string
if (!nextLine.startsWith("#")) {
commandConfig.addToErrString(nextLine);
}
// MUST put a new line for this type of writer
commandConfig.getStdErr().write(newLine);
commandConfig.getStdErr().flush();
}
} catch (IOException e) {
// Or fail and complain about it.
logger.error("Could not logOutput, returning error!", e);
return false;
}
// Completed successfully, return true
return true;
}
/**
* This function is a simple helper function to check and make sure that the
* command status is not set to a flagged error, e.g. failed.
*
* @param current_status to be checked
* @return boolean indicating whether or not status is good to continue (true)
* or whether or not job has failed (returns false)
*/
public boolean checkStatus(CommandStatus current_status) {
if (current_status != CommandStatus.FAILED && current_status != CommandStatus.INFOERROR) {
logger.info("The current status is: " + current_status);
return true;
} else {
String statusString = "The job failed with status: " + current_status;
String checkString = "Check your error logfile for more details! Exiting now!";
logger.error(statusString);
logger.error(checkString);
return false;
}
}
/**
* This function returns the status for a particular command at a given time in
* the operation of the command.
*
* @return - return current status for a particular command
*/
public CommandStatus getStatus() {
return status;
}
/**
* This function sets the status for a particular command to be stat
*
* @param stat - new CommandStatus to be set
*/
public void setStatus(CommandStatus status) {
this.status = status;
return;
}
/**
* This function returns to the user the configuration that was used to create a
* particular command.
*
* @return - the particular CommandConfiguration for this command
*/
public CommandConfiguration getCommandConfiguration() {
return commandConfig;
}
/**
* This function sets the command configuration for a particular command
*
* @param commandConfig - CommandConfiguration to be set
*/
public void setCommandConfiguration(CommandConfiguration commandConfig) {
this.commandConfig = commandConfig;
}
/**
* This function returns to the user the configuration that was used to set up a
* particular connection.
*
* @return - the particular connection configuration for this command
*/
public ConnectionConfiguration getConnectionConfiguration() {
return connectionConfig;
}
/**
* This function sets the configuration that is to be used to set up a
* particular connection.
*
* @param connectionConfig - ConnectionConfiguration to be set
*/
public void setConnectionConfiguration(ConnectionConfiguration connectionConfig) {
this.connectionConfig = connectionConfig;
}
/**
* Setter function for manager, in the event that a custom manager is desired rather
* than the factory class manager.
* See {@link org.eclipse.ice.commands.Command#manager}
*
* @param manager - the ConnectionManager to be set
*/
public void setConnectionManager(ConnectionManager manager) {
this.manager = manager;
}
/**
* Getter function for manager, in the event that a custom manager is desired
* rather than the factory class manager.
* See {@link org.eclipse.ice.commands.Command#manager}
*
* @return - the Command's ConnectionManager
*/
public ConnectionManager getConnectionManager() {
return manager;
}
/**
* Setter for update handler, see {@link org.eclipse.ice.commands.Command#updater}
* @param updater
*/
public void setUpdateHandler(ICommandUpdateHandler updater) {
this.updater = updater;
}
}