/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.agent; import java.net.MalformedURLException; import mazz.i18n.Logger; import org.jboss.remoting.InvokerLocator; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.enterprise.agent.i18n.AgentI18NFactory; import org.rhq.enterprise.agent.i18n.AgentI18NResourceKeys; import org.rhq.enterprise.communications.command.CommandResponse; import org.rhq.enterprise.communications.command.client.RemoteCommunicator; import org.rhq.enterprise.communications.command.impl.generic.GenericCommandClient; import org.rhq.enterprise.communications.command.impl.identify.IdentifyCommand; import org.rhq.enterprise.communications.command.impl.identify.IdentifyCommandResponse; import org.rhq.enterprise.communications.command.server.discovery.AutoDiscoveryListener; /** * This is the agent's listener that will get notified when new RHQ Servers come on and offline. * * <p>Because a remoting server's invoker locator representation may differ slightly on the client-side, this listener * performs some additional work to try to determine what the RHQ Server calls itself (as opposed to what the agent * calls the server). Otherwise, we may get a notification about the RHQ Server and not realize it because we will be * looking for one invoker locator representation when the notification will have a slightly different one (this * illustrates a slight hole in the InvokerLocator.equals() implementation). If we cannot determine what the RHQ * Server's true locator is, then when we compare the locator in the notification with the one we are looking for, they * may look like different locators when they are actually referring to the same server endpoint.</p> * * @author John Mazzitelli */ public class AgentAutoDiscoveryListener implements AutoDiscoveryListener { /** * Logger */ private static final Logger LOG = AgentI18NFactory.getLogger(AgentAutoDiscoveryListener.class); /** * The agent that created this object. This agent has the sender object that this listener will start and stop when * appropriate. */ private final AgentMain m_agent; /** * A communicator we can use to send commands directly to the server */ private RemoteCommunicator m_remoteCommunicator; /** * If known, this will be the locator that the server advertises itself as. If this is not yet known, this will be * <code>null</code>. */ private InvokerLocator m_serverToBeListenedFor; /** * This is simply a flag to eliminate a flood of log messages that would get dumped each time the server failed to * be communicated with. We want to warn once in the case the failure is due to a misconfiguration (the log message * should help diagnose the misconfiguration), but we don't want to continually log warnings in the normal case when * the server just isn't up yet. */ private boolean m_warnedAboutConnectionFailure; /** * Constructor for {@link AgentAutoDiscoveryListener} that is given the agent that created this listener. * * @param agent * @param communicator a communicator that we can use to send commands directly to the server that we are listening * for */ public AgentAutoDiscoveryListener(AgentMain agent, RemoteCommunicator communicator) { m_agent = agent; m_remoteCommunicator = communicator; m_serverToBeListenedFor = null; m_warnedAboutConnectionFailure = false; } /** * If the auto-detected endpoint is the RHQ Server we are looking for, enable the agent to start sending messages to * it. * * <p>If this listener does not yet know what the RHQ Server calls itself, then we assume the new remote server * coming online is our server and so we attempt to directly send our server an {@link IdentifyCommand} to ask it * for its true invoker locator representation. If our server is online, then the identify command's response will * contain the server's true invoker locator and we will use it rather than the locator as configured in the * agent.</p> * * @see AutoDiscoveryListener#serverOnline(InvokerLocator) */ public void serverOnline(InvokerLocator locator) { // if we do not yet know the exact invoker locator that our server calls itself, let's try to send // an identify command to our server via its remote communicator and ask it for its locator if (m_serverToBeListenedFor == null) { m_serverToBeListenedFor = attemptToIdentifyServer(); } if (isServerToBeListenedFor(locator)) { LOG.info(AgentI18NResourceKeys.SERVER_ONLINE, locator); m_agent.getClientCommandSender().startSending(); } return; } /** * If the auto-detected endpoint is the server we are looking for, this tells the agent to stop sending messages. * * @see AutoDiscoveryListener#serverOffline(InvokerLocator) */ public void serverOffline(InvokerLocator locator) { if (isServerToBeListenedFor(locator)) { LOG.info(AgentI18NResourceKeys.SERVER_OFFLINE, locator); // stop sending commands - there is no sense processing the commands currently in the queue since they will fail anyway m_agent.getClientCommandSender().stopSending(false); } return; } /** * This will attempt to identify the server's true invoker locator object by sending it a direct message. If it gets * a response, it will return the server's invoker locator. If, for any reason, this method fails to identify the * server, no exception is thrown and <code>null</code> is returned. * * @return server's invoker locator or <code>null</code> if server could not be identified */ private InvokerLocator attemptToIdentifyServer() { try { GenericCommandClient client = new GenericCommandClient(m_remoteCommunicator); CommandResponse genericResponse = client.invoke(new IdentifyCommand()); IdentifyCommandResponse identifyResponse = new IdentifyCommandResponse(genericResponse); if (identifyResponse.getException() != null) { throw identifyResponse.getException(); } return new InvokerLocator(identifyResponse.getIdentification().getInvokerLocator()); } catch (Throwable ignore) { // This probably just means that the server isn't online yet; we can ignore - we'll be called later for another attempt. // However, we want to log a warning at least once in case this is a configuration error (in which case // the connection will never succeed - without this log message, it will be hard to debug the misconfiguration). if (!m_warnedAboutConnectionFailure) { m_warnedAboutConnectionFailure = true; LOG.debug(AgentI18NResourceKeys.SERVER_ID_FAILURE, ThrowableUtil.getAllMessages(ignore)); } } return null; } /** * Compares the given invoker locator with the RHQ Server locator that this object is listening for. If its the * same, then <code>true</code> is returned; if they are not the same, <code>false</code> is returned. * * @param compare_me the locator to compare with the locator of the RHQ Server this object is listening for * * @return <code>true</code> if the given invoker locator represents the same server endpoint that this object is * listening for */ private boolean isServerToBeListenedFor(InvokerLocator compare_me) { InvokerLocator server_locator = getServerToBeListenedFor(); // we just care about host and port - assume transport and transport params may be different between client and server return server_locator.getHost().equals(compare_me.getHost()) && server_locator.getPort() == compare_me.getPort(); } /** * This returns the endpoint locator of the RHQ Server that our listener is listening for. * * @return the endpoint locator of the server we are listening for * * @throws RuntimeException if the configured locator URI is malformed */ private InvokerLocator getServerToBeListenedFor() { // if we've already established the server's actual invoker locator, then return it immediately if (m_serverToBeListenedFor != null) { return m_serverToBeListenedFor; } // we do not yet know what our server's true locator is, let's just use the locator as configured in the agent String locator_uri = m_agent.getConfiguration().getServerLocatorUri(); InvokerLocator locator; try { locator = new InvokerLocator(locator_uri); } catch (MalformedURLException e) { // this should never happen throw new RuntimeException(LOG.getMsgString(AgentI18NResourceKeys.INVALID_LOCATOR_URI), e); } return locator; } }