/* * IpmiConnector.java * Created on 2011-09-08 * * 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.sync; import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetAddress; import java.util.List; import java.util.Random; import org.apache.log4j.Logger; import com.veraxsystems.vxipmi.api.async.ConnectionHandle; import com.veraxsystems.vxipmi.api.async.IpmiAsyncConnector; 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.CompletionCode; 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.ConnectionManager; import com.veraxsystems.vxipmi.connection.StateConnectionException; /** *

Synchronous 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[])}


    Send message register via * {@link #sendMessage(ConnectionHandle, IpmiCommandCoder)}


    To close session call * {@link #closeSession(ConnectionHandle)}

    When done with work, clean up via {@link #tearDown()}


    */ public class IpmiConnector { private static final Logger logger = Logger.getLogger(IpmiConnector.class); private final IpmiAsyncConnector asyncConnector; private int retries; private int idleTime; private int timeout; private Random random = new Random(System.currentTimeMillis()); /** * Starts {@link IpmiConnector} and initiates the {@link ConnectionManager} at the given port. Wildcard IP address * will be used. * @param port * - the port that will be used by {@link IpmiAsyncConnector} to communicate with the remote hosts. * @throws FileNotFoundException * when properties file was not found * @throws IOException * when properties file was not found */ public IpmiConnector(int port) throws FileNotFoundException, IOException { asyncConnector = new IpmiAsyncConnector(port); loadProperties(); } /** * Starts {@link IpmiConnector} 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 FileNotFoundException * when properties file was not found * @throws IOException * when properties file was not found */ public IpmiConnector(int port, InetAddress address) throws FileNotFoundException, IOException { asyncConnector = new IpmiAsyncConnector(port, address); loadProperties(); } private void loadProperties() throws IOException { PropertiesManager manager = PropertiesManager.getInstance(); retries = Integer.parseInt(manager.getProperty("retries")); idleTime = Integer.parseInt(manager.getProperty("idleTime")); } /** * 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 { return asyncConnector.createConnection(address); } /** * 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 { return asyncConnector.getAvailableCipherSuites(connectionHandle); } /** * The method 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 Exception { return asyncConnector.getAllCipherSuites(connectionHandle); } /** * 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 { return asyncConnector.getChannelAuthenticationCapabilities(connectionHandle, cipherSuite, requestedPrivilegeLevel); } /** * 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 { asyncConnector.openSession(connectionHandle, username, password, bmcKey); } /** * 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 { asyncConnector.closeSession(connectionHandle); } /** * 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 {@link ResponseData} for the request * @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 ResponseData sendMessage(ConnectionHandle connectionHandle, IpmiCommandCoder request) throws Exception { int tries = 0; MessageListener listener = new MessageListener(connectionHandle); asyncConnector.registerListener(listener); ResponseData data = null; int previousTag = -1; while (data == null) { try { ++tries; int tag = asyncConnector.sendMessage(connectionHandle, request); logger.debug("Sending message with tag " + tag + ", try " + tries + ", previous tag " + previousTag); previousTag = tag; data = listener.waitForAnswer(tag, timeout); } catch (IllegalArgumentException e) { throw e; } catch (InterruptedException e) { throw e; } catch (StateConnectionException e) { throw e; } catch (IPMIException e) { if (e.getCompletionCode() == CompletionCode.InitializationInProgress || e.getCompletionCode() == CompletionCode.InsufficientResources || e.getCompletionCode() == CompletionCode.NodeBusy || e.getCompletionCode() == CompletionCode.Timeout) { if (tries > retries) { throw e; } else { long sleepTime = (random.nextLong() % (idleTime / 2)) + (idleTime / 2); Thread.sleep(sleepTime); logger.warn("Receiving message failed, retrying", e); } } else { throw e; } } catch (Exception e) { if (tries > retries) { throw e; } else { if(e instanceof IOException) { // Network exception common case, no stack needed logger.warn("Receiving message failed " + e.getMessage() + ", retrying"); } else { // Unexcepted exception, needs investigation logger.warn("Receiving message failed, retrying", e); } long sleepTime = (random.nextLong() % (idleTime / 2)) + (idleTime / 2); Thread.sleep(sleepTime); } } } asyncConnector.unregisterListener(listener); return data; } /** * Closes the connection with the given handle */ public void closeConnection(ConnectionHandle handle) { asyncConnector.closeConnection(handle); } /** * Finalizes the connector and closes all connections. */ public void tearDown() { asyncConnector.tearDown(); } /** * 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) { asyncConnector.setTimeout(handle, timeout); this.timeout = timeout; } }