001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015: package org.griphyn.vdl.invocation;
016:
017: import java.net.*;
018: import java.io.*;
019: import java.sql.SQLException;
020: import java.util.Iterator;
021:
022: import org.griphyn.vdl.parser.InvocationParser;
023: import org.griphyn.vdl.toolkit.*;
024: import org.griphyn.vdl.dbschema.*;
025: import org.griphyn.vdl.util.Logging;
026: import org.griphyn.vdl.util.ChimeraProperties;
027: import org.griphyn.vdl.directive.*;
028:
029: import org.apache.log4j.Logger;
030: import org.apache.log4j.Level;
031: import org.apache.log4j.ConsoleAppender;
032: import org.apache.log4j.PatternLayout;
033:
034: public class SimpleServer extends Toolkit {
035: private static final int port = 65533;
036:
037: public static boolean c_terminate = false;
038: public static Logger c_logger = null;
039:
040: private boolean m_emptyFail = true;
041: private boolean m_noDBase;
042:
043: DatabaseSchema m_dbschema;
044: InvocationParser m_parser;
045: ServerSocket m_server;
046:
047: public static void setTerminate(boolean b) {
048: c_terminate = b;
049: }
050:
051: public static boolean getTerminate() {
052: return c_terminate;
053: }
054:
055: public void showUsage() {
056: // empty for now
057: }
058:
059: public SimpleServer(int port) throws Exception {
060: super ("SimpleServer");
061:
062: // stand up the connection to the PTC
063: this .m_noDBase = false;
064: ChimeraProperties props = ChimeraProperties.instance();
065: String ptcSchemaName = props.getPTCSchemaName();
066: if (ptcSchemaName == null)
067: m_noDBase = true;
068: if (!m_noDBase) {
069: // ignore -d option for now - grumbl, why?!
070: Connect connect = new Connect();
071: this .m_dbschema = connect.connectDatabase(ptcSchemaName);
072:
073: // check for invocation record support
074: if (!(m_dbschema instanceof PTC)) {
075: c_logger
076: .warn("Your database cannot store invocation records"
077: + ", assuming no-database-mode");
078: m_noDBase = true;
079: }
080: }
081:
082: // create one XML parser -- once
083: m_parser = new InvocationParser(props.getPTCSchemaLocation());
084:
085: // setup socket
086: this .m_server = null;
087: try {
088: byte[] loopback = { 127, 0, 0, 1 };
089: this .m_server = new ServerSocket(port, 5, InetAddress
090: .getByAddress(loopback));
091: // new ServerSocket( port, 5, InetAddress..getLocalHost() );
092: // new ServerSocket( port, 5 );
093: } catch (UnknownHostException e) {
094: c_logger.fatal("Unable to determine own hostname: "
095: + e.getMessage());
096: System.exit(1);
097: } catch (IOException e) {
098: c_logger.fatal("Could not listen on port " + port + ": "
099: + e.getMessage());
100: System.exit(1);
101: }
102: }
103:
104: /**
105: * Copy the content of the file into memory. The copy operation also
106: * weeds out anything that may have been added by the remote batch
107: * scheduler. For instance, PBS is prone to add headers and footers.
108: *
109: * @param input is the file instance from which to read contents.
110: * @return the result code from reading the file
111: */
112: private String extractToMemory(java.io.File input)
113: throws FriendlyNudge {
114: StringWriter out = null;
115:
116: // open the files
117: int p1, p2, state = 0;
118: try {
119: BufferedReader in = new BufferedReader(
120: new FileReader(input));
121: out = new StringWriter(4096);
122:
123: String line = null;
124: while ((line = in.readLine()) != null) {
125: if ((state & 1) == 0) {
126: // try to copy the XML line in any case
127: if ((p1 = line.indexOf("<?xml")) > -1)
128: if ((p2 = line.indexOf("?>", p1)) > -1)
129: out.write(line, p1, p2 + 2);
130: // start state with the correct root element
131: if ((p1 = line.indexOf("<invocation")) > -1) {
132: if (p1 > 0)
133: line = line.substring(p1);
134: ++state;
135: }
136: }
137: if ((state & 1) == 1) {
138: out.write(line);
139: if ((p1 = line.indexOf("</invocation>")) > -1)
140: ++state;
141: }
142: }
143:
144: in.close();
145: out.flush();
146: out.close();
147: } catch (IOException ioe) {
148: throw new FriendlyNudge("While copying " + input.getPath()
149: + " into temp. file: " + ioe.getMessage(), 5);
150: }
151:
152: // some sanity checks
153: if (state == 0)
154: throw new FriendlyNudge(
155: "File "
156: + input.getPath()
157: + " does not contain invocation records, assuming failure",
158: 5);
159: if ((state & 1) == 1)
160: throw new FriendlyNudge(
161: "File "
162: + input.getPath()
163: + " contains an incomplete invocation record, assuming failure",
164: 5);
165:
166: // done
167: return out.toString();
168: }
169:
170: /**
171: * Determines the exit code of an invocation record. Currently,
172: * we will determine the exit code from the main job only.
173: *
174: * @param ivr is the invocation record to put into the database
175: * @return the status code as exit code to signal failure etc.
176: * <pre>
177: * 0 regular exit with exit code 0
178: * 1 regular exit with exit code > 0
179: * 2 failure to run program from kickstart
180: * 3 application had died on signal
181: * 4 application was suspended (should not happen)
182: * 5 failure in exit code parsing
183: * 6 impossible case
184: * </pre>
185: */
186: private int determineExitStatus(InvocationRecord ivr) {
187: boolean seen = false;
188: for (Iterator i = ivr.iterateJob(); i.hasNext();) {
189: Job job = (Job) i.next();
190:
191: // clean-up jobs don't count in failure modes
192: if (job.getTag().equals("cleanup"))
193: continue;
194:
195: // obtains status from job
196: Status status = job.getStatus();
197: if (status == null)
198: return 6;
199:
200: JobStatus js = status.getJobStatus();
201: if (js == null) {
202: // should not happen
203: return 6;
204: } else if (js instanceof JobStatusRegular) {
205: // regular exit code - success or failure?
206: int exitcode = ((JobStatusRegular) js).getExitCode();
207: if (exitcode != 0)
208: return 1;
209: else
210: seen = true;
211: // continue, if exitcode of 0 to implement chaining !!!!
212: } else if (js instanceof JobStatusFailure) {
213: // kickstart failure
214: return 2;
215: } else if (js instanceof JobStatusSignal) {
216: // died on signal
217: return 3;
218: } else if (js instanceof JobStatusSuspend) {
219: // suspended???
220: return 4;
221: } else {
222: // impossible/unknown case
223: return 6;
224: }
225: }
226:
227: // success, or no [matching] jobs
228: return seen ? 0 : 5;
229: }
230:
231: /**
232: * Reads the contents of the specified file, and returns with the
233: * remote exit code contained in the job chain.
234: *
235: * @param filename is the name of the file with the kickstart record.
236: * @return the exit code derived from the remote exit code.
237: */
238: public int checkFile(String filename) {
239: int result = 0;
240:
241: try {
242: // check input file
243: java.io.File check = new java.io.File(filename);
244:
245: // test 1: file exists
246: if (!check.exists())
247: throw new FriendlyNudge("file does not exist "
248: + filename + ", assuming failure", 5);
249:
250: // test 2: file is readable
251: if (!check.canRead())
252: throw new FriendlyNudge("unable to read file "
253: + filename + ", assuming failure", 5);
254:
255: // test 3: file has nonzero size
256: if (check.length() == 0) {
257: if (m_emptyFail) {
258: throw new FriendlyNudge(
259: "file " + filename + " has zero length"
260: + ", assuming failure", 5);
261: } else {
262: throw new FriendlyNudge(
263: "file " + filename + " has zero length"
264: + ", assuming success", 0);
265: }
266: }
267:
268: // test 4: extract XML into tmp file
269: String temp = extractToMemory(check);
270:
271: // test 5: try to parse XML -- but there is only one parser
272: InvocationRecord invocation = null;
273: synchronized (m_parser) {
274: c_logger.info("starting to parse invocation");
275: invocation = m_parser.parse(new StringReader(temp));
276: }
277: ;
278:
279: if (invocation == null)
280: throw new FriendlyNudge(
281: "invalid XML invocation record in " + filename
282: + ", assuming failure", 5);
283: else
284: c_logger.info("invocation was parsed successfully");
285:
286: // insert into database. This trickery works, because we already
287: // checked previously that the dbschema does support invocations.
288: // However, there is only one database connection at a time.
289: if (!m_noDBase) {
290: PTC ptc = (PTC) m_dbschema;
291:
292: synchronized (ptc) {
293: // FIXME: (start,host,pid) may not be a sufficient secondary key
294: if (ptc.getInvocationID(invocation.getStart(),
295: invocation.getHostAddress(), invocation
296: .getPID()) == -1) {
297: c_logger.info("adding invocation to database");
298: // may throw SQLException
299: ptc.saveInvocation(invocation);
300: } else {
301: c_logger
302: .info("invocation already exists, skipping!");
303: }
304: }
305: }
306:
307: // determine result code, just look at the main job for now
308: c_logger.info("determining exit status of main job");
309: result = determineExitStatus(invocation);
310: c_logger.info("exit status = " + result);
311:
312: } catch (FriendlyNudge fn) {
313: c_logger.warn(fn.getMessage());
314: result = fn.getResult();
315:
316: } catch (Exception e) {
317: c_logger.warn(e.getMessage());
318: result = 5;
319: }
320:
321: // done
322: return result;
323: }
324:
325: public static void main(String args[]) throws IOException {
326: // setup logging
327: System.setProperty("log4j.defaultInitOverride", "true");
328: Logger root = Logger.getRootLogger();
329: root.addAppender(new ConsoleAppender(new PatternLayout(
330: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%c{1}] %m%n")));
331: root.setLevel(Level.INFO);
332: c_logger = Logger.getLogger(SimpleServer.class);
333: c_logger.info("starting");
334:
335: SimpleServer me = null;
336: try {
337: me = new SimpleServer(port);
338: } catch (Exception e) {
339: c_logger.fatal("Unable to instantiate a server: "
340: + e.getMessage());
341: System.exit(1);
342: }
343:
344: // run forever
345: try {
346: while (!c_terminate) {
347: new SimpleServerThread(me, me.m_server.accept())
348: .start();
349: }
350: } catch (SocketException se) {
351: // ignore -- closing the server socket in a thread during shutdown
352: // will have accept fail with a socket exception in main()
353: }
354:
355: // done
356: c_logger.info("received shutdown");
357:
358: // count your threads, and the last one locks the
359: // door and switches off the light... Grrr.
360: synchronized (me) {
361: while (SimpleServerThread.c_count > SimpleServerThread.c_cdone) {
362: try {
363: me.wait(5000);
364: } catch (InterruptedException e) {
365: // ignore
366: }
367: }
368: }
369:
370: try {
371: me.m_dbschema.close();
372: } catch (Exception e) {
373: c_logger.warn("During database disconnect: "
374: + e.getMessage());
375: }
376: c_logger.warn("finished shutdown");
377: }
378: }
|