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
153 private static Scheduler sched = null;
154
155 private StyxDirectory root;
156 private StyxDirectory instancesDir;
157 private SGSConfig sgsConfig;
158
159 static
160 {
161 try
162 {
163
164
165 sched = new StdSchedulerFactory().getScheduler();
166 sched.start();
167 log.info("Garbage-collector for SGS instances started");
168
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
178 log.fatal("Could not get instance of SHA-1 algorithm");
179
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
194 this.instancesDir = new StyxDirectory("instances", 0555);
195 this.root.addChild(this.instancesDir);
196
197
198
199
200
201
202
203
204 this.root.addChild(new AsyncStyxFile(this.instancesDir, ".instances"));
205
206
207 InMemoryFile configFile = new InMemoryFile("config", 0444);
208 configFile.setContents(sgsConfig.getConfigXMLForClient());
209 this.root.addChild(configFile);
210
211
212 StyxDirectory docDir = new StyxDirectory("docs", 0555);
213
214 this.root.addChild(docDir);
215
216 InMemoryFile descFile = new InMemoryFile("description", 0444);
217 descFile.setContents(sgsConfig.getDescription());
218 docDir.addChild(descFile);
219
220 Vector docFiles = sgsConfig.getDocFiles();
221 for (int i = 0; i < docFiles.size(); i++)
222 {
223 DocFile docFile = (DocFile)docFiles.get(i);
224
225
226 StyxFile sf = FileOnDisk.getFileOrDirectoryOnDisk(docFile.getLocation());
227
228 sf.setName(docFile.getName());
229
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
279 String hostAddress = this.sgsConfig.getServerConfig().getHostAddress();
280 int port = this.sgsConfig.getServerConfig().getPort();
281 return "styx://" + hostAddress + ":" + port + newInstance.getFullPath();
282 }
283
284
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
306 String id = getUniqueInstanceID();
307
308
309 String newInstanceURL = newInstance(id);
310 byte[] msgBytes = StyxUtils.strToUTF8(newInstanceURL);
311
312
313
314 if (count < msgBytes.length)
315 {
316
317
318
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
339 String jobID = this.root.getName() + "/" + instance.getName();
340 log.debug("jobID = " + jobID);
341 try
342 {
343
344 if (sched.deleteJob(jobID, null))
345 {
346 log.debug("Deleted previous instance of job " + jobID);
347 }
348
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
371 SGSServerConfig config = new SGSServerConfig(args[0]);
372
373 StyxDirectory root = new StyxDirectory("/");
374
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
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 }