package io.eguan.nbdsrv; /* * #%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 javax.management.AttributeChangeNotification; import javax.management.Notification; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NBD server. The server waits for incoming connections on the configured address and port. The list of targets is * dynamic. * * @author oodrive * @author ebredzinski * @author llambert * */ public final class NbdServer extends AbstractServer<ExportServer, NbdExport, NbdServerConfig> implements NbdServerMXBean { /** * Nbd configuration. * */ static final class NbdConfiguration { /** Associated server. Needed to get the list of targets */ private final NbdServer server; /** Bind port. Keep here the original value for the getter */ private final int port; /** Bind address. Keep here the original value for the getter */ private final InetAddress address; /** Trim state */ private final boolean trimEnabled; /** toString does not change */ private final String toStr; /** Flag to tell that the configuration have been loaded */ private final AtomicBoolean loaded = new AtomicBoolean(false); NbdConfiguration(final NbdServer server, final int port, final InetAddress address, final boolean trimEnabled) { this.server = server; this.port = port; this.address = address; this.trimEnabled = trimEnabled; this.toStr = "NbdConfiguration[" + address.getHostAddress() + ":" + port + ",trim=" + trimEnabled + "]"; } /** * Tells if the configuration have been loaded. * * @return <code>true</code> if the Nbd server is started and has read the configuration. */ final boolean isLoaded() { return loaded.get(); } /** * Get the loaded targets. * * @return the list of the NBD exports */ public final List<NbdExport> getTargets() { loaded.set(true); return server.getInnerTargets(); } /** * Gets the port. * * @return the port */ public final int getPort() { return port; } /** * Get the address. * * @return the {@link InetAddress} */ public final InetAddress getTargetAddressInetAddress() { return address; } /** * Tells if trim is enabled. * * @return <code>true</code> if trim is enabled, <code>false</code> otherwise */ public final boolean isTrimEnabled() { return trimEnabled; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public final String toString() { return toStr; } } /** Package logger */ static final Logger LOGGER = LoggerFactory.getLogger(NbdServer.class.getPackage().getName()); /** Default port for the new protocol version */ static final int DEFAULT_NBD_PORT = 10809; /** Current NBD server */ private ExportServer server; /** Current NBD configuration */ private NbdConfiguration serverConfiguration; /** * Create a new server. The bind address and port are taken from the configuration. * * @param configuration * configuration containing the {@link NbdServerConfigurationContext}. */ public NbdServer(@Nonnull final MetaConfiguration configuration) { this(NbdServerInetAddressConfigKey.getInstance().getTypedValue(configuration), NbdServerPortConfigKey .getInstance().getTypedValue(configuration).intValue(), NbdServerTrimConfigKey.getInstance() .getTypedValue(configuration)); } /** * Create a new server that will bind on the given address for the default port (10809). * * @param address * address to bind to */ public NbdServer(final InetAddress address) { this(address, DEFAULT_NBD_PORT); } /** * Create a new server that will bind on the given address and port with trim not enabled. * * @param address * address to bind to * @param port * port to bind to */ public NbdServer(final InetAddress address, final int port) { this(address, port, NbdServerTrimConfigKey.DEFAULT_VALUE.booleanValue()); } /** * 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 * @param trim * is trim enabled * */ public NbdServer(final InetAddress address, final int port, final boolean trim) { super(new NbdServerConfig(address, port, trim), "NBD"); } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#createServer() */ @Override protected final ExportServer createServer(final NbdServerConfig nbdServerConfig) { // Create a new server for the current configuration serverConfiguration = new NbdConfiguration(this, nbdServerConfig.getPort(), nbdServerConfig.getAddress(), nbdServerConfig.isTrimEnabled()); return server = new ExportServer(serverConfiguration); } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#isServerStarted() */ @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(); } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#serverCancel() */ @Override protected final void serverCancel() { if (server != null) { server.cancel(); } } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#serverStopped() */ @Override protected final void serverStopped() { server = null; } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#targetAdded(io.eguan.srv.DeviceTarget, * io.eguan.srv.DeviceTarget) */ @Override protected final void targetAdded(final NbdExport targetNew, final NbdExport targetPrev) { // Add in the server if it is started if (server != null) { // Should be the same as prev.getTarget() or null final NbdExport prevTarget = server.addExport(targetNew); if (prevTarget != null && targetPrev != null) { if (prevTarget != targetPrev) { LOGGER.warn("Multiple definitions of the target " + targetNew.getTargetName()); } } } } /* * (non-Javadoc) * * @see io.eguan.srv.AbstractServer#targetRemoved(java.lang.String, io.eguan.srv.DeviceTarget) */ @Override protected final void targetRemoved(final String name, final NbdExport target) { // Remove from the server if it is started if (server != null) { // Should be the same as removed.getTarget() or null final NbdExport removedTarget = server.removeExport(name); if (removedTarget != null && target != null) { if (removedTarget != target) { LOGGER.warn("Multiple definitions of the target " + name); } } } } /* * (non-Javadoc) * * @see io.eguan.nbdsrv.NbdServerMXBean#getExports() */ @Override public final NbdExportAttributes[] getExports() { getTargetSharedLock().lock(); try { if (server == null) { // Local target list, no activity final Collection<NbdExport> targetList = getTargetMap().values(); final int count = targetList.size(); final NbdExportAttributes[] result = new NbdExportAttributes[count]; final Iterator<NbdExport> ite = targetList.iterator(); for (int i = count - 1; i >= 0; i--) { final NbdExport target = ite.next(); result[i] = new NbdExportAttributes(target.getTargetName(), 0, target.getSize(), target.isReadOnly()); } return result; } else { // Delegate to the server final List<NbdExportStats> stats = server.getTargetStats(); final int statsCount = stats.size(); final NbdExportAttributes[] result = new NbdExportAttributes[statsCount]; for (int i = statsCount - 1; i >= 0; i--) { final NbdExportStats stat = stats.get(i); result[i] = new NbdExportAttributes(stat.getName(), stat.getConnectionCount(), stat.getSize(), stat.isReadOnly()); } return result; } } finally { getTargetSharedLock().unlock(); } } /* * (non-Javadoc) * * @see io.eguan.nbdsrv.NbdServerMXBean#isTrimEnabled() */ @Override public final boolean isTrimEnabled() { return getServerConfig().isTrimEnabled(); } /* * (non-Javadoc) * * @see io.eguan.nbdsrv.NbdServerMXBean#setTrimEnabled() */ @Override public final void setTrimEnabled(final boolean enabled) { getServerConfig().setTrimEnabled(enabled); final Notification n; if (enabled) { n = new AttributeChangeNotification(this, getNotificationSequenceNumber(), System.currentTimeMillis(), "Trim changed", "Trim", "boolean", Boolean.FALSE, Boolean.TRUE); } else { n = new AttributeChangeNotification(this, getNotificationSequenceNumber(), System.currentTimeMillis(), "Trim changed", "Trim", "boolean", Boolean.TRUE, Boolean.FALSE); } getNotificationBroadcasterSupport().sendNotification(n); } /** * Gets a list of the current Nbd targets. * * @return the list of targets. May be empty. */ final List<NbdExport> getInnerTargets() { final List<NbdExport> result = new ArrayList<>(); getTargetSharedLock().lock(); try { final Collection<NbdExport> targetsTmp = getTargetMap().values(); for (final NbdExport target : targetsTmp) { result.add(target); } } 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 NbdServer)) return false; final NbdServer other = (NbdServer) 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 "NbdServer[" + getAddress() + ":" + getPort() + "]"; } }