Changes between Version 12 and Version 13 of net.sf.basedb.opengrid/using
- Timestamp:
- Jan 24, 2017, 11:58:42 AM (8 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
net.sf.basedb.opengrid/using
v12 v13 1 1 = How to use the Open Grid Scheduler package API = 2 In this documentwe will try to describe the main aspects of the programmatic API that other extensions can use in order to access and use Open Grid Clusters.2 In this tutorial we will try to describe the main aspects of the programmatic API that other extensions can use in order to access and use Open Grid Clusters. 3 3 4 4 [[PageOutline(2-3,,inline,numbered)]] … … 6 6 See also the [/chrome/site/net.sf.basedb.opengrid/api/index.html Javadoc API] documentation. 7 7 8 == The use case == 9 10 The use case in this tutorial is that we are going to implement an extension that wants to display a web page inside BASE for starting a job on an Open Grid Cluster. We will concentrate on the Open Grid part of this and will not care about how the extension interacts with BASE. You may want to read more about this in the [http://base.thep.lu.se/chrome/site/latest/html/developer/extensions/index.html BASE documentation]. We will assume that you have been able to display a web page which allows the user to select input parameters for the job. On this web page you also want the user to be able to select an Open Grid Cluster that the job should be submitted to. The list below is an overview of the main steps that are needed. 11 12 1. Display a selection list with available Open Grid Clusters 13 2. The web page submits the information to a servlet 14 3. The servlet creates a job script and submit it to the cluster 15 4. When the job is completed we want to get a notification so that we can import files and other data back into BASE 16 8 17 == Enumerating Open Grid Clusters == 9 18 10 The `OpenGridService` class is typically the starting point for a lot of actions. From this class it is possible to get information about and access all cluster that has been defined in the `opengrid-config.xml` file. The service is a singleton instance. Use the `OpenGridService.getInstance()` method to get the object. Note! It is important that the service is actually running inside BASE. Check the '''Administrate->Services''' page that this is the case. 11 12 To enumerate the available Open Grid Clusters use one of the `OpenGridService.getClusters()` methods. This will return a collection of `OpenGridCluster` instances. Most methods in this class are used for getting configuration information from the `opengrid-config.xml` file. The `OpenGridCluster.getId()` method returns the internal ID of the cluster definition. It is created by combining the username, address and port of the cluster (for example, `griduser@grid.example.com:22`). The ID can the be used with `OpenGridService.getClusterById()` to directly access the same cluster later on. Other useful information can be found in the objects returned by calling `OpenGridCluster.getConnectionInfo()` and `OpenGridCluster.getConfig()`. The `OpenGridCluster.asJSONObject()` contains more or less the same information wrapped up as JSON data. This is useful for transferring information a web interface to allow a user to select a cluster to work with. 19 In this step we want to display a simple selection list on the web page that allows the user to select a pre-defined Open Grid Cluster. 20 We recommend that the list is populated by !JavaScript code which uses AJAX, a servlet and JSON to retreive the information. You'll need to implement all of this in your own extension package. 21 22 The `OpenGridService` class is typically the starting point for a lot of actions. From this class it is possible to get information about and access all clusters that have been defined in the `opengrid-config.xml` file. The service is a singleton, use the `OpenGridService.getInstance()` method to get the instance. Note! It is important that the service is actually running inside BASE. Check the '''Administrate->Services''' page that this is the case, otherwise you will get an empty service. 23 24 To enumerate the available Open Grid Clusters use one of the `OpenGridService.getClusters()` methods. This will return a collection of `OpenGridCluster` instances. Most methods in this class are used for getting configuration information from the `opengrid-config.xml` file. The `OpenGridCluster.getId()` method returns the internal ID of the cluster definition. It is created by combining the username, address and port of the cluster (for example, `griduser@grid.example.com:22`). The ID can then be used with `OpenGridService.getClusterById()` to directly access the same cluster later on. Other useful information can be found in the objects returned by calling `OpenGridCluster.getConnectionInfo()` and `OpenGridCluster.getConfig()`. The `OpenGridCluster.asJSONObject()` contains more or less the same information wrapped up as JSON data. This is useful for transferring information a web interface to allow a user to select a cluster to work with. 13 25 14 26 '''Java code in a servlet running on the BASE web server''' … … 16 28 DbControl dc = ... // We need an open DbControl from BASE 17 29 18 // Options specifying which (extra)information that we want to return30 // Options specifying which information that we want to return 19 31 // Use JSONOptions.DEFAULT to only return the minimal information 20 JSONOptions options = new JSONOptions(); 21 options.enable(JSONOption.CLUSTER_INFO); 22 options.enable(JSONOption.NODE_INFO); 32 JSONOptions options = JSONOptions.DEFAULT; 23 33 24 34 OpenGridService service = OpenGridService.getInstance(); … … 40 50 list.length = 0; 41 51 42 var clusters = response; // Response contains an array with cluster information 52 // The 'response' contains an array with cluster information 53 var clusters = response; 43 54 for (var i = 0; i < clusters.length; i++) 44 55 { … … 58 69 When creating a job script there are a few useful variables that has been set up: 59 70 60 * `{$WD}`: A randomly generated subdirectory in the `<job-folder>` directory. The directory contains the job script which is also the current working directory when the job is started and the directory that is used for communicating data to/from the BASE server. Data in this directory is preserved after the job has finished. When running post-job code this folder can be found by calling `OpenGridCluster.getWorkFolder()`. Files can be downloaded to the BASE server with `OpenGridSession.downloadFile()`, `OpenGridSession.readFile()` or `OpenGridSession.getJobFileAsString()`. The latter method is the simplest one to use for parsing out interesting data from text result files.71 * `{$WD}`: A randomly generated subdirectory in the `<job-folder>` directory. The directory contains the job script. This is also the current working directory when the job is started and the directory that is used for communicating data to/from the BASE server. Data in this directory is preserved after the job has finished. When running post-job code this folder can be found by calling `OpenGridCluster.getWorkFolder()`. Files can be transferred to the BASE server with `OpenGridSession.downloadFile()`, `OpenGridSession.readFile()` or `OpenGridSession.getJobFileAsString()`. The latter method is the simplest one to use for parsing out interesting data from text result files. 61 72 * `{$TMPDIR}`: A temporary working directory that is typically only available on the node the job is running on. Unless the job is started in debug mode, this directory is deleted soon after the has been completed. 62 * `{NSLOTS}`: The number of slots that has been assigned to this job. If the job is starting a multi-threaded analysis program it is common practice to not use more threads that what this value specifies. Note that a single node may run more than one job at the same time so using `nproc` to determine the number of threads may cause resource issues.63 64 In the example code below we assume that we have FASTQ files stored on a file server on the network. The FASTQ files are going to be aligned with Tophat and we have a wrapper script that sets all parameters except the number of threads and the location of the FASTQ files. After Tophat we have a second post-alignment script that does some stuff and save the result in a subdirectory.73 * `{NSLOTS}`: The number of slots that has been assigned to this job. If the job is starting a multi-threaded analysis program it is common practice to not use more threads that what this value specifies. Note that a single node may run more than one job at the same time and that one slot typically corresponds to one cpu core. 74 75 In the code example below we assume that we have FASTQ files stored on a file server on the network. The FASTQ files are going to be aligned with Tophat and we have a wrapper script that sets all parameters except the number of threads and the location of the FASTQ files. After Tophat we have a second post-alignment script that does some stuff and save the result in a subdirectory. 65 76 66 77 {{{ 67 78 ScriptBuilder script = new ScriptBuilder(); 68 // We do not want to hog the network so we copy all files we need to the local cluster node79 // Copy all files we need to the local cluster node 69 80 script.progress(5, "Copying data to temporary folder..."); 70 81 script.cmd("cp /path/to/fastqfiles/*fastq.gz {$TMPDIR}"); 71 82 72 // Wrapper script that calls tophat; we assume all other required parameters are set by the wrapper 83 // Wrapper script that calls tophat 84 // We assume all other required parameters are set by the wrapper 73 85 script.progress(10, "Analysing FASTQ files with Tophat..."); 74 86 script.cmd("tophat-wrapper.sh -p {$NSLOTS} {$TMPDIR}"); … … 79 91 80 92 // Now we only need to copy the results back to our file server. 81 // Remember that the {$TMPDIR} is cleaned automatically so we don't have to mess with that 93 // Remember that the {$TMPDIR} is cleaned automatically so we don't 94 // have to mess with that 82 95 script.progress(90, "Copying analyzed data back to file server"); 83 96 script.cmd("cp {$TMPDIR}/result/* /path/to/resultfiles/"); 84 97 85 // Finally, we copy the logfile to the job directory so that we can extract data from it to BASE 98 // Finally, we copy the logfile to the job directory so that 99 // we can extract data from it to BASE 86 100 script.cmd("cp {$TMPDIR}/logfile {$WD}/logfile"); 87 101 }}} … … 91 105 When the job script has been generated it is time to submit the job to the cluster. For this, you'll need a couple of more objects. The first object is a `JobConfig` instance. Use this for setting various options that are related to the Open Grid [http://gridscheduler.sourceforge.net/htmlman/htmlman1/qsub.html qsub] command. In most cases the default settings should work, but you can for example use the `JobConfig.setPriority()` to change the priority (-p) or `JobConfig.setQsubOption()` to set almost any other option. Some options are set automatically by the job submission procedure and are ignored (-S, -terse, -N, -wd, -o, -e). 92 106 93 You also need a BASE Job item that is an `OTHER` type job. It is recommended that the job is set up so that it can be identified later when notification about it's completion is sent out. Remember that during the time a job executes on the Open Grid Cluster almost anything can happen on the BASE server, including a restart. Do not rely on information that is stored in memory about jobs that has been submitted to the cluster since this information may not be there when the job completes. We recommend using one or more of `Job.setName()`, `Job.setPluginVersion()` and `Job.setItemSubtype()` to be able to identify the job in a reliable manner. We will explain why this is important in the ''Getting notified when a job completes'' section below.107 You also need a BASE Job item that is an `OTHER` type job. It is recommended that the job is set up so that it can easily be identified later when notification about it's completion is sent out. Remember that during the time a job executes on the Open Grid Cluster almost anything can happen on the BASE server, including a restart. Do not rely on information that is stored in memory about jobs that has been submitted to the cluster since this information may not be there when the job completes. We recommend using one or more of `Job.setName()`, `Job.setPluginVersion()` and `Job.setItemSubtype()` to be able to identify the job in a reliable manner. We will explain why this is important in the ''Getting notified when a job completes'' section below. 94 108 95 109 Now it is time to create a `JobDefinition` object. This is basically a compilation containing the job script, the job configuration and the BASE job item. The `JobDefinition` is also used for uploading data files that are needed by the job. Read more about this in the ''Advanced usage'' section below. … … 102 116 * Signal handlers for progress reporting is set up. 103 117 * A callback action is set up on the current `DbControl` that aborts the job if the transaction is not committed. 104 * Later on the `Job.getNode()` property is set to a string that identifies the node the job is running on. Note that this is not the pure name of the node but also include some other information from the Open Grid Cluster.118 * Later on, the `Job.getNode()` property is set to a string that identifies the node the job is running on. Note that this is not the pure name of the node but also include some other information from the Open Grid Cluster. 105 119 106 120 The `OpenGridSession.qsub()` method returns a `CmdResult` object containing a list with `JobStatus` instances. You should check that the `CmdResult.getExitStatus()` returns 0. All other values indicate an error when submitting the jobs and your transaction should be aborted. … … 108 122 {{{ 109 123 DbControl dc = .... // We need an open DbControl from BASE 110 String clusterId = ... // The ID of the cluster that the user selected in the web client124 String clusterId = ... // The ID of the cluster selected by the user 111 125 String jobScript = .... // See the previous example 112 126 … … 115 129 config.setPriority(-500); 116 130 117 // Create a new BASE job and set properties so that we can identify it later 118 Job job = Job.getNew(dc, null, null, null); // All null to create an 'OTHER' type job 131 // Create a new BASE job and set properties so that we can identify 132 // it later. The 'null' paramters creates an 'OTHER' type job. 133 Job job = Job.getNew(dc, null, null, null); 119 134 job.setName("My analysis"); 120 135 job.setPluginVersion("my-analysis-1.0"); … … 128 143 129 144 // Connect to the Open Grid Cluster 130 OpenGridCluster cluster = OpenGridService.getInstance().getClusterById(dc, clusterId); 145 OpenGridService service = OpenGridService.getInstance(); 146 OpenGridCluster cluster = service.getClusterById(dc, clusterId); 131 147 OpenGridSession session = cluster.connect(5); 132 148 try 133 149 { 134 // Submit the job and do not forget the error handling 135 CmdResult<List<JobStatus>> result = session.qsub(dc, Arrays.asList(jobDef)); 150 // Submit the job. Do not forget the error handling! 151 CmdResult<List<JobStatus>> result = 152 session.qsub(dc, Arrays.asList(jobDef)); 136 153 result.throwExceptionIfNonZeroExitStatus(); 137 154 138 // Do not forget to commit the transaction. The job will be aborted otherwise. 155 // Do not forget to commit the transaction. 156 // The job will be aborted otherwise. 139 157 dc.commit(); 140 158 } 141 159 finally 142 160 { 143 // Finally, do not forget to close the connection to the Open Grid Cluster 161 // Finally, do not forget to close the DbControl and 162 // the connection to the Open Grid Cluster 144 163 OpenGrid.close(session); 164 if (dc != null) dc.close(); 145 165 } 146 166 }}} … … 151 171 152 172 * The BASE system for requesting job progress information about external jobs has been setup to send requests to the `OpenGridService` whenever it want new information about a job. This is the reason why it is important to create a BASE job item as a proxy for the Open Grid Cluster jobs. Without it, no progress information is requested and we never get to know when the job has ended. 153 * The `OpenGridService` is polling each registered cluster at regular intervals. Typically once every minute but it may be more or less often depending on if there are any known jobs executing or not. The `OpenGridSession.qstat()` and `OpenGridSession.qacct()` methods are used for this and will detect waiting, running and completed jobs. For running jobs, the service will download the `progress` file (see `ScriptBuilder.progress()` above) and update theinformation in the BASE database.154 * Once a job has been detected as completed the service will in voke the job completion sequence. This is implemented as a custom extension point (`net.sf.basedb.opengrid.job-complete`) that will receive messages about completed jobs. Extensions that want to get notified should extend the extension point. Note that all registered extensions are notified about all jobs. It doesn't matter which extension that originally submitted the job to the cluster. Notifications are sent both for successful and failed jobs. Thus, each extension is responsible for filtering and ignoring notifications about jobs that is of no interest to them. This is why it is important to set name, plug-in version, etc. on the job when submitting it. We recommend that this filtering step is implemented in the `ActionFactory` that is registered for the `net.sf.basedb.opengrid.job-complete` extension point. Note that a single notification may handle more than onejob. Thus, the `prepareContext()` method is called once and without any information about the jobs while the the `getActions()` method is called once for every job.173 * The `OpenGridService` is polling each registered cluster at regular intervals. Typically once every minute but it may be more or less often depending on if there are any known jobs executing or not. The `OpenGridSession.qstat()` and `OpenGridSession.qacct()` methods are used for this and will detect waiting, running and completed jobs. For running jobs, the service downloads the `progress` file (see `ScriptBuilder.progress()` above) and update the progress information in the BASE database. 174 * Once a job has been detected as completed the service will initiate the job completion sequence. This is implemented as a custom extension point (`net.sf.basedb.opengrid.job-complete`) that receive messages about completed jobs. Extensions that want to get notified should extend this extension point. Note that all registered extensions are notified about all jobs. It doesn't matter which extension that originally submitted the job to the cluster. Notifications are sent both for successful and failed jobs. '''Each extension is responsible for filtering and ignoring notifications about jobs that is of no interest to them'''. This is why it is important to set name, plug-in version, etc. on the job when submitting it. We recommend that this filtering step is done in the `ActionFactory` implementation that is registered for the `net.sf.basedb.opengrid.job-complete` extension point. Note that a single notification may handle more than one completed job. Thus, the `prepareContext()` method is called once and without any information about the jobs while the the `getActions()` method is called once for every job. 155 175 156 176 {{{ … … 165 185 public boolean prepareContext(InvokationContext context) 166 186 { 167 // Always true since we do not know anything about the job(s) that have been completed 187 // Always true since we do not know anything 188 // about the job(s) that have been completed 168 189 return true; 169 190 } … … 207 228 }}} 208 229 209 The `ActionFactory.getActions()` implementation should not do anything except checking if the job should be handled or not. It should return `null` if it is not interested in the job, and an implementation of the `JobCompletionHandler` interface otherwise. This interface defines a single method: `JobCompletionHandler.jobCompleted(SessionControl, OpenGridSession, Job, JobStaus)`. The `Job` and `JobStatus` objects are the same as in the `ActionFactory`, but in this method you also get access to a `SessionControl` instance and anconnected `OpenGridSession` to the cluster the job was running on. The `OpenGridSession` can for example be used to download and parse result files. The `SessionControl` can be used to access BASE and update items and/or annotations. The good thing about the `SessionControl` is that it has been automatically configured so that the owner of the job is already logged in and a project (if any is specified on the job) is set as the active project (in the `ActionFactory` the session control is a generic one with the root user logged in).230 The `ActionFactory.getActions()` implementation should not do anything except checking if the job is of interest or not. It should return `null` if it is not interested in the job, and an implementation of the `JobCompletionHandler` interface otherwise. This interface defines a single method: `JobCompletionHandler.jobCompleted(SessionControl, OpenGridSession, Job, JobStaus)`. The `Job` and `JobStatus` objects are the same as in the `ActionFactory`, but in this method you also get access to a `SessionControl` instance and a connected `OpenGridSession` to the cluster the job was running on. The `OpenGridSession` can for example be used to download and parse result files. The `SessionControl` can be used to access BASE and update items and/or annotations. The good thing about the `SessionControl` is that it has been automatically configured so that the owner of the job is already logged in and a project (if any is specified on the job) is set as the active project (in the `ActionFactory` the session control is a generic one with the root user logged in). 210 231 211 232 Do not update the `Job` item since this may interfere with the updates to the job made by the Open Grid extension. The method may return a string to set the status message of the job, or throw an exception to set the job status to ERROR. … … 220 241 221 242 @Override 222 public String jobCompleted(SessionControl sc, OpenGridSession session, Job job, JobStatus status) 243 public String jobCompleted(SessionControl sc, OpenGridSession session, 244 Job job, JobStatus status) 223 245 { 224 246 String jobName = status.getName(); … … 242 264 == Aborting jobs == 243 265 244 This is automatically handled by the Open Grid extension by the same mechanism that is used for progress reporting. The abort is handled by calling the `OpenGridSession.qdel()` method. After that the job is handled just as if any other error had occurred, which means that an extension may take some kind of action when this happens. Tip! Check for exit code 137, which means that the job was aborted by the user.266 This is automatically handled by the Open Grid extension by the same mechanism that is used for progress reporting. The abort is handled by calling the `OpenGridSession.qdel()` method. After that the job is handled just as if any other error had occurred, eg. the job completion sequence is initiated. Extensions that are interested in manually aborted jobs should check for `JobStatus.getStatus() == Job.Status.ERROR` and `JobStatus.getExitCode() == 137`, which indicates that the job was aborted by the user. 245 267 246 268 == Advanced usage == … … 250 272 The `JobDefinition` that is used for submitting a job to an Open Grid Cluster has the ability to upload files that are needed for the job. This is done by calling the `JobDefinition.addFile()` which need an `UploadSource` object as a parameter. The `UploadSource` is an interface but we have provided several implementations that wraps, for example, a `String`, a BASE `File` item or an `InputStream`. 251 273 252 Note that calling the `JobDefinition.addFile()` method doesn't start the upload immediately. The upload happens in the `OpenGridSession.qsub()` method. The file is placed in the subfolder to the `<job-folder>` that has been created for the job .274 Note that calling the `JobDefinition.addFile()` method doesn't start the upload immediately. The upload happens in the `OpenGridSession.qsub()` method. The file is placed in the subfolder to the `<job-folder>` that has been created for the job (the `{$WD}` folder). 253 275 254 276 === Connecting to non-Open Grid servers === 255 277 256 The connection made to Open Grid Clusters is a regular SSH connection. There is really nothing that is special about the connection itself. This means that it is possible to connect to more or less any server that supports SSH. It doesn't matter if the server sis running an Open Grid Cluster or not. Note that servers that are defined in the `opengrid-config.xml` are expected to be Open Grid Cluster servers and the `OpenGridService` implementation will try to call Open Grid commands on them.257 258 However, it is possible to programmatically create a `ConnectionInfo` instance and use it for creating a `RemoteHost` object. From this you can connect to the server by withthe `RemoteHost.connect()` method which returns a `RemoteSession` object. It is very similar to what can be done with `OpenGridCluster`/`OpenGridSession` objects, except that the special methods for calling Open Grid Cluster commands are not available.259 260 Tip! It is possible to create `ConnectionInfo` instance from a BASE `FileServer` item (assuming that the file server contains all required information for connecting via SSH: host, fingerprint, username and password).278 The connection made to Open Grid Clusters is a regular SSH connection. There is really nothing that is special about the connection itself. This means that it is possible to connect to more or less any server that supports SSH. It doesn't matter if the server is running an Open Grid Cluster or not. Note that servers that are defined in the `opengrid-config.xml` are expected to be Open Grid Cluster servers and the `OpenGridService` implementation will try to call Open Grid commands on them. 279 280 However, it is possible to programmatically create a `ConnectionInfo` instance and use it for creating a `RemoteHost` object. With this you can connect to the server by calling the `RemoteHost.connect()` method which returns a `RemoteSession` object. It is very similar to what can be done with `OpenGridCluster`/`OpenGridSession` objects, except that the special methods for calling Open Grid Cluster commands are not available. 281 282 Tip! It is possible to create a `ConnectionInfo` instance from a BASE `FileServer` item (assuming that the file server contains all required information for connecting via SSH: host, fingerprint, username and password). 261 283 262 284 === Tracking non-Open Grid jobs === 263 285 264 Sometimes there are other things going on that are not Open Grid jobs that would be interesting to track. One example is the sequencing progress of a sequencer machine. In this case we want to know when the sequencing has been completed and then start analysis jobs (as Open Grid Cluster jobs). A simple bash script has been implemented (http://baseplugins.thep.lu.se/browser/other/pipeline/trunk/nextseq_status.sh) that checks if all result files from the sequencing are present on the file server or not. We want to run this script at regular intervals and then when all data is present, run some checks and start the analysis jobs. There are three steps to consider altogether:265 266 * The sequencing process should be represented by a BASE job item as a proxy. Progress reporting need to be setup using the extension mechanism implemented in the BASE core. This need to implemented completely by the other extension. It is not possible to re-use the setup that this extension is using.286 Sometimes there are other things going on that are not Open Grid jobs that would be interesting to track. One example is the sequencing progress of a sequencer machine. In this case we want to know when the sequencing has been completed and then start analysis jobs (as Open Grid Cluster jobs). A simple bash script has been implemented (http://baseplugins.thep.lu.se/browser/other/pipeline/trunk/nextseq_status.sh) that checks if all result files from the sequencing are present on the file server or not. We want to run this script at regular intervals. When all data is present, run some checks and start the analysis jobs. There are three steps to consider: 287 288 * The sequencing process should be represented by a BASE job item as a proxy. Progress reporting need to be setup using the extension mechanism implemented in the BASE core. This need to be implemented completely by the other extension. It is not possible to re-use the setup the Open Grid package uses. In the code example below the `SequencingSignalHandler` class is assumed to take care of this. 267 289 268 290 {{{ 269 291 String barcode = ... // Something that identifies the current sequencing 270 String clusterId = ... // The Open Grid Cluster we will use to check status 271 272 // Create a new BASE job and set properties so that we can identify it later 273 Job job = Job.getNew(dc, null, null, null); // All null to create an 'OTHER' type job 292 String clusterId = ... // The Open Grid Cluster we use to check status 293 294 // Create a new BASE job and set properties so that we can identify 295 // it later. 'null' parameters to create an 'OTHER' type job 296 Job job = Job.getNew(dc, null, null, null); 274 297 job.setName("My sequencing"); 275 298 job.setPluginVersion("my-sequencing-1.0"); … … 285 308 }}} 286 309 287 * Once a request for a status update is received by the handler it should call `OpenGridService.asyncJobStatusUpdate(JobIdentifier, JobStatusUpdater)`. The Open Grid extension will then call the `JobStatusUpdater.getJobStatus()` during the next async processing cycle. In the example above, the `JobStatusUpdater` implementation should call the bash script to see how far the sequencing has come and then report that back in a `JobStatus` object. The Open Grid extension will take the responsibility of updatingthe Job item in BASE.310 * Once a request for a status update is received by the `SequencingSignalHandler` it should call `OpenGridService.asyncJobStatusUpdate(JobIdentifier, JobStatusUpdater)`. The Open Grid extension will then call the `JobStatusUpdater.getJobStatus()` during the next asynchronous processing cycle. In the example above, the `JobStatusUpdater` implementation should call the bash script to see how far the sequencing has come and then report that back in a `JobStatus` object. The Open Grid extension will take the responsibility of updating the Job item in BASE. It might be tempting to check the sequencing status directly from the signal handler, but this is not recommended since the signals may arrive quite often. The asynchronous approach is preferable and also gives you automatic updates of the Job item in BASE. 288 311 289 312 {{{ … … 297 320 public void handleSignal(Signal signal) 298 321 { 322 Job job = ... // The BASE Job item 299 323 String barcode = job.getExternalId(); 300 324 String clusterId = job.getServer(); … … 304 328 } 305 329 306 // Execute the bash script and parse out the result to a JobStatus object 307 // The code shown here has been simplified... 308 @Override 309 public JobStatus getJobStatus(OpenGridSession session, JobIdentifier jobId) 330 // Execute the bash script and parse out the result to a 331 // JobStatus object. The code shown here has been simplified... 332 @Override 333 public JobStatus getJobStatus(OpenGridSession session, 334 JobIdentifier jobId) 310 335 { 311 336 String barcode = jobId.getClusterJobId(); 312 337 313 CmdResult<String> sequencingStatus = session.executeCmd("nextseq_status.sh " + barcode, 5); 338 CmdResult<String> sequencingStatus = 339 session.executeCmd("nextseq_status.sh " + barcode, 5); 314 340 if (sequencingStatus .getExitStatus() != 0) 315 341 { 316 logger.error("nextseq_status.sh failed: " + sequencingStatus .toString());342 logger.error("nextseq_status.sh failed: " + sequencingStatus); 317 343 return null; 318 344 } … … 332 358 }}} 333 359 334 * When the sequencing has been completed (` JobStatus == DONE`) this is detected usingthe normal job completion routines in the Open Grid extension which will notify all registered `JobCompletionHandler` implementations. The other extension simply need to extend the `JobCompletionHandler` implementation to be able to detect the sequencing job and then do whatever needs to be done with that.360 * When the sequencing has been completed (`status == Job.Status.DONE`) the normal job completion routines in the Open Grid extension which will notify all registered `JobCompletionHandler` implementations. The other extension simply need to extend the `JobCompletionHandler` implementation to be able to detect the sequencing job and then do whatever needs to be done with that. 335 361 336 362 {{{ … … 360 386 === Reacting to configuration changes === 361 387 362 The `opengrid-config.xml` is normally read in fully when the Open Grid Scheduler service extension is started. Changes to the configuration file are not applied until the service is re-started. For some extensions it may be critical to be able to detect when this happens. Luckily, everything that is needed is already built into the BASE core API. Extensions that need to know when the Open Grid Scheduler service is stoppped or started simply need to register an event handler with the manager in BASE. The event handler should listen to `SERVICE_STOPPED` or `SERVICE_STARTED` events for the `net.sf.basedb.opengrid.service` extension. 363 364 {{{ 365 // We need a filter that listens for SERVICE_STARTED event related to the Open Grid Scheduler service 388 The `opengrid-config.xml` is parsed and loaded into memory when the Open Grid Scheduler service extension is started. Changes to the configuration file are not applied until the service is re-started. For some extensions it may be critical to be able to detect when this happens. Luckily, everything that is needed is already built into the BASE core API. Extensions that need to know when the Open Grid Scheduler service is stoppped or started simply need to register an event handler with the manager in BASE. The event handler should listen to `SERVICE_STOPPED` or `SERVICE_STARTED` events for the `net.sf.basedb.opengrid.service` extension. 389 390 {{{ 391 // We need a filter that listens for SERVICE_STARTED event 392 // related to the Open Grid Scheduler service 366 393 EventFilter serviceStarted = new ExtensionEventFilter( 367 394 "net.sf.basedb.opengrid.service", Services.SERVICE_STARTED); 368 395 369 // We need to implement an event handler which, for example, reloads our own configuration file 396 // We need to implement an event handler which, for example, 397 // reloads our own configuration file 370 398 EventHandler handler = new MyEventHandler(); 371 399 372 400 // Register the event handler with BASE 373 // The classloader parameter is important for not leaking memory401 // The ClassLoader parameter is important for not leaking memory 374 402 // in case this extension is updated or uninstalled 375 403 Registry registry = Application.getExtensionsManager().getRegistry(); 376 registry.registerEventHandler(handler, serviceStarted , this.getClass().getClassLoader()); 404 ClassLoader loader = this.getClass().getClassLoader(); 405 registry.registerEventHandler(handler, serviceStarted , loader); 377 406 }}} 378 407