/**
* Copyright (c) 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.impl.device;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Verify;
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 io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandlerRegistration;
import org.opendaylight.openflowjava.protocol.api.keys.MessageTypeKey;
import org.opendaylight.openflowplugin.api.ConnectionException;
import org.opendaylight.openflowplugin.api.OFConstants;
import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
import org.opendaylight.openflowplugin.api.openflow.connection.OutboundQueueProvider;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceInfo;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceManager;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceState;
import org.opendaylight.openflowplugin.api.openflow.device.MessageTranslator;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContext;
import org.opendaylight.openflowplugin.api.openflow.device.TranslatorLibrary;
import org.opendaylight.openflowplugin.api.openflow.device.Xid;
import org.opendaylight.openflowplugin.api.openflow.device.handlers.ClusterInitializationPhaseHandler;
import org.opendaylight.openflowplugin.api.openflow.device.handlers.MultiMsgCollector;
import org.opendaylight.openflowplugin.api.openflow.lifecycle.ContextChainMastershipState;
import org.opendaylight.openflowplugin.api.openflow.lifecycle.LifecycleService;
import org.opendaylight.openflowplugin.api.openflow.lifecycle.MastershipChangeListener;
import org.opendaylight.openflowplugin.api.openflow.md.core.SwitchConnectionDistinguisher;
import org.opendaylight.openflowplugin.api.openflow.md.core.TranslatorKey;
import org.opendaylight.openflowplugin.api.openflow.md.util.OpenflowVersion;
import org.opendaylight.openflowplugin.api.openflow.registry.ItemLifeCycleRegistry;
import org.opendaylight.openflowplugin.api.openflow.registry.flow.DeviceFlowRegistry;
import org.opendaylight.openflowplugin.api.openflow.registry.flow.FlowDescriptor;
import org.opendaylight.openflowplugin.api.openflow.registry.flow.FlowRegistryKey;
import org.opendaylight.openflowplugin.api.openflow.registry.group.DeviceGroupRegistry;
import org.opendaylight.openflowplugin.api.openflow.registry.meter.DeviceMeterRegistry;
import org.opendaylight.openflowplugin.api.openflow.rpc.ItemLifeCycleKeeper;
import org.opendaylight.openflowplugin.api.openflow.rpc.listener.ItemLifecycleListener;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.MessageSpy;
import org.opendaylight.openflowplugin.extension.api.ConvertorMessageFromOFJava;
import org.opendaylight.openflowplugin.extension.api.ExtensionConverterProviderKeeper;
import org.opendaylight.openflowplugin.extension.api.core.extension.ExtensionConverterProvider;
import org.opendaylight.openflowplugin.extension.api.exception.ConversionException;
import org.opendaylight.openflowplugin.extension.api.path.MessagePath;
import org.opendaylight.openflowplugin.impl.common.ItemLifeCycleSourceImpl;
import org.opendaylight.openflowplugin.impl.datastore.MultipartWriterProvider;
import org.opendaylight.openflowplugin.impl.datastore.MultipartWriterProviderFactory;
import org.opendaylight.openflowplugin.impl.device.initialization.AbstractDeviceInitializer;
import org.opendaylight.openflowplugin.impl.device.initialization.DeviceInitializerProvider;
import org.opendaylight.openflowplugin.impl.device.listener.MultiMsgCollectorImpl;
import org.opendaylight.openflowplugin.impl.device.listener.OpenflowProtocolListenerFullImpl;
import org.opendaylight.openflowplugin.impl.registry.flow.DeviceFlowRegistryImpl;
import org.opendaylight.openflowplugin.impl.registry.flow.FlowRegistryKeyFactory;
import org.opendaylight.openflowplugin.impl.registry.group.DeviceGroupRegistryImpl;
import org.opendaylight.openflowplugin.impl.registry.meter.DeviceMeterRegistryImpl;
import org.opendaylight.openflowplugin.impl.rpc.AbstractRequestContext;
import org.opendaylight.openflowplugin.openflow.md.core.sal.convertor.ConvertorExecutor;
import org.opendaylight.openflowplugin.openflow.md.core.session.SwitchConnectionCookieOFImpl;
import org.opendaylight.openflowplugin.openflow.md.util.InventoryDataServiceUtil;
import org.opendaylight.yang.gen.v1.urn.opendaylight.experimenter.message.service.rev151020.ExperimenterMessageFromDevBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.PortReason;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.Error;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ExperimenterMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FlowRemoved;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PacketIn;
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.protocol.rev130731.experimenter.core.ExperimenterDataOfChoice;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.experimenter.types.rev151020.experimenter.core.message.ExperimenterMessageOfChoice;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.FlowCapableNodeConnectorStatisticsData;
import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.FlowCapableNodeConnectorStatisticsDataBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.OfpRole;
import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SalRoleService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SetRoleInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SetRoleInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SetRoleOutput;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeviceContextImpl implements DeviceContext, ExtensionConverterProviderKeeper {
private static final Logger LOG = LoggerFactory.getLogger(DeviceContextImpl.class);
// TODO: drain factor should be parametrized
private static final float REJECTED_DRAIN_FACTOR = 0.25f;
// TODO: low water mark factor should be parametrized
private static final float LOW_WATERMARK_FACTOR = 0.75f;
// TODO: high water mark factor should be parametrized
private static final float HIGH_WATERMARK_FACTOR = 0.95f;
// Timeout in seconds after what we will give up on propagating role
private static final int SET_ROLE_TIMEOUT = 10;
// Timeout in milliseconds after what we will give up on initializing device
private static final int DEVICE_INIT_TIMEOUT = 9000;
private static final int LOW_WATERMARK = 1000;
private static final int HIGH_WATERMARK = 2000;
private final MultipartWriterProvider writerProvider;
private boolean initialized;
private SalRoleService salRoleService = null;
private final HashedWheelTimer hashedWheelTimer;
private volatile ConnectionContext primaryConnectionContext;
private final DeviceState deviceState;
private final DataBroker dataBroker;
private final Map<SwitchConnectionDistinguisher, ConnectionContext> auxiliaryConnectionContexts;
private TransactionChainManager transactionChainManager;
private DeviceFlowRegistry deviceFlowRegistry;
private DeviceGroupRegistry deviceGroupRegistry;
private DeviceMeterRegistry deviceMeterRegistry;
private PacketInRateLimiter packetInLimiter;
private final MessageSpy messageSpy;
private final ItemLifeCycleKeeper flowLifeCycleKeeper;
private NotificationPublishService notificationPublishService;
private Timeout barrierTaskTimeout;
private final MessageTranslator<PortGrouping, FlowCapableNodeConnector> portStatusTranslator;
private final MessageTranslator<PacketInMessage, PacketReceived> packetInTranslator;
private final MessageTranslator<FlowRemoved, org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowRemoved> flowRemovedTranslator;
private final TranslatorLibrary translatorLibrary;
private final ItemLifeCycleRegistry itemLifeCycleSourceRegistry;
private ExtensionConverterProvider extensionConverterProvider;
private boolean skipTableFeatures;
private boolean switchFeaturesMandatory;
private DeviceInfo deviceInfo;
private final ConvertorExecutor convertorExecutor;
private volatile CONTEXT_STATE state;
private ClusterInitializationPhaseHandler clusterInitializationPhaseHandler;
private final DeviceManager myManager;
private final DeviceInitializerProvider deviceInitializerProvider;
private final boolean useSingleLayerSerialization;
private OutboundQueueProvider outboundQueueProvider;
private boolean isInitialTransactionSubmitted;
DeviceContextImpl(
@Nonnull final ConnectionContext primaryConnectionContext,
@Nonnull final DataBroker dataBroker,
@Nonnull final MessageSpy messageSpy,
@Nonnull final TranslatorLibrary translatorLibrary,
@Nonnull final DeviceManager contextManager,
final ConvertorExecutor convertorExecutor,
final boolean skipTableFeatures,
final HashedWheelTimer hashedWheelTimer,
final boolean useSingleLayerSerialization,
final DeviceInitializerProvider deviceInitializerProvider) {
this.primaryConnectionContext = primaryConnectionContext;
this.outboundQueueProvider = (OutboundQueueProvider) primaryConnectionContext.getOutboundQueueProvider();
this.deviceInfo = primaryConnectionContext.getDeviceInfo();
this.hashedWheelTimer = hashedWheelTimer;
this.deviceInitializerProvider = deviceInitializerProvider;
this.myManager = contextManager;
this.deviceState = new DeviceStateImpl();
this.dataBroker = dataBroker;
this.auxiliaryConnectionContexts = new HashMap<>();
this.messageSpy = messageSpy;
this.packetInLimiter = new PacketInRateLimiter(primaryConnectionContext.getConnectionAdapter(),
/*initial*/ LOW_WATERMARK, /*initial*/HIGH_WATERMARK, this.messageSpy, REJECTED_DRAIN_FACTOR);
this.translatorLibrary = translatorLibrary;
this.portStatusTranslator = translatorLibrary.lookupTranslator(
new TranslatorKey(deviceInfo.getVersion(), PortGrouping.class.getName()));
this.packetInTranslator = translatorLibrary.lookupTranslator(
new TranslatorKey(deviceInfo.getVersion(), PacketIn.class.getName()));
this.flowRemovedTranslator = translatorLibrary.lookupTranslator(
new TranslatorKey(deviceInfo.getVersion(), FlowRemoved.class.getName()));
this.itemLifeCycleSourceRegistry = new ItemLifeCycleRegistryImpl();
this.flowLifeCycleKeeper = new ItemLifeCycleSourceImpl();
this.itemLifeCycleSourceRegistry.registerLifeCycleSource(flowLifeCycleKeeper);
this.state = CONTEXT_STATE.INITIALIZATION;
this.convertorExecutor = convertorExecutor;
this.skipTableFeatures = skipTableFeatures;
this.useSingleLayerSerialization = useSingleLayerSerialization;
this.initialized = false;
writerProvider = MultipartWriterProviderFactory.createDefaultProvider(this);
}
@Override
public boolean initialSubmitTransaction() {
if (initialized) {
isInitialTransactionSubmitted = true;
return transactionChainManager.initialSubmitWriteTransaction();
}
return false;
}
@Override
public void addAuxiliaryConnectionContext(final ConnectionContext connectionContext) {
final SwitchConnectionDistinguisher connectionDistinguisher = createConnectionDistinguisher(connectionContext);
auxiliaryConnectionContexts.put(connectionDistinguisher, connectionContext);
}
private static SwitchConnectionDistinguisher createConnectionDistinguisher(final ConnectionContext connectionContext) {
return new SwitchConnectionCookieOFImpl(connectionContext.getFeatures().getAuxiliaryId());
}
@Override
public void removeAuxiliaryConnectionContext(final ConnectionContext connectionContext) {
final SwitchConnectionDistinguisher connectionDistinguisher = createConnectionDistinguisher(connectionContext);
LOG.debug("auxiliary connection dropped: {}, nodeId:{}", connectionContext.getConnectionAdapter()
.getRemoteAddress(), getDeviceInfo().getLOGValue());
auxiliaryConnectionContexts.remove(connectionDistinguisher);
}
@Override
public DeviceState getDeviceState() {
return deviceState;
}
@Override
public ReadOnlyTransaction getReadTransaction() {
return dataBroker.newReadOnlyTransaction();
}
@Override
public <T extends DataObject> void writeToTransaction(final LogicalDatastoreType store,
final InstanceIdentifier<T> path,
final T data){
if (initialized) {
transactionChainManager.writeToTransaction(store, path, data, false);
}
}
@Override
public <T extends DataObject> void writeToTransactionWithParentsSlow(final LogicalDatastoreType store,
final InstanceIdentifier<T> path,
final T data){
if (initialized) {
transactionChainManager.writeToTransaction(store, path, data, true);
}
}
@Override
public <T extends DataObject> void addDeleteToTxChain(final LogicalDatastoreType store, final InstanceIdentifier<T> path) {
if (initialized) {
transactionChainManager.addDeleteOperationTotTxChain(store, path);
}
}
@Override
public boolean submitTransaction() {
return initialized && transactionChainManager.submitWriteTransaction();
}
@Override
public ConnectionContext getPrimaryConnectionContext() {
return primaryConnectionContext;
}
@Override
public ConnectionContext getAuxiliaryConnectionContexts(final BigInteger cookie) {
return auxiliaryConnectionContexts.get(new SwitchConnectionCookieOFImpl(cookie.longValue()));
}
@Override
public DeviceFlowRegistry getDeviceFlowRegistry() {
return deviceFlowRegistry;
}
@Override
public DeviceGroupRegistry getDeviceGroupRegistry() {
return deviceGroupRegistry;
}
@Override
public DeviceMeterRegistry getDeviceMeterRegistry() {
return deviceMeterRegistry;
}
@Override
public void processReply(final OfHeader ofHeader) {
messageSpy.spyMessage(
ofHeader.getImplementedInterface(),
(ofHeader instanceof Error)
? MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_FAILURE
: MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_SUCCESS);
}
@Override
public void processReply(final Xid xid, final List<? extends OfHeader> ofHeaderList) {
ofHeaderList.forEach(header -> messageSpy.spyMessage(
header.getImplementedInterface(),
(header instanceof Error)
? MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_FAILURE
: MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_SUCCESS));
}
@Override
public void processFlowRemovedMessage(final FlowRemoved flowRemoved) {
//1. translate to general flow (table, priority, match, cookie)
final org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowRemoved flowRemovedNotification =
flowRemovedTranslator.translate(flowRemoved, deviceInfo, null);
if(myManager.isFlowRemovedNotificationOn()) {
// Trigger off a notification
notificationPublishService.offerNotification(flowRemovedNotification);
} else if(LOG.isDebugEnabled()) {
LOG.debug("For nodeId={} isNotificationFlowRemovedOn={}", getDeviceInfo().getLOGValue(), myManager.isFlowRemovedNotificationOn());
}
final ItemLifecycleListener itemLifecycleListener = flowLifeCycleKeeper.getItemLifecycleListener();
if (itemLifecycleListener != null) {
//2. create registry key
final FlowRegistryKey flowRegKey = FlowRegistryKeyFactory.create(getDeviceInfo().getVersion(), flowRemovedNotification);
//3. lookup flowId
final FlowDescriptor flowDescriptor = deviceFlowRegistry.retrieveDescriptor(flowRegKey);
//4. if flowId present:
if (flowDescriptor != null) {
// a) construct flow path
final KeyedInstanceIdentifier<Flow, FlowKey> flowPath = getDeviceInfo().getNodeInstanceIdentifier()
.augmentation(FlowCapableNode.class)
.child(Table.class, flowDescriptor.getTableKey())
.child(Flow.class, new FlowKey(flowDescriptor.getFlowId()));
// b) notify listener
itemLifecycleListener.onRemoved(flowPath);
} else {
LOG.debug("flow id not found: nodeId={} tableId={}, priority={}",
getDeviceInfo().getNodeId(), flowRegKey.getTableId(), flowRemovedNotification.getPriority());
}
}
}
@Override
public void processPortStatusMessage(final PortStatusMessage portStatus) {
messageSpy.spyMessage(portStatus.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_SUCCESS);
if (isInitialTransactionSubmitted) {
try {
writePortStatusMessage(portStatus);
submitTransaction();
} catch (final Exception e) {
LOG.warn("Error processing port status message for port {} on device {}",
portStatus.getPortNo(), getDeviceInfo().getLOGValue(), e);
}
}
}
private void writePortStatusMessage(final PortStatus portStatusMessage) {
final FlowCapableNodeConnector flowCapableNodeConnector = portStatusTranslator
.translate(portStatusMessage, getDeviceInfo(), null);
final KeyedInstanceIdentifier<NodeConnector, NodeConnectorKey> iiToNodeConnector = getDeviceInfo()
.getNodeInstanceIdentifier()
.child(NodeConnector.class, new NodeConnectorKey(InventoryDataServiceUtil
.nodeConnectorIdfromDatapathPortNo(
deviceInfo.getDatapathId(),
portStatusMessage.getPortNo(),
OpenflowVersion.get(deviceInfo.getVersion()))));
if (PortReason.OFPPRADD.equals(portStatusMessage.getReason()) || PortReason.OFPPRMODIFY.equals(portStatusMessage.getReason())) {
// because of ADD status node connector has to be created
writeToTransaction(LogicalDatastoreType.OPERATIONAL, iiToNodeConnector, new NodeConnectorBuilder()
.setKey(iiToNodeConnector.getKey())
.addAugmentation(FlowCapableNodeConnectorStatisticsData.class, new FlowCapableNodeConnectorStatisticsDataBuilder().build())
.addAugmentation(FlowCapableNodeConnector.class, flowCapableNodeConnector)
.build());
} else if (PortReason.OFPPRDELETE.equals(portStatusMessage.getReason())) {
addDeleteToTxChain(LogicalDatastoreType.OPERATIONAL, iiToNodeConnector);
}
}
@Override
public void processPacketInMessage(final PacketInMessage packetInMessage) {
messageSpy.spyMessage(packetInMessage.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH);
final ConnectionAdapter connectionAdapter = getPrimaryConnectionContext().getConnectionAdapter();
final PacketReceived packetReceived = packetInTranslator.translate(packetInMessage, getDeviceInfo(), null);
if (packetReceived == null) {
LOG.debug("Received a null packet from switch {}", connectionAdapter.getRemoteAddress());
messageSpy.spyMessage(packetInMessage.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_TRANSLATE_SRC_FAILURE);
return;
} else {
messageSpy.spyMessage(packetReceived.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_TRANSLATE_OUT_SUCCESS);
}
if (!packetInLimiter.acquirePermit()) {
LOG.debug("Packet limited");
// TODO: save packet into emergency slot if possible
messageSpy.spyMessage(packetReceived.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PACKET_IN_LIMIT_REACHED_AND_DROPPED);
return;
}
final ListenableFuture<?> offerNotification = notificationPublishService.offerNotification(packetReceived);
if (NotificationPublishService.REJECTED.equals(offerNotification)) {
LOG.debug("notification offer rejected");
messageSpy.spyMessage(packetReceived.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_NOTIFICATION_REJECTED);
packetInLimiter.drainLowWaterMark();
packetInLimiter.releasePermit();
return;
}
Futures.addCallback(offerNotification, new FutureCallback<Object>() {
@Override
public void onSuccess(final Object result) {
messageSpy.spyMessage(packetReceived.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_PUBLISHED_SUCCESS);
packetInLimiter.releasePermit();
}
@Override
public void onFailure(final Throwable t) {
messageSpy.spyMessage(packetReceived.getImplementedInterface(), MessageSpy.STATISTIC_GROUP.FROM_SWITCH_NOTIFICATION_REJECTED);
LOG.debug("notification offer failed: {}", t.getMessage());
LOG.trace("notification offer failed..", t);
packetInLimiter.releasePermit();
}
});
}
@Override
public void processExperimenterMessage(final ExperimenterMessage notification) {
// lookup converter
final ExperimenterDataOfChoice vendorData = notification.getExperimenterDataOfChoice();
final MessageTypeKey<? extends ExperimenterDataOfChoice> key = new MessageTypeKey<>(
getDeviceInfo().getVersion(),
(Class<? extends ExperimenterDataOfChoice>) vendorData.getImplementedInterface());
final ConvertorMessageFromOFJava<ExperimenterDataOfChoice, MessagePath> messageConverter = extensionConverterProvider.getMessageConverter(key);
if (messageConverter == null) {
LOG.warn("custom converter for {}[OF:{}] not found",
notification.getExperimenterDataOfChoice().getImplementedInterface(),
getDeviceInfo().getVersion());
return;
}
// build notification
final ExperimenterMessageOfChoice messageOfChoice;
try {
messageOfChoice = messageConverter.convert(vendorData, MessagePath.MESSAGE_NOTIFICATION);
final ExperimenterMessageFromDevBuilder experimenterMessageFromDevBld = new ExperimenterMessageFromDevBuilder()
.setNode(new NodeRef(getDeviceInfo().getNodeInstanceIdentifier()))
.setExperimenterMessageOfChoice(messageOfChoice);
// publish
notificationPublishService.offerNotification(experimenterMessageFromDevBld.build());
} catch (final ConversionException e) {
LOG.error("Conversion of experimenter notification failed", e);
}
}
@Override
public TranslatorLibrary oook() {
return translatorLibrary;
}
@Override
public void setCurrentBarrierTimeout(final Timeout timeout) {
barrierTaskTimeout = timeout;
}
@Override
public Timeout getBarrierTaskTimeout() {
return barrierTaskTimeout;
}
@Override
public void setNotificationPublishService(final NotificationPublishService notificationPublishService) {
this.notificationPublishService = notificationPublishService;
}
@Override
public MessageSpy getMessageSpy() {
return messageSpy;
}
@Override
public void onPublished() {
Verify.verify(CONTEXT_STATE.INITIALIZATION.equals(getState()));
this.state = CONTEXT_STATE.WORKING;
primaryConnectionContext.getConnectionAdapter().setPacketInFiltering(false);
}
@Override
public <T extends OfHeader> MultiMsgCollector<T> getMultiMsgCollector(final RequestContext<List<T>> requestContext) {
return new MultiMsgCollectorImpl<>(this, requestContext);
}
@Override
public void updatePacketInRateLimit(final long upperBound) {
packetInLimiter.changeWaterMarks((int) (LOW_WATERMARK_FACTOR * upperBound), (int) (HIGH_WATERMARK_FACTOR * upperBound));
}
@Override
public ItemLifeCycleRegistry getItemLifeCycleSourceRegistry() {
return itemLifeCycleSourceRegistry;
}
@Override
public void setExtensionConverterProvider(final ExtensionConverterProvider extensionConverterProvider) {
this.extensionConverterProvider = extensionConverterProvider;
}
@Override
public ExtensionConverterProvider getExtensionConverterProvider() {
return extensionConverterProvider;
}
@Override
public synchronized void shutdownConnection() {
if (LOG.isDebugEnabled()) {
LOG.debug("Shutdown method for node {}", getDeviceInfo().getLOGValue());
}
if (CONTEXT_STATE.TERMINATION.equals(getState())) {
LOG.debug("DeviceCtx for Node {} is in termination process.", getDeviceInfo().getLOGValue());
return;
}
if (ConnectionContext.CONNECTION_STATE.RIP.equals(getPrimaryConnectionContext().getConnectionState())) {
LOG.debug("ConnectionCtx for Node {} is in RIP state.", getDeviceInfo().getLOGValue());
return;
}
// Terminate Auxiliary Connection
for (final ConnectionContext connectionContext : auxiliaryConnectionContexts.values()) {
LOG.debug("Closing auxiliary connection {}", connectionContext.getNodeId());
connectionContext.closeConnection(false);
}
// Terminate Primary Connection
getPrimaryConnectionContext().closeConnection(true);
// Close all datastore registries
if (initialized) {
deviceGroupRegistry.close();
deviceFlowRegistry.close();
deviceMeterRegistry.close();
}
}
@Override
public ListenableFuture<Void> shuttingDownDataStoreTransactions() {
return initialized
? this.transactionChainManager.shuttingDown()
: Futures.immediateFuture(null);
}
@VisibleForTesting
TransactionChainManager getTransactionChainManager() {
return this.transactionChainManager;
}
@Override
public void setSwitchFeaturesMandatory(boolean switchFeaturesMandatory) {
this.switchFeaturesMandatory = switchFeaturesMandatory;
}
@Override
public CONTEXT_STATE getState() {
return this.state;
}
@Override
public ListenableFuture<Void> stopClusterServices() {
return initialized
? transactionChainManager.deactivateTransactionManager()
: Futures.immediateFuture(null);
}
@Override
public ServiceGroupIdentifier getServiceIdentifier() {
return this.deviceInfo.getServiceIdentifier();
}
@Override
public DeviceInfo getDeviceInfo() {
return this.deviceInfo;
}
@Override
public void close() {
//NOOP
}
@Override
public void putLifecycleServiceIntoTxChainManager(final LifecycleService lifecycleService){
if (initialized) {
this.transactionChainManager.setLifecycleService(lifecycleService);
}
}
@Override
public boolean canUseSingleLayerSerialization() {
return useSingleLayerSerialization && getDeviceInfo().getVersion() >= OFConstants.OFP_VERSION_1_3;
}
@Override
public boolean isSkipTableFeatures() {
return this.skipTableFeatures;
}
@Override
public void setSalRoleService(@Nonnull SalRoleService salRoleService) {
this.salRoleService = salRoleService;
}
@Override
public void setLifecycleInitializationPhaseHandler(final ClusterInitializationPhaseHandler handler) {
this.clusterInitializationPhaseHandler = handler;
}
@Override
public boolean onContextInstantiateService(final MastershipChangeListener mastershipChangeListener) {
LOG.info("Starting device context cluster services for node {}", deviceInfo.getLOGValue());
lazyTransactionManagerInitialization();
this.transactionChainManager.activateTransactionManager();
try {
final java.util.Optional<AbstractDeviceInitializer> initializer = deviceInitializerProvider
.lookup(deviceInfo.getVersion());
if (initializer.isPresent()) {
initializer
.get()
.initialize(this, switchFeaturesMandatory, writerProvider, convertorExecutor)
.get(DEVICE_INIT_TIMEOUT, TimeUnit.MILLISECONDS);
} else {
throw new ExecutionException(new ConnectionException("Unsupported version " + deviceInfo.getVersion()));
}
} catch (ExecutionException | InterruptedException | TimeoutException ex) {
LOG.warn("Device {} cannot be initialized: ", deviceInfo.getLOGValue(), ex);
return false;
}
Futures.addCallback(sendRoleChangeToDevice(OfpRole.BECOMEMASTER),
new RpcResultFutureCallback(mastershipChangeListener));
final ListenableFuture<List<Optional<FlowCapableNode>>> deviceFlowRegistryFill = getDeviceFlowRegistry().fill();
Futures.addCallback(deviceFlowRegistryFill,
new DeviceFlowRegistryCallback(deviceFlowRegistryFill, mastershipChangeListener));
return this.clusterInitializationPhaseHandler.onContextInstantiateService(mastershipChangeListener);
}
@VisibleForTesting
void lazyTransactionManagerInitialization() {
if (!this.initialized) {
if (LOG.isDebugEnabled()) {
LOG.debug("Transaction chain manager for node {} created", deviceInfo.getLOGValue());
}
this.transactionChainManager = new TransactionChainManager(dataBroker, deviceInfo);
this.deviceFlowRegistry = new DeviceFlowRegistryImpl(deviceInfo.getVersion(), dataBroker, deviceInfo.getNodeInstanceIdentifier());
this.deviceGroupRegistry = new DeviceGroupRegistryImpl();
this.deviceMeterRegistry = new DeviceMeterRegistryImpl();
this.initialized = true;
}
}
@Nullable
@Override
public <T> RequestContext<T> createRequestContext() {
return new AbstractRequestContext<T>(deviceInfo.reserveXidForDeviceMessage()) {
@Override
public void close() {
}
};
}
private ListenableFuture<RpcResult<SetRoleOutput>> sendRoleChangeToDevice(final OfpRole newRole) {
if (LOG.isDebugEnabled()) {
LOG.debug("Sending new role {} to device {}", newRole, deviceInfo.getNodeId());
}
final Future<RpcResult<SetRoleOutput>> setRoleOutputFuture;
if (deviceInfo.getVersion() >= OFConstants.OFP_VERSION_1_3) {
final SetRoleInput setRoleInput = (new SetRoleInputBuilder()).setControllerRole(newRole)
.setNode(new NodeRef(deviceInfo.getNodeInstanceIdentifier())).build();
setRoleOutputFuture = this.salRoleService.setRole(setRoleInput);
final TimerTask timerTask = timeout -> {
if (!setRoleOutputFuture.isDone()) {
LOG.warn("New role {} was not propagated to device {} during {} sec", newRole, deviceInfo.getLOGValue(), SET_ROLE_TIMEOUT);
setRoleOutputFuture.cancel(true);
}
};
hashedWheelTimer.newTimeout(timerTask, SET_ROLE_TIMEOUT, TimeUnit.SECONDS);
} else {
LOG.info("Device: {} with version: {} does not support role", deviceInfo.getLOGValue(), deviceInfo.getVersion());
return Futures.immediateFuture(null);
}
return JdkFutureAdapters.listenInPoolThread(setRoleOutputFuture);
}
@Override
public ListenableFuture<RpcResult<SetRoleOutput>> makeDeviceSlave() {
return sendRoleChangeToDevice(OfpRole.BECOMESLAVE);
}
private class RpcResultFutureCallback implements FutureCallback<RpcResult<SetRoleOutput>> {
private final MastershipChangeListener mastershipChangeListener;
RpcResultFutureCallback(final MastershipChangeListener mastershipChangeListener) {
this.mastershipChangeListener = mastershipChangeListener;
}
@Override
public void onSuccess(@Nullable RpcResult<SetRoleOutput> setRoleOutputRpcResult) {
this.mastershipChangeListener.onMasterRoleAcquired(
deviceInfo,
ContextChainMastershipState.MASTER_ON_DEVICE
);
if (LOG.isDebugEnabled()) {
LOG.debug("Role MASTER was successfully set on device, node {}", deviceInfo.getLOGValue());
}
}
@Override
public void onFailure(final Throwable throwable) {
mastershipChangeListener.onNotAbleToStartMastershipMandatory(
deviceInfo,
"Was not able to set MASTER role on device");
}
}
private class DeviceFlowRegistryCallback implements FutureCallback<List<Optional<FlowCapableNode>>> {
private final ListenableFuture<List<Optional<FlowCapableNode>>> deviceFlowRegistryFill;
private final MastershipChangeListener mastershipChangeListener;
DeviceFlowRegistryCallback(
ListenableFuture<List<Optional<FlowCapableNode>>> deviceFlowRegistryFill,
MastershipChangeListener mastershipChangeListener) {
this.deviceFlowRegistryFill = deviceFlowRegistryFill;
this.mastershipChangeListener = mastershipChangeListener;
}
@Override
public void onSuccess(@Nullable List<Optional<FlowCapableNode>> result) {
if (LOG.isDebugEnabled()) {
// Count all flows we read from datastore for debugging purposes.
// This number do not always represent how many flows were actually added
// to DeviceFlowRegistry, because of possible duplicates.
long flowCount = Optional.fromNullable(result).asSet().stream()
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.flatMap(flowCapableNodeOptional -> flowCapableNodeOptional.asSet().stream())
.filter(Objects::nonNull)
.filter(flowCapableNode -> Objects.nonNull(flowCapableNode.getTable()))
.flatMap(flowCapableNode -> flowCapableNode.getTable().stream())
.filter(Objects::nonNull)
.filter(table -> Objects.nonNull(table.getFlow()))
.flatMap(table -> table.getFlow().stream())
.filter(Objects::nonNull)
.count();
LOG.debug("Finished filling flow registry with {} flows for node: {}", flowCount, deviceInfo.getLOGValue());
}
this.mastershipChangeListener.onMasterRoleAcquired(deviceInfo, ContextChainMastershipState.INITIAL_FLOW_REGISTRY_FILL);
}
@Override
public void onFailure(Throwable t) {
if (deviceFlowRegistryFill.isCancelled()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Cancelled filling flow registry with flows for node: {}", deviceInfo.getLOGValue());
}
} else {
LOG.warn("Failed filling flow registry with flows for node: {} with exception: {}", deviceInfo.getLOGValue(), t);
}
mastershipChangeListener.onNotAbleToStartMastership(
deviceInfo,
"Was not able to fill flow registry on device",
false);
}
}
}