package io.eguan.iscsisrv;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import io.eguan.configuration.MetaConfiguration;
import io.eguan.srv.AbstractServer;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import org.jscsi.target.Configuration;
import org.jscsi.target.Target;
import org.jscsi.target.TargetServer;
import org.jscsi.target.TargetStats;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* iSCSI server. The server waits for incoming connections on the configured address and port. The list of targets is
* dynamic.
*
* @author oodrive
* @author llambert
* @author ebredzinski
*
*/
public final class IscsiServer extends AbstractServer<TargetServer, IscsiTarget, IscsiServerConfig> implements
IscsiServerMXBean {
/** Default iSCSI port (see RFC 3720) */
public static final int DEFAULT_ISCSI_PORT = 3260;
/**
* jSCSI configuration. Override management of the list of targets.
*
*/
static class IscsiConfiguration extends Configuration {
/** Associated server. Needed to get the list of targets */
private final IscsiServer server;
/** Bind address. Keep here the original value for the getter */
private final InetAddress address;
/** toString does not change */
private final String toStr;
/** Flag to tell that the configuration have been loaded */
private final AtomicBoolean loaded = new AtomicBoolean(false);
IscsiConfiguration(final IscsiServer server, final int port, final InetAddress address) {
super(port, address);
this.server = server;
this.address = address;
this.toStr = "IscsiConfiguration[" + getTargetAddress() + ":" + getPort() + "]";
}
/**
* Tells if the configuration have been loaded.
*
* @return <code>true</code> if the jSCSI server is started and has read the configuration.
*/
final boolean isLoaded() {
return loaded.get();
}
/*
* (non-Javadoc)
*
* @see org.jscsi.target.Configuration#getTargets()
*/
@Override
public final List<Target> getTargets() {
loaded.set(true);
return server.getInnerTargets();
}
/*
* (non-Javadoc)
*
* @see org.jscsi.target.Configuration#getTargetAddressInetAddress()
*/
@Override
public final InetAddress getTargetAddressInetAddress() {
return address;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public final String toString() {
return toStr;
}
}
/** Package logger */
static final Logger LOGGER = LoggerFactory.getLogger(IscsiServer.class.getPackage().getName());
/** Current jSCSI server */
private TargetServer server;
/** Current jSCSI configuration */
private IscsiConfiguration serverConfiguration;
/**
* Create a new server. The bind address and port are taken from the configuration.
*
* @param configuration
* configuration containing the {@link IscsiServerConfigurationContext}.
*/
public IscsiServer(@Nonnull final MetaConfiguration configuration) {
this(IscsiServerInetAddressConfigKey.getInstance().getTypedValue(configuration), IscsiServerPortConfigKey
.getInstance().getTypedValue(configuration).intValue());
}
/**
* Create a new server that will bind on the given address for the default port (3260).
*
* @param address
* address to bind to
*/
public IscsiServer(@Nonnull final InetAddress address) {
this(address, DEFAULT_ISCSI_PORT);
}
/**
* Create a new server that will bind on the given address and port.
*
* @param address
* address to bind to
* @param port
* port to bind to
*/
public IscsiServer(@Nonnull final InetAddress address, final int port) {
super(new IscsiServerConfig(address, port), "iSCSI");
}
@Override
protected final TargetServer createServer(final IscsiServerConfig iscsiServerConfig) {
// Create a new server for the current configuration
serverConfiguration = new IscsiConfiguration(this, iscsiServerConfig.getPort(), iscsiServerConfig.getAddress());
return server = new TargetServer(serverConfiguration);
}
@Override
protected final boolean isServerStarted() {
if (serverConfiguration == null) {
// Server stopped
LOGGER.warn("Server stopped before end of start");
return false;
}
// Start failed for some reason
if (!isStarted()) {
// Server abort unexpected (socket bind, ...)
// TODO: throw something?
LOGGER.warn("Server start failed: " + serverConfiguration);
return false;
}
return serverConfiguration.isLoaded();
}
@Override
protected final void serverCancel() {
if (server != null) {
server.cancel();
}
}
@Override
protected final void serverStopped() {
server = null;
serverConfiguration = null;
}
/*
* (non-Javadoc)
*
* @see io.eguan.iscsisrv.IscsiServerMXBean#getTargets()
*/
@Override
public final IscsiTargetAttributes[] getTargets() {
getTargetSharedLock().lock();
try {
if (server == null) {
// Local target list, no activity
final Collection<IscsiTarget> targetList = getTargetMap().values();
final int count = targetList.size();
final IscsiTargetAttributes[] result = new IscsiTargetAttributes[count];
final Iterator<IscsiTarget> ite = targetList.iterator();
for (int i = count - 1; i >= 0; i--) {
final IscsiTarget target = ite.next();
result[i] = new IscsiTargetAttributes(target.getTargetName(), target.getTargetAlias(), 0,
target.getSize(), target.isReadOnly());
}
return result;
}
else {
// Delegate to the server
final List<TargetStats> stats = server.getTargetStats();
final int statsCount = stats.size();
final IscsiTargetAttributes[] result = new IscsiTargetAttributes[statsCount];
for (int i = statsCount - 1; i >= 0; i--) {
final TargetStats stat = stats.get(i);
result[i] = new IscsiTargetAttributes(stat.getName(), stat.getAlias(), stat.getConnectionCount(),
stat.getSize(), stat.isWriteProtected());
}
return result;
}
}
finally {
getTargetSharedLock().unlock();
}
}
@Override
protected final void targetAdded(final IscsiTarget targetNew, final IscsiTarget targetPrev) {
// Add in the server if it is started
if (server != null) {
// Should be the same as prev.getTarget() or null
final Target prevTarget = server.addTarget(targetNew.getTarget());
if (prevTarget != null && targetPrev != null) {
if (prevTarget != targetPrev.getTarget()) {
LOGGER.warn("Multiple definitions of the target " + targetNew.getTargetName());
}
}
}
}
@Override
protected final void targetRemoved(final String name, final IscsiTarget target) {
// Remove from the server if it is started
if (server != null) {
// Should be the same as removed.getTarget() or null
final Target removedTarget = server.removeTarget(name);
if (removedTarget != null && target != null) {
if (removedTarget != target.getTarget()) {
LOGGER.warn("Multiple definitions of the target " + name);
}
}
}
}
/**
* Gets a list of the current jSCSI targets.
*
* @return the list of targets. May be empty.
*/
final List<Target> getInnerTargets() {
final List<Target> result = new ArrayList<>();
getTargetSharedLock().lock();
try {
final Collection<IscsiTarget> targetsTmp = getTargetMap().values();
for (final IscsiTarget target : targetsTmp) {
result.add(target.getTarget());
}
}
finally {
getTargetSharedLock().unlock();
}
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public final int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getAddress().hashCode();
result = prime * result + getPort();
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public final boolean equals(final Object obj) {
if (this == obj)
return true;
if (!(obj instanceof IscsiServer))
return false;
final IscsiServer other = (IscsiServer) obj;
if (getPort() != other.getPort())
return false;
return getAddress().equals(other.getAddress());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public final String toString() {
return "IscsiServer[" + getAddress() + ":" + getPort() + "]";
}
}