/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2010], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.agent.client; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.Socket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.agent.AgentAPI; import org.hyperic.hq.agent.AgentConnectionException; import org.hyperic.hq.agent.AgentRemoteException; import org.hyperic.hq.agent.AgentRemoteValue; import org.hyperic.hq.agent.AgentStreamPair; import org.hyperic.util.timer.StopWatch; /** * The object which represents the connection between the client and the * Agent. It holds Agent contact information, and may perform connection * caching. */ public class AgentConnection { private static final Log log = LogFactory.getLog(AgentConnection.class); private static final int MAX_RETRIES = 5; private static final long SLEEP_TIME = 3000; private static final int SOCKET_TIMEOUT = 60000; private String _agentAddress; private int _agentPort; private AgentAPI _agentAPI; /** * Create a connection to an Agent with the specified address and * port. * * @param agentAddress IP address of the Agent * @param agentPort Port the Agent is listening on */ public AgentConnection(String agentAddress, int agentPort) { _agentAddress = agentAddress; _agentPort = agentPort; _agentAPI = new AgentAPI(); } public String getAgentAddress() { return _agentAddress; } public int getAgentPort() { return _agentPort; } protected Socket getSocket() throws IOException { Socket s; // Connect to the remote agent try { s = new Socket(_agentAddress, _agentPort); } catch(IOException exc){ String msg = "Error connecting to agent @ ("+ _agentAddress+ ":"+ _agentPort+"): " + exc.getMessage(); IOException toThrow = new IOException(msg); // call initCause instead of constructor to be java 1.5 compat toThrow.initCause(exc); throw toThrow; } s.setSoTimeout(SOCKET_TIMEOUT); return s; } /** * Send a command to the remote Agent. This routine blocks, while sending * the data to the agent and waiting for the result. * * @param cmdName Name of the remote method to execute * @param cmdVersion API version number belonging to the command * @param arg Argument to send to the remote method * * @return an AgentRemoteValue object, as returned from the Agent. * * @throws AgentRemoteException indicating an error invoking the method. * @throws AgentConnectionException indicating a failure to connect to, or * communicate with the agent. */ public AgentRemoteValue sendCommand(String cmdName, int cmdVersion, AgentRemoteValue arg) throws AgentRemoteException, AgentConnectionException { if (log.isDebugEnabled()) log.debug(_agentAddress + ":" + _agentPort + " -> " + cmdName); AgentStreamPair sPair = this.sendCommandHeaders(cmdName, cmdVersion, arg); return this.getCommandResult(sPair); } /** * Send a command to the remote Agent. This routine blocks, while sending * the data to the agent and waiting for the result. * * @param cmdName Name of the remote method to execute * @param cmdVersion API version number belonging to the command * @param arg Argument to send to the remote method * @param withRetires Tells the api to retry the command if an IOException is thrown during * its attempt to communicate to the agent * * @return an AgentRemoteValue object, as returned from the Agent. * * @throws AgentRemoteException indicating an error invoking the method. * @throws AgentConnectionException indicating a failure to connect to, or * communicate with the agent. */ public AgentRemoteValue sendCommand(String cmdName, int cmdVersion, AgentRemoteValue arg, boolean withRetries) throws AgentRemoteException, AgentConnectionException { if (log.isDebugEnabled()) log.debug(_agentAddress + ":" + _agentPort + " -> " + cmdName); AgentStreamPair sPair = this.sendCommandHeaders(cmdName, cmdVersion, arg, withRetries); return this.getCommandResult(sPair); } /** * Send the command to the agent, not waiting for it to process the * result. This call must be paired with a single 'getCommandResult' * which is passed the stream pair which is returned from this * routine. By calling sendCommandHeaders and getCommandResult * seperately, callers can use the stream pair to perform special * communication with the remote handlers, not supported by * the agent transportation framework (such as streamed file transfer). * * @param cmdName Name of the remote method to execute * @param cmdVersion API version number belonging to the command * @param arg Argument to send to the remote method * * @return an AgentRemoteValue object, as returned from the Agent. * * @throws AgentConnectionException indicating a failure to connect to, or * communicate with the agent. */ public AgentStreamPair sendCommandHeaders(String cmdName, int cmdVersion, AgentRemoteValue arg) throws AgentConnectionException { return sendCommandHeaders(cmdName, cmdVersion, arg, true); } private AgentStreamPair sendCommandHeaders(String cmdName, int cmdVersion, AgentRemoteValue arg, boolean withRetries) throws AgentConnectionException { final StopWatch watch = new StopWatch(); final boolean debug = log.isDebugEnabled(); try { if (debug) watch.markTimeBegin("cmdName=" + cmdName); if (withRetries) { return sendCommandHeadersWithRetries(cmdName, cmdVersion, arg, MAX_RETRIES); } else { return sendCommandHeadersWithRetries(cmdName, cmdVersion, arg, 1); } } catch(IOException exc){ throw new AgentConnectionException( "Error sending argument: " + exc.getMessage() + ", cmd=" + cmdName, exc); } finally { if (debug) watch.markTimeEnd("cmdName=" + cmdName); if (debug) log.debug(watch); } } private AgentStreamPair sendCommandHeadersWithRetries(String cmdName, int cmdVersion, AgentRemoteValue arg, int maxRetries) throws IOException { IOException ex = null; AgentStreamPair streamPair = null; Socket s = null; int tries = 0; while (tries++ < maxRetries) { try { s = getSocket(); streamPair = new SocketStreamPair(s, s.getInputStream(), s.getOutputStream()); DataOutputStream outputStream = new DataOutputStream(streamPair.getOutputStream()); outputStream.writeInt(_agentAPI.getVersion()); outputStream.writeInt(cmdVersion); outputStream.writeUTF(cmdName); arg.toStream(outputStream); outputStream.flush(); return streamPair; } catch (IOException e) { ex = e; close(s); } if (tries >= maxRetries) { break; } try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { log.debug(e,e); } } if (ex != null) { IOException toThrow = new IOException(ex.getMessage()+ ", retried " + MAX_RETRIES + " times"); // call initCause instead of constructor to be java 1.5 compat toThrow.initCause(ex); throw toThrow; } return streamPair; } private void close(Socket s) { if (s == null) { return; } try { s.close(); } catch(IOException ex) { log.debug(ex, ex); } } /** * Get the result of command execution from the remote command handler. * * @param inStreamPair The pair which was returned from the associated * sendCommandHeaders invocation. * * @return an AgentRemoteValue object, as returned from the Agent. * * @throws AgentRemoteException indicating an error invoking the method. * @throws AgentConnectionException indicating a failure to communicate * with the agent. */ public AgentRemoteValue getCommandResult(AgentStreamPair inStreamPair) throws AgentRemoteException, AgentConnectionException { SocketStreamPair streamPair = (SocketStreamPair)inStreamPair; // Get the response try { DataInputStream inputStream; int isException; inputStream = new DataInputStream(streamPair.getInputStream()); isException = inputStream.readInt(); if(isException == 1){ String exceptionMsg = inputStream.readUTF(); throw new AgentRemoteException(exceptionMsg); } else { AgentRemoteValue result; result = AgentRemoteValue.fromStream(inputStream); return result; } } catch(EOFException exc){ throw new AgentConnectionException("EOF received from Agent"); } catch(IOException exc){ throw new AgentConnectionException("Error reading result: " + exc.getMessage(), exc); } finally { close(streamPair); } } private void close(SocketStreamPair streamPair) { if (streamPair == null) { return; } try { streamPair.close(); } catch(IOException e){ log.debug(e,e); } } public void closeSocket() { try { getSocket().close(); } catch (IOException e) { log.debug(e,e); } } public boolean equals(Object o) { return o instanceof AgentConnection && ((AgentConnection)o).getAgentAddress().equals(getAgentAddress()) && ((AgentConnection)o).getAgentPort() == getAgentPort(); } public int hashCode() { return getAgentAddress().hashCode() + getAgentPort(); } }