001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Contact: sequoia@continuent.org
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: *
019: * Initial developer(s): Emmanuel Cecchet.
020: * Contributor(s): Mathieu Peltier, Nicolas Modrzyk, Duncan Smith.
021: */package org.continuent.sequoia.controller.core;
022:
023: import java.io.File;
024: import java.io.FileReader;
025: import java.net.InetAddress;
026: import java.net.URL;
027: import java.net.URLDecoder;
028: import java.net.UnknownHostException;
029: import java.util.Hashtable;
030:
031: import javax.management.ObjectName;
032:
033: import org.apache.commons.cli.CommandLine;
034: import org.apache.commons.cli.CommandLineParser;
035: import org.apache.commons.cli.GnuParser;
036: import org.apache.commons.cli.HelpFormatter;
037: import org.apache.commons.cli.Option;
038: import org.apache.commons.cli.OptionGroup;
039: import org.apache.commons.cli.Options;
040: import org.apache.commons.cli.ParseException;
041: import org.continuent.sequoia.common.authentication.PasswordAuthenticator;
042: import org.continuent.sequoia.common.i18n.Translate;
043: import org.continuent.sequoia.common.jmx.JmxConstants;
044: import org.continuent.sequoia.common.jmx.JmxException;
045: import org.continuent.sequoia.common.log.Trace;
046: import org.continuent.sequoia.common.net.SSLConfiguration;
047: import org.continuent.sequoia.controller.core.security.ControllerSecurityManager;
048: import org.continuent.sequoia.controller.jmx.HttpAdaptor;
049: import org.continuent.sequoia.controller.jmx.MBeanServerManager;
050: import org.continuent.sequoia.controller.jmx.RmiConnector;
051: import org.continuent.sequoia.controller.monitoring.datacollector.DataCollector;
052: import org.continuent.sequoia.controller.xml.ControllerParser;
053:
054: /**
055: * The <code>ControllerConfiguration</code> class prepares a
056: * <code>Controller</code> object by configurating ports, security, loaded
057: * databases.
058: *
059: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
060: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
061: * @author <a href="mailto:duncan@mightybot.com">Duncan Smith </a>
062: * @version 1.0
063: */
064: public class ControllerConfiguration extends Hashtable {
065: private static final long serialVersionUID = -3766086549425915891L;
066:
067: /**
068: * The different fields that can be set on the command line.
069: */
070: /** The Rmi port value */
071: public static final String RMI_PORT = "rmiPort";
072:
073: /** The jmx port value */
074: public static final String JMX_PORT = "jmxPort";
075:
076: /** The jmx enable value */
077: public static final String JMX_ENABLE = "jmxEnable";
078:
079: /** The xml file possibly used to configure controller */
080: public static final String XML_FILE = "xmlFile";
081:
082: /** The NIC IP address to bind the controller to */
083: public static final String CONTROLLER_IP = "controllerIP";
084:
085: /** The controller port number */
086: public static final String CONTROLLER_PORT = "controllerPort";
087:
088: /** The controller backlog size */
089: public static final String CONTROLLER_BACKLOG = "controllerBackLogSize";
090:
091: /** Add driver enable */
092: public static final String ADD_DRIVER_ENABLE = "addDriverEnable";
093:
094: /** Logger instance. */
095: static Trace logger = Trace.getLogger(Controller.class.getName());
096: static Trace endUserLogger = Trace
097: .getLogger("org.continuent.sequoia.enduser");
098:
099: private Controller controller = null;
100:
101: /**
102: * Configure the controller with parameters
103: *
104: * @param args parameters from the command line
105: */
106: public ControllerConfiguration(String[] args) {
107: System.setProperty("org.xml.sax.driver",
108: "org.apache.crimson.parser.XMLReaderImpl");
109:
110: this .put(CONTROLLER_IP, ControllerConstants.DEFAULT_IP);
111: this
112: .put(CONTROLLER_PORT, ""
113: + ControllerConstants.DEFAULT_PORT);
114: this .put(CONTROLLER_BACKLOG, ""
115: + ControllerConstants.DEFAULT_BACKLOG_SIZE);
116:
117: // Create options object
118: Options options = createOptions();
119:
120: // Parse command line
121: CommandLineParser parser = new GnuParser();
122: CommandLine commandLine = null;
123: try {
124: commandLine = parser.parse(options, args);
125: } catch (ParseException e) {
126: String msg = Translate
127: .get("controller.configure.commandline.error");
128: logger.fatal(msg, e);
129: endUserLogger.fatal(msg, e);
130: printUsage(options);
131: Runtime.getRuntime().exit(1);
132: }
133:
134: // Non-recognized options
135: int n = commandLine.getArgs().length;
136: for (int i = 0; i < n; i++) {
137: String msg = Translate.get(
138: "controller.configure.unknown.option", commandLine
139: .getArgs()[i]);
140: logger.fatal(msg);
141: endUserLogger.fatal(msg);
142: printUsage(options);
143: Runtime.getRuntime().exit(1);
144: }
145: // Handle --help option
146: if (commandLine.hasOption('h')) {
147: if (commandLine.getOptions().length > 1) {
148: String msg = Translate
149: .get("controller.configure.commandline.error");
150: logger.fatal(msg);
151: endUserLogger.fatal(msg);
152: }
153: printUsage(options);
154: Runtime.getRuntime().exit(1);
155: }
156:
157: // Handle --version option
158: if (commandLine.hasOption('v')) {
159: if (commandLine.getOptions().length > 1) {
160: String msg = Translate
161: .get("controller.configure.commandline.error");
162: logger.fatal(msg);
163: endUserLogger.fatal(msg);
164: printUsage(options);
165: } else {
166: ControllerInfo.printOn(System.out);
167: }
168: Runtime.getRuntime().exit(1);
169: }
170:
171: // Handle -f option
172: if (commandLine.hasOption('f')) {
173: String filePath = commandLine.getOptionValue('f');
174: File f = new File(filePath);
175: logger.debug(f.getAbsolutePath());
176: if (f.exists() == false || f.isFile() == false) {
177: String msg = Translate.get(
178: "controller.configure.optional.file.invalid",
179: filePath);
180: logger.fatal(msg);
181: endUserLogger.fatal(msg);
182: System.exit(1);
183: } else
184: this .put(XML_FILE, filePath);
185: } else
186: // default controller configuration file
187: {
188: URL defaultControllerXmlFile = ControllerConfiguration.class
189: .getResource("/"
190: + ControllerConstants.DEFAULT_CONFIG_FILE);
191: if (defaultControllerXmlFile == null) {
192: String msg = Translate
193: .get("controller.configure.file.not.in.classpath");
194: logger.fatal(msg);
195: endUserLogger.fatal(msg);
196: System.exit(1);
197: } else {
198: String file = URLDecoder
199: .decode(defaultControllerXmlFile.getFile());
200: this .put(XML_FILE, file);
201: }
202: }
203:
204: // Handle -rmi option
205: if (commandLine.hasOption('r')) {
206: String s = commandLine.getOptionValue('r');
207: if (s != null) {
208: this .put(JMX_ENABLE, "true");
209: this .put(RMI_PORT, s);
210: this .put(JmxConstants.ADAPTOR_TYPE_RMI, s);
211: }
212: }
213:
214: // Handle -jmx option
215: if (commandLine.hasOption('j')) {
216: String s = commandLine.getOptionValue('j');
217: if (s != null) {
218: this .put(JMX_ENABLE, "true");
219: this .put(JMX_PORT, s);
220: this .put(JmxConstants.ADAPTOR_TYPE_HTTP, s);
221: }
222: }
223:
224: // Handle --ip option
225: if (commandLine.hasOption('i')) {
226: String ipAddress = commandLine.getOptionValue('i');
227: if (ipAddress != null)
228: this .put(CONTROLLER_IP, ipAddress);
229: }
230:
231: // Handle --port option
232: if (commandLine.hasOption('p')) {
233: String port = commandLine.getOptionValue('p');
234: if (port != null)
235: this .put(CONTROLLER_PORT, port);
236: }
237: }
238:
239: /**
240: * This method is going to call a <code>ControllerParser</code> object to
241: * configure controller while parsing file. This method will call <method>
242: * setUpRmi() </method> and <method>setUpJmx() </method> as well as <method>
243: * setUpVirtualDatabases </method> while parsing.
244: *
245: * @param filename path to the xml file to parse from
246: * @throws Exception if configuration fails
247: */
248: public void setUpByXml(String filename) throws Exception {
249: logger.info(Translate.get("controller.configure.loading.file",
250: filename));
251: FileReader fileReader = null;
252: try {
253: fileReader = new FileReader(filename);
254: ControllerParser cparser = new ControllerParser(this );
255: cparser.readXML(fileReader, true);
256: fileReader.close();
257: } catch (Exception e) {
258:
259: logger.warn(Translate.get(
260: "controller.configure.xml.file.error", e), e);
261: throw e;
262: } finally {
263: if (fileReader != null)
264: fileReader.close();
265: }
266: }
267:
268: /**
269: * Test if there is a file to take configuration from, if so call <method>
270: * setUpByXml() </method>
271: *
272: * @return an instanciated and configured object of class
273: * <code>Controller</code>
274: * @throws Exception if configuration fails
275: */
276: private Controller setup() throws Exception {
277: String xml = (String) this .get(XML_FILE);
278:
279: int portNumber = Integer.parseInt((String) this
280: .get(CONTROLLER_PORT));
281: int backlog = Integer.parseInt((String) this
282: .get(CONTROLLER_BACKLOG));
283: /**
284: * @see org.continuent.sequoia.controller.xml.ControllerParser#configureController(Attributes)
285: * for how CONTROLLER_IP is set (unfortunately not the bare user's
286: * entry).
287: */
288: String ipAddress = (String) this .get(CONTROLLER_IP);
289:
290: controller = new Controller(ipAddress, portNumber, backlog);
291: controller.setConfiguration(this );
292: org.continuent.sequoia.controller.management.Controller managedController = new org.continuent.sequoia.controller.management.Controller(
293: controller);
294: ObjectName name = JmxConstants.getControllerObjectName();
295: MBeanServerManager.registerMBean(managedController, name);
296: controller.setNotificationBroadcasterSupport(managedController
297: .getBroadcaster());
298:
299: if (xml != null) {
300: try {
301: setUpByXml(xml);
302: } catch (Exception e) {
303: logger
304: .error(
305: Translate
306: .get(
307: "controller.configure.load.file.failed.minimum.configuration",
308: new String[] { xml,
309: e.getMessage() }),
310: e);
311: }
312: } else
313: setUpJmx();
314:
315: return this .controller;
316: }
317:
318: /**
319: * Retrieve the controller associated with this
320: * <code>ControllerConfiguration</code> instance.
321: *
322: * @return <code>Controller</code> object. Can be null if this method is
323: * called before setup
324: * @throws Exception if an error occurs
325: */
326: public Controller getController() throws Exception {
327: if (controller == null)
328: setup();
329: return this .controller;
330: }
331:
332: /**
333: * Start up the jmx services if enabled.
334: *
335: * @throws JmxException an exception
336: */
337: public void setUpJmx() throws JmxException {
338: boolean jmxEnable = new Boolean((String) get(JMX_ENABLE))
339: .booleanValue();
340: if (jmxEnable == false) {
341: MBeanServerManager.setJmxEnabled(false);
342: logger.info(Translate.get("jmx.configure.disabled"));
343: } else {
344: MBeanServerManager.setJmxEnabled(true);
345: logger.info(Translate.get("jmx.configure.enabled"));
346: // Create and start the JMX agent
347: try {
348: new DataCollector(controller);
349: String hostIP = controller.getIPAddress();
350:
351: logger.info(Translate.get(
352: "controller.configure.start.jmx", hostIP));
353:
354: if (this .containsKey(JmxConstants.ADAPTOR_TYPE_HTTP)) {
355: int port = Integer.parseInt((String) this
356: .get(JmxConstants.ADAPTOR_TYPE_HTTP));
357: HttpAdaptor http = new HttpAdaptor(hostIP, port,
358: null);
359: http.start();
360: }
361: if (this .containsKey(JmxConstants.ADAPTOR_TYPE_RMI)) {
362: SSLConfiguration ssl = null;
363: PasswordAuthenticator authenticator = null;
364: int port = Integer.parseInt((String) this
365: .get(JmxConstants.ADAPTOR_TYPE_RMI));
366: if (this
367: .containsKey(JmxConstants.CONNECTOR_AUTH_USERNAME)) {
368: String username = (String) this
369: .get(JmxConstants.CONNECTOR_AUTH_USERNAME);
370: String password = (String) this
371: .get(JmxConstants.CONNECTOR_AUTH_PASSWORD);
372: authenticator = new PasswordAuthenticator(
373: username, password);
374: }
375: if (this
376: .containsKey(JmxConstants.CONNECTOR_RMI_SSL)) {
377: ssl = (SSLConfiguration) this
378: .get(JmxConstants.CONNECTOR_RMI_SSL);
379: }
380: RmiConnector rmi = new RmiConnector(controller
381: .getControllerName(), hostIP, port,
382: authenticator, ssl);
383: rmi.start();
384: }
385: logger.debug(Translate
386: .get("controller.configure.jmx.started"));
387: } catch (Exception e) {
388: logger.error(Translate.get(
389: "controller.configure.jmx.fail.start", e), e);
390: }
391: }
392: controller.setJmxEnable(jmxEnable);
393: }
394:
395: /**
396: * Set up security settings if needed here.
397: *
398: * @param security to enforce
399: */
400: public void setUpSecurity(ControllerSecurityManager security) {
401: controller.setSecurity(security);
402: }
403:
404: /**
405: * Will load the <code>VirtualDatabase</code> configuration into the
406: * controller.
407: *
408: * @param filePath the path to xml definition of the virtual database
409: * @param virtualName the name of the virtualDatabase to load
410: * @param autoLoad specified if backend should be enabled.
411: * @param checkPoint the check point to load the database from.
412: */
413: public void setUpVirtualDatabase(String filePath,
414: String virtualName, int autoLoad, String checkPoint) {
415: try {
416: controller.loadXmlConfiguration(filePath, virtualName,
417: autoLoad, checkPoint);
418: if (logger.isDebugEnabled())
419: logger.debug(Translate.get(
420: "controller.configure.file.autoload",
421: new String[] { filePath, "" + autoLoad }));
422:
423: } catch (Exception e) {
424: logger.error(Translate.get(
425: "controller.configure.load.file.failed",
426: new String[] { filePath, e.getMessage() }), e);
427: }
428: }
429:
430: /**
431: * Displays usage message.
432: *
433: * @param options available command line options
434: */
435: private static void printUsage(Options options) {
436: String header = Translate.get("controller.commandline.header",
437: ControllerConstants.PRODUCT_NAME);
438: header += System.getProperty("line.separator");
439: header += Translate.get("controller.commandline.options");
440: String footer = Translate.get("controller.commandline.footer");
441:
442: (new HelpFormatter()).printHelp(80,
443: "controller(.sh|.bat) [options]", header, options,
444: footer);
445: }
446:
447: /**
448: * Creates <code>Options</code> object that contains all available options
449: * that can be used launching Sequoia controller.
450: *
451: * @return an <code>Options</code> instance
452: */
453: private static Options createOptions() {
454: Options options = new Options();
455: OptionGroup group = new OptionGroup();
456:
457: // help and verbose options
458: group.addOption(new Option("h", "help", false, Translate
459: .get("controller.commandline.option.help")));
460: group.addOption(new Option("v", "version", false, Translate
461: .get("controller.commandline.option.version")));
462: options.addOptionGroup(group);
463:
464: // RMI port option
465: options.addOption(new Option("r", "rmi", true, Translate.get(
466: "controller.commandline.option.rmi", ""
467: + JmxConstants.DEFAULT_JMX_RMI_PORT)));
468: // JMX port option
469: options.addOption(new Option("j", "jmx", true, Translate.get(
470: "controller.commandline.option.jmx", ""
471: + JmxConstants.DEFAULT_JMX_HTTP_PORT)));
472:
473: // IP option
474: String defaultIp = "127.0.0.1";
475: try {
476: defaultIp = InetAddress.getLocalHost().getHostAddress();
477: } catch (UnknownHostException e) {
478:
479: }
480: options.addOption(new Option("i", "ip", true, Translate.get(
481: "controller.commandline.option.ip", "" + defaultIp)));
482:
483: // Port options
484: options.addOption(new Option("p", "port", true, Translate.get(
485: "controller.commandline.option.port", ""
486: + ControllerConstants.DEFAULT_PORT)));
487:
488: // configuration file option
489: options.addOption(new Option("f", "file", true, Translate
490: .get("controller.commandline.option.file")));
491:
492: return options;
493: }
494: }
|