View Javadoc
1   //******************************************************************************
2   //
3   // File:    Configuration.java
4   // Package: edu.rit.pj.cluster
5   // Unit:    Class edu.rit.pj.cluster.Configuration
6   //
7   // This Java source file is copyright (C) 2012 by Alan Kaminsky. All rights
8   // reserved. For further information, contact the author, Alan Kaminsky, at
9   // ark@cs.rit.edu.
10  //
11  // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
12  // software; you can redistribute it and/or modify it under the terms of the GNU
13  // General Public License as published by the Free Software Foundation; either
14  // version 3 of the License, or (at your option) any later version.
15  //
16  // PJ is distributed in the hope that it will be useful, but WITHOUT ANY
17  // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18  // A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19  //
20  // Linking this library statically or dynamically with other modules is making a
21  // combined work based on this library. Thus, the terms and conditions of the GNU
22  // General Public License cover the whole combination.
23  //
24  // As a special exception, the copyright holders of this library give you
25  // permission to link this library with independent modules to produce an
26  // executable, regardless of the license terms of these independent modules, and
27  // to copy and distribute the resulting executable under terms of your choice,
28  // provided that you also meet, for each linked independent module, the terms
29  // and conditions of the license of that module. An independent module is a module
30  // which is not derived from or based on this library. If you modify this library,
31  // you may extend this exception to your version of the library, but you are not
32  // obligated to do so. If you do not wish to do so, delete this exception
33  // statement from your version.
34  //
35  // A copy of the GNU General Public License is provided in the file gpl.txt. You
36  // may also obtain a copy of the GNU General Public License on the World Wide
37  // Web at http://www.gnu.org/licenses/gpl.html.
38  //
39  //******************************************************************************
40  package edu.rit.pj.cluster;
41  
42  import java.io.File;
43  import java.io.IOException;
44  import java.util.ArrayList;
45  import java.util.List;
46  import java.util.NoSuchElementException;
47  import java.util.Scanner;
48  
49  /**
50   * Class Configuration provides configuration information about a parallel
51   * computer running Parallel Java. The configuration information is read from a
52   * plain text file. Each configuration file entry is on a single line. Lines
53   * beginning with <code>#</code> and blank lines are ignored. The order of the
54   * entries in the file does not matter (unless stated otherwise below). The
55   * items in each entry are separated by whitespace; there cannot be any
56   * whitespace within an item (unless stated otherwise below). The configuration
57   * file entries are:
58   * <UL>
59   *
60   * <LI>
61   * <code>cluster &lt;name&gt;</code>
62   * <BR>The name of the cluster is <code>&lt;name&gt;</code>. The name may contain
63   * whitespace. This entry must be specified; there is no default.
64   *
65   * <LI>
66   * <code>logfile &lt;file&gt;</code>
67   * <BR>The Job Scheduler will append log entries to the log file named
68   * <code>&lt;file&gt;</code>. This entry must be specified; there is no default.
69   *
70   * <LI>
71   * <code>webhost &lt;host&gt;</code>
72   * <BR>The host name for the Job Scheduler's web interface is
73   * <code>&lt;host&gt;</code>. This entry must be specified; there is no default.
74   *
75   * <LI>
76   * <code>webport &lt;port&gt;</code>
77   * <BR>The port number for the Job Scheduler's web interface is
78   * <code>&lt;port&gt;</code>. If not specified, the default port number is 8080.
79   *
80   * <LI>
81   * <code>schedulerhost &lt;host&gt;</code>
82   * <BR>The host name to which the Job Scheduler listens for connections from job
83   * frontend processes is <code>&lt;host&gt;</code>. If not specified, the default is
84   * <code>"localhost"</code>.
85   *
86   * <LI>
87   * <code>schedulerport &lt;port&gt;</code>
88   * <BR>The port number to which the Job Scheduler listens for connections from
89   * job frontend processes is <code>&lt;port&gt;</code>. If not specified, the
90   * default port number is 20617.
91   *
92   * <LI>
93   * <code>frontendhost &lt;host&gt;</code>
94   * <BR>The host name to which job frontend processes listen for connections from
95   * job backend processes is <code>&lt;host&gt;</code>. This entry must be specified;
96   * there is no default.
97   *
98   * <LI>
99   * <code>backend &lt;name&gt; &lt;cpus&gt; &lt;host&gt; &lt;jvm&gt;
100  * &lt;classpath&gt; [&lt;jvmflag&gt; ...]</code>
101  * <BR>The parallel computer includes a backend node named
102  * <code>&lt;name&gt;</code> with <code>&lt;cpus&gt;</code> CPUs. The host name for SSH
103  * remote logins to the backend node is <code>&lt;host&gt;</code>. The full pathname
104  * for executing the Java Virtual Machine (JVM) on the backend node is
105  * <code>&lt;jvm&gt;</code>. The Java class path for the Parallel Java Library on
106  * the backend node is <code>&lt;classpath&gt;</code>. Each
107  * <code>&lt;jvmflag&gt;</code> (zero or more) gives a flag passed to the JVM on the
108  * command line. At least one of this entry must be specified.
109  *
110  * <LI>
111  * <code>backendshell &lt;name&gt; &lt;shell command&gt;</code>
112  * <BR>On the backend node named <code>&lt;name&gt;</code>, use the given shell
113  * command string when starting a job backend process. This entry, if present,
114  * must appear after the corresponding <code>backend &lt;name&gt;</code> entry. If
115  * this entry is omitted, the default shell command string is
116  * <code>"bash&nbsp;-l&nbsp;-c"</code>.
117  *
118  * <LI>
119  * <code>jobtime &lt;time&gt;</code>
120  * <BR>The maximum time in seconds any Parallel Java job is allowed to run. The
121  * Job Scheduler will abort a job if it runs for this many seconds. If not
122  * specified, the default is not to impose a maximum time on jobs. <I>Note:</I>
123  * If the Job Scheduler is configured with a maximum job time and a particular
124  * job is given a maximum time with the <code>-Dpj.jobtime</code> property, the
125  * smaller of the Job Scheduler's maximum job time and the job's maximum time
126  * will be used for that job.
127  * </UL>
128  * <P>
129  * Here is an example of a configuration file:
130  *
131  * <TABLE BORDER=1>
132  * <CAPTION>Parallel Java Job Scheduler configuration file</CAPTION>
133  * <TR>
134  * <TD style="vertical-align:top;">
135  * <PRE> # Parallel Java Job Scheduler configuration file
136  * # Frontend node: tardis.cs.rit.edu
137  * # Backend nodes: dr00-dr09
138  *
139  * cluster RIT CS Tardis Hybrid SMP Cluster
140  * logfile /var/tmp/parajava/scheduler.log
141  * webhost tardis.cs.rit.edu
142  * webport 8080
143  * schedulerhost localhost
144  * schedulerport 20617
145  * frontendhost 10.10.221.1
146  * backend dr00 4 10.10.221.10 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
147  * backend dr01 4 10.10.221.11 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
148  * backend dr02 4 10.10.221.12 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
149  * backend dr03 4 10.10.221.13 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
150  * backend dr04 4 10.10.221.14 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
151  * backend dr05 4 10.10.221.15 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
152  * backend dr06 4 10.10.221.16 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
153  * backend dr07 4 10.10.221.17 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
154  * backend dr08 4 10.10.221.18 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar
155  * backend dr09 4 10.10.221.19 /usr/local/versions/jdk-1.5.0_15/bin/java /var/tmp/parajava/pj.jar</PRE>
156  * </TD>
157  * </TR>
158  * </TABLE>
159  *
160  * @author Alan Kaminsky
161  * @version 20-Jun-2012
162  */
163 public class Configuration {
164 
165 // Hidden data members.
166     // Cluster name.
167     private String myClusterName;
168 
169     // Log file.
170     private String myLogFile;
171 
172     // Web interface host and port.
173     private String myWebHost = Constants.ALL_NETWORK_INTERFACES;
174     private int myWebPort = Constants.WEB_PORT;
175 
176     // Job Scheduler host and port.
177     private String mySchedulerHost = "localhost";
178     private int mySchedulerPort = Constants.PJ_PORT;
179 
180     // Frontend host.
181     private String myFrontendHost;
182 
183     // List of backend information objects.
184     private ArrayList<BackendInfo> myBackendInfo
185             = new ArrayList<BackendInfo>();
186 
187     // Default shell comand string.
188     private static final String DEFAULT_SHELL_COMMAND = "bash -l -c";
189 
190     // Maximum job time. 0 means no maximum.
191     private int myJobTime;
192 
193 // Exported constructors.
194     /**
195      * Construct a new configuration. The configuration information is read from
196      * the given file.
197      *
198      * @param configfile Configuration file name.
199      * @exception IOException Thrown if an I/O error occurred while reading the
200      * configuration file. Thrown if there was an error in the configuration
201      * file.
202      * @throws java.io.IOException if any.
203      */
204     public Configuration(String configfile)
205             throws IOException {
206         parseConfigFile(configfile);
207     }
208 
209 // Exported operations.
210     /**
211      * Returns the cluster name.
212      *
213      * @return Cluster name.
214      */
215     public String getClusterName() {
216         return myClusterName;
217     }
218 
219     /**
220      * Returns the Job Scheduler's log file name.
221      *
222      * @return Log file name.
223      */
224     public String getLogFile() {
225         return myLogFile;
226     }
227 
228     /**
229      * Returns the Job Scheduler's web interface host name.
230      *
231      * @return Host name.
232      */
233     public String getWebHost() {
234         return myWebHost;
235     }
236 
237     /**
238      * Returns the Job Scheduler's web interface port number.
239      *
240      * @return Port number.
241      */
242     public int getWebPort() {
243         return myWebPort;
244     }
245 
246     /**
247      * Returns the Job Scheduler's channel group host name. To send messages to
248      * the Job Scheduler, a job frontend connects a channel to this host.
249      *
250      * @return Host name.
251      */
252     public String getSchedulerHost() {
253         return mySchedulerHost;
254     }
255 
256     /**
257      * Returns the Job Scheduler's channel group port number. To send messages
258      * to the Job Scheduler, a job frontend connects a channel to this port.
259      *
260      * @return Port number.
261      */
262     public int getSchedulerPort() {
263         return mySchedulerPort;
264     }
265 
266     /**
267      * Returns the host name of the cluster's frontend processor.
268      *
269      * @return Host name.
270      */
271     public String getFrontendHost() {
272         return myFrontendHost;
273     }
274 
275     /**
276      * Returns the number of backend processors.
277      *
278      * @return Count.
279      */
280     public int getBackendCount() {
281         return myBackendInfo.size();
282     }
283 
284     /**
285      * Returns information about the given backend processor.
286      *
287      * @param i Index in the range 0 .. <code>getBackendCount()-1</code>.
288      * @return Backend information object.
289      */
290     public BackendInfo getBackendInfo(int i) {
291         return myBackendInfo.get(i);
292     }
293 
294     /**
295      * Returns information about all backend processors.
296      *
297      * @return List of backend information objects.
298      */
299     public List<BackendInfo> getBackendInfoList() {
300         return myBackendInfo;
301     }
302 
303     /**
304      * Returns the maximum job time.
305      *
306      * @return Maximum job time (seconds), or 0 if no maximum.
307      */
308     public int getJobTime() {
309         return myJobTime;
310     }
311 
312 // Hidden operations.
313     /**
314      * Parse the configuration file.
315      *
316      * @param configfile Configuration file name.
317      *
318      * @exception IOException Thrown if an I/O error occurred.
319      */
320     private void parseConfigFile(String configfile)
321             throws IOException {
322         Scanner scanner = null;
323         String line = null;
324         long now = System.currentTimeMillis();
325         try {
326             scanner = new Scanner(new File(configfile));
327             lineloop:
328             while (scanner.hasNextLine()) {
329                 line = scanner.nextLine();
330                 Scanner linescanner = new Scanner(line);
331                 if (!linescanner.hasNext()) {
332                     continue lineloop;
333                 }
334                 String command = linescanner.next();
335                 if (command.charAt(0) == '#') {
336                 } else if (command.equals("cluster")) {
337                     myClusterName = linescanner.nextLine().trim();
338                 } else if (command.equals("logfile")) {
339                     myLogFile = linescanner.next();
340                 } else if (command.equals("webhost")) {
341                     myWebHost = linescanner.next();
342                 } else if (command.equals("webport")) {
343                     myWebPort = Integer.parseInt(linescanner.next());
344                 } else if (command.equals("schedulerhost")) {
345                     mySchedulerHost = linescanner.next();
346                 } else if (command.equals("schedulerport")) {
347                     mySchedulerPort = Integer.parseInt(linescanner.next());
348                 } else if (command.equals("frontendhost")) {
349                     myFrontendHost = linescanner.next();
350                 } else if (command.equals("backend")) {
351                     String name = linescanner.next();
352                     int cpus = linescanner.nextInt();
353                     if (cpus < 1) {
354                         throw new IOException("Invalid backend command, <cpus> must be >= 1: "
355                                 + line);
356                     }
357                     String host = linescanner.next();
358                     String jvm = linescanner.next();
359                     String classpath = linescanner.next();
360                     ArrayList<String> jvmflags = new ArrayList<String>();
361                     while (linescanner.hasNext()) {
362                         jvmflags.add(linescanner.next());
363                     }
364                     BackendInfo backendinfo = new BackendInfo(name, cpus, BackendInfo.State.IDLE,
365                                     now, host, jvm, classpath, jvmflags.toArray(new String[0]), DEFAULT_SHELL_COMMAND);
366                     myBackendInfo.add(backendinfo);
367                 } else if (command.equals("backendshell")) {
368                     String name = linescanner.next();
369                     String shellCommand = linescanner.nextLine().trim();
370                     BackendInfo backendinfo = backendInfoForName(name);
371                     if (backendinfo == null) {
372                         throw new IOException("Invalid backendshell command, no backend named \""
373                                 + name + "\"");
374                     }
375                     backendinfo.shellCommand = shellCommand;
376                 } else if (command.equals("jobtime")) {
377                     int time = linescanner.nextInt();
378                     if (time < 1) {
379                         throw new IOException("Invalid configuration command: " + line);
380                     }
381                     myJobTime = time;
382                 } else {
383                     throw new IOException("Invalid configuration command: " + line);
384                 }
385             }
386             if (myClusterName == null) {
387                 throw new IOException("Missing configuration command: cluster <name>");
388             }
389             if (myLogFile == null) {
390                 throw new IOException("Missing configuration command: logfile <file>");
391             }
392             if (myWebHost == null) {
393                 throw new IOException("Missing configuration command: webhost <host>");
394             }
395             if (myFrontendHost == null) {
396                 throw new IOException("Missing configuration command: frontendhost <host>");
397             }
398             if (myBackendInfo.isEmpty()) {
399                 throw new IOException("Missing configuration command: backend <name> <host> <port>");
400             }
401         } catch (NoSuchElementException exc) {
402             throw new IOException("Invalid configuration command: " + line);
403         } catch (NumberFormatException exc) {
404             throw new IOException("Invalid configuration command: " + line);
405         } finally {
406             if (scanner != null) {
407                 scanner.close();
408             }
409         }
410     }
411 
412 // Hidden operations.
413     /**
414      * Returns the backend info object for the given backend name.
415      *
416      * @param name Backend name.
417      *
418      * @return Backend info, or null if <code>name</code> does not exist.
419      */
420     private BackendInfo backendInfoForName(String name) {
421         for (BackendInfo backendinfo : myBackendInfo) {
422             if (backendinfo.name.equals(name)) {
423                 return backendinfo;
424             }
425         }
426         return null;
427     }
428 
429 }