001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ha.framework.server.util;
023:
024: import java.net.InetAddress;
025: import java.util.ArrayList;
026: import java.util.Vector;
027: import java.io.IOException;
028: import javax.naming.InitialContext;
029: import javax.management.ObjectName;
030:
031: import org.jboss.ha.framework.interfaces.HAPartition;
032: import org.jboss.ha.framework.interfaces.HAPartition.AsynchHAMembershipListener;
033: import org.jboss.system.ServiceMBeanSupport;
034: import org.jboss.system.server.ServerConfigUtil;
035: import org.jboss.logging.Logger;
036: import org.apache.log4j.MDC;
037:
038: /** A cluster parition membership monitor. It can be used to view how
039: the nodes in a cluster are seeing the topology change using either email or
040: a centralized log server.
041:
042: To use this to send email on change notifications use the following log4j.xml
043: fragments:
044:
045: <appender name="SMTP" class="org.apache.log4j.net.SMTPAppender">
046: <param name="To" value="admin@dot.com"/>
047: <param name="From" value="cluster-monitor@dot.com"/>
048: <param name="Subject" value="JBoss Cluster Changes"/>
049: <param name="SMTPHost" value="mailhost"/>
050: <param name="BufferSize" value="8"/>
051: <param name="EvaluatorClass"
052: value="org.jboss.logging.appender.RegexEventEvaluator" />
053: <layout class="org.apache.log4j.PatternLayout">
054: <param name="ConversionPattern" value="[%d{ABSOLUTE},%c{1}] %m%n"/>
055: </layout>
056: </appender>
057:
058: <category name="org.jboss.ha.framework.server.util.TopologyMonitorService.membershipChanged">
059: <priority value="DEBUG" />
060: <appender-ref ref="SMTP"/>
061: </category>
062:
063: You can also have this service notify another MBean of the change to perform
064: arbitrary checks by specifying the MBean name as the TriggerServiceName
065: attribute value. This MBean must have an operation with the following
066: signature:
067: <pre>
068: param: removed ArrayList<AddressPort> of nodes that were removed
069: param: added ArrayList<AddressPort> of nodes that were added
070: param: members ArrayList<AddressPort> of nodes currently in the cluster
071: param: logCategoryName the log4j category name used by the
072: TopologyMonitorService. This should be used for logging to integrate with
073: the TopologyMonitorService output.
074: public void membershipChanged(ArrayList deadMembers, ArrayList newMembers,
075: ArrayList allMembers, String logCategoryName)
076: </pre>
077:
078: @author Scott.Stark@jboss.org
079: @version $Revision: 57188 $
080: */
081: public class TopologyMonitorService extends ServiceMBeanSupport
082: implements TopologyMonitorServiceMBean,
083: AsynchHAMembershipListener {
084: private static final String CHANGE_NAME = TopologyMonitorService.class
085: .getName()
086: + ".membershipChanged";
087: private static Logger changeLog = Logger.getLogger(CHANGE_NAME);
088: private String partitionName = ServerConfigUtil
089: .getDefaultPartitionName();
090: private HAPartition partition;
091: private String hostname;
092: private ObjectName triggerServiceName;
093:
094: public TopologyMonitorService() {
095: }
096:
097: // --- Begin ServiceMBeanSupport overriden methods
098: protected void startService() throws Exception {
099: InitialContext ctx = new InitialContext();
100: String partitionJndiName = "/HAPartition/" + partitionName;
101: partition = (HAPartition) ctx.lookup(partitionJndiName);
102: // Register as a listener of cluster membership changes
103: partition.registerMembershipListener(this );
104: log.info("Registered as MembershipListener");
105: try {
106: hostname = InetAddress.getLocalHost().getHostName();
107: } catch (IOException e) {
108: log.warn("Failed to lookup local hostname", e);
109: hostname = "<unknown>";
110: }
111: }
112:
113: protected void stopService() throws Exception {
114: partition.unregisterMembershipListener(this );
115: }
116:
117: // --- End ServiceMBeanSupport overriden methods
118:
119: // --- Begin TopologyMonitorServiceMBean interface methods
120: public String getPartitionName() {
121: return partitionName;
122: }
123:
124: public void setPartitionName(String name) {
125: this .partitionName = name;
126: }
127:
128: public ObjectName getTriggerServiceName() {
129: return triggerServiceName;
130: }
131:
132: public void setTriggerServiceName(ObjectName triggerServiceName) {
133: this .triggerServiceName = triggerServiceName;
134: }
135:
136: public Vector getClusterNodes() {
137: Vector view = null;
138: try {
139: InitialContext ctx = new InitialContext();
140: String jndiName = "/HAPartition/" + partitionName;
141: HAPartition partition = (HAPartition) ctx.lookup(jndiName);
142: view = partition.getCurrentView();
143: } catch (Exception e) {
144: log.error("Failed to access HAPartition state", e);
145: }
146: return view;
147: }
148:
149: // --- End TopologyMonitorServiceMBean interface methods
150:
151: // --- Begin HAMembershipListener interface methods
152: /** Called when a new partition topology occurs.
153: * @param deadMembers A list of nodes that have died since the previous view
154: * @param newMembers A list of nodes that have joined the partition since
155: * the previous view
156: * @param allMembers A list of nodes that built the current view
157: */
158: public void membershipChanged(final Vector deadMembers,
159: final Vector newMembers, final Vector allMembers) {
160: MDC.put("RegexEventEvaluator", "End membershipChange.*");
161: ArrayList removed = new ArrayList();
162: ArrayList added = new ArrayList();
163: ArrayList members = new ArrayList();
164: changeLog.info("Begin membershipChanged info, hostname="
165: + hostname);
166: changeLog.info("DeadMembers: size=" + deadMembers.size());
167: for (int m = 0; m < deadMembers.size(); m++) {
168: AddressPort addrInfo = getMemberAddress(deadMembers.get(m));
169: removed.add(addrInfo);
170: changeLog.info(addrInfo);
171: }
172: changeLog.info("NewMembers: size=" + newMembers.size());
173: for (int m = 0; m < newMembers.size(); m++) {
174: AddressPort addrInfo = getMemberAddress(newMembers.get(m));
175: added.add(addrInfo);
176: changeLog.info(addrInfo);
177: }
178: changeLog.info("AllMembers: size=" + allMembers.size());
179: for (int m = 0; m < allMembers.size(); m++) {
180: AddressPort addrInfo = getMemberAddress(allMembers.get(m));
181: members.add(addrInfo);
182: changeLog.info(addrInfo);
183: }
184: // Notify the trigger MBean
185: if (triggerServiceName != null) {
186: changeLog.info("Invoking trigger service: "
187: + triggerServiceName);
188: try {
189: Object[] params = { removed, added, members,
190: CHANGE_NAME };
191: String[] sig = { "java.util.ArrayList",
192: "java.util.ArrayList", "java.util.ArrayList",
193: "java.lang.String" };
194: server.invoke(triggerServiceName, "membershipChanged",
195: params, sig);
196: } catch (Throwable t) {
197: changeLog.error("Failed to notify trigger service: "
198: + triggerServiceName, t);
199: log.debug("Failed to notify trigger service: "
200: + triggerServiceName, t);
201: }
202: }
203: changeLog.info("End membershipChanged info, hostname="
204: + hostname);
205: MDC.remove("RegexEventEvaluator");
206: }
207:
208: // --- End HAMembershipListener interface methods
209:
210: /** Use reflection to access the address InetAddress and port if they exist
211: * in the Address implementation
212: */
213: private AddressPort getMemberAddress(Object addr) {
214: AddressPort info = null;
215: try {
216: org.jboss.ha.framework.interfaces.ClusterNode node = (org.jboss.ha.framework.interfaces.ClusterNode) addr;
217:
218: InetAddress inetAddr = node.getOriginalJGAddress()
219: .getIpAddress();
220: Integer port = new Integer(node.getOriginalJGAddress()
221: .getPort());
222: info = new AddressPort(inetAddr, port);
223: } catch (Exception e) {
224: log.warn("Failed to obtain InetAddress/port from addr: "
225: + addr, e);
226: }
227: return info;
228: }
229:
230: public static class AddressPort {
231: InetAddress addr;
232: Integer port;
233:
234: AddressPort(InetAddress addr, Integer port) {
235: this .addr = addr;
236: this .port = port;
237: }
238:
239: public Integer getPort() {
240: return port;
241: }
242:
243: public InetAddress getInetAddress() {
244: return addr;
245: }
246:
247: public String getHostAddress() {
248: return addr.getHostAddress();
249: }
250:
251: public String getHostName() {
252: return addr.getHostName();
253: }
254:
255: public String toString() {
256: return "{host(" + addr + "), port(" + port + ")}";
257: }
258: }
259: }
|