View Javadoc

1   /***
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.gridservice.server;
30  
31  import java.util.Vector;
32  import java.util.Iterator;
33  import java.util.Date;
34  import javax.net.ssl.SSLContext;
35  
36  import java.rmi.server.UID;
37  import java.security.MessageDigest;
38  import java.security.NoSuchAlgorithmException;
39  
40  import org.apache.mina.common.ByteBuffer;
41  import org.apache.log4j.Logger;
42  
43  import org.quartz.Scheduler;
44  import org.quartz.SchedulerException;
45  import org.quartz.impl.StdSchedulerFactory;
46  import org.quartz.Trigger;
47  import org.quartz.JobDetail;
48  
49  import uk.ac.rdg.resc.jstyx.server.StyxFile;
50  import uk.ac.rdg.resc.jstyx.server.AsyncStyxFile;
51  import uk.ac.rdg.resc.jstyx.server.StyxDirectory;
52  import uk.ac.rdg.resc.jstyx.server.StyxFileClient;
53  import uk.ac.rdg.resc.jstyx.server.StyxServer;
54  import uk.ac.rdg.resc.jstyx.server.FileOnDisk;
55  import uk.ac.rdg.resc.jstyx.server.InMemoryFile;
56  
57  import uk.ac.rdg.resc.jstyx.ssl.JonSSLContextFactory;
58  
59  import uk.ac.rdg.resc.jstyx.StyxException;
60  import uk.ac.rdg.resc.jstyx.StyxUtils;
61  
62  import uk.ac.rdg.resc.jstyx.gridservice.config.SGSConfig;
63  import uk.ac.rdg.resc.jstyx.gridservice.config.DocFile;
64  
65  /***
66   * Class representing a Styx Grid Service
67   *
68   * @author Jon Blower
69   * $Revision: 601 $
70   * $Date: 2006-03-20 17:51:50 +0000 (Mon, 20 Mar 2006) $
71   * $Log$
72   * Revision 1.22  2006/03/20 17:51:47  jonblower
73   * Adding authentication to base JStyx system
74   *
75   * Revision 1.21  2006/01/05 12:09:15  jonblower
76   * Restructured configuration to give default values for server settings
77   *
78   * Revision 1.20  2006/01/05 08:57:38  jonblower
79   * Created instance IDs that are unique for all time
80   *
81   * Revision 1.19  2006/01/04 16:45:29  jonblower
82   * Implemented automatic termination of SGS instances using Quartz scheduler
83   *
84   * Revision 1.18  2005/12/07 08:55:04  jonblower
85   * Temporarily changed clone file behaviour to return ID rather than full URL to new service instances
86   *
87   * Revision 1.17  2005/12/01 08:37:23  jonblower
88   * Changed clone file behaviour to return URL to new service instance instead of just the ID
89   *
90   * Revision 1.16  2005/11/09 17:50:45  jonblower
91   * Added config file to namespace
92   *
93   * Revision 1.15  2005/07/29 16:56:07  jonblower
94   * Implementing reading command line asynchronously
95   *
96   * Revision 1.13  2005/05/19 18:42:07  jonblower
97   * Implementing specification of input files required by SGS
98   *
99   * Revision 1.12  2005/05/17 15:10:46  jonblower
100  * Changed structure of SGS to put instances in a directory of their own
101  *
102  * Revision 1.11  2005/05/13 16:49:34  jonblower
103  * Coded dynamic detection and display of service data, also included streams in config file
104  *
105  * Revision 1.10  2005/05/11 15:14:31  jonblower
106  * Implemented more flexible definition of service data elements
107  *
108  * Revision 1.9  2005/05/11 13:45:19  jonblower
109  * Converted SGS config code to use dom4j and Jaxen for XML parsing
110  *
111  * Revision 1.8  2005/05/09 07:13:51  jonblower
112  * Changed getFileOnDisk() to getFileOrDirectoryOnDisk()
113  *
114  * Revision 1.7  2005/04/28 08:11:14  jonblower
115  * Modified permissions handling in documentation directory of SGS
116  *
117  * Revision 1.6  2005/04/27 16:11:43  jonblower
118  * Added capability to add documentation files to SGS namespace
119  *
120  * Revision 1.5  2005/03/24 14:47:47  jonblower
121  * Provided default read() and write() methods for StyxFile so it is no longer abstract
122  *
123  * Revision 1.4  2005/03/24 09:48:31  jonblower
124  * Changed 'count' from long to int throughout for reading and writing
125  *
126  * Revision 1.3  2005/03/24 07:57:41  jonblower
127  * Improved code for reading SSL info from SGSconfig file and included parameter information for the Grid Services in the config file
128  *
129  * Revision 1.2  2005/03/22 17:45:25  jonblower
130  * Now reads SSL switch from config file
131  *
132  * Revision 1.1  2005/03/16 22:16:44  jonblower
133  * Added Styx Grid Service classes to core module
134  *
135  * Revision 1.3  2005/03/16 17:59:35  jonblower
136  * Changed following changes to core JStyx library (replacement of java.nio.ByteBuffers with MINA's ByteBuffers)
137  *
138  * Revision 1.2  2005/02/21 18:13:23  jonblower
139  * Following changes to core JStyx library
140  *
141  * Revision 1.1  2005/02/16 19:22:32  jonblower
142  * Commit adding of SGS files to CVS
143  *
144  */
145 public class StyxGridService
146 {
147     
148     private static final Logger log = Logger.getLogger(StyxGridService.class);
149     
150     private static MessageDigest sha = null;
151     
152     // Quartz scheduler that is used to garbage-collect SGS instances
153     private static Scheduler sched = null;
154     
155     private StyxDirectory root; // The root of the Grid Service
156     private StyxDirectory instancesDir; // Directory to hold SGS instances
157     private SGSConfig sgsConfig; // The configuration for the SGS and its instances
158     
159     static
160     {
161         try
162         {
163             // Create and start the garbage-collector (Quartz scheduler) that
164             // automatically deletes SGS instances
165             sched = new StdSchedulerFactory().getScheduler();
166             sched.start();
167             log.info("Garbage-collector for SGS instances started");
168             // Create an instance of the SHA-1 algorithm
169             sha = MessageDigest.getInstance("SHA-1");
170         }
171         catch(SchedulerException se)
172         {
173             log.error("Could not start garbage-collector for SGS instances");
174         }
175         catch(NoSuchAlgorithmException nsae)
176         {
177             // Should never happen
178             log.fatal("Could not get instance of SHA-1 algorithm");
179             // TODO: exit VM?
180         }
181     }
182     
183     /***
184      * Creates a new StyxGridService.
185      * @param sgsConfig Object containing the configuration of the SGS
186      */
187     public StyxGridService(SGSConfig sgsConfig) throws StyxException
188     {
189         log.debug("Creating StyxGridService called " + sgsConfig.getName());
190         this.root = new StyxDirectory(sgsConfig.getName());
191         this.root.addChild(new CloneFile());
192         
193         // Add read-only directory for the SGS instances
194         this.instancesDir = new StyxDirectory("instances", 0555);
195         this.root.addChild(this.instancesDir);
196         
197         // The ".instances" file is an asynchronous interface to the contents of
198         // the instances directory of the SGS.  The first time this file is read by
199         // a client it will return the files representing the instances of this
200         // SGS.  The second time it is read by the same
201         // client the reply will only arrive when the contents have changed. 
202         // This allows GUIs to automatically update when new SGS instances are
203         // created or destroyed.
204         this.root.addChild(new AsyncStyxFile(this.instancesDir, ".instances"));
205         
206         // Add the XML that was used to create this SGS as a read-only InMemoryFile
207         InMemoryFile configFile = new InMemoryFile("config", 0444);
208         configFile.setContents(sgsConfig.getConfigXMLForClient());
209         this.root.addChild(configFile);
210         
211         // Create documentation tree
212         StyxDirectory docDir = new StyxDirectory("docs", 0555);
213         //docDir.setReadOnly(); TODO Why doesn't this line work?
214         this.root.addChild(docDir);
215         // Add the "description" file as a read-only InMemoryFile
216         InMemoryFile descFile = new InMemoryFile("description", 0444);
217         descFile.setContents(sgsConfig.getDescription());
218         docDir.addChild(descFile);
219         // Add all the documentation files specified in the SGSConfig object
220         Vector docFiles = sgsConfig.getDocFiles();
221         for (int i = 0; i < docFiles.size(); i++)
222         {
223             DocFile docFile = (DocFile)docFiles.get(i);
224             // Create a FileOnDisk Or DirectoryOnDisk that wraps the documentation
225             // file or directory.
226             StyxFile sf = FileOnDisk.getFileOrDirectoryOnDisk(docFile.getLocation());
227             // Set the name of the file or directory
228             sf.setName(docFile.getName());
229             // Add the file to the doc directory
230             docDir.addChild(sf);
231         }
232         
233         this.sgsConfig = sgsConfig;
234     }
235     
236     public StyxDirectory getRoot()
237     {
238         return this.root;
239     }
240     
241     /***
242      * @return a unique ID for an instance.  This is the SHA-1 hash of a
243      * generated java.rmi.server.UID
244      */
245     private static String getUniqueInstanceID()
246     {
247         String uid = new UID().toString();
248         byte[] digest = sha.digest(uid.getBytes());
249         return hexEncode(digest);
250     }
251     
252     /***
253      * @return the hex encoding of the given byte array
254      */
255     private static String hexEncode(byte[] aInput)
256     {
257         StringBuffer result = new StringBuffer();
258         char[] digits = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
259         for (int idx = 0; idx < aInput.length; idx++)
260         {
261             byte b = aInput[idx];
262             result.append( digits[ (b&0xf0) >> 4 ] );
263             result.append( digits[ b&0x0f] );
264         }
265         return result.toString();
266     }
267     
268     /***
269      * Creates a new StyxGridServiceInstance, adds it to the "instances"
270      * directory and returns its full URL.  In future this might create a new instance
271      * on another SGS server for purposes of load balancing.
272      */
273     private String newInstance(String id) throws StyxException
274     {
275         StyxDirectory newInstance = new StyxGridServiceInstance(this, id,
276             this.sgsConfig);
277         this.instancesDir.addChild(newInstance);
278         // Return the full URL to the new service instance
279         String hostAddress = this.sgsConfig.getServerConfig().getHostAddress();
280         int port = this.sgsConfig.getServerConfig().getPort();
281         return "styx://" + hostAddress + ":" + port + newInstance.getFullPath();
282     }
283     
284     // The clone file - reading this file creates a new instance of the Grid Service
285     private class CloneFile extends StyxFile
286     {
287         public CloneFile() throws StyxException
288         {
289             super("clone", 0444);
290         }
291         
292         /***
293          * Reading the clone file causes a new instance of the Grid Service to
294          * be created, and returns the URL to the base of the new instance. The client must request
295          * enough bytes to return the service ID, otherwise a StyxException will
296          * be thrown.
297          * @todo Could also create the SGS when this file is opened, and leave
298          * the file handle open on the ctl file of the new Service
299          */
300         public void read(StyxFileClient client, long offset, int count, int tag)
301             throws StyxException
302         {
303             if (offset == 0)
304             {
305                 // Generate a unique ID for this instance
306                 String id = getUniqueInstanceID();
307                 
308                 // Create a new StyxGridServiceInstance and return its URL
309                 String newInstanceURL = newInstance(id);
310                 byte[] msgBytes = StyxUtils.strToUTF8(newInstanceURL);
311                 // Check that the client has requested enough bytes for the reply
312                 // TODO: we can check this in advance, since we know how long the
313                 // reply will be
314                 if (count < msgBytes.length)
315                 {
316                     // TODO: delete the new instance, or perhaps just return
317                     // the requested number of bytes, storing the message bytes
318                     // for later
319                     throw new StyxException("must request at least " + msgBytes.length + " bytes.");
320                 }
321                 replyRead(client, msgBytes, tag);
322             }
323             else
324             {
325                 replyRead(client, new byte[0], tag);
326             }
327         }        
328     }
329     
330     /***
331      * Schedules the termination of the given SGS instance at the given time.  If
332      * the scheduler has not been initialized (which is very unlikely to happen)
333      * this method will return silently without doing anything.
334      */
335     public void scheduleTermination(StyxGridServiceInstance instance,
336         Date terminationTime)
337     {
338         // Create a job ID that is unique to this instance
339         String jobID = this.root.getName() + "/" + instance.getName();
340         log.debug("jobID = " + jobID);
341         try
342         {
343             // Delete any previous instances of the job
344             if (sched.deleteJob(jobID, null))
345             {
346                 log.debug("Deleted previous instance of job " + jobID);
347             }
348             // Create a new job
349             Trigger trigger = new InstanceTerminatorTrigger(instance, jobID, terminationTime);
350             JobDetail jobDetail = new JobDetail(jobID, null, InstanceTerminatorJob.class);
351             sched.scheduleJob(jobDetail, trigger);
352             log.debug("Scheduled " + jobID + " for termination at " + terminationTime);
353         }
354         catch (SchedulerException se)
355         {
356             log.error("Error scheduling instance for termination: " + se.getMessage());
357         }
358     }
359     
360     public static void main (String[] args)
361     {
362         System.setProperty("java.protocol.handler.pkgs", "uk.ac.rdg.resc.jstyx.client.protocol");
363         if (args.length != 1)
364         {
365             System.err.println("Usage: StyxGridService <config file>");
366             return;
367         }
368         try
369         {
370             // Create the server configuration from the given XML config file
371             SGSServerConfig config = new SGSServerConfig(args[0]);            
372             // Create the root directory
373             StyxDirectory root = new StyxDirectory("/");
374             // Add the SGSs to this directory
375             Iterator it = config.getSGSConfigInfo();
376             while(it.hasNext())
377             {
378                 SGSConfig conf = (SGSConfig)it.next();
379                 root.addChild(new StyxGridService(conf).getRoot());
380             }
381             // Start the server: unsecured for the moment
382             int port = config.getPort();
383             new StyxServer(port, root).start();
384             System.out.println("Started StyxGridServices, listening on port " + port);
385         }
386         catch(Exception e)
387         {
388             e.printStackTrace();
389             return;
390         }
391     }
392     
393 }