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): Nicolas Modrzyk
020: * Contributor(s): ______________________.
021: */package org.continuent.sequoia.console.jmx;
022:
023: import java.io.IOException;
024: import java.util.HashMap;
025: import java.util.HashSet;
026: import java.util.Map;
027: import java.util.Set;
028:
029: import javax.management.Attribute;
030: import javax.management.InstanceNotFoundException;
031: import javax.management.MBeanInfo;
032: import javax.management.MBeanServerConnection;
033: import javax.management.MBeanServerInvocationHandler;
034: import javax.management.MalformedObjectNameException;
035: import javax.management.NotificationListener;
036: import javax.management.ObjectName;
037: import javax.management.monitor.StringMonitor;
038: import javax.management.remote.JMXConnector;
039: import javax.management.remote.JMXConnectorFactory;
040: import javax.management.remote.JMXServiceURL;
041: import javax.naming.Context;
042: import javax.security.auth.Subject;
043:
044: import org.continuent.sequoia.common.authentication.PasswordAuthenticator;
045: import org.continuent.sequoia.common.exceptions.VirtualDatabaseException;
046: import org.continuent.sequoia.common.i18n.ConsoleTranslate;
047: import org.continuent.sequoia.common.jmx.JmxConstants;
048: import org.continuent.sequoia.common.jmx.mbeans.AbstractSchedulerControlMBean;
049: import org.continuent.sequoia.common.jmx.mbeans.BackendTaskQueuesControlMBean;
050: import org.continuent.sequoia.common.jmx.mbeans.ControllerMBean;
051: import org.continuent.sequoia.common.jmx.mbeans.DataCollectorMBean;
052: import org.continuent.sequoia.common.jmx.mbeans.DatabaseBackendMBean;
053: import org.continuent.sequoia.common.jmx.mbeans.ParsingCacheMBean;
054: import org.continuent.sequoia.common.jmx.mbeans.RecoveryLogControlMBean;
055: import org.continuent.sequoia.common.jmx.mbeans.RequestManagerMBean;
056: import org.continuent.sequoia.common.jmx.mbeans.VirtualDatabaseMBean;
057: import org.continuent.sequoia.common.log.Trace;
058: import org.continuent.sequoia.common.users.AdminUser;
059:
060: /**
061: * This class defines a RmiJmxClient that uses Jmx 2.0 specifications to connect
062: * to the RmiSever
063: *
064: * @author <a href="mailto:Nicolas.Modrzyk@inria.fr">Nicolas Modrzyk </a>
065: * @version 1.0
066: */
067: public class RmiJmxClient {
068: private JMXConnector connector;
069: private Object credentials;
070: private String remoteHostAddress;
071: private String remoteHostPort;
072:
073: private NotificationListener notificationListener;
074:
075: // List of last used MBeans
076: private ControllerMBean controllerMBean;
077: private DatabaseBackendMBean backendMBean;
078: private DataCollectorMBean dataMBean;
079:
080: private Trace logger = Trace
081: .getLogger("org.continuent.sequoia.console.jmx");
082:
083: /**
084: * Returns the notificationListener value.
085: *
086: * @return Returns the notificationListener.
087: */
088: public NotificationListener getNotificationListener() {
089: return notificationListener;
090: }
091:
092: /**
093: * Sets the notificationListener value.
094: *
095: * @param notificationListener The notificationListener to set.
096: */
097: public void setNotificationListener(
098: NotificationListener notificationListener) {
099: this .notificationListener = notificationListener;
100: }
101:
102: /**
103: * Returns the credentials value.
104: *
105: * @return Returns the credentials.
106: */
107: public Object getCredentials() {
108: return credentials;
109: }
110:
111: /**
112: * Creates a new <code>RmiJmxClient.java</code> object
113: *
114: * @param port the port of the host to connect to
115: * @param host the host name to connect to
116: * @param jmxUser the jmxUser if one, to be authenticated with
117: * @param jmxPassword the jmxPassword if one, to be authenticated with
118: * @throws IOException if cannot connect
119: */
120: public RmiJmxClient(String port, String host, String jmxUser,
121: String jmxPassword) throws IOException {
122: this (port, host, PasswordAuthenticator.createCredentials(
123: jmxUser, jmxPassword));
124: }
125:
126: /**
127: * Creates a new <code>RmiJmxClient</code> object
128: *
129: * @param url the jmx connector url
130: * @param credentials to use for the connection
131: * @throws IOException if connect fails
132: */
133: public RmiJmxClient(String url, Object credentials)
134: throws IOException {
135: int index = url.indexOf(":");
136: String ip = url.substring(0, index);
137: String port = url.substring(index + 1);
138: connect(port, ip, credentials);
139: }
140:
141: /**
142: * Creates a new <code>RmiJmxClient.java</code> object
143: *
144: * @param port the port of the host to connect to
145: * @param host the host name to connect to
146: * @param credentials to use for the connection
147: * @throws IOException if connect fails
148: */
149: public RmiJmxClient(String port, String host, Object credentials)
150: throws IOException {
151: connect(port, host, credentials);
152: }
153:
154: /**
155: * Connect to the MBean server
156: *
157: * @param port the port of the host to connect to
158: * @param host the host name to connect to
159: * @param credentials to use for the connection
160: * @throws IOException if connect fails
161: */
162: public void connect(String port, String host, Object credentials)
163: throws IOException {
164:
165: JMXServiceURL address = new JMXServiceURL(
166: "service:jmx:rmi:///jndi/rmi://" + host + ":" + port
167: + "/jmxrmi");
168:
169: Map environment = new HashMap();
170:
171: // use username and password for authentication of connections
172: // with the controller, the values are compared to the ones
173: // specified in the controller.xml config file.
174: if (credentials != null) {
175: // this line is not required if no username/password has been configered
176: environment.put(JMXConnector.CREDENTIALS, credentials);
177: }
178:
179: this .credentials = credentials;
180:
181: connector = JMXConnectorFactory.connect(address, environment);
182: remoteHostAddress = host;
183: remoteHostPort = port;
184: invalidateMBeans();
185: }
186:
187: /**
188: * Invalidate all MBeans. When connecting to a new Controller, all the local
189: * MBean instances must be invalidated (since they refered to the previous
190: * Controller and its associated MBean server).
191: */
192: private void invalidateMBeans() {
193: controllerMBean = null;
194: dataMBean = null;
195: backendMBean = null;
196: }
197:
198: /**
199: * List of all the mbean on the current server
200: *
201: * @return a set of <tt>ObjectInstance</tt>
202: * @throws Exception if fails
203: */
204: public Set listSequoiaMBeans() throws Exception {
205: Set set = connector.getMBeanServerConnection().queryMBeans(
206: new ObjectName("sequoia:*"), null);
207: return set;
208: }
209:
210: /**
211: * Get the mbean information
212: *
213: * @param mbean the <tt>ObjectName</tt> of the mbean to access
214: * @return <tt>MBeanInfo</tt> object
215: * @throws Exception if fails
216: */
217: public MBeanInfo getMBeanInfo(ObjectName mbean) throws Exception {
218: return connector.getMBeanServerConnection().getMBeanInfo(mbean);
219: }
220:
221: /**
222: * Get the value of an attribute on the given mbean
223: *
224: * @param mbean the <tt>ObjectName</tt> of the mbean to access
225: * @param attribute the attribute name
226: * @return <tt>Object</tt> being the value returned by the get <Attribute>
227: * method
228: * @throws Exception if fails
229: */
230: public Object getAttributeValue(ObjectName mbean, String attribute)
231: throws Exception {
232: return connector.getMBeanServerConnection().getAttribute(mbean,
233: attribute);
234: }
235:
236: /**
237: * Change an attribute value
238: *
239: * @param mbean the <tt>ObjectName</tt> of the mbean to access
240: * @param attribute the attribute name
241: * @param value the attribute new value
242: * @throws Exception if fails
243: */
244: public void setAttributeValue(ObjectName mbean, String attribute,
245: Object value) throws Exception {
246: Attribute att = new Attribute(attribute, value);
247: connector.getMBeanServerConnection().setAttribute(mbean, att);
248: }
249:
250: /**
251: * Set the current subject for authentication
252: *
253: * @param user the user login
254: * @param password the user password
255: */
256: private Subject getSubject(String user, String password) {
257: // we build a subject for authentication
258: AdminUser dbUser = new AdminUser(user, password);
259: Set principals = new HashSet();
260: principals.add(dbUser);
261: return new Subject(true, principals, new HashSet(),
262: new HashSet());
263: }
264:
265: /**
266: * Get a reference to the virtualdatabaseMbean with the given authentication
267: *
268: * @param database the virtual database name
269: * @param user the user recognized as the <code>VirtualDatabaseUser</code>
270: * @param password the password for the <code>VirtualDatabaseUser</code>
271: * @return <code>VirtualDatabaseMBean</code> instance
272: * @throws IOException if cannot connect to MBean
273: * @throws InstanceNotFoundException if cannot locate MBean
274: * @throws VirtualDatabaseException if virtual database fails
275: */
276: public VirtualDatabaseMBean getVirtualDatabaseProxy(
277: String database, String user, String password)
278: throws InstanceNotFoundException, IOException,
279: VirtualDatabaseException {
280: if (!isValidConnection()) {
281: try {
282: reconnect();
283: } catch (Exception e) {
284: throw new IOException(ConsoleTranslate
285: .get("jmx.server.connection.lost")); //$NON-NLS-1$
286: }
287: }
288: ObjectName db;
289: try {
290: db = JmxConstants.getVirtualDataBaseObjectName(database);
291: } catch (MalformedObjectNameException e) {
292: throw new VirtualDatabaseException(e);
293: }
294: // we open a connection for this subject, all subsequent calls with this
295: // connection will be executed on the behalf of our subject.
296: MBeanServerConnection delegateConnection = connector
297: .getMBeanServerConnection(getSubject(user, password));
298:
299: if (!delegateConnection.isRegistered(db)) {
300: throw new VirtualDatabaseException(ConsoleTranslate
301: .get("virtualdatabase.mbean.not.accessible")); //$NON-NLS-1$
302: }
303:
304: // we create a proxy to the virtual database
305: VirtualDatabaseMBean local = (VirtualDatabaseMBean) MBeanServerInvocationHandler
306: .newProxyInstance(delegateConnection, db,
307: VirtualDatabaseMBean.class, false);
308: checkAccessible(local);
309: // Check authentication
310: boolean authenticated = false;
311: try {
312: authenticated = local.checkAdminAuthentication(user,
313: password);
314: } catch (Exception e) {
315: logger
316: .warn(
317: "Exception while checking virtual database admin authentication",
318: e);
319: throw new VirtualDatabaseException(
320: "Could not check authentication. MBean is not accessible.");
321: }
322: if (!authenticated)
323: throw new VirtualDatabaseException("Authentication Failed");
324:
325: // Add notification listener
326: if (notificationListener != null) {
327: delegateConnection.addNotificationListener(db,
328: notificationListener, null, null);
329:
330: // CounterMonitor cm = new CounterMonitor();
331: // cm.setNotify(true);
332: // cm.setGranularityPeriod(100);
333: // cm.setObservedObject(db);
334: // cm.setObservedAttribute("currentNbOfThreads");
335: // cm.setThreshold(new Integer(6));
336: // cm.start();
337: }
338:
339: return local;
340: }
341:
342: /**
343: * Check that the VirtualDatabaseMBean is accessible. if
344: * <code>virtualDbMBean</code> is <code>null</code>, do nothing
345: *
346: * @param virtualDbMBean the VirtualDatabaseMBean to check
347: * @throws VirtualDatabaseException if the VirtualDatabaseMBean is not
348: * accessible
349: */
350: private void checkAccessible(VirtualDatabaseMBean virtualDbMBean)
351: throws VirtualDatabaseException {
352: if (virtualDbMBean == null) {
353: return;
354: }
355: // we try to get the name of the virtual database from the mbean to
356: // check if it is accessible
357: try {
358: virtualDbMBean.getVirtualDatabaseName();
359: } catch (Exception e) {
360: throw new VirtualDatabaseException(ConsoleTranslate
361: .get("virtualdatabase.mbean.not.accessible")); //$NON-NLS-1$
362: }
363: }
364:
365: /**
366: * Get a proxy to the ControllerMBean
367: *
368: * @return <code>ControllerMBean</code> instance
369: * @throws IOException if cannot connect to MBean
370: */
371: public ControllerMBean getControllerProxy() throws IOException {
372:
373: if (controllerMBean != null && isValidConnection()) {
374: return controllerMBean;
375: } else {
376: if (!isValidConnection()) {
377: try {
378: reconnect();
379: } catch (Exception e) {
380: throw new IOException(ConsoleTranslate
381: .get("jmx.server.connection.lost")); //$NON-NLS-1$
382: }
383: }
384: ObjectName db;
385: try {
386: db = JmxConstants.getControllerObjectName();
387: } catch (MalformedObjectNameException e) {
388: throw new IOException(e.getMessage());
389: }
390:
391: // we create a new proxy to the controller
392: controllerMBean = (ControllerMBean) MBeanServerInvocationHandler
393: .newProxyInstance(connector
394: .getMBeanServerConnection(), db,
395: ControllerMBean.class, false);
396:
397: // Add notification listener
398: if (notificationListener != null) {
399: try {
400: connector.getMBeanServerConnection()
401: .addNotificationListener(db,
402: notificationListener, null, null);
403: } catch (Exception e) {
404: throw new IOException(
405: "Could not register listener on the mbean");
406: }
407: }
408:
409: return controllerMBean;
410: }
411: }
412:
413: /**
414: * Get a proxy to the DataCollectorMBean
415: *
416: * @return <code>DataCollectorMBean</code> instance
417: * @throws IOException if fails
418: */
419: public DataCollectorMBean getDataCollectorProxy()
420: throws IOException {
421:
422: if (dataMBean != null && isValidConnection()) {
423: return dataMBean;
424: } else {
425: if (!isValidConnection())
426: reconnect();
427: ObjectName db = JmxConstants.getDataCollectorObjectName();
428:
429: // we create a new proxy to the data collector
430: dataMBean = (DataCollectorMBean) MBeanServerInvocationHandler
431: .newProxyInstance(connector
432: .getMBeanServerConnection(), db,
433: DataCollectorMBean.class, false);
434: return dataMBean;
435: }
436: }
437:
438: /**
439: * Get a proxy to the DatabaseBackendMBean
440: *
441: * @return <code>DatabaseBackendMBean</code> instance
442: * @param vdb virtual database name
443: * @param backend backend name
444: * @param user user name
445: * @param password password name
446: * @throws IOException if cannot connect to MBean
447: * @throws InstanceNotFoundException if cannot locate MBean
448: */
449: public DatabaseBackendMBean getDatabaseBackendProxy(String vdb,
450: String backend, String user, String password)
451: throws InstanceNotFoundException, IOException {
452: if (backendMBean != null && isValidConnection()) {
453: try {
454: if (backendMBean.getName().equals(backend))
455: return backendMBean;
456: } catch (Exception e) {
457: // backend is no more there
458: }
459: }
460:
461: if (!isValidConnection())
462: reconnect();
463:
464: ObjectName backendObjectName;
465: try {
466: backendObjectName = JmxConstants
467: .getDatabaseBackendObjectName(vdb, backend);
468: } catch (MalformedObjectNameException e) {
469: throw new IOException(e.getMessage());
470: }
471:
472: MBeanServerConnection delegateConnection = connector
473: .getMBeanServerConnection(getSubject(user, password));
474:
475: if (notificationListener != null) {
476: delegateConnection
477: .addNotificationListener(backendObjectName,
478: notificationListener, null, null);
479: StringMonitor sm = new StringMonitor();
480: sm.setObservedObject(backendObjectName);
481: sm.setObservedAttribute("LastKnownCheckpoint");
482: sm.setStringToCompare("hello");
483: sm.setGranularityPeriod(100);
484: sm.setNotifyDiffer(true);
485: sm
486: .addNotificationListener(notificationListener,
487: null, null);
488: sm.start();
489: }
490:
491: // we create a proxy to the database backend
492: backendMBean = (DatabaseBackendMBean) MBeanServerInvocationHandler
493: .newProxyInstance(delegateConnection,
494: backendObjectName, DatabaseBackendMBean.class,
495: false);
496: return backendMBean;
497: }
498:
499: /**
500: * Returns a proxy on a BackendTaskQueuesControlMBean.
501: *
502: * @param vdb name of the virtual database
503: * @param backend name of the backend
504: * @param user user login for the virtual database
505: * @param password user password fort the virtual database
506: * @return a proxy on a BackendTaskQueuesControlMBean
507: * @throws IOException if an I/O exception occured
508: */
509: public BackendTaskQueuesControlMBean getBackendTaskQueues(
510: String vdb, String backend, String user, String password)
511: throws IOException {
512: if (!isValidConnection())
513: reconnect();
514:
515: ObjectName taskQueuesObjectName;
516: try {
517: taskQueuesObjectName = JmxConstants
518: .getBackendTaskQueuesObjectName(vdb, backend);
519: } catch (MalformedObjectNameException e) {
520: throw new IOException(e.getMessage());
521: }
522:
523: MBeanServerConnection delegateConnection = connector
524: .getMBeanServerConnection(getSubject(user, password));
525:
526: // we create a proxy to the database backend
527: return (BackendTaskQueuesControlMBean) MBeanServerInvocationHandler
528: .newProxyInstance(delegateConnection,
529: taskQueuesObjectName,
530: BackendTaskQueuesControlMBean.class, false);
531: }
532:
533: /**
534: * Returns a proxy on a RecoveryLogControlMBean.
535: *
536: * @param vdb name of the virtual databaseTODO: getRecoveryLog definition.
537: * @param user user login for the virtual database
538: * @param password user password fort the virtual database
539: * @return a proxy on a RecoveryLogControlMBean
540: * @throws IOException if an I/O exception occured
541: */
542: public RecoveryLogControlMBean getRecoveryLog(String vdb,
543: String user, String password) throws IOException {
544: if (!isValidConnection())
545: reconnect();
546:
547: ObjectName recoveryLogObjectName;
548: try {
549: recoveryLogObjectName = JmxConstants
550: .getRecoveryLogObjectName(vdb);
551: } catch (MalformedObjectNameException e) {
552: throw new IOException(e.getMessage());
553: }
554:
555: MBeanServerConnection delegateConnection = connector
556: .getMBeanServerConnection(getSubject(user, password));
557:
558: // we create a proxy to the database backend
559: return (RecoveryLogControlMBean) MBeanServerInvocationHandler
560: .newProxyInstance(delegateConnection,
561: recoveryLogObjectName,
562: RecoveryLogControlMBean.class, false);
563: }
564:
565: /**
566: * Returns a proxy on the given vdb Scheduler.
567: *
568: * @param vdb name of the virtual database
569: * @param user user login for the virtual database
570: * @param password user password fort the virtual database
571: * @return a proxy on an AbstractScheduler
572: * @throws IOException if an I/O exception occured
573: */
574: public AbstractSchedulerControlMBean getAbstractScheduler(
575: String vdb, String user, String password)
576: throws IOException {
577: if (!isValidConnection())
578: reconnect();
579:
580: ObjectName recoveryLogObjectName;
581: try {
582: recoveryLogObjectName = JmxConstants
583: .getAbstractSchedulerObjectName(vdb);
584: } catch (MalformedObjectNameException e) {
585: throw new IOException(e.getMessage());
586: }
587:
588: MBeanServerConnection delegateConnection = connector
589: .getMBeanServerConnection(getSubject(user, password));
590:
591: // we create a proxy to the database backend
592: return (AbstractSchedulerControlMBean) MBeanServerInvocationHandler
593: .newProxyInstance(delegateConnection,
594: recoveryLogObjectName,
595: AbstractSchedulerControlMBean.class, false);
596: }
597:
598: /**
599: * Returns a proxy on the given vdb Scheduler parsing cache
600: *
601: * @param vdb name of the virtual database
602: * @param user user login for the virtual database
603: * @param password user password fort the virtual database
604: * @return a proxy on a ParsingCache
605: * @throws IOException if an I/O exception occured
606: */
607: public ParsingCacheMBean getParsingCache(String vdb, String user,
608: String password) throws IOException {
609: if (!isValidConnection())
610: reconnect();
611:
612: ObjectName parsingCacheObjectName;
613: try {
614: parsingCacheObjectName = JmxConstants
615: .getParsingCacheObjectName(vdb);
616: } catch (MalformedObjectNameException e) {
617: throw new IOException(e.getMessage());
618: }
619:
620: MBeanServerConnection delegateConnection = connector
621: .getMBeanServerConnection(getSubject(user, password));
622:
623: // we create a proxy to the database backend
624: return (ParsingCacheMBean) MBeanServerInvocationHandler
625: .newProxyInstance(delegateConnection,
626: parsingCacheObjectName,
627: ParsingCacheMBean.class, false);
628: }
629:
630: /**
631: * Returns a proxy on the RequestManager of the given virtual database.
632: *
633: * @param vdb name of the virtual database
634: * @param user user login for the virtual database
635: * @param password user password fort the virtual database
636: * @return a proxy on a RequestManager
637: * @throws IOException if an I/O exception occured
638: */
639: public RequestManagerMBean getRequestManager(String vdb,
640: String user, String password) throws IOException {
641: if (!isValidConnection())
642: reconnect();
643:
644: ObjectName requestManagerObjectName;
645: try {
646: requestManagerObjectName = JmxConstants
647: .getRequestManagerObjectName(vdb);
648: } catch (MalformedObjectNameException e) {
649: throw new IOException(e.getMessage());
650: }
651:
652: MBeanServerConnection delegateConnection = connector
653: .getMBeanServerConnection(getSubject(user, password));
654:
655: // we create a proxy to the database backend
656: return (RequestManagerMBean) MBeanServerInvocationHandler
657: .newProxyInstance(delegateConnection,
658: requestManagerObjectName,
659: RequestManagerMBean.class, false);
660: }
661:
662: /**
663: * Get the controller name used for jmx connection This is
664: * [hostname]:[jmxServerPort]
665: *
666: * @return <code>remoteHostName+":"+remoteHostPort</code>
667: */
668: public String getRemoteName() {
669: return remoteHostAddress + ":" + remoteHostPort;
670: }
671:
672: /**
673: * Returns the remoteHostAddress value.
674: *
675: * @return Returns the remoteHostAddress.
676: */
677: public String getRemoteHostAddress() {
678: return remoteHostAddress;
679: }
680:
681: /**
682: * Returns the remoteHostPort value.
683: *
684: * @return Returns the remoteHostPort.
685: */
686: public String getRemoteHostPort() {
687: return remoteHostPort;
688: }
689:
690: /**
691: * Reconnect to the same mbean server
692: *
693: * @throws IOException if reconnection failed
694: */
695: public void reconnect() throws IOException {
696: connect(remoteHostPort, remoteHostAddress, credentials);
697: }
698:
699: /**
700: * Test if the connection with the mbean server is still valid
701: *
702: * @return true if it is
703: */
704: public boolean isValidConnection() {
705: try {
706: connector.getMBeanServerConnection().getMBeanCount();
707: return true;
708: } catch (Exception e) {
709: controllerMBean = null;
710: backendMBean = null;
711: dataMBean = null;
712: return false;
713: }
714: }
715: }
|