/*
* IpmiAsyncConnector.java
* Created on 2011-09-07
*
* Copyright (c) Verax Systems 2011.
* All rights reserved.
*
* This software is furnished under a license. Use, duplication,
* disclosure and all other uses are restricted to the rights
* specified in the written license agreement.
*/
package com.veraxsystems.vxipmi.api.async;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.veraxsystems.vxipmi.api.async.messages.IpmiError;
import com.veraxsystems.vxipmi.api.async.messages.IpmiResponse;
import com.veraxsystems.vxipmi.api.async.messages.IpmiResponseData;
import com.veraxsystems.vxipmi.coding.commands.IpmiCommandCoder;
import com.veraxsystems.vxipmi.coding.commands.PrivilegeLevel;
import com.veraxsystems.vxipmi.coding.commands.ResponseData;
import com.veraxsystems.vxipmi.coding.commands.session.GetChannelAuthenticationCapabilitiesResponseData;
import com.veraxsystems.vxipmi.coding.payload.lan.IPMIException;
import com.veraxsystems.vxipmi.coding.security.CipherSuite;
import com.veraxsystems.vxipmi.common.PropertiesManager;
import com.veraxsystems.vxipmi.connection.Connection;
import com.veraxsystems.vxipmi.connection.ConnectionException;
import com.veraxsystems.vxipmi.connection.ConnectionListener;
import com.veraxsystems.vxipmi.connection.ConnectionManager;
import com.veraxsystems.vxipmi.connection.StateConnectionException;
/**
*
* Asynchronous API for connecting to BMC via IPMI.
*
*
* Creating connection consists of the following steps:
*
Create {@link Connection} and get associated with it
* {@link ConnectionHandle} via {@link #createConnection(InetAddress)}
* Get {@link CipherSuite}s that are available for the connection via
* {@link #getAvailableCipherSuites(ConnectionHandle)}
* Pick {@link CipherSuite} and {@link PrivilegeLevel} that will be used
* during session and get
* {@link GetChannelAuthenticationCapabilitiesResponseData} to find out allowed
* authentication options via
* {@link #getChannelAuthenticationCapabilities(ConnectionHandle, CipherSuite, PrivilegeLevel)}
* Provide username, password and (if the BMC needs it) the BMC Kg key and
* start session via
* {@link #openSession(ConnectionHandle, String, String, byte[])}
*
*
*
* To send message register for receiving answers via
* {@link #registerListener(IpmiListener)} and send message via
* {@link #sendMessage(ConnectionHandle, IpmiCommandCoder)}
*
*
*
* To close session call {@link #closeSession(ConnectionHandle)}
*
*
*/
public class IpmiAsyncConnector implements ConnectionListener {
private ConnectionManager connectionManager;
private int retries;
private List listeners;
private static Logger logger = Logger.getLogger(IpmiAsyncConnector.class);
/**
* Starts {@link IpmiAsyncConnector} and initiates the
* {@link ConnectionManager} at the given port. The wildcard IP address will
* be used.
*
* @param port
* - the port that will be used by {@link IpmiAsyncConnector} to
* communicate with the remote hosts.
* @throws IOException
* when properties file was not found
*/
public IpmiAsyncConnector(int port) throws IOException {
listeners = new ArrayList();
connectionManager = new ConnectionManager(port);
loadProperties();
}
/**
* Starts {@link IpmiAsyncConnector} and initiates the
* {@link ConnectionManager} at the given port and IP interface.
*
* @param port
* - the port that will be used by {@link IpmiAsyncConnector} to
* communicate with the remote hosts.
* @param address
* - the IP address that will be used by
* {@link IpmiAsyncConnector} to communicate with the remote
* hosts.
* @throws IOException
* when properties file was not found
*/
public IpmiAsyncConnector(int port, InetAddress address) throws IOException {
listeners = new ArrayList();
connectionManager = new ConnectionManager(port, address);
loadProperties();
}
private void loadProperties() throws IOException {
retries = Integer.parseInt(PropertiesManager.getInstance().getProperty("retries"));
}
/**
* Creates connection to the remote host.
*
* @param address
* - {@link InetAddress} of the remote host
* @return handle to the connection to the remote host
* @throws IOException
* when properties file was not found
* @throws FileNotFoundException
* when properties file was not found
*/
public ConnectionHandle createConnection(InetAddress address)
throws FileNotFoundException, IOException {
int handle = connectionManager.createConnection(address);
connectionManager.getConnection(handle).registerListener(this);
return new ConnectionHandle(handle);
}
/**
* Gets {@link CipherSuite}s available for the connection with the remote
* host.
*
* @param connectionHandle
* {@link ConnectionHandle} to the connection created before
* @see #createConnection(InetAddress)
* @return list of the {@link CipherSuite}s that are allowed during the
* connection
* @throws Exception
* when sending message to the managed system fails
*/
public List getAvailableCipherSuites(
ConnectionHandle connectionHandle) throws Exception {
int tries = 0;
List result = null;
while (tries <= retries && result == null) {
try {
++tries;
result = connectionManager
.getAvailableCipherSuites(connectionHandle.getHandle());
} catch (IPMIException e) {
logger.warn("Failed to receive answer, cause:", e);
if (tries > retries) {
throw e;
}
} catch (StateConnectionException e) {
// state error, no retry
throw e;
} catch (Exception e) {
if(e instanceof IOException) {
// Normal network error
logger.warn("Failed to receive answer, cause: " + e.getMessage());
} else {
logger.warn("Failed to receive answer, cause:", e);
}
if (tries > retries) {
throw e;
}
}
}
return result;
}
/**getAvailableCipherSuites fails on some IPMI, because it's an anonymous request and
* some IPMI interfaces don't like that.
*
* So getAllCipherSuites simulate a request (and keep the state machine happy)
* but returns all cipher, without requesting the IPMI.
*
* @param connectionHandle
* {@link ConnectionHandle} to the connection created before
* @see #createConnection(InetAddress)
* @return list of the {@link CipherSuite}s that are allowed during the
* connection
* @throws Exception
* when sending message to the managed system fails
*/
public List getAllCipherSuites(
ConnectionHandle connectionHandle) throws StateConnectionException {
List result = connectionManager.getAllCipherSuites(connectionHandle.getHandle());
return result;
}
/**
* Gets the authentication capabilities for the connection with the remote
* host.
*
* @param connectionHandle
* - {@link ConnectionHandle} associated with the host
* @param cipherSuite
* - {@link CipherSuite} that will be used during the connection
* @param requestedPrivilegeLevel
* - {@link PrivilegeLevel} that is requested for the session
* @return - {@link GetChannelAuthenticationCapabilitiesResponseData}
* @throws ConnectionException
* when connection is in the state that does not allow to
* perform this operation.
* @throws Exception
* when sending message to the managed system fails
*/
public GetChannelAuthenticationCapabilitiesResponseData getChannelAuthenticationCapabilities(
ConnectionHandle connectionHandle, CipherSuite cipherSuite,
PrivilegeLevel requestedPrivilegeLevel) throws Exception {
int tries = 0;
GetChannelAuthenticationCapabilitiesResponseData result = null;
while (tries <= retries && result == null) {
try {
++tries;
result = connectionManager
.getChannelAuthenticationCapabilities(
connectionHandle.getHandle(), cipherSuite,
requestedPrivilegeLevel);
connectionHandle.setCipherSuite(cipherSuite);
connectionHandle.setPrivilegeLevel(requestedPrivilegeLevel);
} catch (StateConnectionException e) {
// state error, no retry
throw e;
} catch (Exception e) {
if(e instanceof IOException) {
// Normal network error
logger.warn("Failed to receive answer, cause:" + e.getMessage());
} else {
logger.warn("Failed to receive answer, cause:", e);
}
logger.warn("Failed to receive answer, cause:", e);
if (tries > retries) {
throw e;
}
}
}
return result;
}
/**
* Establishes the session with the remote host.
*
* @param connectionHandle
* - {@link ConnectionHandle} associated with the remote host.
* @param username
* - the username
* @param password
* - password matching the username
* @param bmcKey
* - the key that should be provided if the two-key
* authentication is enabled, null otherwise.
* @throws ConnectionException
* when connection is in the state that does not allow to
* perform this operation.
* @throws Exception
* when sending message to the managed system or initializing
* one of the cipherSuite's algorithms fails
*/
public void openSession(ConnectionHandle connectionHandle, String username,
String password, byte[] bmcKey) throws Exception {
int tries = 0;
boolean succeded = false;
while (tries <= retries && !succeded) {
try {
++tries;
connectionManager.startSession(connectionHandle.getHandle(),
connectionHandle.getCipherSuite(),
connectionHandle.getPrivilegeLevel(), username,
password, bmcKey);
succeded = true;
} catch (StateConnectionException e) {
// state error, no retry
throw e;
} catch (Exception e) {
if(e instanceof IOException) {
// Normal network error
logger.warn("Failed to receive answer, cause:" + e.getMessage());
} else {
logger.warn("Failed to receive answer, cause:", e);
}
if (tries > retries) {
throw e;
}
}
}
return;
}
/**
* Closes the session with the remote host if it is currently in open state.
*
* @param connectionHandle
* - {@link ConnectionHandle} associated with the remote host.
* @throws ConnectionException
* when connection is in the state that does not allow to
* perform this operation.
* @throws Exception
* when sending message to the managed system or initializing
* one of the cipherSuite's algorithms fails
*/
public void closeSession(ConnectionHandle connectionHandle)
throws Exception {
if (!connectionManager.getConnection(connectionHandle.getHandle())
.isSessionValid()) {
return;
}
int tries = 0;
boolean succeded = false;
while (tries <= retries && !succeded) {
try {
++tries;
connectionManager.getConnection(connectionHandle.getHandle()).closeSession();
succeded = true;
} catch (StateConnectionException e) {
// state error, no retry
throw e;
} catch (Exception e) {
if(e instanceof IOException) {
// Normal network error
logger.warn("Failed to receive answer, cause:" + e.getMessage());
} else {
logger.warn("Failed to receive answer, cause:", e);
}
if (tries > retries) {
throw e;
}
}
}
return;
}
/**
* Sends the IPMI message to the remote host.
*
* @param connectionHandle
* - {@link ConnectionHandle} associated with the remote host.
* @param request
* - {@link IpmiCommandCoder} containing the request to be sent
* @return ID of the message that will be also attached to the response to
* pair request with response if queue was not full and message was
* sent, -1 if sending of the message failed.
*
* @throws ConnectionException
* when connection is in the state that does not allow to
* perform this operation.
* @throws Exception
* when sending message to the managed system or initializing
* one of the cipherSuite's algorithms fails
*/
public int sendMessage(ConnectionHandle connectionHandle,
IpmiCommandCoder request) throws Exception {
int tries = 0;
int tag = -1;
while (tries <= retries && tag < 0) {
try {
++tries;
while (tag < 0) {
tag = connectionManager.getConnection(
connectionHandle.getHandle()).sendIpmiCommand(request);
if (tag < 0) {
Thread.sleep(10); // tag < 0 means that MessageQueue is
// full so we need to wait and retry
}
}
logger.debug("Sending message with tag " + tag + ", try " + tries);
} catch (StateConnectionException e) {
// state error, no retry
throw e;
} catch (Exception e) {
if(e instanceof IOException) {
// Normal network error
logger.warn("Failed to receive answer, cause:" + e.getMessage());
} else {
logger.warn("Failed to receive answer, cause:", e);
}
if (tries > retries) {
throw e;
}
}
}
return tag;
}
/**
* Registers the listener so it will be notified of incoming messages.
*
* @param listener
* {@link IpmiListener} to notify
*/
public void registerListener(IpmiListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Unregisters the listener so it will no longer receive notifications of
* received answers.
*
* @param listener
* - the {@link IpmiListener} to unregister
*/
public void unregisterListener(IpmiListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
@Override
public void notify(ResponseData responseData, int handle, int tag,
Exception exception) {
IpmiResponse response = null;
if (responseData == null || exception != null) {
if (exception == null) {
exception = new Exception("Empty response");
}
response = new IpmiError(exception, tag, new ConnectionHandle(
handle));
} else {
response = new IpmiResponseData(responseData, tag,
new ConnectionHandle(handle));
}
synchronized (listeners) {
for (IpmiListener listener : listeners) {
if (listener != null) {
listener.notify(response);
}
}
}
}
/**
* Closes the connection with the given handle
*/
public void closeConnection(ConnectionHandle handle) {
connectionManager.getConnection(handle.getHandle()).unregisterListener(
this);
connectionManager.closeConnection(handle.getHandle());
}
/**
* Finalizes the connector and closes all connections.
*/
public void tearDown() {
connectionManager.close();
}
/**
* Changes the timeout value for connection with the given handle.
* @param handle
* - {@link ConnectionHandle} associated with the remote host.
* @param timeout
* - new timeout value in ms
*/
public void setTimeout(ConnectionHandle handle, int timeout) {
connectionManager.getConnection(handle.getHandle()).setTimeout(timeout);
}
}