
org.jscsi.initiator.connection.Session Maven / Gradle / Ivy
/**
* 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 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;
/**
* Session
*
* 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 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
* targetSessionIdentifyingHandle
.
*/
protected boolean tsihChanged;
/** The Target Session Identifying Handle. */
protected short targetSessionIdentifyingHandle;
/** The Logger interface. */
private static final Logger LOGGER = LoggerFactory.getLogger(Session.class);
/** The Configuration
instance for this session. */
protected final Configuration configuration;
/** The LinkFactory
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 outstandingTasks;
/**
* Handles the load balancing of the task distribution to the opened
* connections.
*/
protected final AbstractTaskBalancer taskBalancer;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Constructor to create a new, empty AbsSession
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 InetSocketAddress
of the leading connection
* of this session.
* @param initExecutor
* The ExecutorService
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(maxConnections);
executor = initExecutor;
taskBalancer = new SimpleTaskBalancer(connections);
outstandingTasks = new ConcurrentHashMap();
// 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 Session
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 SERIAL_BITS = 32
.
*/
public final void incrementInitiatorTaskTag() {
initiatorTaskTag.increment();
}
/**
* Has the iSCSI Target enough resources to accept more incoming PDU?
*
* @return true
, if the iSCSI Target has enough resources to
* accept more incoming PDUs. Else false
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 Connections
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 Connection
object of this Session
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 SERIAL_BITS = 32
.
*/
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 IPhase
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 IPhase
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 IPhase
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 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 IPhase
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 write(final ByteBuffer src, final int logicalBlockAddress,
final long transferLength) throws TaskExecutionException {
return executeTask(new WriteTask(this, src, logicalBlockAddress, transferLength));
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Returns the current LoginStage
object.
*
* @return The instance to the current LoginStage
.
*/
public final LoginStage getPhase() {
return phase.getStage();
}
/**
* This method sets the current IPhase
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 executeTask(final ITask task) throws TaskExecutionException {
if (task instanceof IOTask) {
final Future 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);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy