/**
* Copyright (c) 2012, University of Konstanz, Distributed Systems Group All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or other materials provided with the
* distribution. * Neither the name of the University of Konstanz nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
*/
package org.jscsi.initiator.connection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import org.jscsi.exception.NoSuchConnectionException;
import org.jscsi.exception.TaskExecutionException;
import org.jscsi.initiator.Configuration;
import org.jscsi.initiator.LinkFactory;
import org.jscsi.initiator.connection.phase.IPhase;
import org.jscsi.initiator.connection.phase.SecurityNegotiationPhase;
import org.jscsi.initiator.connection.state.LoginRequestState;
import org.jscsi.initiator.taskbalancer.AbstractTaskBalancer;
import org.jscsi.initiator.taskbalancer.SimpleTaskBalancer;
import org.jscsi.parser.datasegment.OperationalTextKey;
import org.jscsi.parser.datasegment.SettingsMap;
import org.jscsi.parser.login.LoginStage;
import org.jscsi.utils.SerialArithmeticNumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <h1>Session</h1>
* <p/>
* A session or Initiator Target Nexus is a directed communication from an iSCSI Initiator to an iSCSI Target. Each
* session can contain several connections. This allows a better usage of bandwidth and decreases latency times. The
* Abstract Class is used to implement serveral single- and multithreaded variants of Sessions
*
* @author Volker Wildi, University of Konstanz
* @author Patrice Matthias Brend'amour, University of Kontanz
* @author Sebastian Graf, University of Kontanz
*/
public final class Session {
/** The unique name of the connected iSCSI Target. */
protected final String targetName;
/** The unique name of the connected iSCSI Target. */
protected final InetSocketAddress inetSocketAddress;
/** The maximum number of connections, which are allowed in this session. */
private int maxConnections;
/** The index of the next used connection ID. */
protected short nextFreeConnectionID;
/** The session is in this phase. */
protected IPhase phase;
/**
* This instance contains the informations about the capacity of the connected target.
*/
protected final TargetCapacityInformations capacityInformations;
/** A List object with all open connections. */
protected final LinkedBlockingQueue<Connection> connections;
/** The Command Sequence Number of this session. */
protected final SerialArithmeticNumber commandSequenceNumber;
/** The Maximum Command Sequence Number of this session. */
protected final SerialArithmeticNumber maximumCommandSequenceNumber;
/**
* The initiator uses this Initiator Task Tag to relate data to the appropriate command. And the target uses this
* tag to correlate the data to the appropriate command that it received earlier.
*/
protected final SerialArithmeticNumber initiatorTaskTag;
/**
* Flag to indicate, if the login phase of this session is successfully completed. This flag is also used for the
* protection of a reseting of <code>targetSessionIdentifyingHandle</code>.
*/
protected boolean tsihChanged;
/** The Target Session Identifying Handle. */
protected short targetSessionIdentifyingHandle;
/** The Logger interface. */
private static final Logger LOGGER = LoggerFactory.getLogger(Session.class);
/** The <code>Configuration</code> instance for this session. */
protected final Configuration configuration;
/** The <code>LinkFactory</code> instance for this session. */
protected final LinkFactory factory;
/** Executor to work with all task to be commited. */
private final ExecutorService executor;
/** Contains all queues, which are till now not successfully finished. */
// FIXME: Support me!
private final ConcurrentHashMap<ITask , Connection> outstandingTasks;
/**
* Handles the load balancing of the task distribution to the opened connections.
*/
protected final AbstractTaskBalancer taskBalancer;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Constructor to create a new, empty <code>AbsSession</code> object with a maximum number of allowed connections to
* a given iSCSI Target. This is the abstract definition for Session implementations
*
* @param linkFactory The LinkFactory which called the Constructor
* @param initConfiguration The configuration to use within this session.
* @param initTargetName The name of the iSCSI Target.
* @param inetAddress The <code>InetSocketAddress</code> of the leading connection of this session.
* @param initExecutor The <code>ExecutorService</code> for the Connection Threads
* @throws Exception if anything happens
*/
public Session (final LinkFactory linkFactory, final Configuration initConfiguration, final String initTargetName, final InetSocketAddress inetAddress, final ExecutorService initExecutor) throws Exception {
maxConnections = Integer.parseInt(initConfiguration.getSessionSetting(initTargetName, OperationalTextKey.MAX_CONNECTIONS));
factory = linkFactory;
configuration = initConfiguration;
commandSequenceNumber = new SerialArithmeticNumber();
maximumCommandSequenceNumber = new SerialArithmeticNumber(1);
nextFreeConnectionID = 1;
inetSocketAddress = inetAddress;
initiatorTaskTag = new SerialArithmeticNumber(1);
targetName = initTargetName;
phase = new SecurityNegotiationPhase();
capacityInformations = new TargetCapacityInformations();
connections = new LinkedBlockingQueue<Connection>(maxConnections);
executor = initExecutor;
taskBalancer = new SimpleTaskBalancer(connections);
outstandingTasks = new ConcurrentHashMap<ITask , Connection>();
// Add the leading connection
addNewConnection();
/*
* We have to check whether the MaxConnection setting in our Configuration is correct. There might be a wrong
* setting for a target e.g. the target only supports one connection but we think it can handle two.
*/
maxConnections = Integer.parseInt(configuration.getSessionSetting(targetName, OperationalTextKey.MAX_CONNECTIONS));
int targetMaxC = connections.peek().getSettingAsInt(OperationalTextKey.MAX_CONNECTIONS);
if (targetMaxC < maxConnections) {
maxConnections = targetMaxC;
}
// Add more Connections
// TODO Do something more intelligent here. Always adding the maximum
// isn't
// always a good idea
addConnections(maxConnections - 1);
}
/**
* Returns the Target Session Identifying Handle (TSID) of this <code>Session</code> object.
*
* @return The current Target Session Identifying Handle (TSIH)
*/
public final short getTargetSessionIdentifyingHandle () {
return targetSessionIdentifyingHandle;
}
/**
* Sets the Target Session Identifying Handle (TSIH) to the given value. This TSIH is specified at the Login Phase
* by the target in a new session. So, it can only set one time.
*
* @param tsih The new Target Session Identifying Handle.
*/
public final void setTargetSessionIdentifyingHandle (final short tsih) {
if (!tsihChanged) {
targetSessionIdentifyingHandle = tsih;
tsihChanged = true;
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Returns the Command Sequence Number of this session.
*
* @return The current Command Sequence Number.
*/
public final int getCommandSequenceNumber () {
return commandSequenceNumber.getValue();
}
/**
* Sets the Maximum Command Sequence Number to a new value.
*
* @param newMaximumCommandSequenceNumber The new Maximum Command Sequence Number.
*/
public final void setMaximumCommandSequenceNumber (final int newMaximumCommandSequenceNumber) {
maximumCommandSequenceNumber.setValue(newMaximumCommandSequenceNumber);
}
/**
* Returns the Maximum Command Sequence Number of this session.
*
* @return The current Maximum Command Sequence Number.
*/
public final SerialArithmeticNumber getMaximumCommandSequenceNumber () {
return maximumCommandSequenceNumber;
}
/**
* Returns the Initiator Task Tag of this session.
*
* @return The Initiator Task Tag.
*/
public final int getInitiatorTaskTag () {
return initiatorTaskTag.getValue();
}
/**
* Increments the Initiator Task Tag as defined in RFC1982 where <code>SERIAL_BITS = 32</code>.
*/
public final void incrementInitiatorTaskTag () {
initiatorTaskTag.increment();
}
/**
* Has the iSCSI Target enough resources to accept more incoming PDU?
*
* @return <code>true</code>, if the iSCSI Target has enough resources to accept more incoming PDUs. Else
* <code>false</code> and hold out for sending.
*/
public final boolean hasTargetMoreResources () {
return maximumCommandSequenceNumber.compareTo(commandSequenceNumber.getValue()) > 0;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Returns the name of the iSCSI Target of this session.
*
* @return The name of the iSCSI Target.
*/
public final String getTargetName () {
return targetName;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Adds a number of new connections to this session.
*
* @param max The number of Connections to open.
* @throws Exception if any error occurs.
*/
public final void addConnections (final int max) throws Exception {
if (connections.size() < maxConnections) {
for (int i = 1; i < max; i++) {
addNewConnection();
}
}
}
/**
* Adds a new connection to this session with the next free connection ID (if the maximum number is not reached).
*
* @return The connection ID of the newly created connection.
* @throws Exception if any error occurs.
*/
protected final short addNewConnection () throws Exception {
if (connections.size() < maxConnections) {
final Connection connection = factory.getConnection(this, configuration, inetSocketAddress, nextFreeConnectionID);
connection.nextState(new LoginRequestState(connection, LoginStage.FULL_FEATURE_PHASE));
// login phase successful, so we can add a new connection
connections.add(connection);
// only needed on the leading login connection
if (connections.size() == 1) {
phase.getCapacity(this, capacityInformations);
if (connection.getSettingAsInt(OperationalTextKey.MAX_CONNECTIONS) > 1) {
phase.login(this);
}
}
return nextFreeConnectionID++;
} else {
LOGGER.warn("Unused new connection -> ignored!");
return nextFreeConnectionID;
}
}
/**
* Updates the MaxConnection setting, so that it grows/shrinks the Connectionlist.
*
* @param max The maximum number of concurrent <code>Connections</code> to a target.
*/
public void updateMaxConnections (final int max) {
try {
Connection conn = taskBalancer.getConnection();
int update = 0;
int targetMaxC = connections.peek().getSettingAsInt(OperationalTextKey.MAX_CONNECTIONS);
if (targetMaxC <= max) {
if (targetMaxC > maxConnections) {
update = targetMaxC - maxConnections;
maxConnections = targetMaxC;
}
} else {
if (max >= maxConnections) {
update = max - maxConnections;
maxConnections = max;
}
}
SettingsMap sm = new SettingsMap();
sm.add(OperationalTextKey.MAX_CONNECTIONS, String.valueOf(maxConnections));
conn.update(sm);
taskBalancer.releaseConnection(conn);
if (update > 0) {
addConnections(update);
} else {
for (int i = -1; i >= update; i--) {
taskBalancer.getConnection().close();
}
}
} catch (Exception e) {
// DO Nothing
}
}
/**
* Returns the next free <code>Connection</code> object of this <code>Session</code> object.
*
* @return The connection to use for the next task.
* @throws NoSuchConnectionException If there is no such connection.
*/
public final Connection getNextFreeConnection () throws NoSuchConnectionException {
return taskBalancer.getConnection();
}
/**
* Increments the Command Sequence Number as defined in RFC1982, where <code>SERIAL_BITS = 32</code>.
*/
public final void incrementCommandSequenceNumber () {
commandSequenceNumber.increment();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Closes this session instances with all opened connections.
*
* @throws IOException if an I/O error occurs.
*/
public final void close () throws IOException {
LOGGER.info("Closing was requested.");
for (Connection c : connections) {
c.close();
}
connections.clear();
// stop session task thread
factory.closedSession(this);
executor.shutdown();
}
/**
* Returns the used block size of the connected iSCSI Target.
*
* @return The used block size in bytes.
*/
public final long getBlockSize () {
return capacityInformations.getBlockSize();
}
/**
* Returns the capacity (in blocks) of the connected iSCSI Target.
*
* @return The capacity in blocks.
*/
public final long getCapacity () {
return capacityInformations.getSize();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This method invokes the same called method of the current <code>IPhase</code> instance.
*
*
* @throws Exception if any error occurs.
*/
public final void login () throws Exception {
executeTask(new LoginTask(this));
}
/**
* This method invokes the same called method of the current <code>IPhase</code> instance.
*
*
* @throws TaskExecutionException if any error occurs.
*/
public final void logout () throws TaskExecutionException {
executeTask(new LogoutTask(this));
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This method invokes the same called method of the current <code>IPhase</code> instance.
*
*
* @param dst Store the read bytes to this buffer.
* @param logicalBlockAddress The logical block address of the device to begin the read operation.
* @param transferLength The number of bytes to read from the device.
* @throws Exception if any error occurs.
* @return The Future object.
* @throws TaskExecutionException if execution fails
*/
public final Future<Void> read (final ByteBuffer dst, final int logicalBlockAddress, final long transferLength) throws TaskExecutionException {
return executeTask(new ReadTask(this, dst, logicalBlockAddress, transferLength));
}
/**
* This method invokes the same called method of the current <code>IPhase</code> instance.
*
*
* @param src Write the remaining bytes to the device.
* @param logicalBlockAddress The logical block address of the device to begin the write operation.
* @param transferLength The number of bytes to write to the device.
* @throws Exception if any error occurs.
* @return The Future object.
* @throws TaskExecutionException if execution fails
*/
public final Future<Void> write (final ByteBuffer src, final int logicalBlockAddress, final long transferLength) throws TaskExecutionException {
return executeTask(new WriteTask(this, src, logicalBlockAddress, transferLength));
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Returns the current <code>LoginStage</code> object.
*
* @return The instance to the current <code>LoginStage</code>.
*/
public final LoginStage getPhase () {
return phase.getStage();
}
/**
* This method sets the current <code>IPhase</code> instance to the given value.
*
* @param newPhase The new instance to switch to.
*/
public final void setPhase (final IPhase newPhase) {
phase = newPhase;
LOGGER.trace("Switching to phase " + newPhase.getClass().getSimpleName());
}
/**
* This methods appends the given task to the end of the taskQueue and set the calling thread is sleep state.
*
* @param task The task to append to the end of the taskQueue.
* @throws InterruptedException if another thread interrupted the current thread before or while the current thread
* was waiting for a notification. The interrupted status of the current thread is cleared when this
* exception is thrown.
* @throws ExecutionException if anything happens while execution
*/
private final Future<Void> executeTask (final ITask task) throws TaskExecutionException {
if (task instanceof IOTask) {
final Future<Void> returnVal = executor.submit((IOTask) task);
return returnVal;
} else {
try {
task.call();
} catch (final Exception exc) {
throw new TaskExecutionException(new ExecutionException(exc));
}
return null;
}
// LOGGER.info("Added a " + task + " to the TaskQueue");
}
/**
* removes Task from outstandingTasks.
*
* @param ftask The Task which was finished .
*/
public final void finishedTask (final ITask ftask) {
try {
taskBalancer.releaseConnection(outstandingTasks.get(ftask));
} catch (NoSuchConnectionException e) {
e.printStackTrace();
}
outstandingTasks.remove(ftask);
LOGGER.debug("Finished a " + ftask + " for the session " + targetName);
}
/**
* restarts a Task from outstandingTasks.
*
* @param task The failed Task.
* @throws ExecutionException for failed restart of the task
*/
public final void restartTask (final ITask task) throws ExecutionException {
try {
if (task != null) {
if (task instanceof IOTask) {
executor.submit((IOTask) task);
} else {
task.call();
}
taskBalancer.releaseConnection(outstandingTasks.get(task));
outstandingTasks.remove(task);
}
LOGGER.debug("Restarted a Task out of the outstandingTasks Queue");
} catch (Exception e) {
throw new ExecutionException(e);
}
}
/**
* Adds a Task to the outstandingTasks Hashmap.
*
* @param connection The Connection where the Task will be started
* @param task The Task which was started.
*/
public final void addOutstandingTask (final Connection connection, final ITask task) {
outstandingTasks.put(task, connection);
LOGGER.debug("Added a Task to the outstandingTasks Queue");
}
/**
* Adds a Task to the outstandingTasks Hashmap.
*
* @param connection The Connection which will be released
* @throws NoSuchConnectionException if any errors occur
*/
public final void releaseUsedConnection (final Connection connection) throws NoSuchConnectionException {
taskBalancer.releaseConnection(connection);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}