/**
* Copyright (c) 2013 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 java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
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.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.hello.Elements;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.JdkFutureAdapters;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
/**
* @author mirehak
*
*/
public class HandshakeManagerImpl implements HandshakeManager {
private static final long activeXID = 20L;
private static final Logger LOG = LoggerFactory
.getLogger(HandshakeManagerImpl.class);
private Short lastProposedVersion;
private Short lastReceivedVersion;
private final List<Short> versionOrder;
private final ConnectionAdapter connectionAdapter;
private Short version;
private ErrorHandler errorHandler;
private Short highestVersion;
private Long activeXid;
private HandshakeListener handshakeListener;
private boolean useVersionBitmap;
/**
* @param connectionAdapter connection adaptor for switch
* @param highestVersion highest openflow version
* @param versionOrder list of version in order for connection protocol negotiation
*/
public HandshakeManagerImpl(ConnectionAdapter connectionAdapter, Short highestVersion,
List<Short> versionOrder) {
this.highestVersion = highestVersion;
this.versionOrder = versionOrder;
this.connectionAdapter = connectionAdapter;
}
@Override
public void setHandshakeListener(HandshakeListener handshakeListener) {
this.handshakeListener = handshakeListener;
}
@Override
public synchronized void shake(HelloMessage receivedHello) {
if (version != null) {
// Some switches respond with a second HELLO acknowledging our HELLO
// but we've already completed the handshake based on the negotiated
// version and have registered this switch.
LOG.debug("Hello recieved after handshake already settled ... ignoring.");
return;
}
LOG.trace("handshake STARTED");
setActiveXid(activeXID);
try {
if (receivedHello == null) {
// first Hello sending
sendHelloMessage(highestVersion, getNextXid());
lastProposedVersion = highestVersion;
LOG.trace("ret - firstHello+wait");
return;
}
// process the 2. and later hellos
Short remoteVersion = receivedHello.getVersion();
List<Elements> elements = receivedHello.getElements();
setActiveXid(receivedHello.getXid());
List<Boolean> remoteVersionBitmap = MessageFactory.digVersions(elements);
LOG.debug("Hello message: version={}, xid={}, bitmap={}", remoteVersion,
receivedHello.getXid(), remoteVersionBitmap);
if (useVersionBitmap && remoteVersionBitmap != null) {
// versionBitmap on both sides -> ONE STEP DECISION
handleVersionBitmapNegotiation(elements);
} else {
// versionBitmap missing at least on one side -> STEP-BY-STEP NEGOTIATION applying
handleStepByStepVersionNegotiation(remoteVersion);
}
} catch (Exception ex) {
errorHandler.handleException(ex, null);
LOG.trace("ret - shake fail - closing");
handshakeListener.onHandshakeFailure();
}
}
/**
* @param remoteVersion remote version
* @throws Exception exception
*/
private void handleStepByStepVersionNegotiation(final Short remoteVersion) throws Exception {
LOG.debug("remoteVersion:{} lastProposedVersion:{}, highestVersion:{}",
remoteVersion, lastProposedVersion, highestVersion);
if (lastProposedVersion == null) {
// first hello has not been sent yet, send it and either wait for next remote
// version or proceed
lastProposedVersion = proposeNextVersion(remoteVersion);
final Long nextHelloXid = getNextXid();
ListenableFuture<Void> helloResult = sendHelloMessage(lastProposedVersion, nextHelloXid);
Futures.addCallback(helloResult, new FutureCallback<Void>() {
@Override
public void onSuccess(Void result) {
try {
stepByStepVersionSubStep(remoteVersion, lastProposedVersion);
} catch (Exception e) {
errorHandler.handleException(e, null);
handshakeListener.onHandshakeFailure();
}
}
@Override
public void onFailure(Throwable t) {
LOG.info("hello sending seriously failed [{}]", nextHelloXid);
LOG.trace("detail of hello send problem", t);
}
});
} else {
stepByStepVersionSubStep(remoteVersion, lastProposedVersion);
}
}
private void stepByStepVersionSubStep(Short remoteVersion, Short lastProposedVersion) throws Exception {
if (remoteVersion.equals(lastProposedVersion)) {
postHandshake(lastProposedVersion, getNextXid());
LOG.trace("ret - OK - switch answered with lastProposedVersion");
} else {
checkNegotiationStalling(remoteVersion);
if (remoteVersion > (lastProposedVersion == null ? highestVersion : this.lastProposedVersion)) {
// wait for next version
LOG.trace("ret - wait");
} else {
//propose lower version
handleLowerVersionProposal(remoteVersion);
}
}
}
/**
* @param remoteVersion remote version
* @throws Exception exception
*/
private void handleLowerVersionProposal(Short remoteVersion) throws Exception {
Short proposedVersion;
// find the version from header version field
proposedVersion = proposeNextVersion(remoteVersion);
lastProposedVersion = proposedVersion;
sendHelloMessage(proposedVersion, getNextXid());
if (! Objects.equals(proposedVersion, remoteVersion)) {
LOG.trace("ret - sent+wait");
} else {
LOG.trace("ret - sent+OK");
postHandshake(proposedVersion, getNextXid());
}
}
/**
* @param elements version elements
* @throws Exception exception
*/
private void handleVersionBitmapNegotiation(List<Elements> elements) throws Exception {
final Short proposedVersion = proposeCommonBitmapVersion(elements);
if (lastProposedVersion == null) {
// first hello has not been sent yet
Long nexHelloXid = getNextXid();
ListenableFuture<Void> helloDone = sendHelloMessage(proposedVersion, nexHelloXid);
Futures.addCallback(helloDone, new FutureCallback<Void>() {
@Override
public void onSuccess(Void result) {
LOG.trace("ret - DONE - versionBitmap");
postHandshake(proposedVersion, getNextXid());
}
@Override
public void onFailure(Throwable t) {
// NOOP
}
});
LOG.trace("next proposal [{}] with versionBitmap hooked ..", nexHelloXid);
} else {
LOG.trace("ret - DONE - versionBitmap");
postHandshake(proposedVersion, getNextXid());
}
}
/**
*
* @return next tx id
*/
private Long getNextXid() {
activeXid += 1;
return activeXid;
}
/**
* @param xid tx id
*/
private void setActiveXid(Long xid) {
this.activeXid = xid;
}
/**
* @param remoteVersion remove version
*/
private void checkNegotiationStalling(Short remoteVersion) {
if (lastReceivedVersion != null && lastReceivedVersion.equals(remoteVersion)) {
throw new IllegalStateException("version negotiation stalled: version = "+remoteVersion);
}
lastReceivedVersion = remoteVersion;
}
@Override
public Short getVersion() {
return version;
}
/**
* find common highest supported bitmap version
* @param list bitmap list
* @return proposed bitmap value
*/
protected Short proposeCommonBitmapVersion(List<Elements> list) {
Short supportedHighestVersion = null;
if((null != list) && (0 != list.size())) {
for(Elements element : list) {
List<Boolean> bitmap = element.getVersionBitmap();
// check for version bitmap
for(short bitPos : ConnectionConductor.VERSION_ORDER) {
// with all the version it should work.
if(bitmap.get(bitPos % Integer.SIZE)) {
supportedHighestVersion = bitPos;
break;
}
}
}
if(null == supportedHighestVersion) {
LOG.trace("versionBitmap: no common version found");
throw new IllegalArgumentException("no common version found in versionBitmap");
}
}
return supportedHighestVersion;
}
/**
* find supported version based on remoteVersion
* @param remoteVersion openflow version supported by remote entity
* @return openflow version
*/
protected short proposeNextVersion(short remoteVersion) {
Short proposal = null;
for (short offer : versionOrder) {
if (offer <= remoteVersion) {
proposal = offer;
break;
}
}
if (proposal == null) {
throw new IllegalArgumentException("no equal or lower version found, unsupported version: "
+ remoteVersion);
}
return proposal;
}
/**
* send hello reply without versionBitmap
* @param helloVersion initial hello version for openflow connection negotiation
* @param helloXid transaction id
* @throws Exception
*/
private ListenableFuture<Void> sendHelloMessage(Short helloVersion, final Long helloXid) throws Exception {
HelloInput helloInput = MessageFactory.createHelloInput(helloVersion, helloXid, versionOrder);
final SettableFuture<Void> resultFtr = SettableFuture.create();
LOG.debug("sending hello message: version{}, xid={}, version bitmap={}",
helloVersion, helloXid, MessageFactory.digVersions(helloInput.getElements()));
Future<RpcResult<Void>> helloResult = connectionAdapter.hello(helloInput);
ListenableFuture<RpcResult<Void>> rpcResultListenableFuture = JdkFutureAdapters.listenInPoolThread(helloResult);
Futures.addCallback(rpcResultListenableFuture, new FutureCallback<RpcResult<Void>>() {
@Override
public void onSuccess(RpcResult<Void> result) {
if (result.isSuccessful()) {
LOG.debug("hello successfully sent, xid={}, addr={}", helloXid, connectionAdapter.getRemoteAddress());
resultFtr.set(null);
} else {
for (RpcError error : result.getErrors()) {
LOG.debug("hello sending failed [{}]: i:{} s:{} m:{}, addr:{}", helloXid,
error.getInfo(), error.getSeverity(), error.getMessage(),
connectionAdapter.getRemoteAddress());
if (error.getCause() != null) {
LOG.trace("DETAIL of sending hello failure", error.getCause());
}
}
resultFtr.cancel(false);
handshakeListener.onHandshakeFailure();
}
}
@Override
public void onFailure(Throwable t) {
LOG.warn("sending of hello failed seriously [{}, addr:{}]: {}", helloXid,
connectionAdapter.getRemoteAddress(), t.getMessage());
LOG.trace("DETAIL of sending of hello failure:", t);
resultFtr.cancel(false);
handshakeListener.onHandshakeFailure();
}
});
LOG.trace("sending hello message [{}] - result hooked ..", helloXid);
return resultFtr;
}
/**
* after handshake set features, register to session
* @param proposedVersion proposed openflow version
* @param xid transaction id
*/
protected void postHandshake(final Short proposedVersion, final Long xid) {
// set version
version = proposedVersion;
LOG.debug("version set: {}", proposedVersion);
// request features
GetFeaturesInputBuilder featuresBuilder = new GetFeaturesInputBuilder();
featuresBuilder.setVersion(version).setXid(xid);
LOG.debug("sending feature request for version={} and xid={}", version, xid);
Future<RpcResult<GetFeaturesOutput>> featuresFuture = connectionAdapter
.getFeatures(featuresBuilder.build());
Futures.addCallback(JdkFutureAdapters.listenInPoolThread(featuresFuture),
new FutureCallback<RpcResult<GetFeaturesOutput>>() {
@Override
public void onSuccess(RpcResult<GetFeaturesOutput> rpcFeatures) {
LOG.trace("features are back");
if (rpcFeatures.isSuccessful()) {
GetFeaturesOutput featureOutput = rpcFeatures.getResult();
LOG.debug("obtained features: datapathId={}",
featureOutput.getDatapathId());
LOG.debug("obtained features: auxiliaryId={}",
featureOutput.getAuxiliaryId());
LOG.trace("handshake SETTLED: version={}, datapathId={}, auxiliaryId={}",
version, featureOutput.getDatapathId(), featureOutput.getAuxiliaryId());
handshakeListener.onHandshakeSuccessful(featureOutput, proposedVersion);
} else {
// handshake failed
LOG.warn("issuing disconnect during handshake [{}]", connectionAdapter.getRemoteAddress());
for (RpcError rpcError : rpcFeatures.getErrors()) {
LOG.debug("handshake - features failure [{}]: i:{} | m:{} | s:{}", xid,
rpcError.getInfo(), rpcError.getMessage(), rpcError.getSeverity(),
rpcError.getCause()
);
}
handshakeListener.onHandshakeFailure();
}
LOG.debug("postHandshake DONE");
}
@Override
public void onFailure(Throwable t) {
LOG.warn("getting feature failed seriously [{}, addr:{}]: {}", xid,
connectionAdapter.getRemoteAddress(), t.getMessage());
LOG.trace("DETAIL of sending of hello failure:", t);
}
});
LOG.debug("future features [{}] hooked ..", xid);
}
@Override
public void setUseVersionBitmap(boolean useVersionBitmap) {
this.useVersionBitmap = useVersionBitmap;
}
@Override
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
}