Changes between Version 12 and Version 13 of net.sf.basedb.opengrid/using


Ignore:
Timestamp:
Jan 24, 2017, 11:58:42 AM (7 years ago)
Author:
Nicklas Nordborg
Comment:

Several updates to the documentation

Legend:

Unmodified
Added
Removed
Modified
  • net.sf.basedb.opengrid/using

    v12 v13  
    11= How to use the Open Grid Scheduler package API =
    2 In this document 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.
     2In 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.
    33
    44[[PageOutline(2-3,,inline,numbered)]]
     
    66See also the [/chrome/site/net.sf.basedb.opengrid/api/index.html Javadoc API] documentation.
    77
     8== The use case ==
     9
     10The 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
    817== Enumerating Open Grid Clusters ==
    918
    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.
     19In 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.
     20We 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
     22The `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
     24To 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.
    1325
    1426'''Java code in a servlet running on the BASE web server'''
     
    1628DbControl dc = ... // We need an open DbControl from BASE
    1729
    18 // Options specifying which (extra) information that we want to return
     30// Options specifying which information that we want to return
    1931// 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);
     32JSONOptions options = JSONOptions.DEFAULT;
    2333
    2434OpenGridService service = OpenGridService.getInstance();
     
    4050list.length = 0;
    4151
    42 var clusters = response; // Response contains an array with cluster information
     52// The 'response' contains an array with cluster information
     53var clusters = response;
    4354for (var i = 0; i < clusters.length; i++)
    4455{
     
    5869When creating a job script there are a few useful variables that has been set up:
    5970
    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.
    6172 * `{$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
     75In 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.
    6576
    6677{{{
    6778ScriptBuilder script = new ScriptBuilder();
    68 // We do not want to hog the network so we copy all files we need to the local cluster node
     79// Copy all files we need to the local cluster node
    6980script.progress(5, "Copying data to temporary folder...");
    7081script.cmd("cp /path/to/fastqfiles/*fastq.gz {$TMPDIR}");
    7182
    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
    7385script.progress(10, "Analysing FASTQ files with Tophat...");
    7486script.cmd("tophat-wrapper.sh -p {$NSLOTS} {$TMPDIR}");
     
    7991
    8092// 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
    8295script.progress(90, "Copying analyzed data back to file server");
    8396script.cmd("cp {$TMPDIR}/result/* /path/to/resultfiles/");
    8497
    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
    86100script.cmd("cp {$TMPDIR}/logfile {$WD}/logfile");
    87101}}}
     
    91105When 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).
    92106
    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.
     107You 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.
    94108
    95109Now 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.
     
    102116 * Signal handlers for progress reporting is set up.
    103117 * 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.
    105119
    106120The `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.
     
    108122{{{
    109123DbControl dc = ....     // We need an open DbControl from BASE
    110 String clusterId = ...  // The ID of the cluster that the user selected in the web client
     124String clusterId = ...  // The ID of the cluster selected by the user
    111125String jobScript = .... // See the previous example
    112126
     
    115129config.setPriority(-500);
    116130
    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.
     133Job job = Job.getNew(dc, null, null, null);
    119134job.setName("My analysis");
    120135job.setPluginVersion("my-analysis-1.0");
     
    128143
    129144// Connect to the Open Grid Cluster
    130 OpenGridCluster cluster = OpenGridService.getInstance().getClusterById(dc, clusterId);
     145OpenGridService service = OpenGridService.getInstance();
     146OpenGridCluster cluster = service.getClusterById(dc, clusterId);
    131147OpenGridSession session = cluster.connect(5);
    132148try
    133149{
    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));
    136153   result.throwExceptionIfNonZeroExitStatus();
    137154
    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.
    139157   dc.commit();
    140158}
    141159finally
    142160{
    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
    144163   OpenGrid.close(session);
     164   if (dc != null) dc.close();
    145165}
    146166}}}
     
    151171
    152172 * 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 the information in the BASE database.
    154  * Once a job has been detected as completed the service will invoke 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 one 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.
     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.
    155175
    156176{{{
     
    165185   public boolean prepareContext(InvokationContext context)
    166186   {
    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
    168189      return true;
    169190   }
     
    207228}}}
    208229
    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 an 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).
     230The `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).
    210231
    211232Do 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.
     
    220241       
    221242   @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)
    223245   {
    224246      String jobName = status.getName();
     
    242264== Aborting jobs ==
    243265
    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.
     266This 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.
    245267
    246268== Advanced usage ==
     
    250272The `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`.
    251273
    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.
     274Note 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).
    253275
    254276=== Connecting to non-Open Grid servers ===
    255277
    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 servers 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.
    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 with 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.
    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).
     278The 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
     280However, 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
     282Tip! 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).
    261283
    262284=== Tracking non-Open Grid jobs ===
    263285
    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.
     286Sometimes 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.
    267289
    268290{{{
    269291String 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
     292String 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
     296Job job = Job.getNew(dc, null, null, null);
    274297job.setName("My sequencing");
    275298job.setPluginVersion("my-sequencing-1.0");
     
    285308}}}
    286309
    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 updating the 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.
    288311
    289312{{{
     
    297320   public void handleSignal(Signal signal)
    298321   {
     322      Job job = ... // The BASE Job item
    299323      String barcode = job.getExternalId();
    300324      String clusterId = job.getServer();
     
    304328   }
    305329
    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)
    310335   {
    311336      String barcode = jobId.getClusterJobId();
    312337               
    313       CmdResult<String> sequencingStatus = session.executeCmd("nextseq_status.sh " + barcode, 5);
     338      CmdResult<String> sequencingStatus =
     339         session.executeCmd("nextseq_status.sh " + barcode, 5);
    314340      if (sequencingStatus .getExitStatus() != 0)
    315341      {
    316          logger.error("nextseq_status.sh failed: " + sequencingStatus.toString());
     342         logger.error("nextseq_status.sh failed: " + sequencingStatus);
    317343         return null;
    318344      }
     
    332358}}}
    333359
    334  * When the sequencing has been completed (`JobStatus == DONE`) this is detected using 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.
     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.
    335361
    336362{{{
     
    360386=== Reacting to configuration changes ===
    361387
    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
     388The `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
    366393EventFilter serviceStarted = new ExtensionEventFilter(
    367394   "net.sf.basedb.opengrid.service", Services.SERVICE_STARTED);
    368395
    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
    370398EventHandler handler = new MyEventHandler();
    371399
    372400// Register the event handler with BASE
    373 // The classloader parameter is important for not leaking memory
     401// The ClassLoader parameter is important for not leaking memory
    374402// in case this extension is updated or uninstalled
    375403Registry registry = Application.getExtensionsManager().getRegistry();
    376 registry.registerEventHandler(handler, serviceStarted , this.getClass().getClassLoader());
     404ClassLoader loader = this.getClass().getClassLoader();
     405registry.registerEventHandler(handler, serviceStarted , loader);
    377406}}}
    378407