001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: jianwu $'
006 * '$Date: 2012-10-11 21:29:56 +0000 (Thu, 11 Oct 2012) $' 
007 * '$Revision: 30866 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.job;
031
032import java.io.File;
033import java.util.Vector;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037
038/**
039 * Class Job to describe a standalone job. It is assumed that a job will be
040 * submitted only once and then forgotten. Do not resubmit the same Job object
041 * but create another one.
042 */
043
044public class Job {
045
046        public JobStatusInfo status;
047
048        private String myID; // the key of this object in the hash table
049        // becomes set in setJobID() called from jobFactory;
050        private String workdirPath; // path of (remote) working directory for
051        // job submission and files
052
053        private File localWorkdir; // local working directory for the job (output)
054        private String localWorkdirPath; // path of local working directory for
055        // output files
056
057        // private String submitFile; // the submitFile of the job;
058        // It is to be created or given by the user.
059        // private boolean submitFileIsLocal; // is the submitFile created here, or
060        // is it on the remote site? default=true (i.e.local);
061
062        private JobManager jmgr; // the jobmanager which handles this job
063
064        private String arguments; // argument string for the job
065        
066        private int numTasks = 0; //number of tasks
067
068        private static final Log log = LogFactory.getLog(Job.class.getName());
069        private static final boolean isDebugging = log.isDebugEnabled();
070
071        /** An array of jobs that must successfully complete before this one can run. */
072        private Job[] _dependentJobs;
073        
074        public class JobFile {
075                String filename;
076                boolean isLocal;
077
078                JobFile(String filename, boolean isLocal) {
079                        this.filename = filename;
080                        this.isLocal = isLocal;
081                }
082        };
083
084        public class JobFiles {
085                JobFile executable; // the executable file, local or remote
086                JobFile submitfile; // the submitfile, local or remote, given by user or
087                                                        // created by supporter class
088                Vector inputfiles; // of type JobFile, input files to be declared in
089                                                        // submit file
090                Vector otherinputfiles; // of type String, other files to be staged
091                                                                // before submission
092                JobFile binfile; // scheduler script to be staged to binpath
093                                                // if bin path is not given stage to workingdir
094                Vector outputfiles; // of type String
095
096        };
097
098        public JobFiles jobfiles;
099
100        /**
101         * Constructor is called from JobFactory. jobID is a unique id for the jobs.
102         * Currently only for the current applications. Later it will be an uuid.
103         */
104        protected Job(String jobID) {
105                status = new JobStatusInfo();
106                // submitFileIsLocal = true;
107                jobfiles = new JobFiles();
108                jobfiles.inputfiles = new Vector();
109                jobfiles.otherinputfiles = new Vector();
110                jobfiles.outputfiles = new Vector();
111                myID = jobID;
112                status.statusCode = JobStatusCode.NotInQueue;
113        }
114
115        /**
116         * Get the key of the Job object in the hash table. Used by JobManager to
117         * create unique files based on Job's key Do not mix it with status.jobID,
118         * which is the real job id in the remote queue.
119         */
120        public String getJobID() {
121                return myID;
122        }
123
124        public void setNumTasks(int nt) {
125                numTasks = nt;
126        }
127
128        public int getNumTasks() {
129                return numTasks;
130        }
131
132
133        /**
134         * Set the executable for the job. <i>executablePath</i> is the path to the
135         * executable <i>isLocal</i> true means a local file, false means the
136         * executable is on the remote site <i>arguments</i> are the arguments to
137         * be passed to the job. This will work only if the submission file is
138         * created by the JobManager and not provided as is in the setSubmitFile()
139         * method.
140         * 
141         * @return true if succeeds. Throws JobException if isLocal is true but
142         *         executable is not found locally.
143         */
144        public boolean setExecutable(String executablePath, boolean isLocal,
145                        String arguments) throws JobException {
146
147                if (isLocal) {
148                        File f = new File(executablePath);
149                        if (!f.isFile()) {
150                                throw new JobException("Local executable file "
151                                                + executablePath + " is actually not a file."
152                                                + " Did you want to declare it remote file?");
153                        }
154                }
155
156                jobfiles.executable = new JobFile(executablePath, isLocal);
157                this.arguments = arguments;
158                return true;
159        }
160
161        /**
162         * Set an input file for job. It can be either locally present or remotely.
163         * 
164         * @return true at success, but throws JobException if file is assumed to be
165         *         a local file and it does not exist.
166         */
167        public boolean setInputFile(String path, boolean isLocal)
168                        throws JobException {
169
170                if (isLocal) {
171                        File f = new File(path);
172                        if (!f.isFile()) {
173                                throw new JobException("Local input file " + path
174                                                + " is actually not a file."
175                                                + " Did you want to declare it remote file?");
176                        }
177                }
178                if (path == null || path.trim().length() == 0)
179                        throw new JobException(
180                                        "Your parameter as input file string is empty");
181
182                jobfiles.inputfiles.add(new JobFile(path, isLocal));
183                return true;
184        }
185
186        /**
187         * Set file to be staged to bin path. It can be either locally present or
188         * remotely. It is currently used for staging default fork script
189         * jmgr-fork.sh from job/resources
190         * 
191         * @return true at success, but throws JobException if file is assumed to be
192         *         a local file and it does not exist.
193         */
194        public void setBinFile(String binFile, boolean isLocal) throws JobException {
195
196                if (isLocal) {
197                        File f = new File(binFile);
198                        if (!f.isFile()) {
199                                throw new JobException("Local file " + binFile
200                                                + " is actually not a file."
201                                                + " Did you want to declare it remote file?");
202                        }
203                }
204                jobfiles.binfile = new JobFile(binFile, isLocal);
205        }
206
207        /**
208         * Set an "other" input file for job. It will be staged to the remote site
209         * into the remote working dir but it will not be used in creating the
210         * submission file.
211         * 
212         * @return true at success, but throws JobException if file is assumed to be
213         *         a local file and it does not exist.
214         */
215        public boolean setOtherInputFile(String path) throws JobException {
216
217                File f = new File(path);
218                if (!f.isFile()) {
219                        throw new JobException("'Other' input file " + path
220                                        + " is actually not a file.");
221                }
222                if (path == null || path.trim().length() == 0)
223                        throw new JobException(
224                                        "Your parameter as input file string is empty");
225
226                jobfiles.otherinputfiles.add(path);
227                return true;
228        }
229
230        /**
231         * Set an output file for job. It should be the name of output file which
232         * will be produced by the job.
233         */
234        public void setOutputFile(String path) throws JobException {
235
236                if (path == null || path.trim().length() == 0)
237                        throw new JobException(
238                                        "Your parameter as output file string is empty");
239
240                jobfiles.outputfiles.add(path);
241        }
242
243        /**
244         * Set and create the local working directory. For local execution, it will
245         * be the same as the working directory, so it is not necessary to set. For
246         * remote execution, the result of the remote job will be brought to this
247         * directory (not implemented!).
248         * 
249         * @return true if creation succeeded, throws JobException otherwise
250         */
251        public boolean setLocalWorkdir(String path) throws JobException {
252
253                File dir = new File(path);
254                if (!dir.exists()) {
255                        if (!dir.mkdirs())
256                                throw new JobException(
257                                                "Path "
258                                                                + path
259                                                                + " cannot be created to be the local working directory for the job");
260                } else if (!dir.isDirectory()) {
261                        throw new JobException("Path " + path
262                                        + " exists but is not a directory");
263                }
264
265                localWorkdirPath = path;
266                localWorkdir = dir;
267                return true;
268        }
269
270        /**
271         * Set the (remote) working directory. The directory given as path must
272         * exist already. The actual working directory of a job will be this path
273         * appended with the job's myID. That directory will be created
274         * automatically and the job submission will be issued from this directory.
275         * I.e. relative input/output filenames in the job submission refer files
276         * under this directory. To get back the actual working directory name, use
277         * getWorkdirPath().
278         * 
279         * @return if path==null, it returns false, otherwise true
280         */
281        public boolean setWorkdir(String path) {
282                return setWorkdir(path, true);
283        }
284
285        /**
286         * Set the (remote) working directory. If "createUniqueSubdir" is set to
287         * false, then the given dir is used as working dir. If "createUniqueSubdir"
288         * is set to true, the directory given as path must exist already and tThe
289         * actual working directory will be this path appended with the job's myID.
290         * That directory will be created automatically and the job submission will
291         * be issued from this directory. I.e. relative input/output filenames in
292         * the job submission refer files under this directory. To get back the
293         * actual working directory name, use getWorkdirPath().
294         * 
295         * @return if path==null, it returns false, otherwise true
296         */
297        public boolean setWorkdir(String path, boolean createUniqueSubdir) {
298                String sep;
299                if (path == null)
300                        return false;
301                if (createUniqueSubdir) {
302                        if (path.endsWith("/"))
303                                sep = "";
304                        else
305                                sep = "/";
306                        workdirPath = path + sep + myID;
307                } else {
308                        workdirPath = path;
309                }
310                return true;
311        }
312
313        public String getLocalWorkdirPath() {
314                return localWorkdirPath;
315        }
316
317        public String getWorkdirPath() {
318                return workdirPath;
319        }
320
321        /**
322         * Give a predefined submit file for the job. This is the most general
323         * possibility for advanced users, to submit jobs to a specific job manager.
324         * 
325         * For basic users, do not use this method. Just define the executable,
326         * input and output files for the Job and the JobManager will create an
327         * appropriate submit file for the selected job manager. !!!That is not
328         * implemented in the support classes, so you must use this method always!!!
329         * 
330         * @input submitFile: The predefined submit file. It will be directly used
331         *        at job submission without altering it.
332         * @input isItLocal: Is it here locally, or already you refer to a remote
333         *        submission file. In the first case, it will be copied to the
334         *        remote site before job submission. It throws JobException if the
335         *        submitFile is to be a local file but does not exist.
336         */
337        public void setSubmitFile(String submitFile, boolean isItLocal)
338                        throws JobException {
339
340                if (isItLocal) {
341                        File f = new File(submitFile);
342                        if (!f.isFile()) {
343                                throw new JobException("Local file " + submitFile
344                                                + " is actually not a file."
345                                                + " If you want to declare it remote file, uncheck the cmdFileLocal option.");
346                        }
347                }
348                jobfiles.submitfile = new JobFile(submitFile, isItLocal);
349        }
350
351        /**
352         * Submit a job, called from Job.submit(); boolean <i>overwrite</i>
353         * indicates whether old files that exist on the same directory should be
354         * removed before staging new files. As long jobIDs are not really unique,
355         * this is worth to be true. <i>options</i> can be a special options string
356         * for the actual jobmanager.
357         * 
358         * @return: jobID as String if submission is successful (it is submitted and
359         *          real jobID of the submitted job can be retrieved) Real jobID can
360         *          also be found in job.status.jobID, but you probably do not need
361         *          it except for logging. on error throws JobException
362         */
363        public String submit(JobManager jobmanager, boolean overwrite,
364                        String options) throws JobException {
365
366                if (jobmanager == null) {
367                        throw new JobException(
368                                        "Valid Jobmanager is needed at job submission");
369                }
370                this.jmgr = jobmanager;
371
372                // set defaults if things are unset or throw exception
373                checkDefaults();
374
375                status.jobID = jmgr.submit(this, overwrite, options);
376
377                // The job has been successfully submitted (or an exception has been
378                // thrown)
379                status.statusCode = JobStatusCode.Wait;
380
381                return status.jobID;
382        }
383
384        /**
385         * Reconnect to a executing job. As long jobIDs are not really unique, this
386         * is worth to be true. <i>options</i> can be a special options string for
387         * the actual jobmanager.
388         * 
389         * @return: jobID as String if submission is successful (it is submitted and
390         *          real jobID of the submitted job can be retrieved) Real jobID can
391         *          also be found in job.status.jobID, but you probably do not need
392         *          it except for logging. on error throws JobException
393         */
394        public boolean reconnect(JobManager jobmanager) throws JobException {
395
396                if (jobmanager == null) {
397                        throw new JobException(
398                                        "Valid Jobmanager is needed at job reconnect");
399                }
400                this.jmgr = jobmanager;
401
402                // set defaults if things are unset or throw exception
403                checkDefaults();
404                status.statusCode = JobStatusCode.Wait;
405                status(); // successful query if not throws exception
406                return true;
407        }
408
409        /**
410         * If something important is not set, set default here or throw an exception
411         */
412        private void checkDefaults() throws JobException {
413
414                if (localWorkdirPath == null) {
415                        String home = System.getProperty("user.home");
416                        if ( System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 ) {
417                                home = System.getenv().get("HOMEPATH");
418                        }
419                        setLocalWorkdir(new String(
420                                        home + File.separator + ".hpcc" + File.separator + myID));
421                }
422                if (workdirPath == null)
423                        setWorkdir(new String(".hpcc"));
424
425                // if ( jobfiles.executable == null )
426                // throw new JobException("Job has not executable file specified.");
427
428        }
429
430        /**
431         * Check the status of the job
432         * 
433         * @return: true if succeeded The JobStatusInfo data struct of Job is
434         *          altered: (job.status) throws JobException on error, or you call
435         *          for a non-submitted job
436         */
437        public boolean status() throws JobException {
438
439                if (jmgr == null) {
440                        throw new JobException(
441                                        "Valid Jobmanager is needed before checking job status");
442                }
443
444                if (status.jobID == null) {
445                        throw new JobException("The job is not submitted. "
446                                        + "Or at least, it has no real jobID, so we lost it.");
447                }
448
449                if (status.statusCode == JobStatusCode.Error) // no need to check it
450                        // again
451                        return true;
452                
453                JobStatusInfo stat = jmgr.status(status.jobID,numTasks);
454                status.statusCode = stat.statusCode;
455                if(numTasks > 0) {
456                        ((TaskParallelJobStatusInfo)status).taskStatusCodes = 
457                                ((TaskParallelJobStatusInfo)stat).taskStatusCodes;
458                }
459
460                return true;
461        }
462
463        /**
464         * Remove job from the queue (either running or waiting)
465         * 
466         * @return: true if succeeded, false is not throws JobException on error, or
467         *          you call for a non-submitted job
468         */
469        public boolean deleteFromQueue() throws JobException {
470
471                if (jmgr == null) {
472                        throw new JobException(
473                                        "Valid Jobmanager is needed before removing the job");
474                }
475
476                if (status.jobID == null) {
477                        throw new JobException("The job is not submitted. "
478                                        + "Or at least, it has no real jobID, so we lost it.");
479                }
480
481                boolean stat = jmgr.delete(status.jobID);
482                return stat;
483        }
484        
485        /** Specify a set of jobs that must successfully complete before this
486         *  can start.
487         */
488        public void setDependentJobs(Job[] dependentJobs) {
489            _dependentJobs = dependentJobs;
490        }
491        
492        /** Get a set of jobs that must successfully complete before this job
493         *  can start. If there are none, returns null.
494         */
495        public Job[] getDependentJobs() {
496            return _dependentJobs;
497        }
498
499        /*
500         * ---------------- Protected methods for JobManager and JobSupport classes
501         * ------------
502         */
503        public String getSubmitFile() {
504                if (jobfiles.submitfile != null)
505                        return jobfiles.submitfile.filename;
506                return null;
507        }
508
509        protected boolean isSubmitFileLocal() {
510                if (jobfiles.submitfile != null)
511                        return jobfiles.submitfile.isLocal;
512                return false;
513        }
514
515        protected String getBinFile() {
516                if (jobfiles.binfile != null)
517                        return jobfiles.binfile.filename;
518                return null;
519        }
520
521        protected boolean isBinFileLocal() {
522                if (jobfiles.binfile != null)
523                        return jobfiles.binfile.isLocal;
524                return false;
525        }
526
527        /**
528         * Return vector of local files that should be staged to the remote site for
529         * a job. Vector elements are type of File!
530         * 
531         * @return: Vector of File objects. On error throws JobException
532         */
533        protected Vector getLocalFiles() {
534
535                Vector files = new Vector();
536
537                if (jobfiles.executable != null && jobfiles.executable.isLocal)
538                        files.add(new File(jobfiles.executable.filename));
539
540                int i;
541                int inputs = jobfiles.inputfiles.size();
542                for (i = 0; i < inputs; i++) {
543                        Job.JobFile f = (Job.JobFile) (jobfiles.inputfiles.get(i));
544                        if (f.isLocal)
545                                files.add(new File(f.filename));
546                }
547
548                int others = jobfiles.otherinputfiles.size();
549                for (i = 0; i < others; i++) {
550                        files.add(new File((String) jobfiles.otherinputfiles.get(i)));
551                }
552
553                if (jobfiles.submitfile != null && jobfiles.submitfile.isLocal)
554                        files.add(new File(jobfiles.submitfile.filename));
555
556                return files;
557        }
558
559        /**
560         * Return vector of remote files that should be copied into to remote job
561         * directory before submission. Vector elements are type of Strings!
562         * 
563         * @return: Vector of File objects. On error throws JobException
564         */
565        protected Vector getRemoteFiles() {
566
567                Vector files = new Vector();
568
569                if (jobfiles.executable != null && !jobfiles.executable.isLocal)
570                        files.add(jobfiles.executable.filename);
571
572                int i;
573                int inputs = jobfiles.inputfiles.size();
574                for (i = 0; i < inputs; i++) {
575                        Job.JobFile f = (Job.JobFile) (jobfiles.inputfiles.get(i));
576                        if (!f.isLocal)
577                                files.add(f.filename);
578                }
579
580                if (jobfiles.submitfile != null && !jobfiles.submitfile.isLocal)
581                        files.add(jobfiles.submitfile.filename);
582
583                return files;
584        }
585
586} // end-of-class-Job
587