/** * Copyright (c) 2013, 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowplugin.openflow.md.core; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Futures; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter; import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener; import org.opendaylight.openflowplugin.api.OFConstants; import org.opendaylight.openflowplugin.api.openflow.connection.HandshakeContext; import org.opendaylight.openflowplugin.api.openflow.md.core.ConnectionConductor; import org.opendaylight.openflowplugin.api.openflow.md.core.ErrorHandler; import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeListener; import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeManager; import org.opendaylight.openflowplugin.api.openflow.md.core.NotificationEnqueuer; import org.opendaylight.openflowplugin.api.openflow.md.core.NotificationQueueWrapper; import org.opendaylight.openflowplugin.api.openflow.md.core.SwitchConnectionDistinguisher; import org.opendaylight.openflowplugin.api.openflow.md.core.session.SessionContext; import org.opendaylight.openflowplugin.api.openflow.md.core.session.SessionManager; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueKeeper; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueKeeper.QueueType; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueProcessor; import org.opendaylight.openflowplugin.api.openflow.md.queue.WaterMarkListener; import org.opendaylight.openflowplugin.api.openflow.md.queue.WaterMarkListenerImpl; import org.opendaylight.openflowplugin.openflow.md.core.session.OFSessionUtil; import org.opendaylight.openflowplugin.openflow.md.core.session.PortFeaturesUtil; import org.opendaylight.openflowplugin.openflow.md.queue.QueueKeeperFactory; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutput; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoReplyInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoRequestMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ErrorMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ExperimenterMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FlowRemovedMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReplyMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OpenflowProtocolListener; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PacketInMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortGrouping; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortStatus; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortStatusMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEvent; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SwitchIdleEvent; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SystemNotificationsListener; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author mirehak */ public class ConnectionConductorImpl implements OpenflowProtocolListener, SystemNotificationsListener, ConnectionConductor, ConnectionReadyListener, HandshakeListener, NotificationEnqueuer, AutoCloseable { /** * ingress queue limit */ private static final int INGRESS_QUEUE_MAX_SIZE = 200; protected static final Logger LOG = LoggerFactory .getLogger(ConnectionConductorImpl.class); /* * variable to make BitMap-based negotiation enabled / disabled. it will * help while testing and isolating issues related to processing of BitMaps * from switches. */ private boolean isBitmapNegotiationEnable = true; protected ErrorHandler errorHandler; private final ConnectionAdapter connectionAdapter; private ConnectionConductor.CONDUCTOR_STATE conductorState; private Short version; protected SwitchConnectionDistinguisher auxiliaryKey; protected SessionContext sessionContext; private QueueProcessor<OfHeader, DataObject> queueProcessor; private QueueKeeper<OfHeader> queue; private ThreadPoolExecutor hsPool; private HandshakeManager handshakeManager; private boolean firstHelloProcessed; private PortFeaturesUtil portFeaturesUtils; private int conductorId; private int ingressMaxQueueSize; private HandshakeContext handshakeContext; /** * @param connectionAdapter connection adaptor for switch */ public ConnectionConductorImpl(ConnectionAdapter connectionAdapter) { this(connectionAdapter, INGRESS_QUEUE_MAX_SIZE); } /** * @param connectionAdapter connection adaptor for switch * @param ingressMaxQueueSize ingress queue limit (blocking) */ public ConnectionConductorImpl(ConnectionAdapter connectionAdapter, int ingressMaxQueueSize) { this.connectionAdapter = connectionAdapter; this.ingressMaxQueueSize = ingressMaxQueueSize; conductorState = CONDUCTOR_STATE.HANDSHAKING; firstHelloProcessed = false; handshakeManager = new HandshakeManagerImpl(connectionAdapter, ConnectionConductor.VERSION_ORDER.get(0), ConnectionConductor.VERSION_ORDER); handshakeManager.setUseVersionBitmap(isBitmapNegotiationEnable); handshakeManager.setHandshakeListener(this); portFeaturesUtils = PortFeaturesUtil.getInstance(); } @Override public void init() { int handshakeThreadLimit = 1; hsPool = new ThreadPoolLoggingExecutor(handshakeThreadLimit, handshakeThreadLimit, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), "OFHandshake-" + conductorId); connectionAdapter.setMessageListener(this); connectionAdapter.setSystemListener(this); connectionAdapter.setConnectionReadyListener(this); WaterMarkListener waterMarkListener = new WaterMarkListenerImpl( connectionAdapter); queue = QueueKeeperFactory.createFairQueueKeeper(queueProcessor, ingressMaxQueueSize, waterMarkListener); } @Override public void setQueueProcessor( QueueProcessor<OfHeader, DataObject> queueProcessor) { this.queueProcessor = queueProcessor; } /** * @param errorHandler the errorHandler to set */ @Override public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; handshakeManager.setErrorHandler(errorHandler); } @Override public void onEchoRequestMessage(final EchoRequestMessage echoRequestMessage) { new Thread(new Runnable() { @Override public void run() { LOG.debug("echo request received: " + echoRequestMessage.getXid()); EchoReplyInputBuilder builder = new EchoReplyInputBuilder(); builder.setVersion(echoRequestMessage.getVersion()); builder.setXid(echoRequestMessage.getXid()); builder.setData(echoRequestMessage.getData()); getConnectionAdapter().echoReply(builder.build()); } }).start(); } @Override public void onErrorMessage(ErrorMessage errorMessage) { enqueueMessage(errorMessage); } /** * @param message */ private void enqueueMessage(OfHeader message) { enqueueMessage(message, QueueType.DEFAULT); } @Override public void enqueueNotification(NotificationQueueWrapper notification) { enqueueMessage(notification); } /** * @param message * @param queueType enqueue type */ private void enqueueMessage(OfHeader message, QueueType queueType) { queue.push(message, this, queueType); } @Override public void onExperimenterMessage(ExperimenterMessage experimenterMessage) { enqueueMessage(experimenterMessage); } @Override public void onFlowRemovedMessage(FlowRemovedMessage message) { enqueueMessage(message); } /** * version negotiation happened as per following steps: 1. If HelloMessage * version field has same version, continue connection processing. If * HelloMessage version is lower than supported versions, just disconnect. * 2. If HelloMessage contains bitmap and common version found in bitmap * then continue connection processing. if no common version found, just * disconnect. 3. If HelloMessage version is not supported, send * HelloMessage with lower supported version. 4. If Hello message received * again with not supported version, just disconnect. */ @Override public void onHelloMessage(final HelloMessage hello) { LOG.debug("processing HELLO.xid: {}", hello.getXid()); firstHelloProcessed = true; checkState(CONDUCTOR_STATE.HANDSHAKING); HandshakeStepWrapper handshakeStepWrapper = new HandshakeStepWrapper( hello, handshakeManager, connectionAdapter); hsPool.submit(handshakeStepWrapper); } /** * @return rpc-response timeout in [ms] */ protected long getMaxTimeout() { // TODO:: get from configuration return 2000; } /** * @return milliseconds */ protected TimeUnit getMaxTimeoutUnit() { // TODO:: get from configuration return TimeUnit.MILLISECONDS; } @Override public void onMultipartReplyMessage(MultipartReplyMessage message) { enqueueMessage(message); } @Override public void onPacketInMessage(PacketInMessage message) { enqueueMessage(message, QueueKeeper.QueueType.UNORDERED); } @Override public void onPortStatusMessage(PortStatusMessage message) { try { processPortStatusMsg(message); } finally { enqueueMessage(message); } } protected void processPortStatusMsg(PortStatus msg) { if (msg.getReason().getIntValue() == 2) { updatePort(msg); } else if (msg.getReason().getIntValue() == 0) { updatePort(msg); } else if (msg.getReason().getIntValue() == 1) { deletePort(msg); } } protected void updatePort(PortStatus msg) { Long portNumber = msg.getPortNo(); Boolean portBandwidth = portFeaturesUtils.getPortBandwidth(msg); if (portBandwidth == null) { LOG.debug( "can't get bandwidth info from port: {}, aborting port update", msg.toString()); } else { if (null != this.sessionContext) { //FIXME these two properties are never used in code this.getSessionContext().getPhysicalPorts().put(portNumber, msg); this.getSessionContext().getPortsBandwidth() .put(portNumber, portBandwidth); } else { LOG.warn("Trying to process update port message before session context was created."); } } } protected void deletePort(PortGrouping port) { Long portNumber = port.getPortNo(); this.getSessionContext().getPhysicalPorts().remove(portNumber); this.getSessionContext().getPortsBandwidth().remove(portNumber); } @Override public void onSwitchIdleEvent(SwitchIdleEvent notification) { new Thread(new Runnable() { @Override public void run() { if (!CONDUCTOR_STATE.WORKING.equals(getConductorState())) { // idle state in any other conductorState than WORKING means // real // problem and wont be handled by echoReply, but // disconnection disconnect(); OFSessionUtil.getSessionManager().invalidateOnDisconnect( ConnectionConductorImpl.this); } else { LOG.debug( "first idle state occured, sessionCtx={}|auxId={}", sessionContext, auxiliaryKey); EchoInputBuilder builder = new EchoInputBuilder(); builder.setVersion(getVersion()); builder.setXid(getSessionContext().getNextXid()); Future<RpcResult<EchoOutput>> echoReplyFuture = getConnectionAdapter() .echo(builder.build()); try { RpcResult<EchoOutput> echoReplyValue = echoReplyFuture .get(getMaxTimeout(), getMaxTimeoutUnit()); if (echoReplyValue.isSuccessful()) { setConductorState(CONDUCTOR_STATE.WORKING); } else { for (RpcError replyError : echoReplyValue .getErrors()) { Throwable cause = replyError.getCause(); LOG.error( "while receiving echoReply in TIMEOUTING state: " + cause.getMessage(), cause); } // switch issue occurred throw new Exception("switch issue occurred"); } } catch (Exception e) { LOG.error("while waiting for echoReply in TIMEOUTING state: " + e.getMessage()); errorHandler.handleException(e, sessionContext); // switch is not responding disconnect(); OFSessionUtil.getSessionManager() .invalidateOnDisconnect( ConnectionConductorImpl.this); } } } }).start(); } /** * @param conductorState the connectionState to set */ @Override public void setConductorState(CONDUCTOR_STATE conductorState) { this.conductorState = conductorState; } @Override public CONDUCTOR_STATE getConductorState() { return conductorState; } /** * @param expectedState connection conductor state */ protected void checkState(CONDUCTOR_STATE expectedState) { if (!conductorState.equals(expectedState)) { LOG.warn("State of connection to switch {} is not correct, " + "terminating the connection", connectionAdapter.getRemoteAddress()); throw new IllegalStateException("Expected state: " + expectedState + ", actual state:" + conductorState); } } @Override public void onDisconnectEvent(DisconnectEvent arg0) { SessionManager sessionManager = OFSessionUtil.getSessionManager(); sessionManager.invalidateOnDisconnect(this); close(); } @Override public Short getVersion() { return version; } @Override public Future<Boolean> disconnect() { LOG.trace("disconnecting: sessionCtx={}|auxId={}", sessionContext, auxiliaryKey); Future<Boolean> result = null; if (connectionAdapter.isAlive()) { result = connectionAdapter.disconnect(); } else { LOG.debug("connection already disconnected"); result = Futures.immediateFuture(true); } close(); return result; } @Override public void setConnectionCookie(SwitchConnectionDistinguisher auxiliaryKey) { this.auxiliaryKey = auxiliaryKey; } @Override public void setSessionContext(SessionContext sessionContext) { this.sessionContext = sessionContext; } @Override public SwitchConnectionDistinguisher getAuxiliaryKey() { return auxiliaryKey; } @Override public SessionContext getSessionContext() { return sessionContext; } @Override public ConnectionAdapter getConnectionAdapter() { return connectionAdapter; } @Override public void onConnectionReady() { LOG.debug("connection is ready-to-use"); if (!firstHelloProcessed) { checkState(CONDUCTOR_STATE.HANDSHAKING); HandshakeStepWrapper handshakeStepWrapper = new HandshakeStepWrapper( null, handshakeManager, connectionAdapter); hsPool.execute(handshakeStepWrapper); firstHelloProcessed = true; } else { LOG.debug("already touched by hello message"); } } @Override public void onHandshakeSuccessful(GetFeaturesOutput featureOutput, Short negotiatedVersion) { postHandshakeBasic(featureOutput, negotiatedVersion); } @Override public void onHandshakeFailure() { LOG.info("OF handshake failed, doing cleanup."); close(); } /** * used by tests * * @param featureOutput feature request output * @param negotiatedVersion negotiated openflow connection version */ protected void postHandshakeBasic(GetFeaturesOutput featureOutput, Short negotiatedVersion) { version = negotiatedVersion; if (version == OFConstants.OFP_VERSION_1_0) { // Because the GetFeaturesOutput contains information about the port // in OF1.0 (that we would otherwise get from the PortDesc) we have // to pass // it up for parsing to convert into a NodeConnectorUpdate // // BUG-1988 - this must be the first item in queue in order not to // get behind link-up message enqueueMessage(featureOutput); } SessionContext sessionContext = OFSessionUtil.registerSession(this, featureOutput, negotiatedVersion); hsPool.shutdown(); hsPool.purge(); conductorState = CONDUCTOR_STATE.WORKING; QueueKeeperFactory.plugQueue(queueProcessor, queue); } /** * @param isBitmapNegotiationEnable the isBitmapNegotiationEnable to set */ public void setBitmapNegotiationEnable(boolean isBitmapNegotiationEnable) { this.isBitmapNegotiationEnable = isBitmapNegotiationEnable; } @Override public void setId(int conductorId) { this.conductorId = conductorId; } @Override public void close() { conductorState = CONDUCTOR_STATE.RIP; if (handshakeContext != null) { try { handshakeContext.close(); } catch (Exception e) { LOG.warn("Closing handshake context failed: {}", e.getMessage()); LOG.debug("Detail in hanshake context close:", e); } } else { //This condition will occure when Old Helium openflowplugin implementation will be used. shutdownPoolPolitely(); } } private void shutdownPoolPolitely() { LOG.debug("Terminating handshake pool for node {}", connectionAdapter.getRemoteAddress()); hsPool.shutdown(); try { hsPool.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { LOG.debug("Error while awaiting termination of pool. Will force shutdown now."); } finally { hsPool.purge(); if (!hsPool.isTerminated()) { hsPool.shutdownNow(); } LOG.debug("is handshake pool for node {} is terminated : {}", connectionAdapter.getRemoteAddress(), hsPool.isTerminated()); } } @Override public void setHandshakeContext(HandshakeContext handshakeContext) { this.handshakeContext = handshakeContext; } @VisibleForTesting ThreadPoolExecutor getHsPool() { return hsPool; } }