From f519771f547cc6f58d52a6c0ca0076db04507ae6 Mon Sep 17 00:00:00 2001 From: Christopher Price Date: Wed, 31 Jul 2013 12:31:06 -0700 Subject: [PATCH] Initial clone of OF plugin 1.0 into the openflowplugin repository with small amendment Change-Id: I8bb86a604d9dcfd46dd7ec9b5f50a10c29e4fca8 Signed-off-by: Christopher Price --- .gitignore | 19 + commons/pom.xml | 40 + commons/space_and_tabs_checks.xml | 17 + openflowplugin/pom.xml | 85 + .../openflow/IDataPacketListen.java | 39 + .../openflow/IDataPacketMux.java | 31 + .../openflow/IDiscoveryListener.java | 19 + .../openflow/IFlowProgrammerNotifier.java | 22 + .../openflow/IInventoryProvider.java | 18 + .../IInventoryShimExternalListener.java | 18 + .../IInventoryShimInternalListener.java | 47 + .../openflow/IOFStatisticsListener.java | 20 + .../openflow/IOFStatisticsManager.java | 118 ++ .../openflow/IReadFilterInternalListener.java | 46 + .../openflow/IReadServiceFilter.java | 110 ++ .../openflow/IRefreshInternalProvider.java | 34 + .../ITopologyServiceShimListener.java | 47 + .../openflow/core/IController.java | 62 + .../openflow/core/IMessageListener.java | 26 + .../openflow/core/IMessageReadWrite.java | 53 + .../openflowplugin/openflow/core/ISwitch.java | 231 +++ .../openflow/core/ISwitchStateListener.java | 31 + .../openflow/core/internal/Controller.java | 413 ++++ .../openflow/core/internal/ControllerIO.java | 93 + .../internal/MessageReadWriteService.java | 151 ++ .../core/internal/PriorityMessage.java | 92 + .../SecureMessageReadWriteService.java | 420 +++++ .../core/internal/StatisticsCollector.java | 81 + .../openflow/core/internal/SwitchEvent.java | 64 + .../openflow/core/internal/SwitchHandler.java | 943 ++++++++++ .../core/internal/SynchronousMessage.java | 78 + .../openflow/internal/Activator.java | 460 +++++ .../openflow/internal/DataPacketMuxDemux.java | 455 +++++ .../openflow/internal/DataPacketServices.java | 55 + .../internal/DescStatisticsConverter.java | 53 + .../openflow/internal/DiscoveryService.java | 1671 +++++++++++++++++ .../openflow/internal/FlowConverter.java | 755 ++++++++ .../internal/FlowProgrammerNotifier.java | 117 ++ .../internal/FlowProgrammerService.java | 803 ++++++++ .../internal/FlowStatisticsConverter.java | 94 + .../openflow/internal/InventoryService.java | 296 +++ .../internal/InventoryServiceHelper.java | 167 ++ .../internal/InventoryServiceShim.java | 576 ++++++ .../internal/OFStatisticsManager.java | 1214 ++++++++++++ .../openflow/internal/PortConverter.java | 73 + .../internal/PortStatisticsConverter.java | 78 + .../openflow/internal/ReadService.java | 285 +++ .../openflow/internal/ReadServiceFilter.java | 646 +++++++ .../openflow/internal/TableConverter.java | 30 + .../internal/TableStatisticsConverter.java | 63 + .../internal/TopologyServiceShim.java | 833 ++++++++ .../openflow/internal/TopologyServices.java | 165 ++ .../openflow/internal/Utils.java | 80 + .../vendorextension/v6extension/V6Error.java | 106 ++ .../v6extension/V6FlowMod.java | 242 +++ .../vendorextension/v6extension/V6Match.java | 1614 ++++++++++++++++ .../v6extension/V6StatsReply.java | 394 ++++ .../v6extension/V6StatsRequest.java | 176 ++ .../internal/FlowProgrammerServiceTest.java | 408 ++++ .../v6extension/V6ExtensionTest.java | 224 +++ 60 files changed, 15601 insertions(+) create mode 100755 .gitignore create mode 100755 commons/pom.xml create mode 100755 commons/space_and_tabs_checks.xml create mode 100644 openflowplugin/pom.xml create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketListen.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketMux.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDiscoveryListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IFlowProgrammerNotifier.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryProvider.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimExternalListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimInternalListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsManager.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadFilterInternalListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadServiceFilter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IRefreshInternalProvider.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/ITopologyServiceShimListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IController.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageReadWrite.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitch.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitchStateListener.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/Controller.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/ControllerIO.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/MessageReadWriteService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/PriorityMessage.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SecureMessageReadWriteService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/StatisticsCollector.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchEvent.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchHandler.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SynchronousMessage.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Activator.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketMuxDemux.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketServices.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DescStatisticsConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DiscoveryService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerNotifier.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowStatisticsConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceHelper.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceShim.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/OFStatisticsManager.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortStatisticsConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadService.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadServiceFilter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableStatisticsConverter.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServiceShim.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServices.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Utils.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Error.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6FlowMod.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Match.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsReply.java create mode 100644 openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsRequest.java create mode 100644 openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java create mode 100644 openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6ExtensionTest.java diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000000..48d2af9a5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.idea/ +target/ +*.class +*.iml +**/target +**/bin +dist +**/logs +products +repository +workspace +*~ +target +.classpath +.project +.settings +MANIFEST.MF + + diff --git a/commons/pom.xml b/commons/pom.xml new file mode 100755 index 0000000000..bb81be5749 --- /dev/null +++ b/commons/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + 3.0 + + + org.opendaylight.controller + commons.opendaylight + 1.4.0-SNAPSHOT + + org.opendaylight.openflowplugin + commons.openflowplugin + 0.0.1-SNAPSHOT + pom + + + + + opendaylight-release + opendaylight-release + http://nexus.opendaylight.org/content/repositories/opendaylight.release/ + + + + opendaylight-snapshot + opendaylight-snapshot + http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/ + + + + + + central2 + central2 + http://nexus.opendaylight.org/content/repositories/central2/ + + + diff --git a/commons/space_and_tabs_checks.xml b/commons/space_and_tabs_checks.xml new file mode 100755 index 0000000000..49a5802444 --- /dev/null +++ b/commons/space_and_tabs_checks.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/openflowplugin/pom.xml b/openflowplugin/pom.xml new file mode 100644 index 0000000000..b213c8d358 --- /dev/null +++ b/openflowplugin/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + org.opendaylight.openflowplugin + commons.openflowplugin + 0.0.1-SNAPSHOT + ../commons + + + openflowplugin + 0.0.1-SNAPSHOT + bundle + + + + + org.apache.felix + maven-bundle-plugin + 2.3.6 + true + + + + org.opendaylight.controller.sal.packet, + org.opendaylight.controller.sal.action, + org.opendaylight.controller.sal.discovery, + org.opendaylight.controller.sal.topology, + org.opendaylight.controller.sal.core, + org.opendaylight.controller.sal.flowprogrammer, + org.opendaylight.controller.sal.reader, + org.opendaylight.controller.sal.inventory, + org.opendaylight.controller.sal.match, + org.opendaylight.controller.sal.utils, + org.opendaylight.controller.sal.connection, + org.apache.commons.lang3.builder, + org.apache.commons.lang3.tuple, + org.apache.felix.dm, + org.slf4j, + org.eclipse.osgi.framework.console, + org.osgi.framework, + javax.net.ssl + + + org.opendaylight.openflowplugin.openflow.internal + + + org.openflow.openflowj + + + false + + + org.opendaylight.openflowplugin.openflow.internal.Activator + + + ${project.basedir}/META-INF + + + + + + + org.opendaylight.controller + sal + 0.5.0-SNAPSHOT + + + org.opendaylight.controller + sal.connection + 0.1.0-SNAPSHOT + + + org.opendaylight.controller.thirdparty + org.openflow.openflowj + 1.0.2-SNAPSHOT + + + junit + junit + 4.8.1 + test + + + diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketListen.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketListen.java new file mode 100644 index 0000000000..c92918ef1d --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketListen.java @@ -0,0 +1,39 @@ + +/* + * 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 + */ + +/** + * @file IDataPacketListen.java + * + * @brief Interface to dispatch locally in the protocol plugin the + * data packets, intended especially for Discovery, main difference + * here with the analogous of SAL is that this is Global + * inherently + * + */ +package org.opendaylight.openflowplugin.openflow; + +import org.opendaylight.controller.sal.packet.RawPacket; +import org.opendaylight.controller.sal.packet.PacketResult; + +/** + * Interface to dispatch locally in the protocol plugin the + * data packets, intended especially for Discovery, main difference + * here with the analogous of SAL is that this is Global + * inherently. + */ +public interface IDataPacketListen { + /** + * Dispatch received data packet + * + * @param inPkt + * The incoming raw packet + * @return Possible results for Data packet processing handler + */ + public PacketResult receiveDataPacket(RawPacket inPkt); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketMux.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketMux.java new file mode 100644 index 0000000000..ad1f210cd9 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDataPacketMux.java @@ -0,0 +1,31 @@ + +/* + * 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; + +/** + * @file IDataPacketMux.java + * + * @brief Simple wrapped interface for the IPluginInDataPacketService + * which will be only exported by DataPacketServices mux/demux + * component and will be only accessible by the openflow protocol + * plugin + */ + +import org.opendaylight.controller.sal.packet.IPluginInDataPacketService; + +/** + * Simple wrapped interface for the IPluginInDataPacketService + * which will be only exported by DataPacketServices mux/demux + * component and will be only accessible by the openflow protocol + * plugin + */ +public interface IDataPacketMux extends IPluginInDataPacketService { + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDiscoveryListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDiscoveryListener.java new file mode 100644 index 0000000000..98cb6230e9 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IDiscoveryListener.java @@ -0,0 +1,19 @@ +/* + * 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; + +import org.opendaylight.controller.sal.discovery.IDiscoveryService; + +/** + * The interface provides method to notify the local plugin listener when an + * edge is discovered or removed. + */ +public interface IDiscoveryListener extends IDiscoveryService { + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IFlowProgrammerNotifier.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IFlowProgrammerNotifier.java new file mode 100644 index 0000000000..60e3e8bd2c --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IFlowProgrammerNotifier.java @@ -0,0 +1,22 @@ +/* + * 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; + +import org.opendaylight.controller.sal.flowprogrammer.IPluginOutFlowProgrammerService; + +/** + * Interface which defines the methods exposed by the Flow Programmer Notifier. + * Their implementation relays the asynchronous messages received from the + * network nodes to the the SAL Flow Programmer Notifier Service on the proper + * container. + */ +public interface IFlowProgrammerNotifier extends + IPluginOutFlowProgrammerService { + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryProvider.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryProvider.java new file mode 100644 index 0000000000..2ea55c92ee --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryProvider.java @@ -0,0 +1,18 @@ +/* + * 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; + +import org.opendaylight.controller.sal.inventory.IPluginInInventoryService; + +/** + * The Interface provides inventory service to the local plugin modules + */ +public interface IInventoryProvider extends IPluginInInventoryService { + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimExternalListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimExternalListener.java new file mode 100644 index 0000000000..06179581b0 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimExternalListener.java @@ -0,0 +1,18 @@ + +/* + * 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; + +/** + * Wrapper of Interface that provides inventory updates locally in the protocol + * plugin. + */ +public interface IInventoryShimExternalListener extends + IInventoryShimInternalListener { +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimInternalListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimInternalListener.java new file mode 100644 index 0000000000..bf1b44b333 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IInventoryShimInternalListener.java @@ -0,0 +1,47 @@ + +/* + * 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; + +import java.util.Set; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; + +/** + * The Interface provides inventory updates to inventory listeners within the + * protocol plugin + */ +public interface IInventoryShimInternalListener { + /** + * Updates node and its properties + * + * @param node {@link org.opendaylight.controller.sal.core.Node} being updated + * @param type {@link org.opendaylight.controller.sal.core.UpdateType} + * @param props set of {@link org.opendaylight.controller.sal.core.Property} such as + * {@link org.opendaylight.controller.sal.core.Description} and/or + * {@link org.opendaylight.controller.sal.core.Tier} etc. + */ + public void updateNode(Node node, UpdateType type, Set props); + + /** + * Updates node connector and its properties + * + * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector} being updated + * @param type {@link org.opendaylight.controller.sal.core.UpdateType} + * @param props set of {@link org.opendaylight.controller.sal.core.Property} such as + * {@link org.opendaylight.controller.sal.core.Description} and/or + * {@link org.opendaylight.controller.sal.core.State} etc. + */ + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props); + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsListener.java new file mode 100644 index 0000000000..bc8943023a --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsListener.java @@ -0,0 +1,20 @@ +package org.opendaylight.openflowplugin.openflow; + +import java.util.List; + +import org.openflow.protocol.statistics.OFStatistics; + +/** + * Interface defines the api which gets called when the information + * contained in the OF statistics reply message from a network is updated with + * new one. + */ +public interface IOFStatisticsListener { + public void descriptionStatisticsRefreshed(Long switchId, List description); + + public void flowStatisticsRefreshed(Long switchId, List flows); + + public void portStatisticsRefreshed(Long switchId, List ports); + + public void tableStatisticsRefreshed(Long switchId, List tables); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsManager.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsManager.java new file mode 100644 index 0000000000..7ed7c13260 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IOFStatisticsManager.java @@ -0,0 +1,118 @@ + +/* + * 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; + +import java.util.List; + +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFStatisticsType; + +/** + * Interface to expose the openflow statistics collected on the switches + */ +public interface IOFStatisticsManager { + /** + * Return all the statistics for all the flows present on the specified switch + * + * @param switchId the openflow datapath id + * @return the list of openflow statistics + */ + List getOFFlowStatistics(Long switchId); + + /** + * Return all the statistics for all the flows present on the specified switch + * + * @param switchId the openflow datapath id + * @param ofMatch the openflow match to query. If null, the query is intended for all the flows + * @param priority Priority of the wanted flow + * @return the list of openflow statistics + */ + List getOFFlowStatistics(Long switchId, OFMatch ofMatch, short priority); + + /** + * Return the description statistics for the specified switch. + * + * @param switchId the openflow datapath id + * @return the list of openflow statistics + */ + List getOFDescStatistics(Long switchId); + + /** + * Returns the statistics for all the ports on the specified switch + * + * @param switchId the openflow datapath id + * @return the list of openflow statistics + */ + List getOFPortStatistics(Long switchId); + + /** + * Returns the statistics for the specified switch port + * + * @param switchId the openflow datapath id + * @param portId the openflow switch port id + * @return the list of openflow statistics + */ + List getOFPortStatistics(Long switchId, short portId); + + /** + * Returns the number of flows installed on the switch + * + * @param switchId the openflow datapath id + * @return the number of flows installed on the switch + */ + int getFlowsNumber(long switchId); + + /** + * Send a statistics request message to the specified switch and returns + * the switch response. It blocks the caller until the response has arrived + * from the switch or the request has timed out + * + * @param switchId the openflow datapath id of the target switch + * @param statType the openflow statistics type + * @param target the target object. For flow statistics it is the OFMatch. + * For port statistics, it is the port id. If null the query + * will be performed for all the targets for the specified + * statistics type. + * + * @param timeout the timeout in milliseconds the system will wait for a response + * from the switch, before declaring failure + * @return the list of openflow statistics + */ + List queryStatistics(Long switchId, + OFStatisticsType statType, Object target); + + /** + * Returns the averaged transmit rate for the passed switch port + * + * @param switchId the openflow datapath id of the target switch + * @param portId the openflow switch port id + * @return the median transmit rate in bits per second + */ + long getTransmitRate(Long switchId, Short port); + + /** + * Returns the statistics for the specified switch table + * + * @param switchId the openflow datapath id + * @param tableId the openflow switch table id + * @return the list of openflow statistics + */ + List getOFTableStatistics(Long switchId, Byte tableId); + + /** + * Returns all the table statistics for the node specified + * + * @param switchId the openflow datapath id + * @return the list of openflow statistics + */ + List getOFTableStatistics(Long switchId); + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadFilterInternalListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadFilterInternalListener.java new file mode 100644 index 0000000000..ed1de91d69 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadFilterInternalListener.java @@ -0,0 +1,46 @@ +package org.opendaylight.openflowplugin.openflow; + +import java.util.List; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.reader.FlowOnNode; +import org.opendaylight.controller.sal.reader.NodeConnectorStatistics; +import org.opendaylight.controller.sal.reader.NodeDescription; +import org.opendaylight.controller.sal.reader.NodeTableStatistics; + +/** + * The Interface provides statistics updates to ReaderFilter listeners within + * the protocol plugin + */ +public interface IReadFilterInternalListener { + + /** + * Notifies the hardware view of all the flow installed on the specified network node + * @param node + * @return + */ + public void nodeFlowStatisticsUpdated(Node node, List flowStatsList); + + /** + * Notifies the hardware view of the specified network node connector + * @param node + * @return + */ + public void nodeConnectorStatisticsUpdated(Node node, List ncStatsList); + + /** + * Notifies all the table statistics for a node + * @param node + * @return + */ + public void nodeTableStatisticsUpdated(Node node, List tableStatsList); + + /** + * Notifies the hardware view of all the flow installed on the specified network node + * @param node + * @return + */ + public void nodeDescriptionStatisticsUpdated(Node node, NodeDescription nodeDescription); + + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadServiceFilter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadServiceFilter.java new file mode 100644 index 0000000000..b83ef14886 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IReadServiceFilter.java @@ -0,0 +1,110 @@ + +/* + * 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; + +import java.util.List; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.NodeTable; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.reader.FlowOnNode; +import org.opendaylight.controller.sal.reader.NodeConnectorStatistics; +import org.opendaylight.controller.sal.reader.NodeDescription; +import org.opendaylight.controller.sal.reader.NodeTableStatistics; + +/** + * Interface to serve the hardware information requests coming from SAL + * It is implemented by the respective OF1.0 plugin component + * + */ +public interface IReadServiceFilter { + /** + * Returns the hardware image for the specified flow + * on the specified network node for the passed container + * + * @param container + * @param node + * @param flow + * @param cached + * @return + */ + public FlowOnNode readFlow(String container, Node node, Flow flow, + boolean cached); + + /** + * Returns the hardware view of all the flow installed + * on the specified network node for the passed container + * + * @param container + * @param node + * @param cached + * @return + */ + public List readAllFlow(String container, Node node, + boolean cached); + + /** + * Returns the description of the network node as provided by the node itself + * + * @param node + * @param cached + * @return + */ + public NodeDescription readDescription(Node node, boolean cached); + + /** + * Returns the hardware view of the specified network node connector + * for the given container + * @param node + * @return + */ + public NodeConnectorStatistics readNodeConnector(String container, + NodeConnector nodeConnector, boolean cached); + + /** + * Returns the hardware info for all the node connectors on the + * specified network node for the given container + * + * @param node + * @return + */ + public List readAllNodeConnector(String container, + Node node, boolean cached); + + /** + * Returns the table statistics of the node as specified by the given container + * @param node + * @param cached + * @return + */ + public NodeTableStatistics readNodeTable(String container, + NodeTable nodeTable, boolean cached); + + /** + * Returns the table statistics of all the tables for the specified node + * + * @param node + * @return + */ + public List readAllNodeTable(String containerName, + Node node, boolean cached); + + /** + * Returns the average transmit rate for the specified node conenctor on + * the given container. If the node connector does not belong to the passed + * container a zero value is returned + * + * @param container + * @param nodeConnector + * @return tx rate [bps] + */ + public long getTransmitRate(String container, NodeConnector nodeConnector); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IRefreshInternalProvider.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IRefreshInternalProvider.java new file mode 100644 index 0000000000..42d61a6052 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/IRefreshInternalProvider.java @@ -0,0 +1,34 @@ + +/* + * 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; + +/** + * @file IRefreshInternalProvider.java + * + * @brief Topology refresh notifications requested by application + * to be fetched from the plugin + * + * For example, an application that has been started late, will want to + * be up to date with the latest topology. Hence, it requests for a + * topology refresh from the plugin. + */ + +/** + * Topology refresh requested by the application from the plugin + * + */ + +public interface IRefreshInternalProvider { + + /** + * @param containerName + */ + public void requestRefresh(String containerName); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/ITopologyServiceShimListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/ITopologyServiceShimListener.java new file mode 100644 index 0000000000..c0d803ce82 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/ITopologyServiceShimListener.java @@ -0,0 +1,47 @@ +/* + * 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; + +import java.util.List; +import java.util.Set; + +import org.opendaylight.controller.sal.core.Edge; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.topology.TopoEdgeUpdate; + +/** + * The Interface provides Edge updates to the topology listeners + */ +public interface ITopologyServiceShimListener { + /** + * Called to update on Edge in the topology graph + * + * @param topoedgeupdateList + * List of topoedgeupdates Each topoedgeupdate includes edge, its + * Properties ( BandWidth and/or Latency etc) and update type. + */ + public void edgeUpdate(List topoedgeupdateList); + + /** + * Called when an Edge utilization is above the safe threshold configured on + * the controller + * + * @param {@link org.opendaylight.controller.sal.core.Edge} + */ + public void edgeOverUtilized(Edge edge); + + /** + * Called when the Edge utilization is back to normal, below the safety + * threshold level configured on the controller + * + * @param {@link org.opendaylight.controller.sal.core.Edge} + */ + public void edgeUtilBackToNormal(Edge edge); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IController.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IController.java new file mode 100644 index 0000000000..31763b2a7f --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IController.java @@ -0,0 +1,62 @@ + +/* + * 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.core; + +import java.util.Map; + +import org.openflow.protocol.OFType; + +/** + * This interface defines an abstraction of the Open Flow Controller that allows applications to control and manage the Open Flow switches. + * + */ +public interface IController { + + /** + * Allows application to start receiving OF messages received from switches. + * @param type the type of OF message that applications want to receive + * @param listener: Object that implements the IMessageListener + */ + public void addMessageListener(OFType type, IMessageListener listener); + + /** + * Allows application to stop receiving OF message received from switches. + * @param type The type of OF message that applications want to stop receiving + * @param listener The object that implements the IMessageListener + */ + public void removeMessageListener(OFType type, IMessageListener listener); + + /** + * Allows application to start receiving switch state change events. + * @param listener The object that implements the ISwitchStateListener + */ + public void addSwitchStateListener(ISwitchStateListener listener); + + /** + * Allows application to stop receiving switch state change events. + * @param listener The object that implements the ISwitchStateListener + */ + public void removeSwitchStateListener(ISwitchStateListener listener); + + /** + * Returns a map containing all the OF switches that are currently connected to the Controller. + * @return Map of ISwitch + */ + public Map getSwitches(); + + /** + * Returns the ISwitch of the given switchId. + * + * @param switchId The switch ID + * @return ISwitch if present, null otherwise + */ + public ISwitch getSwitch(Long switchId); + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageListener.java new file mode 100644 index 0000000000..d561f4979a --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageListener.java @@ -0,0 +1,26 @@ + +/* + * 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.core; + +import org.openflow.protocol.OFMessage; + +/** + * Interface to be implemented by applications that want to receive OF messages. + * + */ +public interface IMessageListener { + /** + * This method is called by the Controller when a message is received from a switch. + * Application who is interested in receiving OF Messages needs to implement this method. + * @param sw The ISwitch which sent the OF message + * @param msg The OF message + */ + public void receive(ISwitch sw, OFMessage msg); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageReadWrite.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageReadWrite.java new file mode 100644 index 0000000000..611e95e74b --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/IMessageReadWrite.java @@ -0,0 +1,53 @@ + +/* + * 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.core; + +import java.util.List; + +import org.openflow.protocol.OFMessage; + +/** + * This interface defines low level routines to read/write messages on an open + * socket channel. If secure communication is desired, these methods also perform + * encryption and decryption of the network data. + */ +public interface IMessageReadWrite { + /** + * Sends the OF message out over the socket channel. For secure + * communication, the data will be encrypted. + * + * @param msg OF message to be sent + * @throws Exception + */ + public void asyncSend(OFMessage msg) throws Exception; + + /** + * Resumes sending the remaining messages in the outgoing buffer + * @throws Exception + */ + public void resumeSend() throws Exception; + + /** + * Reads the incoming network data from the socket and retrieves the OF + * messages. For secure communication, the data will be decrypted first. + * + * @return list of OF messages + * @throws Exception + */ + public List readMessages() throws Exception; + + /** + * Proper clean up when the switch connection is closed + * + * @return + * @throws Exception + */ + public void stop() throws Exception; +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitch.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitch.java new file mode 100644 index 0000000000..ac042e7160 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitch.java @@ -0,0 +1,231 @@ +/* + * 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.core; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPhysicalPort; +import org.openflow.protocol.OFStatisticsRequest; + +/** + * This interface defines an abstraction of an Open Flow Switch. + * + */ +public interface ISwitch { + /** + * Gets a unique XID. + * + * @return XID + */ + public int getNextXid(); + + /** + * Returns the Switch's ID. + * + * @return the Switch's ID + */ + public Long getId(); + + /** + * Returns the Switch's table numbers supported by datapath + * + * @return the tables + */ + public Byte getTables(); + + /** + * Returns the Switch's bitmap of supported ofp_action_type + * + * @return the actions + */ + public Integer getActions(); + + /** + * Returns the Switch's bitmap of supported ofp_capabilities + * + * @return the capabilities + */ + public Integer getCapabilities(); + + /** + * Returns the Switch's buffering capacity in Number of Pkts + * + * @return the buffers + */ + public Integer getBuffers(); + + /** + * Returns the Date when the switch was connected. + * + * @return Date The date when the switch was connected + */ + public Date getConnectedDate(); + + /** + * This method puts the message in an outgoing priority queue with normal + * priority. It will be served after high priority messages. The method + * should be used for non-critical messages such as statistics request, + * discovery packets, etc. An unique XID is generated automatically and + * inserted into the message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + public Integer asyncSend(OFMessage msg); + + /** + * This method puts the message in an outgoing priority queue with normal + * priority. It will be served after high priority messages. The method + * should be used for non-critical messages such as statistics request, + * discovery packets, etc. The specified XID is inserted into the message. + * + * @param msg + * The OF message to be Sent + * @param xid + * The XID to be used in the message + * @return The XID used + */ + public Integer asyncSend(OFMessage msg, int xid); + + /** + * This method puts the message in an outgoing priority queue with high + * priority. It will be served first before normal priority messages. The + * method should be used for critical messages such as hello, echo reply + * etc. An unique XID is generated automatically and inserted into the + * message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + public Integer asyncFastSend(OFMessage msg); + + /** + * This method puts the message in an outgoing priority queue with high + * priority. It will be served first before normal priority messages. The + * method should be used for critical messages such as hello, echo reply + * etc. The specified XID is inserted into the message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + public Integer asyncFastSend(OFMessage msg, int xid); + + /** + * Sends the OF message followed by a Barrier Request with a unique XID + * which is automatically generated, and waits for a result from the switch. + * + * @param msg + * The message to be sent + * @return An Object which has one of the followings instances/values: + * Boolean with value true to indicate the message has been + * successfully processed and acknowledged by the switch; Boolean + * with value false to indicate the message has failed to be + * processed by the switch within a period of time or OFError to + * indicate that the message has been denied by the switch which + * responded with OFError. + */ + public Object syncSend(OFMessage msg); + + /** + * Returns a map containing all OFPhysicalPorts of this switch. + * + * @return The Map of OFPhysicalPort + */ + public Map getPhysicalPorts(); + + /** + * Returns a Set containing all port IDs of this switch. + * + * @return The Set of port ID + */ + public Set getPorts(); + + /** + * Returns OFPhysicalPort of the specified portNumber of this switch. + * + * @param portNumber + * The port ID + * @return OFPhysicalPort for the specified PortNumber + */ + public OFPhysicalPort getPhysicalPort(Short portNumber); + + /** + * Returns the bandwidth of the specified portNumber of this switch. + * + * @param portNumber + * the port ID + * @return bandwidth + */ + public Integer getPortBandwidth(Short portNumber); + + /** + * Returns True if the port is enabled, + * + * @param portNumber + * @return True if the port is enabled + */ + public boolean isPortEnabled(short portNumber); + + /** + * Returns True if the port is enabled. + * + * @param port + * @return True if the port is enabled + */ + public boolean isPortEnabled(OFPhysicalPort port); + + /** + * Returns a list containing all enabled ports of this switch. + * + * @return: List containing all enabled ports of this switch + */ + public List getEnabledPorts(); + + /** + * Sends OFStatisticsRequest with a unique XID generated automatically and + * waits for a result from the switch. + * + * @param req + * the OF Statistic Request to be sent + * @return Object has one of the following instances/values:: + * List, a list of statistics records received from + * the switch as response from the request; OFError if the switch + * failed handle the request or NULL if timeout has occurred while + * waiting for the response. + */ + public Object getStatistics(OFStatisticsRequest req); + + /** + * Returns true if the switch has reached the operational state (has sent + * FEATURE_REPLY to the controller). + * + * @return true if the switch is operational + */ + public boolean isOperational(); + + /** + * Send Barrier message synchronously. The caller will be blocked until the + * Barrier reply arrives. + */ + Object syncSendBarrierMessage(); + + /** + * Send Barrier message asynchronously. The caller is not blocked. The + * Barrier message will be sent in a transmit thread which will be blocked + * until the Barrier reply arrives. + */ + Object asyncSendBarrierMessage(); +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitchStateListener.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitchStateListener.java new file mode 100644 index 0000000000..d82f0d73a9 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/ISwitchStateListener.java @@ -0,0 +1,31 @@ + +/* + * 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.core; + +/** + * Interface to be implemented by applications that want to receive switch state event changes. + * + */ +public interface ISwitchStateListener { + /** + * This method is invoked by Controller when a switch has been connected to the Controller. + * Application who wants to receive this event needs to implement this method. + * @param sw The switch that has just connected. + */ + public void switchAdded(ISwitch sw); + + /** + * This method is invoked by Controller when a switch has been disconnected from the Controller. + * Application who wants to receive this event needs to implement this method. + * @param sw The switch that has just disconnected. + */ + public void switchDeleted(ISwitch sw); + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/Controller.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/Controller.java new file mode 100644 index 0000000000..6ede781fc7 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/Controller.java @@ -0,0 +1,413 @@ +/* + * 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.core.internal; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.opendaylight.controller.sal.connection.ConnectionConstants; +import org.opendaylight.controller.sal.connection.IPluginInConnectionService; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.utils.Status; +import org.opendaylight.controller.sal.utils.StatusCode; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageListener; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.opendaylight.openflowplugin.openflow.core.ISwitchStateListener; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFType; +import org.openflow.util.HexString; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Controller implements IController, CommandProvider, IPluginInConnectionService { + private static final Logger logger = LoggerFactory + .getLogger(Controller.class); + private ControllerIO controllerIO; + private Thread switchEventThread; + private ConcurrentHashMap switches; + private BlockingQueue switchEvents; + // only 1 message listener per OFType + private ConcurrentMap messageListeners; + // only 1 switch state listener + private ISwitchStateListener switchStateListener; + private AtomicInteger switchInstanceNumber; + private int MAXQUEUESIZE = 50000; + + /* + * this thread monitors the switchEvents queue for new incoming events from + * switch + */ + private class EventHandler implements Runnable { + @Override + public void run() { + + while (true) { + try { + SwitchEvent ev = switchEvents.take(); + SwitchEvent.SwitchEventType eType = ev.getEventType(); + ISwitch sw = ev.getSwitch(); + switch (eType) { + case SWITCH_ADD: + Long sid = sw.getId(); + ISwitch existingSwitch = switches.get(sid); + if (existingSwitch != null) { + logger.info("Replacing existing {} with New {}", + existingSwitch, sw); + disconnectSwitch(existingSwitch); + } + switches.put(sid, sw); + notifySwitchAdded(sw); + break; + case SWITCH_DELETE: + disconnectSwitch(sw); + break; + case SWITCH_ERROR: + disconnectSwitch(sw); + break; + case SWITCH_MESSAGE: + OFMessage msg = ev.getMsg(); + if (msg != null) { + IMessageListener listener = messageListeners + .get(msg.getType()); + if (listener != null) { + listener.receive(sw, msg); + } + } + break; + default: + logger.error("Unknown switch event {}", eType.ordinal()); + } + } catch (InterruptedException e) { + switchEvents.clear(); + return; + } + } + } + + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + public void init() { + logger.debug("Initializing!"); + this.switches = new ConcurrentHashMap(); + this.switchEvents = new LinkedBlockingQueue(MAXQUEUESIZE); + this.messageListeners = new ConcurrentHashMap(); + this.switchStateListener = null; + this.switchInstanceNumber = new AtomicInteger(0); + registerWithOSGIConsole(); + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + public void start() { + logger.debug("Starting!"); + /* + * start a thread to handle event coming from the switch + */ + switchEventThread = new Thread(new EventHandler(), "SwitchEvent Thread"); + switchEventThread.start(); + + // spawn a thread to start to listen on the open flow port + controllerIO = new ControllerIO(this); + try { + controllerIO.start(); + } catch (IOException ex) { + logger.error("Caught exception while starting:", ex); + } + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + public void stop() { + for (Iterator> it = switches.entrySet().iterator(); it + .hasNext();) { + Entry entry = it.next(); + ((SwitchHandler) entry.getValue()).stop(); + it.remove(); + } + switchEventThread.interrupt(); + try { + controllerIO.shutDown(); + } catch (IOException ex) { + logger.error("Caught exception while stopping:", ex); + } + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + public void destroy() { + } + + @Override + public void addMessageListener(OFType type, IMessageListener listener) { + IMessageListener currentListener = this.messageListeners.get(type); + if (currentListener != null) { + logger.warn("{} is already listened by {}", type, + currentListener); + } + this.messageListeners.put(type, listener); + logger.debug("{} is now listened by {}", type, listener); + } + + @Override + public void removeMessageListener(OFType type, IMessageListener listener) { + IMessageListener currentListener = this.messageListeners.get(type); + if ((currentListener != null) && (currentListener == listener)) { + logger.debug("{} listener {} is Removed", type, listener); + this.messageListeners.remove(type); + } + } + + @Override + public void addSwitchStateListener(ISwitchStateListener listener) { + if (this.switchStateListener != null) { + logger.warn("Switch events are already listened by {}", + this.switchStateListener); + } + this.switchStateListener = listener; + logger.debug("Switch events are now listened by {}", listener); + } + + @Override + public void removeSwitchStateListener(ISwitchStateListener listener) { + if ((this.switchStateListener != null) + && (this.switchStateListener == listener)) { + logger.debug("SwitchStateListener {} is Removed", listener); + this.switchStateListener = null; + } + } + + public void handleNewConnection(Selector selector, + SelectionKey serverSelectionKey) { + ServerSocketChannel ssc = (ServerSocketChannel) serverSelectionKey + .channel(); + SocketChannel sc = null; + try { + sc = ssc.accept(); + // create new switch + int i = this.switchInstanceNumber.addAndGet(1); + String instanceName = "SwitchHandler-" + i; + SwitchHandler switchHandler = new SwitchHandler(this, sc, instanceName); + switchHandler.start(); + if (sc.isConnected()) { + logger.info("Switch:{} is connected to the Controller", + sc.socket().getRemoteSocketAddress() + .toString().split("/")[1]); + } + + } catch (IOException e) { + return; + } + } + + private void disconnectSwitch(ISwitch sw) { + if (((SwitchHandler) sw).isOperational()) { + Long sid = sw.getId(); + if (this.switches.remove(sid, sw)) { + logger.warn("{} is Disconnected", sw); + notifySwitchDeleted(sw); + } + } + ((SwitchHandler) sw).stop(); + sw = null; + } + + private void notifySwitchAdded(ISwitch sw) { + if (switchStateListener != null) { + switchStateListener.switchAdded(sw); + } + } + + private void notifySwitchDeleted(ISwitch sw) { + if (switchStateListener != null) { + switchStateListener.switchDeleted(sw); + } + } + + private synchronized void addSwitchEvent(SwitchEvent event) { + try { + this.switchEvents.put(event); + } catch (InterruptedException e) { + logger.debug("SwitchEvent caught Interrupt Exception"); + } + } + + public void takeSwitchEventAdd(ISwitch sw) { + SwitchEvent ev = new SwitchEvent( + SwitchEvent.SwitchEventType.SWITCH_ADD, sw, null); + addSwitchEvent(ev); + } + + public void takeSwitchEventDelete(ISwitch sw) { + SwitchEvent ev = new SwitchEvent( + SwitchEvent.SwitchEventType.SWITCH_DELETE, sw, null); + addSwitchEvent(ev); + } + + public void takeSwitchEventError(ISwitch sw) { + SwitchEvent ev = new SwitchEvent( + SwitchEvent.SwitchEventType.SWITCH_ERROR, sw, null); + addSwitchEvent(ev); + } + + public void takeSwitchEventMsg(ISwitch sw, OFMessage msg) { + if (messageListeners.get(msg.getType()) != null) { + SwitchEvent ev = new SwitchEvent( + SwitchEvent.SwitchEventType.SWITCH_MESSAGE, sw, msg); + addSwitchEvent(ev); + } + } + + @Override + public Map getSwitches() { + return this.switches; + } + + @Override + public ISwitch getSwitch(Long switchId) { + return this.switches.get(switchId); + } + + public void _controllerShowSwitches(CommandInterpreter ci) { + Set sids = switches.keySet(); + StringBuffer s = new StringBuffer(); + int size = sids.size(); + if (size == 0) { + ci.print("switches: empty"); + return; + } + Iterator iter = sids.iterator(); + s.append("Total: " + size + " switches\n"); + while (iter.hasNext()) { + Long sid = iter.next(); + Date date = switches.get(sid).getConnectedDate(); + String switchInstanceName = ((SwitchHandler) switches.get(sid)) + .getInstanceName(); + s.append(switchInstanceName + "/" + HexString.toHexString(sid) + + " connected since " + date.toString() + "\n"); + } + ci.print(s.toString()); + return; + } + + public void _controllerReset(CommandInterpreter ci) { + ci.print("...Disconnecting the communication to all switches...\n"); + stop(); + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + } finally { + ci.print("...start to accept connections from switches...\n"); + start(); + } + } + + public void _controllerShowConnConfig(CommandInterpreter ci) { + String str = System.getProperty("secureChannelEnabled"); + if ((str != null) && (str.trim().equalsIgnoreCase("true"))) { + ci.print("The Controller and Switch should communicate through TLS connetion.\n"); + + String keyStoreFile = System.getProperty("controllerKeyStore"); + String trustStoreFile = System.getProperty("controllerTrustStore"); + if ((keyStoreFile == null) || keyStoreFile.trim().isEmpty()) { + ci.print("controllerKeyStore not specified in ./configuration/config.ini\n"); + } else { + ci.print("controllerKeyStore=" + keyStoreFile + "\n"); + } + if ((trustStoreFile == null) || trustStoreFile.trim().isEmpty()) { + ci.print("controllerTrustStore not specified in ./configuration/config.ini\n"); + } else { + ci.print("controllerTrustStore=" + trustStoreFile + "\n"); + } + } else { + ci.print("The Controller and Switch should communicate through TCP connetion.\n"); + } + } + + private void registerWithOSGIConsole() { + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()) + .getBundleContext(); + bundleContext.registerService(CommandProvider.class.getName(), this, + null); + } + + @Override + public String getHelp() { + StringBuffer help = new StringBuffer(); + help.append("---Open Flow Controller---\n"); + help.append("\t controllerShowSwitches\n"); + help.append("\t controllerReset\n"); + help.append("\t controllerShowConnConfig\n"); + return help.toString(); + } + + @Override + public Status disconnect(Node node) { + ISwitch sw = getSwitch((Long) node.getID()); + if (sw != null) disconnectSwitch(sw); + return new Status(StatusCode.SUCCESS); + } + + @Override + public Node connect(String connectionIdentifier, Map params) { + return null; + } + + /** + * View Change notification + */ + public void notifyClusterViewChanged() { + for (ISwitch sw : switches.values()) { + notifySwitchAdded(sw); + } + } + + /** + * Node Disconnected from the node's master controller. + */ + @Override + public void notifyNodeDisconnectFromMaster(Node node) { + ISwitch sw = switches.get((Long)node.getID()); + if (sw != null) notifySwitchAdded(sw); + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/ControllerIO.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/ControllerIO.java new file mode 100644 index 0000000000..763e0cb651 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/ControllerIO.java @@ -0,0 +1,93 @@ +/* + * 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.core.internal; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Iterator; + +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ControllerIO { + private static final Logger logger = LoggerFactory + .getLogger(ControllerIO.class); + private static Short defaultOpenFlowPort = 6633; + private Short openFlowPort; + private SelectionKey serverSelectionKey; + private IController listener; + private ServerSocketChannel serverSocket; + private Selector selector; + private boolean running; + private Thread controllerIOThread; + + public ControllerIO(IController l) { + this.listener = l; + this.openFlowPort = defaultOpenFlowPort; + String portString = System.getProperty("of.listenPort"); + if (portString != null) { + try { + openFlowPort = Short.decode(portString).shortValue(); + } catch (NumberFormatException e) { + logger.warn("Invalid port:{}, use default({})", portString, + openFlowPort); + } + } + } + + public void start() throws IOException { + this.running = true; + // obtain a selector + this.selector = SelectorProvider.provider().openSelector(); + // create the listening socket + this.serverSocket = ServerSocketChannel.open(); + this.serverSocket.configureBlocking(false); + this.serverSocket.socket().bind( + new java.net.InetSocketAddress(openFlowPort)); + this.serverSocket.socket().setReuseAddress(true); + // register this socket for accepting incoming connections + this.serverSelectionKey = this.serverSocket.register(selector, + SelectionKey.OP_ACCEPT); + controllerIOThread = new Thread(new Runnable() { + @Override + public void run() { + while (running) { + try { + // wait for an incoming connection + selector.select(0); + Iterator selectedKeys = selector + .selectedKeys().iterator(); + while (selectedKeys.hasNext()) { + SelectionKey skey = selectedKeys.next(); + selectedKeys.remove(); + if (skey.isValid() && skey.isAcceptable()) { + ((Controller) listener).handleNewConnection( + selector, serverSelectionKey); + } + } + } catch (Exception e) { + continue; + } + } + } + }, "ControllerI/O Thread"); + controllerIOThread.start(); + logger.info("Controller is now listening on port {}", openFlowPort); + } + + public void shutDown() throws IOException { + this.running = false; + this.selector.wakeup(); + this.serverSocket.close(); + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/MessageReadWriteService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/MessageReadWriteService.java new file mode 100644 index 0000000000..5b63d456ee --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/MessageReadWriteService.java @@ -0,0 +1,151 @@ +/* + * 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.core.internal; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.opendaylight.openflowplugin.openflow.core.IMessageReadWrite; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.factory.BasicFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements methods to read/write messages over an established + * socket channel. The data exchange is in clear text format. + */ +public class MessageReadWriteService implements IMessageReadWrite { + private static final Logger logger = LoggerFactory + .getLogger(MessageReadWriteService.class); + private static final int bufferSize = 1024 * 1024; + + private Selector selector; + private SelectionKey clientSelectionKey; + private SocketChannel socket; + private ByteBuffer inBuffer; + private ByteBuffer outBuffer; + private BasicFactory factory; + + public MessageReadWriteService(SocketChannel socket, Selector selector) + throws ClosedChannelException { + this.socket = socket; + this.selector = selector; + this.factory = new BasicFactory(); + this.inBuffer = ByteBuffer.allocateDirect(bufferSize); + this.outBuffer = ByteBuffer.allocateDirect(bufferSize); + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ); + } + + /** + * Sends the OF message out over the socket channel. + * + * @param msg + * OF message to be sent + * @throws Exception + */ + @Override + public void asyncSend(OFMessage msg) throws IOException { + synchronized (outBuffer) { + int msgLen = msg.getLengthU(); + if (outBuffer.remaining() < msgLen) { + // increase the buffer size so that it can contain this message + ByteBuffer newBuffer = ByteBuffer.allocateDirect(outBuffer + .capacity() + msgLen); + outBuffer.flip(); + newBuffer.put(outBuffer); + outBuffer = newBuffer; + } + } + synchronized (outBuffer) { + msg.writeTo(outBuffer); + + if (!socket.isOpen()) { + return; + } + + outBuffer.flip(); + socket.write(outBuffer); + outBuffer.compact(); + if (outBuffer.position() > 0) { + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_WRITE, this); + } + logger.trace("Message sent: {}", msg); + } + } + + /** + * Resumes sending the remaining messages in the outgoing buffer + * + * @throws Exception + */ + @Override + public void resumeSend() throws IOException { + synchronized (outBuffer) { + if (!socket.isOpen()) { + return; + } + + outBuffer.flip(); + socket.write(outBuffer); + outBuffer.compact(); + if (outBuffer.position() > 0) { + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_WRITE, this); + } else { + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ, this); + } + } + } + + /** + * Reads the incoming network data from the socket and retrieves the OF + * messages. + * + * @return list of OF messages + * @throws Exception + */ + @Override + public List readMessages() throws IOException { + if (!socket.isOpen()) { + return null; + } + + List msgs = null; + int bytesRead = -1; + bytesRead = socket.read(inBuffer); + if (bytesRead < 0) { + throw new AsynchronousCloseException(); + } + + inBuffer.flip(); + msgs = factory.parseMessages(inBuffer); + if (inBuffer.hasRemaining()) { + inBuffer.compact(); + } else { + inBuffer.clear(); + } + return msgs; + } + + @Override + public void stop() { + inBuffer = null; + outBuffer = null; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/PriorityMessage.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/PriorityMessage.java new file mode 100644 index 0000000000..47629b5e81 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/PriorityMessage.java @@ -0,0 +1,92 @@ +/* + * 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.core.internal; + +import java.util.concurrent.atomic.AtomicLong; + +import org.openflow.protocol.OFMessage; + +/** + * This class describes an OpenFlow message with priority + */ +class PriorityMessage { + OFMessage msg; + int priority; + final static AtomicLong seq = new AtomicLong(); + final long seqNum; + boolean syncReply; // set to true if we want to be blocked until the response arrives + + public PriorityMessage(OFMessage msg, int priority) { + this.msg = msg; + this.priority = priority; + this.seqNum = seq.getAndIncrement(); + this.syncReply = false; + } + + public PriorityMessage(OFMessage msg, int priority, boolean syncReply) { + this(msg, priority); + this.syncReply = syncReply; + } + + public OFMessage getMsg() { + return msg; + } + + public void setMsg(OFMessage msg) { + this.msg = msg; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((msg == null) ? 0 : msg.hashCode()); + result = prime * result + priority; + result = prime * result + (int) (seqNum ^ (seqNum >>> 32)); + result = prime * result + (syncReply ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PriorityMessage other = (PriorityMessage) obj; + if (msg == null) { + if (other.msg != null) + return false; + } else if (!msg.equals(other.msg)) + return false; + if (priority != other.priority) + return false; + if (seqNum != other.seqNum) + return false; + if (syncReply != other.syncReply) + return false; + return true; + } + + @Override + public String toString() { + return "PriorityMessage [msg=" + msg + ", priority=" + priority + + ", seqNum=" + seqNum + ", syncReply=" + syncReply + "]"; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SecureMessageReadWriteService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SecureMessageReadWriteService.java new file mode 100644 index 0000000000..cc44694151 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SecureMessageReadWriteService.java @@ -0,0 +1,420 @@ +/* + * 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.core.internal; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + +import org.opendaylight.openflowplugin.openflow.core.IMessageReadWrite; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.factory.BasicFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements methods to read/write messages over an established + * socket channel. The data exchange is encrypted/decrypted by SSLEngine. + */ +public class SecureMessageReadWriteService implements IMessageReadWrite { + private static final Logger logger = LoggerFactory + .getLogger(SecureMessageReadWriteService.class); + + private Selector selector; + private SelectionKey clientSelectionKey; + private SocketChannel socket; + private BasicFactory factory; + + private SSLEngine sslEngine; + private SSLEngineResult sslEngineResult; // results from sslEngine last operation + private ByteBuffer myAppData; // clear text message to be sent + private ByteBuffer myNetData; // encrypted message to be sent + private ByteBuffer peerAppData; // clear text message received from the + // switch + private ByteBuffer peerNetData; // encrypted message from the switch + private FileInputStream kfd = null, tfd = null; + + public SecureMessageReadWriteService(SocketChannel socket, Selector selector) + throws Exception { + this.socket = socket; + this.selector = selector; + this.factory = new BasicFactory(); + + try { + createSecureChannel(socket); + createBuffers(sslEngine); + } catch (Exception e) { + stop(); + throw e; + } + } + + /** + * Bring up secure channel using SSL Engine + * + * @param socket + * TCP socket channel + * @throws Exception + */ + private void createSecureChannel(SocketChannel socket) throws Exception { + String keyStoreFile = System.getProperty("controllerKeyStore"); + String keyStorePassword = System + .getProperty("controllerKeyStorePassword"); + String trustStoreFile = System.getProperty("controllerTrustStore"); + String trustStorePassword = System + .getProperty("controllerTrustStorePassword"); + + if (keyStoreFile != null) { + keyStoreFile = keyStoreFile.trim(); + } + if ((keyStoreFile == null) || keyStoreFile.isEmpty()) { + throw new FileNotFoundException( + "controllerKeyStore not specified in ./configuration/config.ini"); + } + if (keyStorePassword != null) { + keyStorePassword = keyStorePassword.trim(); + } + if ((keyStorePassword == null) || keyStorePassword.isEmpty()) { + throw new FileNotFoundException( + "controllerKeyStorePassword not specified in ./configuration/config.ini"); + } + if (trustStoreFile != null) { + trustStoreFile = trustStoreFile.trim(); + } + if ((trustStoreFile == null) || trustStoreFile.isEmpty()) { + throw new FileNotFoundException( + "controllerTrustStore not specified in ./configuration/config.ini"); + } + if (trustStorePassword != null) { + trustStorePassword = trustStorePassword.trim(); + } + if ((trustStorePassword == null) || trustStorePassword.isEmpty()) { + throw new FileNotFoundException( + "controllerTrustStorePassword not specified in ./configuration/config.ini"); + } + + KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ts = KeyStore.getInstance("JKS"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + kfd = new FileInputStream(keyStoreFile); + tfd = new FileInputStream(trustStoreFile); + ks.load(kfd, keyStorePassword.toCharArray()); + ts.load(tfd, trustStorePassword.toCharArray()); + kmf.init(ks, keyStorePassword.toCharArray()); + tmf.init(ts); + + SecureRandom random = new SecureRandom(); + random.nextInt(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random); + sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(false); + sslEngine.setNeedClientAuth(true); + + // Do initial handshake + doHandshake(socket, sslEngine); + + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ); + } + + /** + * Sends the OF message out over the socket channel. The message is + * encrypted by SSL Engine. + * + * @param msg + * OF message to be sent + * @throws Exception + */ + @Override + public void asyncSend(OFMessage msg) throws Exception { + synchronized (myAppData) { + int msgLen = msg.getLengthU(); + if (myAppData.remaining() < msgLen) { + // increase the buffer size so that it can contain this message + ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData + .capacity() + msgLen); + myAppData.flip(); + newBuffer.put(myAppData); + myAppData = newBuffer; + } + } + synchronized (myAppData) { + msg.writeTo(myAppData); + myAppData.flip(); + sslEngineResult = sslEngine.wrap(myAppData, myNetData); + logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult); + runDelegatedTasks(sslEngineResult, sslEngine); + + if (!socket.isOpen()) { + return; + } + + myNetData.flip(); + socket.write(myNetData); + if (myNetData.hasRemaining()) { + myNetData.compact(); + } else { + myNetData.clear(); + } + + if (myAppData.hasRemaining()) { + myAppData.compact(); + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_WRITE, this); + } else { + myAppData.clear(); + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ, this); + } + + logger.trace("Message sent: {}", msg); + } + } + + /** + * Resumes sending the remaining messages in the outgoing buffer + * + * @throws Exception + */ + @Override + public void resumeSend() throws Exception { + synchronized (myAppData) { + myAppData.flip(); + sslEngineResult = sslEngine.wrap(myAppData, myNetData); + logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult); + runDelegatedTasks(sslEngineResult, sslEngine); + + if (!socket.isOpen()) { + return; + } + + myNetData.flip(); + socket.write(myNetData); + if (myNetData.hasRemaining()) { + myNetData.compact(); + } else { + myNetData.clear(); + } + + if (myAppData.hasRemaining()) { + myAppData.compact(); + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_WRITE, this); + } else { + myAppData.clear(); + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ, this); + } + } + } + + /** + * Reads the incoming network data from the socket, decryptes them and then + * retrieves the OF messages. + * + * @return list of OF messages + * @throws Exception + */ + @Override + public List readMessages() throws Exception { + if (!socket.isOpen()) { + return null; + } + + List msgs = null; + int bytesRead = -1; + int countDown = 50; + + bytesRead = socket.read(peerNetData); + if (bytesRead < 0) { + logger.debug("Message read operation failed"); + throw new AsynchronousCloseException(); + } + + do { + peerNetData.flip(); + sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData); + if (peerNetData.hasRemaining()) { + peerNetData.compact(); + } else { + peerNetData.clear(); + } + logger.trace("sslEngine unwrap result: {}", sslEngineResult); + runDelegatedTasks(sslEngineResult, sslEngine); + } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK) + && peerNetData.hasRemaining() && (--countDown > 0)); + + if (countDown == 0) { + logger.trace("countDown reaches 0. peerNetData pos {} lim {}", + peerNetData.position(), peerNetData.limit()); + } + + peerAppData.flip(); + msgs = factory.parseMessages(peerAppData); + if (peerAppData.hasRemaining()) { + peerAppData.compact(); + } else { + peerAppData.clear(); + } + + this.clientSelectionKey = this.socket.register(this.selector, + SelectionKey.OP_READ, this); + + return msgs; + } + + /** + * If the result indicates that we have outstanding tasks to do, go ahead + * and run them in this thread. + */ + private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) + throws Exception { + + if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + logger.debug("\trunning delegated task..."); + runnable.run(); + } + HandshakeStatus hsStatus = engine.getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_TASK) { + throw new Exception("handshake shouldn't need additional tasks"); + } + logger.debug("\tnew HandshakeStatus: {}", hsStatus); + } + } + + private void doHandshake(SocketChannel socket, SSLEngine engine) + throws Exception { + SSLSession session = engine.getSession(); + ByteBuffer myAppData = ByteBuffer.allocate(session + .getApplicationBufferSize()); + ByteBuffer peerAppData = ByteBuffer.allocate(session + .getApplicationBufferSize()); + ByteBuffer myNetData = ByteBuffer.allocate(session + .getPacketBufferSize()); + ByteBuffer peerNetData = ByteBuffer.allocate(session + .getPacketBufferSize()); + + // Begin handshake + engine.beginHandshake(); + SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus(); + + // Process handshaking message + while (hs != SSLEngineResult.HandshakeStatus.FINISHED + && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + switch (hs) { + case NEED_UNWRAP: + // Receive handshaking data from peer + if (socket.read(peerNetData) < 0) { + throw new AsynchronousCloseException(); + } + + // Process incoming handshaking data + peerNetData.flip(); + SSLEngineResult res = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + hs = res.getHandshakeStatus(); + + // Check status + switch (res.getStatus()) { + case OK: + // Handle OK status + break; + } + break; + + case NEED_WRAP: + // Empty the local network packet buffer. + myNetData.clear(); + + // Generate handshaking data + res = engine.wrap(myAppData, myNetData); + hs = res.getHandshakeStatus(); + + // Check status + switch (res.getStatus()) { + case OK: + myNetData.flip(); + + // Send the handshaking data to peer + while (myNetData.hasRemaining()) { + if (socket.write(myNetData) < 0) { + throw new AsynchronousCloseException(); + } + } + break; + } + break; + + case NEED_TASK: + // Handle blocking tasks + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + logger.debug("\trunning delegated task..."); + runnable.run(); + } + hs = engine.getHandshakeStatus(); + if (hs == HandshakeStatus.NEED_TASK) { + throw new Exception( + "handshake shouldn't need additional tasks"); + } + logger.debug("\tnew HandshakeStatus: {}", hs); + break; + } + } + } + + private void createBuffers(SSLEngine engine) { + SSLSession session = engine.getSession(); + this.myAppData = ByteBuffer + .allocate(session.getApplicationBufferSize()); + this.peerAppData = ByteBuffer.allocate(session + .getApplicationBufferSize()); + this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize()); + this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize()); + } + + @Override + public void stop() throws IOException { + this.sslEngine = null; + this.sslEngineResult = null; + this.myAppData = null; + this.myNetData = null; + this.peerAppData = null; + this.peerNetData = null; + + if (this.kfd != null) { + this.kfd.close(); + this.kfd = null; + } + if (this.tfd != null) { + this.tfd.close(); + this.tfd = null; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/StatisticsCollector.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/StatisticsCollector.java new file mode 100644 index 0000000000..60085b4705 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/StatisticsCollector.java @@ -0,0 +1,81 @@ + +/* + * 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.core.internal; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; + +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFStatisticsReply; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.statistics.OFStatistics; + +public class StatisticsCollector implements Callable { + + private ISwitch sw; + private Integer xid; + private OFStatisticsRequest request; + private CountDownLatch latch; + private Object result; + private List stats; + + public StatisticsCollector(ISwitch sw, int xid, OFStatisticsRequest request) { + this.sw = sw; + this.xid = xid; + this.request = request; + latch = new CountDownLatch(1); + result = new Object(); + stats = new CopyOnWriteArrayList(); + } + + /* + * accumulate the stats records in result + * Returns: true: if this is the last record + * false: more to come + */ + public boolean collect(OFStatisticsReply reply) { + synchronized (result) { + stats.addAll(reply.getStatistics()); + if ((reply.getFlags() & 0x01) == 0) { + // all stats are collected, done + result = stats; + return true; + } else { + // still waiting for more to come + return false; + } + } + } + + @Override + public Object call() throws Exception { + sw.asyncSend(request, xid); + // free up the request as it is no longer needed + request = null; + // wait until all stats replies are received or timeout + latch.await(); + return result; + } + + public Integer getXid() { + return this.xid; + } + + public void wakeup() { + this.latch.countDown(); + } + + public void wakeup(OFError errorMsg) { + + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchEvent.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchEvent.java new file mode 100644 index 0000000000..c7f037f8d9 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchEvent.java @@ -0,0 +1,64 @@ + +/* + * 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.core.internal; + +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFMessage; + +public class SwitchEvent { + + public static enum SwitchEventType { + SWITCH_ADD, SWITCH_DELETE, SWITCH_ERROR, SWITCH_MESSAGE, + } + + private SwitchEventType eventType; + private ISwitch sw; + private OFMessage msg; + + public SwitchEvent(SwitchEventType type, ISwitch sw, OFMessage msg) { + this.eventType = type; + this.sw = sw; + this.msg = msg; + } + + public SwitchEventType getEventType() { + return this.eventType; + } + + public ISwitch getSwitch() { + return this.sw; + } + + public OFMessage getMsg() { + return this.msg; + } + + @Override + public String toString() { + String s; + switch (this.eventType) { + case SWITCH_ADD: + s = "SWITCH_ADD"; + break; + case SWITCH_DELETE: + s = "SWITCH_DELETE"; + break; + case SWITCH_ERROR: + s = "SWITCH_ERROR"; + break; + case SWITCH_MESSAGE: + s = "SWITCH_MESSAGE"; + break; + default: + s = "?" + this.eventType.ordinal() + "?"; + } + return "Switch Event: " + s; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchHandler.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchHandler.java new file mode 100644 index 0000000000..c5879cab28 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SwitchHandler.java @@ -0,0 +1,943 @@ +/* + * 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.core.internal; + +import java.io.IOException; +import java.net.SocketException; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageReadWrite; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFBarrierReply; +import org.openflow.protocol.OFBarrierRequest; +import org.openflow.protocol.OFEchoReply; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFFeaturesReply; +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFGetConfigReply; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPhysicalPort; +import org.openflow.protocol.OFPhysicalPort.OFPortConfig; +import org.openflow.protocol.OFPhysicalPort.OFPortFeatures; +import org.openflow.protocol.OFPhysicalPort.OFPortState; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFPortStatus; +import org.openflow.protocol.OFPortStatus.OFPortReason; +import org.openflow.protocol.OFSetConfig; +import org.openflow.protocol.OFStatisticsReply; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.OFType; +import org.openflow.protocol.factory.BasicFactory; +import org.openflow.util.HexString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SwitchHandler implements ISwitch { + private static final Logger logger = LoggerFactory + .getLogger(SwitchHandler.class); + private static final int SWITCH_LIVENESS_TIMER = 5000; + private static final int switchLivenessTimeout = getSwitchLivenessTimeout(); + private final int MESSAGE_RESPONSE_TIMER = 2000; + + private final String instanceName; + private final ISwitch thisISwitch; + private final IController core; + private Long sid; + private Integer buffers; + private Integer capabilities; + private Byte tables; + private Integer actions; + private Selector selector; + private final SocketChannel socket; + private final BasicFactory factory; + private final AtomicInteger xid; + private SwitchState state; + private Timer periodicTimer; + private final Map physicalPorts; + private final Map portBandwidth; + private final Date connectedDate; + private Long lastMsgReceivedTimeStamp; + private Boolean probeSent; + private final ExecutorService executor; + private final ConcurrentHashMap> messageWaitingDone; + private boolean running; + private IMessageReadWrite msgReadWriteService; + private Thread switchHandlerThread; + private Integer responseTimerValue; + private PriorityBlockingQueue transmitQ; + private Thread transmitThread; + + private enum SwitchState { + NON_OPERATIONAL(0), WAIT_FEATURES_REPLY(1), WAIT_CONFIG_REPLY(2), OPERATIONAL( + 3); + + private int value; + + private SwitchState(int value) { + this.value = value; + } + + @SuppressWarnings("unused") + public int value() { + return this.value; + } + } + + public SwitchHandler(Controller core, SocketChannel sc, String name) { + this.instanceName = name; + this.thisISwitch = this; + this.sid = (long) 0; + this.buffers = (int) 0; + this.capabilities = (int) 0; + this.tables = (byte) 0; + this.actions = (int) 0; + this.core = core; + this.socket = sc; + this.factory = new BasicFactory(); + this.connectedDate = new Date(); + this.lastMsgReceivedTimeStamp = connectedDate.getTime(); + this.physicalPorts = new HashMap(); + this.portBandwidth = new HashMap(); + this.state = SwitchState.NON_OPERATIONAL; + this.probeSent = false; + this.xid = new AtomicInteger(this.socket.hashCode()); + this.periodicTimer = null; + this.executor = Executors.newFixedThreadPool(4); + this.messageWaitingDone = new ConcurrentHashMap>(); + this.responseTimerValue = MESSAGE_RESPONSE_TIMER; + String rTimer = System.getProperty("of.messageResponseTimer"); + if (rTimer != null) { + try { + responseTimerValue = Integer.decode(rTimer); + } catch (NumberFormatException e) { + logger.warn( + "Invalid of.messageResponseTimer: {} use default({})", + rTimer, MESSAGE_RESPONSE_TIMER); + } + } + } + + public void start() { + try { + startTransmitThread(); + setupCommChannel(); + sendFirstHello(); + startHandlerThread(); + } catch (Exception e) { + reportError(e); + } + } + + private void startHandlerThread() { + switchHandlerThread = new Thread(new Runnable() { + @Override + public void run() { + running = true; + while (running) { + try { + // wait for an incoming connection + selector.select(0); + Iterator selectedKeys = selector + .selectedKeys().iterator(); + while (selectedKeys.hasNext()) { + SelectionKey skey = selectedKeys.next(); + selectedKeys.remove(); + if (skey.isValid() && skey.isWritable()) { + resumeSend(); + } + if (skey.isValid() && skey.isReadable()) { + handleMessages(); + } + } + } catch (Exception e) { + reportError(e); + } + } + } + }, instanceName); + switchHandlerThread.start(); + } + + public void stop() { + running = false; + cancelSwitchTimer(); + try { + selector.wakeup(); + selector.close(); + } catch (Exception e) { + } + try { + socket.close(); + } catch (Exception e) { + } + try { + msgReadWriteService.stop(); + } catch (Exception e) { + } + executor.shutdown(); + + msgReadWriteService = null; + + if (switchHandlerThread != null) { + switchHandlerThread.interrupt(); + } + if (transmitThread != null) { + transmitThread.interrupt(); + } + } + + @Override + public int getNextXid() { + return this.xid.incrementAndGet(); + } + + /** + * This method puts the message in an outgoing priority queue with normal + * priority. It will be served after high priority messages. The method + * should be used for non-critical messages such as statistics request, + * discovery packets, etc. An unique XID is generated automatically and + * inserted into the message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + @Override + public Integer asyncSend(OFMessage msg) { + return asyncSend(msg, getNextXid()); + } + + private Object syncSend(OFMessage msg, int xid) { + return syncMessageInternal(msg, xid, true); + } + + /** + * This method puts the message in an outgoing priority queue with normal + * priority. It will be served after high priority messages. The method + * should be used for non-critical messages such as statistics request, + * discovery packets, etc. The specified XID is inserted into the message. + * + * @param msg + * The OF message to be Sent + * @param xid + * The XID to be used in the message + * @return The XID used + */ + @Override + public Integer asyncSend(OFMessage msg, int xid) { + msg.setXid(xid); + if (transmitQ != null) { + transmitQ.add(new PriorityMessage(msg, 0)); + } + return xid; + } + + /** + * This method puts the message in an outgoing priority queue with high + * priority. It will be served first before normal priority messages. The + * method should be used for critical messages such as hello, echo reply + * etc. An unique XID is generated automatically and inserted into the + * message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + @Override + public Integer asyncFastSend(OFMessage msg) { + return asyncFastSend(msg, getNextXid()); + } + + /** + * This method puts the message in an outgoing priority queue with high + * priority. It will be served first before normal priority messages. The + * method should be used for critical messages such as hello, echo reply + * etc. The specified XID is inserted into the message. + * + * @param msg + * The OF message to be sent + * @return The XID used + */ + @Override + public Integer asyncFastSend(OFMessage msg, int xid) { + msg.setXid(xid); + if (transmitQ != null) { + transmitQ.add(new PriorityMessage(msg, 1)); + } + return xid; + } + + public void resumeSend() { + try { + if (msgReadWriteService != null) { + msgReadWriteService.resumeSend(); + } + } catch (Exception e) { + reportError(e); + } + } + + /** + * This method bypasses the transmit queue and sends the message over the + * socket directly. If the input xid is not null, the specified xid is + * inserted into the message. Otherwise, an unique xid is generated + * automatically and inserted into the message. + * + * @param msg + * Message to be sent + * @param xid + * Message xid + */ + private void asyncSendNow(OFMessage msg, Integer xid) { + if (xid == null) { + xid = getNextXid(); + } + msg.setXid(xid); + + asyncSendNow(msg); + } + + /** + * This method bypasses the transmit queue and sends the message over the + * socket directly. + * + * @param msg + * Message to be sent + */ + private void asyncSendNow(OFMessage msg) { + if (msgReadWriteService == null) { + logger.warn( + "asyncSendNow: {} is not sent because Message ReadWrite Service is not available.", + msg); + return; + } + + try { + msgReadWriteService.asyncSend(msg); + } catch (Exception e) { + reportError(e); + } + } + + public void handleMessages() { + List msgs = null; + + try { + if (msgReadWriteService != null) { + msgs = msgReadWriteService.readMessages(); + } + } catch (Exception e) { + reportError(e); + } + + if (msgs == null) { + logger.debug("{} is down", this); + // the connection is down, inform core + reportSwitchStateChange(false); + return; + } + for (OFMessage msg : msgs) { + logger.trace("Message received: {}", msg); + this.lastMsgReceivedTimeStamp = System.currentTimeMillis(); + OFType type = msg.getType(); + switch (type) { + case HELLO: + // send feature request + OFMessage featureRequest = factory + .getMessage(OFType.FEATURES_REQUEST); + asyncFastSend(featureRequest); + // delete all pre-existing flows + OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + OFFlowMod flowMod = (OFFlowMod) factory + .getMessage(OFType.FLOW_MOD); + flowMod.setMatch(match).setCommand(OFFlowMod.OFPFC_DELETE) + .setOutPort(OFPort.OFPP_NONE) + .setLength((short) OFFlowMod.MINIMUM_LENGTH); + asyncFastSend(flowMod); + this.state = SwitchState.WAIT_FEATURES_REPLY; + startSwitchTimer(); + break; + case ECHO_REQUEST: + OFEchoReply echoReply = (OFEchoReply) factory + .getMessage(OFType.ECHO_REPLY); + // respond immediately + asyncSendNow(echoReply, msg.getXid()); + break; + case ECHO_REPLY: + this.probeSent = false; + break; + case FEATURES_REPLY: + processFeaturesReply((OFFeaturesReply) msg); + break; + case GET_CONFIG_REPLY: + // make sure that the switch can send the whole packet to the + // controller + if (((OFGetConfigReply) msg).getMissSendLength() == (short) 0xffff) { + this.state = SwitchState.OPERATIONAL; + } + break; + case BARRIER_REPLY: + processBarrierReply((OFBarrierReply) msg); + break; + case ERROR: + processErrorReply((OFError) msg); + break; + case PORT_STATUS: + processPortStatusMsg((OFPortStatus) msg); + break; + case STATS_REPLY: + processStatsReply((OFStatisticsReply) msg); + break; + case PACKET_IN: + break; + default: + break; + } // end of switch + if (isOperational()) { + ((Controller) core).takeSwitchEventMsg(thisISwitch, msg); + } + } // end of for + } + + private void processPortStatusMsg(OFPortStatus msg) { + OFPhysicalPort port = msg.getDesc(); + if (msg.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) { + updatePhysicalPort(port); + } else if (msg.getReason() == (byte) OFPortReason.OFPPR_ADD.ordinal()) { + updatePhysicalPort(port); + } else if (msg.getReason() == (byte) OFPortReason.OFPPR_DELETE + .ordinal()) { + deletePhysicalPort(port); + } + + } + + private void startSwitchTimer() { + this.periodicTimer = new Timer(); + this.periodicTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + Long now = System.currentTimeMillis(); + if ((now - lastMsgReceivedTimeStamp) > switchLivenessTimeout) { + if (probeSent) { + // switch failed to respond to our probe, consider + // it down + logger.warn("{} is idle for too long, disconnect", + toString()); + reportSwitchStateChange(false); + } else { + // send a probe to see if the switch is still alive + logger.debug( + "Send idle probe (Echo Request) to {}", + this); + probeSent = true; + OFMessage echo = factory + .getMessage(OFType.ECHO_REQUEST); + asyncFastSend(echo); + } + } else { + if (state == SwitchState.WAIT_FEATURES_REPLY) { + // send another features request + OFMessage request = factory + .getMessage(OFType.FEATURES_REQUEST); + asyncFastSend(request); + } else { + if (state == SwitchState.WAIT_CONFIG_REPLY) { + // send another config request + OFSetConfig config = (OFSetConfig) factory + .getMessage(OFType.SET_CONFIG); + config.setMissSendLength((short) 0xffff) + .setLengthU(OFSetConfig.MINIMUM_LENGTH); + asyncFastSend(config); + OFMessage getConfig = factory + .getMessage(OFType.GET_CONFIG_REQUEST); + asyncFastSend(getConfig); + } + } + } + } catch (Exception e) { + reportError(e); + } + } + }, SWITCH_LIVENESS_TIMER, SWITCH_LIVENESS_TIMER); + } + + private void cancelSwitchTimer() { + if (this.periodicTimer != null) { + this.periodicTimer.cancel(); + } + } + + private void reportError(Exception e) { + if (e instanceof AsynchronousCloseException + || e instanceof InterruptedException + || e instanceof SocketException || e instanceof IOException + || e instanceof ClosedSelectorException) { + if (logger.isDebugEnabled()) { + logger.debug("Caught exception {}", e.getMessage()); + } + } else { + logger.warn("Caught exception ", e); + } + // notify core of this error event and disconnect the switch + ((Controller) core).takeSwitchEventError(this); + } + + private void reportSwitchStateChange(boolean added) { + if (added) { + ((Controller) core).takeSwitchEventAdd(this); + } else { + ((Controller) core).takeSwitchEventDelete(this); + } + } + + @Override + public Long getId() { + return this.sid; + } + + private void processFeaturesReply(OFFeaturesReply reply) { + if (this.state == SwitchState.WAIT_FEATURES_REPLY) { + this.sid = reply.getDatapathId(); + this.buffers = reply.getBuffers(); + this.capabilities = reply.getCapabilities(); + this.tables = reply.getTables(); + this.actions = reply.getActions(); + // notify core of this error event + for (OFPhysicalPort port : reply.getPorts()) { + updatePhysicalPort(port); + } + // config the switch to send full data packet + OFSetConfig config = (OFSetConfig) factory + .getMessage(OFType.SET_CONFIG); + config.setMissSendLength((short) 0xffff).setLengthU( + OFSetConfig.MINIMUM_LENGTH); + asyncFastSend(config); + // send config request to make sure the switch can handle the set + // config + OFMessage getConfig = factory.getMessage(OFType.GET_CONFIG_REQUEST); + asyncFastSend(getConfig); + this.state = SwitchState.WAIT_CONFIG_REPLY; + // inform core that a new switch is now operational + reportSwitchStateChange(true); + } + } + + private void updatePhysicalPort(OFPhysicalPort port) { + Short portNumber = port.getPortNumber(); + physicalPorts.put(portNumber, port); + portBandwidth + .put(portNumber, + port.getCurrentFeatures() + & (OFPortFeatures.OFPPF_10MB_FD.getValue() + | OFPortFeatures.OFPPF_10MB_HD + .getValue() + | OFPortFeatures.OFPPF_100MB_FD + .getValue() + | OFPortFeatures.OFPPF_100MB_HD + .getValue() + | OFPortFeatures.OFPPF_1GB_FD + .getValue() + | OFPortFeatures.OFPPF_1GB_HD + .getValue() | OFPortFeatures.OFPPF_10GB_FD + .getValue())); + } + + private void deletePhysicalPort(OFPhysicalPort port) { + Short portNumber = port.getPortNumber(); + physicalPorts.remove(portNumber); + portBandwidth.remove(portNumber); + } + + @Override + public boolean isOperational() { + return ((this.state == SwitchState.WAIT_CONFIG_REPLY) || (this.state == SwitchState.OPERATIONAL)); + } + + @Override + public String toString() { + try { + return ("Switch:" + + socket.socket().getRemoteSocketAddress().toString().split("/")[1] + + " SWID:" + (isOperational() ? HexString + .toHexString(this.sid) : "unknown")); + } catch (Exception e) { + return (isOperational() ? HexString.toHexString(this.sid) + : "unknown"); + } + + } + + @Override + public Date getConnectedDate() { + return this.connectedDate; + } + + public String getInstanceName() { + return instanceName; + } + + @Override + public Object getStatistics(OFStatisticsRequest req) { + int xid = getNextXid(); + StatisticsCollector worker = new StatisticsCollector(this, xid, req); + messageWaitingDone.put(xid, worker); + Future submit = executor.submit(worker); + Object result = null; + try { + result = submit.get(responseTimerValue, TimeUnit.MILLISECONDS); + return result; + } catch (Exception e) { + logger.warn("Timeout while waiting for {} replies", req.getType()); + result = null; // to indicate timeout has occurred + return result; + } + } + + @Override + public Object syncSend(OFMessage msg) { + int xid = getNextXid(); + return syncSend(msg, xid); + } + + /* + * Either a BarrierReply or a OFError is received. If this is a reply for an + * outstanding sync message, wake up associated task so that it can continue + */ + private void processBarrierReply(OFBarrierReply msg) { + Integer xid = msg.getXid(); + SynchronousMessage worker = (SynchronousMessage) messageWaitingDone + .remove(xid); + if (worker == null) { + return; + } + worker.wakeup(); + } + + private void processErrorReply(OFError errorMsg) { + OFMessage offendingMsg = errorMsg.getOffendingMsg(); + Integer xid; + if (offendingMsg != null) { + xid = offendingMsg.getXid(); + } else { + xid = errorMsg.getXid(); + } + /* + * the error can be a reply to a synchronous message or to a statistic + * request message + */ + Callable worker = messageWaitingDone.remove(xid); + if (worker == null) { + return; + } + if (worker instanceof SynchronousMessage) { + ((SynchronousMessage) worker).wakeup(errorMsg); + } else { + ((StatisticsCollector) worker).wakeup(errorMsg); + } + } + + private void processStatsReply(OFStatisticsReply reply) { + Integer xid = reply.getXid(); + StatisticsCollector worker = (StatisticsCollector) messageWaitingDone + .get(xid); + if (worker == null) { + return; + } + if (worker.collect(reply)) { + // if all the stats records are received (collect() returns true) + // then we are done. + messageWaitingDone.remove(xid); + worker.wakeup(); + } + } + + @Override + public Map getPhysicalPorts() { + return this.physicalPorts; + } + + @Override + public OFPhysicalPort getPhysicalPort(Short portNumber) { + return this.physicalPorts.get(portNumber); + } + + @Override + public Integer getPortBandwidth(Short portNumber) { + return this.portBandwidth.get(portNumber); + } + + @Override + public Set getPorts() { + return this.physicalPorts.keySet(); + } + + @Override + public Byte getTables() { + return this.tables; + } + + @Override + public Integer getActions() { + return this.actions; + } + + @Override + public Integer getCapabilities() { + return this.capabilities; + } + + @Override + public Integer getBuffers() { + return this.buffers; + } + + @Override + public boolean isPortEnabled(short portNumber) { + return isPortEnabled(physicalPorts.get(portNumber)); + } + + @Override + public boolean isPortEnabled(OFPhysicalPort port) { + if (port == null) { + return false; + } + int portConfig = port.getConfig(); + int portState = port.getState(); + if ((portConfig & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0) { + return false; + } + if ((portState & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0) { + return false; + } + if ((portState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK + .getValue()) { + return false; + } + return true; + } + + @Override + public List getEnabledPorts() { + List result = new ArrayList(); + synchronized (this.physicalPorts) { + for (OFPhysicalPort port : physicalPorts.values()) { + if (isPortEnabled(port)) { + result.add(port); + } + } + } + return result; + } + + /* + * Transmit thread polls the message out of the priority queue and invokes + * messaging service to transmit it over the socket channel + */ + class PriorityMessageTransmit implements Runnable { + @Override + public void run() { + running = true; + while (running) { + try { + PriorityMessage pmsg = transmitQ.take(); + msgReadWriteService.asyncSend(pmsg.msg); + /* + * If syncReply is set to true, wait for the response back. + */ + if (pmsg.syncReply) { + syncMessageInternal(pmsg.msg, pmsg.msg.getXid(), false); + } + } catch (InterruptedException ie) { + reportError(new InterruptedException( + "PriorityMessageTransmit thread interrupted")); + } catch (Exception e) { + reportError(e); + } + } + transmitQ = null; + } + } + + /* + * Setup and start the transmit thread + */ + private void startTransmitThread() { + this.transmitQ = new PriorityBlockingQueue(11, + new Comparator() { + @Override + public int compare(PriorityMessage p1, PriorityMessage p2) { + if (p2.priority != p1.priority) { + return p2.priority - p1.priority; + } else { + return (p2.seqNum < p1.seqNum) ? 1 : -1; + } + } + }); + this.transmitThread = new Thread(new PriorityMessageTransmit()); + this.transmitThread.start(); + } + + /* + * Setup communication services + */ + private void setupCommChannel() throws Exception { + this.selector = SelectorProvider.provider().openSelector(); + this.socket.configureBlocking(false); + this.socket.socket().setTcpNoDelay(true); + this.msgReadWriteService = getMessageReadWriteService(); + } + + private void sendFirstHello() { + try { + OFMessage msg = factory.getMessage(OFType.HELLO); + asyncFastSend(msg); + } catch (Exception e) { + reportError(e); + } + } + + private IMessageReadWrite getMessageReadWriteService() throws Exception { + String str = System.getProperty("secureChannelEnabled"); + return ((str != null) && (str.trim().equalsIgnoreCase("true"))) ? new SecureMessageReadWriteService( + socket, selector) : new MessageReadWriteService(socket, + selector); + } + + /** + * Send Barrier message synchronously. The caller will be blocked until the + * Barrier reply is received. + */ + @Override + public Object syncSendBarrierMessage() { + OFBarrierRequest barrierMsg = new OFBarrierRequest(); + return syncSend(barrierMsg); + } + + /** + * Send Barrier message asynchronously. The caller is not blocked. The + * Barrier message will be sent in a transmit thread which will be blocked + * until the Barrier reply is received. + */ + @Override + public Object asyncSendBarrierMessage() { + if (transmitQ == null) { + return Boolean.FALSE; + } + + OFBarrierRequest barrierMsg = new OFBarrierRequest(); + int xid = getNextXid(); + + barrierMsg.setXid(xid); + transmitQ.add(new PriorityMessage(barrierMsg, 0, true)); + + return Boolean.TRUE; + } + + /** + * This method returns the switch liveness timeout value. If controller did + * not receive any message from the switch for such a long period, + * controller will tear down the connection to the switch. + * + * @return The timeout value + */ + private static int getSwitchLivenessTimeout() { + String timeout = System.getProperty("of.switchLivenessTimeout"); + int rv = 60500; + + try { + if (timeout != null) { + rv = Integer.parseInt(timeout); + } + } catch (Exception e) { + } + + return rv; + } + + /** + * This method performs synchronous operations for a given message. If + * syncRequest is set to true, the message will be sent out followed by a + * Barrier request message. Then it's blocked until the Barrier rely arrives + * or timeout. If syncRequest is false, it simply skips the message send and + * just waits for the response back. + * + * @param msg + * Message to be sent + * @param xid + * Message XID + * @param request + * If set to true, the message the message will be sent out + * followed by a Barrier request message. If set to false, it + * simply skips the sending and just waits for the Barrier reply. + * @return the result + */ + private Object syncMessageInternal(OFMessage msg, int xid, boolean syncRequest) { + SynchronousMessage worker = new SynchronousMessage(this, xid, msg, syncRequest); + messageWaitingDone.put(xid, worker); + Object result = null; + Boolean status = false; + Future submit = executor.submit(worker); + try { + result = submit.get(responseTimerValue, TimeUnit.MILLISECONDS); + messageWaitingDone.remove(xid); + if (result == null) { + // if result is null, then it means the switch can handle this + // message successfully + // convert the result into a Boolean with value true + status = true; + // logger.debug("Successfully send " + + // msg.getType().toString()); + result = status; + } else { + // if result is not null, this means the switch can't handle + // this message + // the result if OFError already + if (logger.isDebugEnabled()) { + logger.debug("Send {} failed --> {}", msg.getType(), + (result)); + } + } + return result; + } catch (Exception e) { + logger.warn("Timeout while waiting for {} reply", msg.getType() + .toString()); + // convert the result into a Boolean with value false + status = false; + result = status; + return result; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SynchronousMessage.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SynchronousMessage.java new file mode 100644 index 0000000000..32962c771d --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/core/internal/SynchronousMessage.java @@ -0,0 +1,78 @@ +/* + * 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.core.internal; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; + +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFBarrierRequest; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFMessage; + +/** + * This class implements synchronous operations on message send to a switch. If + * syncRequest is set to true, it sends the requested message to the switch + * followed by a Barrier request message. It returns the result once it gets the + * reply from the switch or after a timeout. If the protocol does not dictate + * the switch to reply the processing status for a particular message, the + * Barrier request forces the switch to reply saying whether or not the message + * processing was successful for messages sent to the switch up to this point. + * If syncRequest is false, it simply skips the message send and just waits for + * the response back. + */ +public class SynchronousMessage implements Callable { + private ISwitch sw; + private Integer xid; + private OFMessage syncMsg; + protected CountDownLatch latch; + private Object result; + private boolean syncRequest; + + public SynchronousMessage(ISwitch sw, Integer xid, OFMessage msg, + boolean syncRequest) { + this.sw = sw; + this.xid = xid; + syncMsg = msg; + latch = new CountDownLatch(1); + result = null; + this.syncRequest = syncRequest; + } + + @Override + public Object call() throws Exception { + /* + * Send out message only if syncRequest is set to true. Otherwise, just + * wait for the Barrier response back. + */ + if (syncRequest) { + sw.asyncSend(syncMsg, xid); + if (!(syncMsg instanceof OFBarrierRequest)) { + OFBarrierRequest barrierMsg = new OFBarrierRequest(); + sw.asyncSend(barrierMsg, xid); + } + } + latch.await(); + return result; + } + + public Integer getXid() { + return this.xid; + } + + public void wakeup() { + this.latch.countDown(); + } + + public void wakeup(OFError e) { + result = e; + wakeup(); + } + +} \ No newline at end of file diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Activator.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Activator.java new file mode 100644 index 0000000000..ee6befb3aa --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Activator.java @@ -0,0 +1,460 @@ +/* + * 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.internal; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.felix.dm.Component; +import org.opendaylight.controller.sal.connection.IPluginInConnectionService; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.flowprogrammer.IPluginInFlowProgrammerService; +import org.opendaylight.controller.sal.flowprogrammer.IPluginOutFlowProgrammerService; +import org.opendaylight.controller.sal.inventory.IPluginInInventoryService; +import org.opendaylight.controller.sal.inventory.IPluginOutInventoryService; +import org.opendaylight.controller.sal.packet.IPluginInDataPacketService; +import org.opendaylight.controller.sal.packet.IPluginOutDataPacketService; +import org.opendaylight.controller.sal.reader.IPluginInReadService; +import org.opendaylight.controller.sal.reader.IPluginOutReadService; +import org.opendaylight.controller.sal.topology.IPluginInTopologyService; +import org.opendaylight.controller.sal.topology.IPluginOutTopologyService; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.openflowplugin.openflow.IDataPacketListen; +import org.opendaylight.openflowplugin.openflow.IDataPacketMux; +import org.opendaylight.openflowplugin.openflow.IDiscoveryListener; +import org.opendaylight.openflowplugin.openflow.IFlowProgrammerNotifier; +import org.opendaylight.openflowplugin.openflow.IInventoryProvider; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.IInventoryShimInternalListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsManager; +import org.opendaylight.openflowplugin.openflow.IReadFilterInternalListener; +import org.opendaylight.openflowplugin.openflow.IReadServiceFilter; +import org.opendaylight.openflowplugin.openflow.IRefreshInternalProvider; +import org.opendaylight.openflowplugin.openflow.ITopologyServiceShimListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageListener; +import org.opendaylight.openflowplugin.openflow.core.internal.Controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Openflow protocol plugin Activator + * + * + */ +public class Activator extends ComponentActivatorAbstractBase { + protected static final Logger logger = LoggerFactory + .getLogger(Activator.class); + + /** + * Function called when the activator starts just after some initializations + * are done by the ComponentActivatorAbstractBase. + * + */ + public void init() { + } + + /** + * Function called when the activator stops just before the cleanup done by + * ComponentActivatorAbstractBase + * + */ + public void destroy() { + } + + /** + * Function that is used to communicate to dependency manager the list of + * known implementations for services inside a container + * + * + * @return An array containing all the CLASS objects that will be + * instantiated in order to get an fully working implementation + * Object + */ + public Object[] getImplementations() { + Object[] res = { TopologyServices.class, DataPacketServices.class, + InventoryService.class, ReadService.class, + FlowProgrammerNotifier.class }; + return res; + } + + /** + * Function that is called when configuration of the dependencies is + * required. + * + * @param c + * dependency manager Component object, used for configuring the + * dependencies exported and imported + * @param imp + * Implementation class that is being configured, needed as long + * as the same routine can configure multiple implementations + * @param containerName + * The containerName being configured, this allow also optional + * per-container different behavior if needed, usually should not + * be the case though. + */ + public void configureInstance(Component c, Object imp, String containerName) { + if (imp.equals(TopologyServices.class)) { + // export the service to be used by SAL + c.setInterface( + new String[] { IPluginInTopologyService.class.getName(), + ITopologyServiceShimListener.class.getName() }, null); + // Hook the services coming in from SAL, as optional in + // case SAL is not yet there, could happen + c.add(createContainerServiceDependency(containerName) + .setService(IPluginOutTopologyService.class) + .setCallbacks("setPluginOutTopologyService", + "unsetPluginOutTopologyService").setRequired(false)); + c.add(createServiceDependency() + .setService(IRefreshInternalProvider.class) + .setCallbacks("setRefreshInternalProvider", + "unsetRefreshInternalProvider").setRequired(false)); + } + + if (imp.equals(InventoryService.class)) { + // export the service + c.setInterface( + new String[] { + IPluginInInventoryService.class.getName(), + IInventoryShimInternalListener.class.getName(), + IInventoryProvider.class.getName() }, null); + + // Now lets add a service dependency to make sure the + // provider of service exists + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createContainerServiceDependency(containerName) + .setService(IPluginOutInventoryService.class) + .setCallbacks("setPluginOutInventoryServices", + "unsetPluginOutInventoryServices") + .setRequired(false)); + } + + if (imp.equals(DataPacketServices.class)) { + // export the service to be used by SAL + Dictionary props = new Hashtable(); + // Set the protocolPluginType property which will be used + // by SAL + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), Node.NodeIDType.OPENFLOW); + c.setInterface(IPluginInDataPacketService.class.getName(), props); + // Hook the services coming in from SAL, as optional in + // case SAL is not yet there, could happen + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + // This is required for the transmission to happen properly + c.add(createServiceDependency().setService(IDataPacketMux.class) + .setCallbacks("setIDataPacketMux", "unsetIDataPacketMux") + .setRequired(true)); + c.add(createContainerServiceDependency(containerName) + .setService(IPluginOutDataPacketService.class) + .setCallbacks("setPluginOutDataPacketService", + "unsetPluginOutDataPacketService") + .setRequired(false)); + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + if (imp.equals(ReadService.class)) { + // export the service to be used by SAL + Dictionary props = new Hashtable(); + // Set the protocolPluginType property which will be used + // by SAL + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), Node.NodeIDType.OPENFLOW); + c.setInterface(new String[] { + IReadFilterInternalListener.class.getName(), + IPluginInReadService.class.getName() }, props); + + c.add(createServiceDependency() + .setService(IReadServiceFilter.class) + .setCallbacks("setService", "unsetService") + .setRequired(true)); + + c.add(createContainerServiceDependency(containerName) + .setService(IPluginOutReadService.class) + .setCallbacks("setPluginOutReadServices", + "unsetPluginOutReadServices") + .setRequired(false)); + + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + if (imp.equals(FlowProgrammerNotifier.class)) { + // export the service to be used by SAL + Dictionary props = new Hashtable(); + // Set the protocolPluginType property which will be used + // by SAL + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), Node.NodeIDType.OPENFLOW); + c.setInterface(IFlowProgrammerNotifier.class.getName(), props); + + c.add(createContainerServiceDependency(containerName) + .setService(IPluginOutFlowProgrammerService.class) + .setCallbacks("setPluginOutFlowProgrammerService", + "unsetPluginOutFlowProgrammerService") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + } + + /** + * Function that is used to communicate to dependency manager the list of + * known implementations for services that are container independent. + * + * + * @return An array containing all the CLASS objects that will be + * instantiated in order to get an fully working implementation + * Object + */ + public Object[] getGlobalImplementations() { + Object[] res = { Controller.class, OFStatisticsManager.class, + FlowProgrammerService.class, ReadServiceFilter.class, + DiscoveryService.class, DataPacketMuxDemux.class, InventoryService.class, + InventoryServiceShim.class, TopologyServiceShim.class }; + return res; + } + + /** + * Function that is called when configuration of the dependencies is + * required. + * + * @param c + * dependency manager Component object, used for configuring the + * dependencies exported and imported + * @param imp + * Implementation class that is being configured, needed as long + * as the same routine can configure multiple implementations + */ + public void configureGlobalInstance(Component c, Object imp) { + + if (imp.equals(Controller.class)) { + logger.debug("Activator configureGlobalInstance( ) is called"); + Dictionary props = new Hashtable(); + props.put("name", "Controller"); + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), Node.NodeIDType.OPENFLOW); + c.setInterface(new String[] { IController.class.getName(), + IPluginInConnectionService.class.getName()}, + props); + } + + if (imp.equals(FlowProgrammerService.class)) { + // export the service to be used by SAL + Dictionary props = new Hashtable(); + // Set the protocolPluginType property which will be used + // by SAL + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), Node.NodeIDType.OPENFLOW); + c.setInterface( + new String[] { + IPluginInFlowProgrammerService.class.getName(), + IMessageListener.class.getName(), + IContainerListener.class.getName(), + IInventoryShimExternalListener.class.getName() }, + props); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + + c.add(createServiceDependency() + .setService(IFlowProgrammerNotifier.class) + .setCallbacks("setFlowProgrammerNotifier", + "unsetsetFlowProgrammerNotifier") + .setRequired(false)); + + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + if (imp.equals(ReadServiceFilter.class)) { + + c.setInterface(new String[] { + IReadServiceFilter.class.getName(), + IContainerListener.class.getName(), + IOFStatisticsListener.class.getName() }, null); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IOFStatisticsManager.class) + .setCallbacks("setService", "unsetService") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IReadFilterInternalListener.class) + .setCallbacks("setReadFilterInternalListener", + "unsetReadFilterInternalListener") + .setRequired(false)); + } + + if (imp.equals(OFStatisticsManager.class)) { + + c.setInterface(new String[] { IOFStatisticsManager.class.getName(), + IInventoryShimExternalListener.class.getName() }, null); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IOFStatisticsListener.class) + .setCallbacks("setStatisticsListener", + "unsetStatisticsListener").setRequired(false)); + } + + if (imp.equals(DiscoveryService.class)) { + // export the service + c.setInterface( + new String[] { + IInventoryShimExternalListener.class.getName(), + IDataPacketListen.class.getName(), + IContainerListener.class.getName() }, null); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createContainerServiceDependency( + GlobalConstants.DEFAULT.toString()) + .setService(IInventoryProvider.class) + .setCallbacks("setInventoryProvider", + "unsetInventoryProvider").setRequired(true)); + c.add(createServiceDependency().setService(IDataPacketMux.class) + .setCallbacks("setIDataPacketMux", "unsetIDataPacketMux") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IDiscoveryListener.class) + .setCallbacks("setDiscoveryListener", + "unsetDiscoveryListener").setRequired(true)); + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + // DataPacket mux/demux services, which is teh actual engine + // doing the packet switching + if (imp.equals(DataPacketMuxDemux.class)) { + c.setInterface(new String[] { IDataPacketMux.class.getName(), + IContainerListener.class.getName(), + IInventoryShimExternalListener.class.getName() }, null); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IPluginOutDataPacketService.class) + .setCallbacks("setPluginOutDataPacketService", + "unsetPluginOutDataPacketService") + .setRequired(false)); + // See if there is any local packet dispatcher + c.add(createServiceDependency() + .setService(IDataPacketListen.class) + .setCallbacks("setIDataPacketListen", + "unsetIDataPacketListen").setRequired(false)); + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + if (imp.equals(InventoryService.class)) { + // export the service + Dictionary props = new Hashtable(); + props.put("scope", "Global"); + + c.setInterface( + new String[] { IPluginInInventoryService.class.getName(), + IInventoryShimInternalListener.class.getName(), + IInventoryProvider.class.getName() }, props); + + // Now lets add a service dependency to make sure the + // provider of service exists + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IPluginOutInventoryService.class, "(scope=Global)") + .setCallbacks("setPluginOutInventoryServices", + "unsetPluginOutInventoryServices") + .setRequired(false)); + } + + if (imp.equals(InventoryServiceShim.class)) { + c.setInterface(new String[] { IContainerListener.class.getName(), + IOFStatisticsListener.class.getName()}, null); + + c.add(createServiceDependency() + .setService(IController.class, "(name=Controller)") + .setCallbacks("setController", "unsetController") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IInventoryShimInternalListener.class, "(!(scope=Global))") + .setCallbacks("setInventoryShimInternalListener", + "unsetInventoryShimInternalListener") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IInventoryShimInternalListener.class, "(scope=Global)") + .setCallbacks("setInventoryShimGlobalInternalListener", + "unsetInventoryShimGlobalInternalListener") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IInventoryShimExternalListener.class) + .setCallbacks("setInventoryShimExternalListener", + "unsetInventoryShimExternalListener") + .setRequired(false)); + c.add(createServiceDependency() + .setService(IPluginOutConnectionService.class) + .setCallbacks("setIPluginOutConnectionService", + "unsetIPluginOutConnectionService") + .setRequired(false)); + } + + if (imp.equals(TopologyServiceShim.class)) { + c.setInterface(new String[] { IDiscoveryListener.class.getName(), + IContainerListener.class.getName(), + IRefreshInternalProvider.class.getName(), + IInventoryShimExternalListener.class.getName() }, null); + c.add(createServiceDependency() + .setService(ITopologyServiceShimListener.class) + .setCallbacks("setTopologyServiceShimListener", + "unsetTopologyServiceShimListener") + .setRequired(true)); + c.add(createServiceDependency() + .setService(IOFStatisticsManager.class) + .setCallbacks("setStatisticsManager", + "unsetStatisticsManager").setRequired(false)); + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketMuxDemux.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketMuxDemux.java new file mode 100644 index 0000000000..20625074ca --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketMuxDemux.java @@ -0,0 +1,455 @@ +/* + * 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.internal; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPacketIn; +import org.openflow.protocol.OFPacketOut; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFType; +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.action.OFActionOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.ConstructionException; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.packet.IPluginOutDataPacketService; +import org.opendaylight.controller.sal.packet.PacketResult; +import org.opendaylight.controller.sal.packet.RawPacket; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.openflowplugin.openflow.IDataPacketListen; +import org.opendaylight.openflowplugin.openflow.IDataPacketMux; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageListener; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; + +public class DataPacketMuxDemux implements IContainerListener, + IMessageListener, IDataPacketMux, IInventoryShimExternalListener { + protected static final Logger logger = LoggerFactory + .getLogger(DataPacketMuxDemux.class); + private IController controller = null; + private ConcurrentMap swID2ISwitch = new ConcurrentHashMap(); + // Gives a map between a Container and all the DataPacket listeners on SAL + private ConcurrentMap pluginOutDataPacketServices = new ConcurrentHashMap(); + // Gives a map between a NodeConnector and the containers to which it + // belongs + private ConcurrentMap> nc2Container = new ConcurrentHashMap>(); + // Gives a map between a Container and the FlowSpecs on it + private ConcurrentMap> container2FlowSpecs = new ConcurrentHashMap>(); + // Track local data packet listener + private List iDataPacketListen = new CopyOnWriteArrayList(); + private IPluginOutConnectionService connectionOutService; + + void setIDataPacketListen(IDataPacketListen s) { + if (this.iDataPacketListen != null) { + if (!this.iDataPacketListen.contains(s)) { + logger.debug("Added new IDataPacketListen"); + this.iDataPacketListen.add(s); + } + } + } + + void unsetIDataPacketListen(IDataPacketListen s) { + if (this.iDataPacketListen != null) { + if (this.iDataPacketListen.contains(s)) { + logger.debug("Removed IDataPacketListen"); + this.iDataPacketListen.remove(s); + } + } + } + + void setPluginOutDataPacketService(Map props, + IPluginOutDataPacketService s) { + if (props == null) { + logger.error("Didn't receive the service properties"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("containerName not supplied"); + return; + } + if (this.pluginOutDataPacketServices != null) { + // It's expected only one SAL per container as long as the + // replication is done in the SAL implementation toward + // the different APPS + this.pluginOutDataPacketServices.put(containerName, s); + logger.debug("New outService for container: {}", containerName); + } + } + + void unsetPluginOutDataPacketService(Map props, + IPluginOutDataPacketService s) { + if (props == null) { + logger.error("Didn't receive the service properties"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("containerName not supplied"); + return; + } + if (this.pluginOutDataPacketServices != null) { + this.pluginOutDataPacketServices.remove(containerName); + logger.debug("Removed outService for container: {}", containerName); + } + } + + void setController(IController s) { + logger.debug("Controller provider set in DATAPACKET SERVICES"); + this.controller = s; + } + + void unsetController(IController s) { + if (this.controller == s) { + logger.debug("Controller provider UNset in DATAPACKET SERVICES"); + this.controller = null; + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + this.controller.addMessageListener(OFType.PACKET_IN, this); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + this.controller.removeMessageListener(OFType.PACKET_IN, this); + + // Clear state that may need to be reused on component restart + this.pluginOutDataPacketServices.clear(); + this.nc2Container.clear(); + this.container2FlowSpecs.clear(); + this.controller = null; + this.swID2ISwitch.clear(); + } + + @Override + public void receive(ISwitch sw, OFMessage msg) { + if (sw == null || msg == null + || this.pluginOutDataPacketServices == null) { + // Something fishy, we cannot do anything + logger.debug( + "sw: {} and/or msg: {} and/or pluginOutDataPacketServices: {} is null!", + new Object[] { sw, msg, this.pluginOutDataPacketServices }); + return; + } + + Long ofSwitchID = Long.valueOf(sw.getId()); + try { + Node n = new Node(Node.NodeIDType.OPENFLOW, ofSwitchID); + if (!connectionOutService.isLocal(n)) { + logger.debug("Connection service refused DataPacketMuxDemux receive {} {}", sw, msg); + return; + } + } + catch (Exception e) { + return; + } + + if (msg instanceof OFPacketIn) { + OFPacketIn ofPacket = (OFPacketIn) msg; + Short ofPortID = Short.valueOf(ofPacket.getInPort()); + + try { + Node n = new Node(Node.NodeIDType.OPENFLOW, ofSwitchID); + NodeConnector p = PortConverter.toNodeConnector(ofPortID, n); + RawPacket dataPacket = new RawPacket(ofPacket.getPacketData()); + dataPacket.setIncomingNodeConnector(p); + + // Try to dispatch the packet locally, in here we will + // pass the parsed packet simply because once the + // packet infra is settled all the packets will passed + // around as parsed and read-only + for (int i = 0; i < this.iDataPacketListen.size(); i++) { + IDataPacketListen s = this.iDataPacketListen.get(i); + if (s.receiveDataPacket(dataPacket).equals( + PacketResult.CONSUME)) { + logger.trace("Consumed locally data packet"); + return; + } + } + + // Now dispatch the packet toward SAL at least for + // default container, we need to revisit this in a general + // slicing architecture refresh + IPluginOutDataPacketService defaultOutService = this.pluginOutDataPacketServices + .get(GlobalConstants.DEFAULT.toString()); + if (defaultOutService != null) { + defaultOutService.receiveDataPacket(dataPacket); + if (logger.isTraceEnabled()) { + logger.trace( + "Dispatched to apps a frame of size: {} on " + + "container: {}: {}", new Object[] { + ofPacket.getPacketData().length, + GlobalConstants.DEFAULT.toString(), + HexEncode.bytesToHexString(dataPacket + .getPacketData()) }); + } + } + // Now check the mapping between nodeConnector and + // Container and later on optimally filter based on + // flowSpec + List containersRX = this.nc2Container.get(p); + if (containersRX != null) { + for (int i = 0; i < containersRX.size(); i++) { + String container = containersRX.get(i); + IPluginOutDataPacketService s = this.pluginOutDataPacketServices + .get(container); + if (s != null) { + // TODO add filtering on a per-flowSpec base + s.receiveDataPacket(dataPacket); + if (logger.isTraceEnabled()) { + logger.trace( + "Dispatched to apps a frame of size: {}" + + " on container: {}: {}", new Object[] { + ofPacket.getPacketData().length, + container, + HexEncode.bytesToHexString(dataPacket + .getPacketData()) }); + } + } + } + } + + // This is supposed to be the catch all for all the + // DataPacket hence we will assume it has been handled + return; + } catch (ConstructionException cex) { + } + + // If we reach this point something went wrong. + return; + } else { + // We don't care about non-data packets + return; + } + } + + @Override + public void transmitDataPacket(RawPacket outPkt) { + // Sanity check area + if (outPkt == null) { + logger.debug("outPkt is null!"); + return; + } + + NodeConnector outPort = outPkt.getOutgoingNodeConnector(); + if (outPort == null) { + logger.debug("outPort is null! outPkt: {}", outPkt); + return; + } + + if (!connectionOutService.isLocal(outPort.getNode())) { + logger.debug("data packets will not be sent to {} in a non-master controller", outPort.toString()); + return; + } + + + if (!outPort.getType().equals( + NodeConnector.NodeConnectorIDType.OPENFLOW)) { + // The output Port is not of type OpenFlow + logger.debug("outPort is not OF Type! outPort: {}", outPort); + return; + } + + Short port = (Short) outPort.getID(); + Long swID = (Long) outPort.getNode().getID(); + ISwitch sw = this.swID2ISwitch.get(swID); + + if (sw == null) { + // If we cannot get the controller descriptor we cannot even + // send out the frame + logger.debug("swID: {} - sw is null!", swID); + return; + } + + byte[] data = outPkt.getPacketData(); + // build action + OFActionOutput action = new OFActionOutput().setPort(port); + // build packet out + OFPacketOut po = new OFPacketOut() + .setBufferId(OFPacketOut.BUFFER_ID_NONE) + .setInPort(OFPort.OFPP_NONE) + .setActions(Collections.singletonList((OFAction) action)) + .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); + + po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + + data.length); + po.setPacketData(data); + + // send PACKET_OUT at high priority + sw.asyncFastSend(po); + logger.trace("Transmitted a frame of size: {}", data.length); + } + + public void addNode(Node node, Set props) { + if (node == null) { + logger.debug("node is null!"); + return; + } + + long sid = (Long) node.getID(); + ISwitch sw = controller.getSwitches().get(sid); + if (sw == null) { + logger.debug("sid: {} - sw is null!", sid); + return; + } + this.swID2ISwitch.put(sw.getId(), sw); + } + + public void removeNode(Node node) { + if (node == null) { + logger.debug("node is null!"); + return; + } + + long sid = (Long) node.getID(); + ISwitch sw = controller.getSwitches().get(sid); + if (sw == null) { + logger.debug("sid: {} - sw is null!", sid); + return; + } + this.swID2ISwitch.remove(sw.getId()); + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, + short newTag, UpdateType t) { + // Do nothing + } + + @Override + public void containerFlowUpdated(String containerName, + ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) { + if (this.container2FlowSpecs == null) { + logger.error("container2FlowSpecs is NULL"); + return; + } + List fSpecs = this.container2FlowSpecs + .get(containerName); + if (fSpecs == null) { + fSpecs = new CopyOnWriteArrayList(); + } + switch (t) { + case ADDED: + if (!fSpecs.contains(previousFlow)) { + fSpecs.add(previousFlow); + } + break; + case REMOVED: + if (fSpecs.contains(previousFlow)) { + fSpecs.remove(previousFlow); + } + break; + case CHANGED: + break; + } + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector p, + UpdateType t) { + if (this.nc2Container == null) { + logger.error("nc2Container is NULL"); + return; + } + List containers = this.nc2Container.get(p); + if (containers == null) { + containers = new CopyOnWriteArrayList(); + } + boolean updateMap = false; + switch (t) { + case ADDED: + if (!containers.contains(containerName)) { + containers.add(containerName); + updateMap = true; + } + break; + case REMOVED: + if (containers.contains(containerName)) { + containers.remove(containerName); + updateMap = true; + } + break; + case CHANGED: + break; + } + if (updateMap) { + if (containers.isEmpty()) { + // Do cleanup to reduce memory footprint if no + // elements to be tracked + this.nc2Container.remove(p); + } else { + this.nc2Container.put(p, containers); + } + } + } + + @Override + public void containerModeUpdated(UpdateType t) { + // do nothing + } + + @Override + public void updateNode(Node node, UpdateType type, Set props) { + switch (type) { + case ADDED: + addNode(node, props); + break; + case REMOVED: + removeNode(node); + break; + default: + break; + } + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props) { + // do nothing + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketServices.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketServices.java new file mode 100644 index 0000000000..9f2cf4201a --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DataPacketServices.java @@ -0,0 +1,55 @@ + +/* + * 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.internal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.packet.IPluginInDataPacketService; +import org.opendaylight.controller.sal.packet.RawPacket; +import org.opendaylight.openflowplugin.openflow.IDataPacketMux; + +public class DataPacketServices implements IPluginInDataPacketService { + protected static final Logger logger = LoggerFactory + .getLogger(DataPacketServices.class); + private IDataPacketMux iDataPacketMux = null; + private IPluginOutConnectionService connectionOutService; + + void setIDataPacketMux(IDataPacketMux s) { + this.iDataPacketMux = s; + } + + void unsetIDataPacketMux(IDataPacketMux s) { + if (this.iDataPacketMux == s) { + this.iDataPacketMux = null; + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + + @Override + public void transmitDataPacket(RawPacket outPkt) { + NodeConnector nc = outPkt.getOutgoingNodeConnector(); + if (connectionOutService != null && connectionOutService.isLocal(nc.getNode())) { + this.iDataPacketMux.transmitDataPacket(outPkt); + } else { + logger.debug("{} is dropped in the controller "+outPkt); + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DescStatisticsConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DescStatisticsConverter.java new file mode 100644 index 0000000000..1a9ff32b71 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DescStatisticsConverter.java @@ -0,0 +1,53 @@ +/* + * 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.internal; + +import java.util.List; + +import org.opendaylight.controller.sal.reader.NodeDescription; +import org.openflow.protocol.statistics.OFDescriptionStatistics; +import org.openflow.protocol.statistics.OFStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for converting openflow description statistics into SAL + * NodeDescription object + */ +public class DescStatisticsConverter { + private static final Logger log = LoggerFactory + .getLogger(DescStatisticsConverter.class); + NodeDescription hwDesc; + OFDescriptionStatistics ofDesc; + + public DescStatisticsConverter(List statsList) { + this.hwDesc = null; + this.ofDesc = (statsList == null || statsList.isEmpty())? + null : (OFDescriptionStatistics) statsList.get(0); + } + public DescStatisticsConverter(OFDescriptionStatistics desc) { + this.hwDesc = null; + this.ofDesc = desc; + } + + public NodeDescription getHwDescription() { + if (hwDesc == null && ofDesc != null) { + hwDesc = new NodeDescription(); + hwDesc.setManufacturer(ofDesc.getManufacturerDescription()); + hwDesc.setHardware(ofDesc.getHardwareDescription()); + hwDesc.setSoftware(ofDesc.getSoftwareDescription()); + hwDesc.setDescription(ofDesc.getDatapathDescription()); + hwDesc.setSerialNumber(ofDesc.getSerialNumber()); + } + log.trace("OFDescriptionStatistics: {}", ofDesc); + log.trace("NodeDescription: {}", hwDesc); + return hwDesc; + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DiscoveryService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DiscoveryService.java new file mode 100644 index 0000000000..71e9a09c9e --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/DiscoveryService.java @@ -0,0 +1,1671 @@ +/* + * 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.internal; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedBlockingQueue; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.openflow.protocol.OFPhysicalPort; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Config; +import org.opendaylight.controller.sal.core.ConstructionException; +import org.opendaylight.controller.sal.core.Edge; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.State; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.packet.Ethernet; +import org.opendaylight.controller.sal.packet.LLDP; +import org.opendaylight.controller.sal.packet.LLDPTLV; +import org.opendaylight.controller.sal.packet.LinkEncap; +import org.opendaylight.controller.sal.packet.PacketResult; +import org.opendaylight.controller.sal.packet.RawPacket; +import org.opendaylight.controller.sal.utils.EtherTypes; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.controller.sal.utils.NetUtils; +import org.opendaylight.controller.sal.utils.NodeConnectorCreator; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.opendaylight.controller.sal.utils.Status; +import org.opendaylight.controller.sal.utils.StatusCode; +import org.opendaylight.openflowplugin.openflow.IDataPacketListen; +import org.opendaylight.openflowplugin.openflow.IDataPacketMux; +import org.opendaylight.openflowplugin.openflow.IDiscoveryListener; +import org.opendaylight.openflowplugin.openflow.IInventoryProvider; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; + +/** + * The class describes neighbor discovery service for an OpenFlow network. + */ +public class DiscoveryService implements IInventoryShimExternalListener, IDataPacketListen, IContainerListener, + CommandProvider { + private static Logger logger = LoggerFactory.getLogger(DiscoveryService.class); + private IController controller = null; + private IDiscoveryListener discoveryListener = null; + private IInventoryProvider inventoryProvider = null; + private IDataPacketMux iDataPacketMux = null; + // High priority list containing newly added ports which will be served first + private List readyListHi = null; + // List containing all the ports which will be served periodically + private List readyListLo = null; + // Staging area during quiet period + private List stagingList = null; + // Wait for next discovery packet. The map contains the time elapsed since + // the last received LLDP frame on each node connector + private ConcurrentMap holdTime = null; + // Allow one more retry for newly added ports. This map contains the time + // period elapsed since last discovery pkt transmission on the port. + private ConcurrentMap elapsedTime = null; + // OpenFlow edges keyed by head connector + private ConcurrentMap edgeMap = null; + // The map contains aging entry keyed by head connector of Production edge + private ConcurrentMap agingMap = null; + // Production edges keyed by head connector + private ConcurrentMap prodMap = null; + + private Timer discoveryTimer; + private DiscoveryTimerTask discoveryTimerTask; + private final static long discoveryTimerTick = 2L * 1000; // per tick in msec + private int discoveryTimerTickCount = 0; // main tick counter + // Max # of ports handled in one batch + private int discoveryBatchMaxPorts; + // Periodically restart batching process + private int discoveryBatchRestartTicks; + private int discoveryBatchPausePeriod = 2; + // Pause after this point + private int discoveryBatchPauseTicks; + private int discoveryTimeoutTicks; + private int discoveryThresholdTicks; + private int discoveryAgeoutTicks; + // multiple of discoveryBatchRestartTicks + private int discoveryConsistencyCheckMultiple = 2; + // CC tick counter + private int discoveryConsistencyCheckTickCount; + // # of times CC gets called + private int discoveryConsistencyCheckCallingTimes = 0; + // # of cases CC corrected + private int discoveryConsistencyCheckCorrected = 0; + // Enable or disable CC + private boolean discoveryConsistencyCheckEnabled = true; + // Enable or disable aging + private boolean discoveryAgingEnabled = true; + // Global flag to enable or disable LLDP snooping + private boolean discoverySnoopingEnabled = true; + // The list of ports that will not do LLDP snooping + private List discoverySnoopingDisableList; + private BlockingQueue transmitQ; + private Thread transmitThread; + private Boolean throttling = false; // if true, no more batching. + private volatile Boolean shuttingDown = false; + + private LLDPTLV chassisIdTlv, portIdTlv, ttlTlv, customTlv; + private IPluginOutConnectionService connectionOutService; + + class DiscoveryTransmit implements Runnable { + private final BlockingQueue transmitQ; + + DiscoveryTransmit(BlockingQueue transmitQ) { + this.transmitQ = transmitQ; + } + + @Override + public void run() { + while (true) { + try { + NodeConnector nodeConnector = transmitQ.take(); + RawPacket outPkt = createDiscoveryPacket(nodeConnector); + sendDiscoveryPacket(nodeConnector, outPkt); + nodeConnector = null; + } catch (InterruptedException e1) { + logger.warn("DiscoveryTransmit interupted", e1.getMessage()); + if (shuttingDown) { + return; + } + } catch (Exception e2) { + logger.error("", e2); + } + } + } + } + + class DiscoveryTimerTask extends TimerTask { + @Override + public void run() { + checkTimeout(); + checkAging(); + doConsistencyCheck(); + doDiscovery(); + } + } + + public enum DiscoveryPeriod { + INTERVAL (300), + AGEOUT (120), + THRESHOLD (30); + + private int time; // sec + private int tick; // tick + + DiscoveryPeriod(int time) { + this.time = time; + this.tick = time2Tick(time); + } + + public int getTime() { + return time; + } + + public void setTime(int time) { + this.time = time; + this.tick = time2Tick(time); + } + + public int getTick() { + return tick; + } + + public void setTick(int tick) { + this.time = tick2Time(tick); + this.tick = tick; + } + + private int time2Tick(int time) { + return (int) (time / (discoveryTimerTick / 1000)); + } + + private int tick2Time(int tick) { + return (int) (tick * (discoveryTimerTick / 1000)); + } + } + + private RawPacket createDiscoveryPacket(NodeConnector nodeConnector) { + String nodeId = HexEncode.longToHexString((Long) nodeConnector.getNode().getID()); + + // Create LLDP ChassisID TLV + byte[] cidValue = LLDPTLV.createChassisIDTLVValue(nodeId); + chassisIdTlv.setType(LLDPTLV.TLVType.ChassisID.getValue()).setLength((short) cidValue.length) + .setValue(cidValue); + + // Create LLDP PortID TLV + String portId = nodeConnector.getNodeConnectorIDString(); + byte[] pidValue = LLDPTLV.createPortIDTLVValue(portId); + portIdTlv.setType(LLDPTLV.TLVType.PortID.getValue()).setLength((short) pidValue.length).setValue(pidValue); + + // Create LLDP Custom TLV + byte[] customValue = LLDPTLV.createCustomTLVValue(nodeConnector.toString()); + customTlv.setType(LLDPTLV.TLVType.Custom.getValue()).setLength((short) customValue.length) + .setValue(customValue); + + // Create LLDP Custom Option list + List customList = new ArrayList(); + customList.add(customTlv); + + // Create discovery pkt + LLDP discoveryPkt = new LLDP(); + discoveryPkt.setChassisId(chassisIdTlv).setPortId(portIdTlv).setTtl(ttlTlv).setOptionalTLVList(customList); + + RawPacket rawPkt = null; + try { + // Create ethernet pkt + byte[] sourceMac = getSourceMACFromNodeID(nodeId); + Ethernet ethPkt = new Ethernet(); + ethPkt.setSourceMACAddress(sourceMac).setDestinationMACAddress(LLDP.LLDPMulticastMac) + .setEtherType(EtherTypes.LLDP.shortValue()).setPayload(discoveryPkt); + + byte[] data = ethPkt.serialize(); + rawPkt = new RawPacket(data); + rawPkt.setOutgoingNodeConnector(nodeConnector); + } catch (ConstructionException cex) { + logger.warn("RawPacket creation caught exception {}", cex.getMessage()); + } catch (Exception e) { + logger.error("Failed to serialize the LLDP packet: " + e); + } + + return rawPkt; + } + + private void sendDiscoveryPacket(NodeConnector nodeConnector, RawPacket outPkt) { + if (nodeConnector == null) { + logger.debug("Can not send discovery packet out since nodeConnector is null"); + return; + } + + if (!connectionOutService.isLocal(nodeConnector.getNode())) { + logger.debug("Discoery packets will not be sent to {} in a non-master controller", nodeConnector.toString()); + return; + } + + if (outPkt == null) { + logger.debug("Can not send discovery packet out since outPkt is null"); + return; + } + + long sid = (Long) nodeConnector.getNode().getID(); + ISwitch sw = controller.getSwitches().get(sid); + + if (sw == null) { + logger.debug("Can not send discovery packet out since switch {} is null", sid); + return; + } + + if (!sw.isOperational()) { + logger.debug("Can not send discovery packet out since switch {} is not operational", sw); + return; + } + + if (this.iDataPacketMux == null) { + logger.debug("Can not send discovery packet out since DataPacket service is not available"); + return; + } + + logger.trace("Sending topology discovery pkt thru {}", nodeConnector); + this.iDataPacketMux.transmitDataPacket(outPkt); + } + + @Override + public PacketResult receiveDataPacket(RawPacket inPkt) { + if (inPkt == null) { + logger.debug("Ignoring null packet"); + return PacketResult.IGNORED; + } + + byte[] data = inPkt.getPacketData(); + if (data.length <= 0) { + logger.trace("Ignoring zero length packet"); + return PacketResult.IGNORED; + } + + if (!inPkt.getEncap().equals(LinkEncap.ETHERNET)) { + logger.trace("Ignoring non ethernet packet"); + return PacketResult.IGNORED; + } + + NodeConnector nodeConnector = inPkt.getIncomingNodeConnector(); + if (((Short) nodeConnector.getID()).equals(NodeConnector.SPECIALNODECONNECTORID)) { + logger.trace("Ignoring ethernet packet received on special port: " + + inPkt.getIncomingNodeConnector().toString()); + return PacketResult.IGNORED; + } + + if (!connectionOutService.isLocal(nodeConnector.getNode())) { + logger.debug("Discoery packets will not be processed from {} in a non-master controller", nodeConnector.toString()); + return PacketResult.IGNORED; + } + + Ethernet ethPkt = new Ethernet(); + try { + ethPkt.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte); + } catch (Exception e) { + logger.warn("Failed to decode LLDP packet from {}: {}", inPkt.getIncomingNodeConnector(), e); + return PacketResult.IGNORED; + } + + if (ethPkt.getPayload() instanceof LLDP) { + NodeConnector dst = inPkt.getIncomingNodeConnector(); + if (isEnabled(dst)) { + if (!processDiscoveryPacket(dst, ethPkt)) { + // Snoop the discovery pkt if not generated from us + snoopDiscoveryPacket(dst, ethPkt); + } + return PacketResult.CONSUME; + } + } + return PacketResult.IGNORED; + } + + /* + * Snoop incoming discovery frames generated by the production network + * neighbor switch + */ + private void snoopDiscoveryPacket(NodeConnector dstNodeConnector, Ethernet ethPkt) { + if (!this.discoverySnoopingEnabled || discoverySnoopingDisableList.contains(dstNodeConnector)) { + logger.trace("Discarded received discovery packet on {} since snooping is turned off", dstNodeConnector); + return; + } + + if ((dstNodeConnector == null) || (ethPkt == null)) { + logger.trace("Quit snooping discovery packet: Null node connector or packet"); + return; + } + + LLDP lldp = (LLDP) ethPkt.getPayload(); + + try { + String nodeId = LLDPTLV.getHexStringValue(lldp.getChassisId().getValue(), lldp.getChassisId().getLength()); + String portId = LLDPTLV.getStringValue(lldp.getPortId().getValue(), lldp.getPortId().getLength()); + byte[] systemNameBytes = null; + // get system name if present in the LLDP pkt + for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { + if (lldptlv.getType() == LLDPTLV.TLVType.SystemName.getValue()) { + systemNameBytes = lldptlv.getValue(); + break; + } + } + String nodeName = (systemNameBytes == null) ? nodeId + : new String(systemNameBytes, Charset.defaultCharset()); + Node srcNode = new Node(Node.NodeIDType.PRODUCTION, nodeName); + NodeConnector srcNodeConnector = NodeConnectorCreator.createNodeConnector( + NodeConnector.NodeConnectorIDType.PRODUCTION, portId, srcNode); + + Edge edge = null; + Set props = null; + edge = new Edge(srcNodeConnector, dstNodeConnector); + props = getProps(dstNodeConnector); + + updateProdEdge(edge, props); + } catch (Exception e) { + logger.warn("Caught exception ", e); + } + } + + /* + * Handle discovery frames generated by our controller + * + * @return true if it's a success + */ + private boolean processDiscoveryPacket(NodeConnector dstNodeConnector, Ethernet ethPkt) { + if ((dstNodeConnector == null) || (ethPkt == null)) { + logger.trace("Ignoring processing of discovery packet: Null node connector or packet"); + return false; + } + + logger.trace("Handle discovery packet {} from {}", ethPkt, dstNodeConnector); + + LLDP lldp = (LLDP) ethPkt.getPayload(); + + List optionalTLVList = lldp.getOptionalTLVList(); + if (optionalTLVList == null) { + logger.info("The discovery packet with null custom option from {}", dstNodeConnector); + return false; + } + + Node srcNode = null; + NodeConnector srcNodeConnector = null; + for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { + if (lldptlv.getType() == LLDPTLV.TLVType.Custom.getValue()) { + String ncString = LLDPTLV.getCustomString(lldptlv.getValue(), lldptlv.getLength()); + srcNodeConnector = NodeConnector.fromString(ncString); + if (srcNodeConnector != null) { + srcNode = srcNodeConnector.getNode(); + } + } + } + + if ((srcNode == null) || (srcNodeConnector == null)) { + logger.trace("Received non-controller generated discovery packet from {}", dstNodeConnector); + return false; + } + + // push it out to Topology + Edge edge = null; + Set props = null; + try { + edge = new Edge(srcNodeConnector, dstNodeConnector); + props = getProps(dstNodeConnector); + } catch (ConstructionException e) { + logger.error("Caught exception ", e); + } + addEdge(edge, props); + + logger.trace("Received discovery packet for Edge {}", edge); + + return true; + } + + public Map getPropMap(NodeConnector nodeConnector) { + if (nodeConnector == null) { + return null; + } + + if (inventoryProvider == null) { + return null; + } + + Map> props = inventoryProvider.getNodeConnectorProps(false); + if (props == null) { + return null; + } + + return props.get(nodeConnector); + } + + public Property getProp(NodeConnector nodeConnector, String propName) { + Map propMap = getPropMap(nodeConnector); + if (propMap == null) { + return null; + } + + Property prop = propMap.get(propName); + return prop; + } + + public Set getProps(NodeConnector nodeConnector) { + Map propMap = getPropMap(nodeConnector); + if (propMap == null) { + return null; + } + + Set props = new HashSet(propMap.values()); + return props; + } + + private boolean isEnabled(NodeConnector nodeConnector) { + if (nodeConnector == null) { + return false; + } + + Config config = (Config) getProp(nodeConnector, Config.ConfigPropName); + State state = (State) getProp(nodeConnector, State.StatePropName); + return ((config != null) && (config.getValue() == Config.ADMIN_UP) && (state != null) && (state.getValue() == State.EDGE_UP)); + } + + private boolean isTracked(NodeConnector nodeConnector) { + if (readyListHi.contains(nodeConnector)) { + return true; + } + + if (readyListLo.contains(nodeConnector)) { + return true; + } + + if (holdTime.keySet().contains(nodeConnector)) { + return true; + } + + if (stagingList.contains(nodeConnector)) { + return true; + } + + return false; + } + + private Set getWorkingSet() { + Set workingSet = new HashSet(); + Set removeSet = new HashSet(); + + for (NodeConnector nodeConnector : readyListHi) { + if (isOverLimit(workingSet.size())) { + break; + } + + workingSet.add(nodeConnector); + removeSet.add(nodeConnector); + + // Put it in the map and start the timer. It may need retry. + elapsedTime.put(nodeConnector, 0); + } + readyListHi.removeAll(removeSet); + + removeSet.clear(); + for (NodeConnector nodeConnector : readyListLo) { + if (isOverLimit(workingSet.size())) { + break; + } + + workingSet.add(nodeConnector); + removeSet.add(nodeConnector); + } + readyListLo.removeAll(removeSet); + + return workingSet; + } + + private Boolean isOverLimit(int size) { + return ((size >= discoveryBatchMaxPorts) && !throttling); + } + + private void addDiscovery() { + Map switches = controller.getSwitches(); + Set sidSet = switches.keySet(); + if (sidSet == null) { + return; + } + for (Long sid : sidSet) { + Node node = NodeCreator.createOFNode(sid); + addDiscovery(node); + } + } + + private void addDiscovery(Node node) { + Map switches = controller.getSwitches(); + ISwitch sw = switches.get(node.getID()); + List ports = sw.getEnabledPorts(); + if (ports == null) { + return; + } + for (OFPhysicalPort port : ports) { + NodeConnector nodeConnector = NodeConnectorCreator.createOFNodeConnector(port.getPortNumber(), node); + if (!readyListHi.contains(nodeConnector)) { + readyListHi.add(nodeConnector); + } + } + } + + private void addDiscovery(NodeConnector nodeConnector) { + if (isTracked(nodeConnector)) { + return; + } + + readyListHi.add(nodeConnector); + } + + private Set getRemoveSet(Collection c, Node node) { + Set removeSet = new HashSet(); + if (c == null) { + return removeSet; + } + for (NodeConnector nodeConnector : c) { + if (node.equals(nodeConnector.getNode())) { + Edge edge1 = edgeMap.get(nodeConnector); + if (edge1 != null) { + removeSet.add(nodeConnector); + + // check reverse direction + Edge edge2 = edgeMap.get(edge1.getTailNodeConnector()); + if ((edge2 != null) && node.equals(edge2.getTailNodeConnector().getNode())) { + removeSet.add(edge2.getHeadNodeConnector()); + } + } + } + } + return removeSet; + } + + private void removeDiscovery(Node node) { + Set removeSet; + + removeSet = getRemoveSet(readyListHi, node); + readyListHi.removeAll(removeSet); + + removeSet = getRemoveSet(readyListLo, node); + readyListLo.removeAll(removeSet); + + removeSet = getRemoveSet(stagingList, node); + stagingList.removeAll(removeSet); + + removeSet = getRemoveSet(holdTime.keySet(), node); + for (NodeConnector nodeConnector : removeSet) { + holdTime.remove(nodeConnector); + } + + removeSet = getRemoveSet(edgeMap.keySet(), node); + for (NodeConnector nodeConnector : removeSet) { + removeEdge(nodeConnector, false); + } + + removeSet = getRemoveSet(prodMap.keySet(), node); + for (NodeConnector nodeConnector : removeSet) { + removeProdEdge(nodeConnector); + } + } + + private void removeDiscovery(NodeConnector nodeConnector) { + readyListHi.remove(nodeConnector); + readyListLo.remove(nodeConnector); + stagingList.remove(nodeConnector); + holdTime.remove(nodeConnector); + removeEdge(nodeConnector, false); + removeProdEdge(nodeConnector); + } + + private void checkTimeout() { + Set removeSet = new HashSet(); + int ticks; + + Set monitorSet = holdTime.keySet(); + if (monitorSet != null) { + for (NodeConnector nodeConnector : monitorSet) { + ticks = holdTime.get(nodeConnector); + holdTime.put(nodeConnector, ++ticks); + if (ticks >= discoveryTimeoutTicks) { + // timeout the edge + removeSet.add(nodeConnector); + logger.trace("Discovery timeout {}", nodeConnector); + } + } + } + + for (NodeConnector nodeConnector : removeSet) { + removeEdge(nodeConnector); + } + + Set retrySet = new HashSet(); + Set ncSet = elapsedTime.keySet(); + if ((ncSet != null) && (ncSet.size() > 0)) { + for (NodeConnector nodeConnector : ncSet) { + ticks = elapsedTime.get(nodeConnector); + elapsedTime.put(nodeConnector, ++ticks); + if (ticks >= discoveryThresholdTicks) { + retrySet.add(nodeConnector); + } + } + + for (NodeConnector nodeConnector : retrySet) { + // Allow one more retry + readyListLo.add(nodeConnector); + elapsedTime.remove(nodeConnector); + if (connectionOutService.isLocal(nodeConnector.getNode())) { + transmitQ.add(nodeConnector); + } + } + } + } + + private void checkAging() { + if (!discoveryAgingEnabled) { + return; + } + + Set removeSet = new HashSet(); + int ticks; + + Set agingSet = agingMap.keySet(); + if (agingSet != null) { + for (NodeConnector nodeConnector : agingSet) { + ticks = agingMap.get(nodeConnector); + agingMap.put(nodeConnector, ++ticks); + if (ticks > discoveryAgeoutTicks) { + // age out the edge + removeSet.add(nodeConnector); + logger.trace("Discovery age out {}", nodeConnector); + } + } + } + + for (NodeConnector nodeConnector : removeSet) { + removeProdEdge(nodeConnector); + } + } + + private void doDiscovery() { + if (++discoveryTimerTickCount <= discoveryBatchPauseTicks) { + for (NodeConnector nodeConnector : getWorkingSet()) { + if (connectionOutService.isLocal(nodeConnector.getNode())) { + transmitQ.add(nodeConnector); + // Move to staging area after it's served + if (!stagingList.contains(nodeConnector)) { + stagingList.add(nodeConnector); + } + } + } + } else if (discoveryTimerTickCount >= discoveryBatchRestartTicks) { + discoveryTimerTickCount = 0; + for (NodeConnector nodeConnector : stagingList) { + if (!readyListLo.contains(nodeConnector)) { + readyListLo.add(nodeConnector); + } + } + stagingList.removeAll(readyListLo); + } + } + + private void doConsistencyCheck() { + if (!discoveryConsistencyCheckEnabled) { + return; + } + + if (++discoveryConsistencyCheckTickCount % getDiscoveryConsistencyCheckInterval() != 0) { + return; + } + + discoveryConsistencyCheckCallingTimes++; + + Set removeSet = new HashSet(); + Set ncSet = edgeMap.keySet(); + if (ncSet == null) { + return; + } + for (NodeConnector nodeConnector : ncSet) { + if (!isEnabled(nodeConnector)) { + removeSet.add(nodeConnector); + discoveryConsistencyCheckCorrected++; + logger.debug("ConsistencyChecker: remove disabled {}", nodeConnector); + continue; + } + + if (!isTracked(nodeConnector)) { + stagingList.add(nodeConnector); + discoveryConsistencyCheckCorrected++; + logger.debug("ConsistencyChecker: add back untracked {}", nodeConnector); + continue; + } + } + + for (NodeConnector nodeConnector : removeSet) { + removeEdge(nodeConnector, false); + } + + // remove stale entries + removeSet.clear(); + for (NodeConnector nodeConnector : stagingList) { + if (!isEnabled(nodeConnector)) { + removeSet.add(nodeConnector); + discoveryConsistencyCheckCorrected++; + logger.debug("ConsistencyChecker: remove disabled {}", nodeConnector); + } + } + stagingList.removeAll(removeSet); + + // Get a snapshot of all the existing switches + Map switches = this.controller.getSwitches(); + for (ISwitch sw : switches.values()) { + for (OFPhysicalPort port : sw.getEnabledPorts()) { + Node node = NodeCreator.createOFNode(sw.getId()); + NodeConnector nodeConnector = NodeConnectorCreator.createOFNodeConnector(port.getPortNumber(), node); + if (!isTracked(nodeConnector)) { + stagingList.add(nodeConnector); + discoveryConsistencyCheckCorrected++; + logger.debug("ConsistencyChecker: add back untracked {}", nodeConnector); + } + } + } + } + + private void addEdge(Edge edge, Set props) { + if (edge == null) { + return; + } + + NodeConnector src = edge.getTailNodeConnector(); + NodeConnector dst = edge.getHeadNodeConnector(); + if (!src.getType().equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) { + holdTime.put(dst, 0); + } else { + agingMap.put(dst, 0); + } + elapsedTime.remove(src); + + // notify + updateEdge(edge, UpdateType.ADDED, props); + logger.trace("Add edge {}", edge); + } + + /** + * Update Production Edge + * + * @param edge + * The Production Edge + * @param props + * Properties associated with the edge + */ + private void updateProdEdge(Edge edge, Set props) { + NodeConnector edgePort = edge.getHeadNodeConnector(); + + /* Do not update in case there is an existing OpenFlow link */ + if (edgeMap.get(edgePort) != null) { + logger.trace("Discarded edge {} since there is an existing OF link {}", edge, edgeMap.get(edgePort)); + return; + } + + /* Look for any existing Production Edge */ + Edge oldEdge = prodMap.get(edgePort); + if (oldEdge == null) { + /* Let's add a new one */ + addEdge(edge, props); + } else if (!edge.equals(oldEdge)) { + /* Remove the old one first */ + removeProdEdge(oldEdge.getHeadNodeConnector()); + /* Then add the new one */ + addEdge(edge, props); + } else { + /* o/w, just reset the aging timer */ + NodeConnector dst = edge.getHeadNodeConnector(); + agingMap.put(dst, 0); + } + } + + /** + * Remove Production Edge for a given edge port + * + * @param edgePort + * The OF edge port + */ + private void removeProdEdge(NodeConnector edgePort) { + agingMap.remove(edgePort); + + Edge edge = null; + Set prodKeySet = prodMap.keySet(); + if ((prodKeySet != null) && (prodKeySet.contains(edgePort))) { + edge = prodMap.get(edgePort); + prodMap.remove(edgePort); + } + + // notify Topology + if (this.discoveryListener != null) { + this.discoveryListener.notifyEdge(edge, UpdateType.REMOVED, null); + } + logger.trace("Remove edge {}", edge); + } + + /* + * Remove OpenFlow edge + */ + private void removeEdge(NodeConnector nodeConnector, boolean stillEnabled) { + holdTime.remove(nodeConnector); + readyListLo.remove(nodeConnector); + readyListHi.remove(nodeConnector); + + if (stillEnabled) { + // keep discovering + if (!stagingList.contains(nodeConnector)) { + stagingList.add(nodeConnector); + } + } else { + // stop it + stagingList.remove(nodeConnector); + } + + Edge edge = null; + Set edgeKeySet = edgeMap.keySet(); + if ((edgeKeySet != null) && (edgeKeySet.contains(nodeConnector))) { + edge = edgeMap.get(nodeConnector); + edgeMap.remove(nodeConnector); + } + + // notify Topology + if (this.discoveryListener != null) { + this.discoveryListener.notifyEdge(edge, UpdateType.REMOVED, null); + } + logger.trace("Remove {}", nodeConnector); + } + + private void removeEdge(NodeConnector nodeConnector) { + removeEdge(nodeConnector, isEnabled(nodeConnector)); + } + + private void updateEdge(Edge edge, UpdateType type, Set props) { + if (discoveryListener == null) { + return; + } + + this.discoveryListener.notifyEdge(edge, type, props); + + NodeConnector src = edge.getTailNodeConnector(), dst = edge.getHeadNodeConnector(); + if (!src.getType().equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) { + if (type == UpdateType.ADDED) { + edgeMap.put(dst, edge); + } else { + edgeMap.remove(dst); + } + } else { + /* + * Save Production edge into different DB keyed by the Edge port + */ + if (type == UpdateType.ADDED) { + prodMap.put(dst, edge); + } else { + prodMap.remove(dst); + } + } + } + + private void moveToReadyListHi(NodeConnector nodeConnector) { + if (readyListLo.contains(nodeConnector)) { + readyListLo.remove(nodeConnector); + } else if (stagingList.contains(nodeConnector)) { + stagingList.remove(nodeConnector); + } + readyListHi.add(nodeConnector); + } + + private void registerWithOSGIConsole() { + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext(); + bundleContext.registerService(CommandProvider.class.getName(), this, null); + } + + private int getDiscoveryConsistencyCheckInterval() { + return discoveryConsistencyCheckMultiple * discoveryBatchRestartTicks; + } + + @Override + public String getHelp() { + StringBuffer help = new StringBuffer(); + help.append("---Topology Discovery---\n"); + help.append("\t prlh - Print readyListHi entries\n"); + help.append("\t prll - Print readyListLo entries\n"); + help.append("\t psl - Print stagingList entries\n"); + help.append("\t pht - Print hold time\n"); + help.append("\t pet - Print elapsed time\n"); + help.append("\t ptick - Print tick time in msec\n"); + help.append("\t pcc - Print CC info\n"); + help.append("\t psize - Print sizes of all the lists\n"); + help.append("\t ptm - Print timeout info\n"); + help.append("\t ecc - Enable CC\n"); + help.append("\t dcc - Disable CC\n"); + help.append("\t scc [multiple] - Set/show CC multiple and interval\n"); + help.append("\t sports [ports] - Set/show max ports per batch\n"); + help.append("\t spause [ticks] - Set/show pause period\n"); + help.append("\t sdi [ticks] - Set/show discovery interval in ticks\n"); + help.append("\t addsw - Add a switch\n"); + help.append("\t remsw - Remove a switch\n"); + help.append("\t page - Print aging info\n"); + help.append("\t sage - Set/Show aging time limit\n"); + help.append("\t eage - Enable aging\n"); + help.append("\t dage - Disable aging\n"); + help.append("\t pthrot - Print throttling\n"); + help.append("\t ethrot - Enable throttling\n"); + help.append("\t dthrot - Disable throttling\n"); + help.append("\t psnp - Print LLDP snooping\n"); + help.append("\t esnp - Enable LLDP snooping\n"); + help.append("\t dsnp - Disable LLDP snooping\n"); + return help.toString(); + } + + private List sortList(Collection ncs) { + List ncStrArray = new ArrayList(); + for (NodeConnector nc : ncs) { + ncStrArray.add(nc.toString()); + } + Collections.sort(ncStrArray); + + List sortedNodeConnectors = new ArrayList(); + for (String ncStr : ncStrArray) { + sortedNodeConnectors.add(NodeConnector.fromString(ncStr)); + } + + return sortedNodeConnectors; + } + + public void _prlh(CommandInterpreter ci) { + ci.println("readyListHi\n"); + for (NodeConnector nodeConnector : sortList(readyListHi)) { + if (nodeConnector == null) { + continue; + } + ci.println(nodeConnector); + } + ci.println("Total number of Node Connectors: " + readyListHi.size()); + } + + public void _prll(CommandInterpreter ci) { + ci.println("readyListLo\n"); + for (NodeConnector nodeConnector : sortList(readyListLo)) { + if (nodeConnector == null) { + continue; + } + ci.println(nodeConnector); + } + ci.println("Total number of Node Connectors: " + readyListLo.size()); + } + + public void _psl(CommandInterpreter ci) { + ci.println("stagingList\n"); + for (NodeConnector nodeConnector : sortList(stagingList)) { + if (nodeConnector == null) { + continue; + } + ci.println(nodeConnector); + } + ci.println("Total number of Node Connectors: " + stagingList.size()); + } + + public void _pht(CommandInterpreter ci) { + ci.println(" NodeConnector Last rx LLDP (sec)"); + for (ConcurrentMap.Entry entry: holdTime.entrySet()) { + ci.println(entry.getKey() + "\t\t" + entry.getValue() * (discoveryTimerTick / 1000)); + } + ci.println("\nSize: " + holdTime.size() + "\tTimeout: " + discoveryTimeoutTicks * (discoveryTimerTick / 1000) + + " sec"); + } + + public void _pet(CommandInterpreter ci) { + ci.println(" NodeConnector Elapsed Time (sec)"); + for (ConcurrentMap.Entry entry: elapsedTime.entrySet()) { + ci.println(entry.getKey() + "\t\t" + entry.getValue() * (discoveryTimerTick / 1000)); + } + ci.println("\nSize: " + elapsedTime.size() + "\tThreshold: " + DiscoveryPeriod.THRESHOLD.getTime() + " sec"); + } + + public void _ptick(CommandInterpreter ci) { + ci.println("Current timer is " + discoveryTimerTick + " msec per tick"); + } + + public void _pcc(CommandInterpreter ci) { + if (discoveryConsistencyCheckEnabled) { + ci.println("ConsistencyChecker is currently enabled"); + } else { + ci.println("ConsistencyChecker is currently disabled"); + } + ci.println("Interval " + getDiscoveryConsistencyCheckInterval()); + ci.println("Multiple " + discoveryConsistencyCheckMultiple); + ci.println("Number of times called " + discoveryConsistencyCheckCallingTimes); + ci.println("Corrected count " + discoveryConsistencyCheckCorrected); + } + + public void _ptm(CommandInterpreter ci) { + ci.println("Timeout " + discoveryTimeoutTicks + " ticks, " + discoveryTimerTick / 1000 + " sec per tick."); + } + + public void _psize(CommandInterpreter ci) { + ci.println("readyListLo size " + readyListLo.size() + "\n" + "readyListHi size " + readyListHi.size() + "\n" + + "stagingList size " + stagingList.size() + "\n" + "holdTime size " + holdTime.size() + "\n" + + "edgeMap size " + edgeMap.size() + "\n" + "prodMap size " + prodMap.size() + "\n" + "agingMap size " + + agingMap.size() + "\n" + "elapsedTime size " + elapsedTime.size()); + } + + public void _page(CommandInterpreter ci) { + if (this.discoveryAgingEnabled) { + ci.println("Aging is enabled"); + } else { + ci.println("Aging is disabled"); + } + ci.println("Current aging time limit " + this.discoveryAgeoutTicks); + ci.println("\n"); + ci.println(" Edge Aging "); + Collection prodSet = prodMap.values(); + if (prodSet == null) { + return; + } + for (Edge edge : prodSet) { + Integer aging = agingMap.get(edge.getHeadNodeConnector()); + if (aging != null) { + ci.println(edge + " " + aging); + } + } + ci.println("\n"); + ci.println(" NodeConnector Edge "); + Set keySet = prodMap.keySet(); + if (keySet == null) { + return; + } + for (NodeConnector nc : keySet) { + ci.println(nc + " " + prodMap.get(nc)); + } + return; + } + + public void _sage(CommandInterpreter ci) { + String val = ci.nextArgument(); + if (val == null) { + ci.println("Please enter aging time limit. Current value " + this.discoveryAgeoutTicks); + return; + } + try { + this.discoveryAgeoutTicks = Integer.parseInt(val); + } catch (Exception e) { + ci.println("Please enter a valid number"); + } + return; + } + + public void _eage(CommandInterpreter ci) { + this.discoveryAgingEnabled = true; + ci.println("Aging is enabled"); + return; + } + + public void _dage(CommandInterpreter ci) { + this.discoveryAgingEnabled = false; + ci.println("Aging is disabled"); + return; + } + + public void _scc(CommandInterpreter ci) { + String val = ci.nextArgument(); + if (val == null) { + ci.println("Please enter CC multiple. Current multiple " + discoveryConsistencyCheckMultiple + + " (interval " + getDiscoveryConsistencyCheckInterval() + ") calling times " + + discoveryConsistencyCheckCallingTimes); + return; + } + try { + discoveryConsistencyCheckMultiple = Integer.parseInt(val); + } catch (Exception e) { + ci.println("Please enter a valid number"); + } + return; + } + + public void _ecc(CommandInterpreter ci) { + this.discoveryConsistencyCheckEnabled = true; + ci.println("ConsistencyChecker is enabled"); + return; + } + + public void _dcc(CommandInterpreter ci) { + this.discoveryConsistencyCheckEnabled = false; + ci.println("ConsistencyChecker is disabled"); + return; + } + + public void _psnp(CommandInterpreter ci) { + if (this.discoverySnoopingEnabled) { + ci.println("Discovery snooping is globally enabled"); + } else { + ci.println("Discovery snooping is globally disabled"); + } + + ci.println("\nDiscovery snooping is locally disabled on these ports"); + for (NodeConnector nodeConnector : discoverySnoopingDisableList) { + ci.println(nodeConnector); + } + return; + } + + public void _esnp(CommandInterpreter ci) { + String val = ci.nextArgument(); + + if (val == null) { + ci.println("Usage: esnp "); + } else if (val.equalsIgnoreCase("all")) { + this.discoverySnoopingEnabled = true; + ci.println("Discovery snooping is globally enabled"); + } else { + NodeConnector nodeConnector = NodeConnector.fromString(val); + if (nodeConnector != null) { + discoverySnoopingDisableList.remove(nodeConnector); + ci.println("Discovery snooping is locally enabled on port " + nodeConnector); + } else { + ci.println("Entered invalid NodeConnector " + val); + } + } + return; + } + + public void _dsnp(CommandInterpreter ci) { + String val = ci.nextArgument(); + + if (val == null) { + ci.println("Usage: dsnp "); + } else if (val.equalsIgnoreCase("all")) { + this.discoverySnoopingEnabled = false; + ci.println("Discovery snooping is globally disabled"); + } else { + NodeConnector nodeConnector = NodeConnector.fromString(val); + if (nodeConnector != null) { + discoverySnoopingDisableList.add(nodeConnector); + ci.println("Discovery snooping is locally disabled on port " + nodeConnector); + } else { + ci.println("Entered invalid NodeConnector " + val); + } + } + return; + } + + public void _spause(CommandInterpreter ci) { + String val = ci.nextArgument(); + String out = "Please enter pause period less than " + discoveryBatchRestartTicks + ". Current pause period is " + + discoveryBatchPausePeriod + " ticks, pause at " + discoveryBatchPauseTicks + " ticks, " + + discoveryTimerTick / 1000 + " sec per tick."; + + if (val != null) { + try { + int pause = Integer.parseInt(val); + if (pause < discoveryBatchRestartTicks) { + discoveryBatchPausePeriod = pause; + discoveryBatchPauseTicks = getDiscoveryPauseInterval(); + return; + } + } catch (Exception e) { + } + } + + ci.println(out); + } + + public void _sdi(CommandInterpreter ci) { + String val = ci.nextArgument(); + String out = "Please enter discovery interval in ticks. Current value is " + discoveryBatchRestartTicks + " ticks, " + + discoveryTimerTick / 1000 + " sec per tick."; + + if (val != null) { + try { + int ticks = Integer.parseInt(val); + DiscoveryPeriod.INTERVAL.setTick(ticks); + discoveryBatchRestartTicks = getDiscoveryInterval(); + discoveryBatchPauseTicks = getDiscoveryPauseInterval(); + discoveryTimeoutTicks = getDiscoveryTimeout(); + return; + } catch (Exception e) { + } + } + ci.println(out); + } + + public void _sports(CommandInterpreter ci) { + String val = ci.nextArgument(); + if (val == null) { + ci.println("Please enter max ports per batch. Current value is " + discoveryBatchMaxPorts); + return; + } + try { + discoveryBatchMaxPorts = Integer.parseInt(val); + } catch (Exception e) { + ci.println("Please enter a valid number"); + } + return; + } + + public void _addsw(CommandInterpreter ci) { + String val = ci.nextArgument(); + Long sid; + try { + sid = Long.parseLong(val); + Node node = NodeCreator.createOFNode(sid); + addDiscovery(node); + } catch (Exception e) { + ci.println("Please enter a valid number"); + } + return; + } + + public void _remsw(CommandInterpreter ci) { + String val = ci.nextArgument(); + Long sid; + try { + sid = Long.parseLong(val); + Node node = NodeCreator.createOFNode(sid); + removeDiscovery(node); + } catch (Exception e) { + ci.println("Please enter a valid number"); + } + return; + } + + public void _pthrot(CommandInterpreter ci) { + if (this.throttling) { + ci.println("Throttling is enabled"); + } else { + ci.println("Throttling is disabled"); + } + } + + public void _ethrot(CommandInterpreter ci) { + this.throttling = true; + ci.println("Throttling is enabled"); + return; + } + + public void _dthrot(CommandInterpreter ci) { + this.throttling = false; + ci.println("Throttling is disabled"); + return; + } + + @Override + public void updateNode(Node node, UpdateType type, Set props) { + switch (type) { + case ADDED: + addNode(node, props); + break; + case REMOVED: + removeNode(node); + break; + default: + break; + } + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, UpdateType type, Set props) { + Config config = null; + State state = null; + boolean enabled = false; + + for (Property prop : props) { + if (prop.getName().equals(Config.ConfigPropName)) { + config = (Config) prop; + } else if (prop.getName().equals(State.StatePropName)) { + state = (State) prop; + } + } + enabled = ((config != null) && (config.getValue() == Config.ADMIN_UP) && (state != null) && (state.getValue() == State.EDGE_UP)); + + switch (type) { + case ADDED: + if (enabled) { + addDiscovery(nodeConnector); + logger.trace("ADDED enabled {}", nodeConnector); + } else { + logger.trace("ADDED disabled {}", nodeConnector); + } + break; + case CHANGED: + if (enabled) { + addDiscovery(nodeConnector); + logger.trace("CHANGED enabled {}", nodeConnector); + } else { + removeDiscovery(nodeConnector); + logger.trace("CHANGED disabled {}", nodeConnector); + } + break; + case REMOVED: + removeDiscovery(nodeConnector); + logger.trace("REMOVED enabled {}", nodeConnector); + break; + default: + return; + } + } + + public void addNode(Node node, Set props) { + if (node == null) { + return; + } + + addDiscovery(node); + } + + public void removeNode(Node node) { + if (node == null) { + return; + } + + removeDiscovery(node); + } + + void setController(IController s) { + this.controller = s; + } + + void unsetController(IController s) { + if (this.controller == s) { + this.controller = null; + } + } + + public void setInventoryProvider(IInventoryProvider service) { + this.inventoryProvider = service; + } + + public void unsetInventoryProvider(IInventoryProvider service) { + this.inventoryProvider = null; + } + + public void setIDataPacketMux(IDataPacketMux service) { + this.iDataPacketMux = service; + } + + public void unsetIDataPacketMux(IDataPacketMux service) { + if (this.iDataPacketMux == service) { + this.iDataPacketMux = null; + } + } + + void setDiscoveryListener(IDiscoveryListener s) { + this.discoveryListener = s; + } + + void unsetDiscoveryListener(IDiscoveryListener s) { + if (this.discoveryListener == s) { + this.discoveryListener = null; + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + + private void initDiscoveryPacket() { + // Create LLDP ChassisID TLV + chassisIdTlv = new LLDPTLV(); + chassisIdTlv.setType(LLDPTLV.TLVType.ChassisID.getValue()); + + // Create LLDP PortID TLV + portIdTlv = new LLDPTLV(); + portIdTlv.setType(LLDPTLV.TLVType.PortID.getValue()); + + // Create LLDP TTL TLV + byte[] ttl = new byte[] { (byte) 0, (byte) 120 }; + ttlTlv = new LLDPTLV(); + ttlTlv.setType(LLDPTLV.TLVType.TTL.getValue()).setLength((short) ttl.length).setValue(ttl); + + customTlv = new LLDPTLV(); + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + logger.trace("Init called"); + + transmitQ = new LinkedBlockingQueue(); + + readyListHi = new CopyOnWriteArrayList(); + readyListLo = new CopyOnWriteArrayList(); + stagingList = new CopyOnWriteArrayList(); + holdTime = new ConcurrentHashMap(); + elapsedTime = new ConcurrentHashMap(); + edgeMap = new ConcurrentHashMap(); + agingMap = new ConcurrentHashMap(); + prodMap = new ConcurrentHashMap(); + discoverySnoopingDisableList = new CopyOnWriteArrayList(); + + discoveryBatchRestartTicks = getDiscoveryInterval(); + discoveryBatchPauseTicks = getDiscoveryPauseInterval(); + discoveryTimeoutTicks = getDiscoveryTimeout(); + discoveryThresholdTicks = getDiscoveryThreshold(); + discoveryAgeoutTicks = getDiscoveryAgeout(); + discoveryConsistencyCheckTickCount = discoveryBatchPauseTicks; + discoveryBatchMaxPorts = getDiscoveryBatchMaxPorts(); + + discoveryTimer = new Timer("DiscoveryService"); + discoveryTimerTask = new DiscoveryTimerTask(); + + transmitThread = new Thread(new DiscoveryTransmit(transmitQ)); + + initDiscoveryPacket(); + + registerWithOSGIConsole(); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + transmitQ = null; + readyListHi = null; + readyListLo = null; + stagingList = null; + holdTime = null; + edgeMap = null; + agingMap = null; + prodMap = null; + discoveryTimer = null; + discoveryTimerTask = null; + transmitThread = null; + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + discoveryTimer.schedule(discoveryTimerTask, discoveryTimerTick, discoveryTimerTick); + transmitThread.start(); + } + + /** + * Function called after registering the service in OSGi service registry. + */ + void started() { + /* get a snapshot of all the existing switches */ + addDiscovery(); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + shuttingDown = true; + discoveryTimer.cancel(); + transmitThread.interrupt(); + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, short newTag, UpdateType t) { + } + + @Override + public void containerFlowUpdated(String containerName, ContainerFlow previousFlow, ContainerFlow currentFlow, + UpdateType t) { + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector p, UpdateType t) { + switch (t) { + case ADDED: + moveToReadyListHi(p); + break; + default: + break; + } + } + + @Override + public void containerModeUpdated(UpdateType t) { + // do nothing + } + + private byte[] getSourceMACFromNodeID(String nodeId) { + byte[] cid = HexEncode.bytesFromHexString(nodeId); + byte[] sourceMac = new byte[6]; + int pos = cid.length - sourceMac.length; + + if (pos >= 0) { + System.arraycopy(cid, pos, sourceMac, 0, sourceMac.length); + } + + return sourceMac; + } + + private int getDiscoveryTicks(DiscoveryPeriod dp, String val) { + if (dp == null) { + return 0; + } + + if (val != null) { + try { + dp.setTime(Integer.parseInt(val)); + } catch (Exception e) { + } + } + + return dp.getTick(); + } + + /** + * This method returns the interval which determines how often the discovery + * packets will be sent. + * + * @return The discovery interval in ticks + */ + private int getDiscoveryInterval() { + String intvl = System.getProperty("of.discoveryInterval"); + return getDiscoveryTicks(DiscoveryPeriod.INTERVAL, intvl); + } + + /** + * This method returns the timeout value in receiving subsequent discovery packets on a port. + * + * @return The discovery timeout in ticks + */ + private int getDiscoveryTimeout() { + String val = System.getProperty("of.discoveryTimeoutMultiple"); + int multiple = 2; + + if (val != null) { + try { + multiple = Integer.parseInt(val); + } catch (Exception e) { + } + } + return getDiscoveryInterval() * multiple + 3; + } + + /** + * This method returns the user configurable threshold value + * + * @return The discovery threshold value in ticks + */ + private int getDiscoveryThreshold() { + String val = System.getProperty("of.discoveryThreshold"); + return getDiscoveryTicks(DiscoveryPeriod.THRESHOLD, val); + } + + /** + * This method returns the discovery entry aging time in ticks. + * + * @return The aging time in ticks + */ + private int getDiscoveryAgeout() { + return getDiscoveryTicks(DiscoveryPeriod.AGEOUT, null); + } + + /** + * This method returns the pause interval + * + * @return The pause interval in ticks + */ + private int getDiscoveryPauseInterval() { + if (discoveryBatchRestartTicks > discoveryBatchPausePeriod) { + return discoveryBatchRestartTicks - discoveryBatchPausePeriod; + } else { + return discoveryBatchRestartTicks - 1; + } + } + + /** + * This method returns the user configurable maximum number of ports handled + * in one discovery batch. + * + * @return The maximum number of ports + */ + private int getDiscoveryBatchMaxPorts() { + String val = System.getProperty("of.discoveryBatchMaxPorts"); + int ports = 1024; + + if (val != null) { + try { + ports = Integer.parseInt(val); + } catch (Exception e) { + } + } + return ports; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowConverter.java new file mode 100644 index 0000000000..cad6d870e6 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowConverter.java @@ -0,0 +1,755 @@ +/* + * 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.internal; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import org.opendaylight.controller.sal.action.Action; +import org.opendaylight.controller.sal.action.ActionType; +import org.opendaylight.controller.sal.action.Controller; +import org.opendaylight.controller.sal.action.Drop; +import org.opendaylight.controller.sal.action.Flood; +import org.opendaylight.controller.sal.action.FloodAll; +import org.opendaylight.controller.sal.action.HwPath; +import org.opendaylight.controller.sal.action.Loopback; +import org.opendaylight.controller.sal.action.Output; +import org.opendaylight.controller.sal.action.PopVlan; +import org.opendaylight.controller.sal.action.SetDlDst; +import org.opendaylight.controller.sal.action.SetDlSrc; +import org.opendaylight.controller.sal.action.SetNwDst; +import org.opendaylight.controller.sal.action.SetNwSrc; +import org.opendaylight.controller.sal.action.SetNwTos; +import org.opendaylight.controller.sal.action.SetTpDst; +import org.opendaylight.controller.sal.action.SetTpSrc; +import org.opendaylight.controller.sal.action.SetVlanId; +import org.opendaylight.controller.sal.action.SetVlanPcp; +import org.opendaylight.controller.sal.action.SwPath; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.match.Match; +import org.opendaylight.controller.sal.match.MatchField; +import org.opendaylight.controller.sal.match.MatchType; +import org.opendaylight.controller.sal.utils.NetUtils; +import org.opendaylight.controller.sal.utils.NodeConnectorCreator; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6FlowMod; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6Match; +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPacketOut; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFVendor; +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.action.OFActionDataLayer; +import org.openflow.protocol.action.OFActionDataLayerDestination; +import org.openflow.protocol.action.OFActionDataLayerSource; +import org.openflow.protocol.action.OFActionNetworkLayerAddress; +import org.openflow.protocol.action.OFActionNetworkLayerDestination; +import org.openflow.protocol.action.OFActionNetworkLayerSource; +import org.openflow.protocol.action.OFActionNetworkTypeOfService; +import org.openflow.protocol.action.OFActionOutput; +import org.openflow.protocol.action.OFActionStripVirtualLan; +import org.openflow.protocol.action.OFActionTransportLayer; +import org.openflow.protocol.action.OFActionTransportLayerDestination; +import org.openflow.protocol.action.OFActionTransportLayerSource; +import org.openflow.protocol.action.OFActionVirtualLanIdentifier; +import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint; +import org.openflow.util.U16; +import org.openflow.util.U32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for converting a SAL Flow into the OF flow and vice-versa + */ +public class FlowConverter { + protected static final Logger logger = LoggerFactory + .getLogger(FlowConverter.class); + + /* + * The value 0xffff (OFP_VLAN_NONE) is used to indicate + * that no VLAN ID is set for OF Flow. + */ + private static final short OFP_VLAN_NONE = (short) 0xffff; + + private Flow flow; // SAL Flow + private OFMatch ofMatch; // OF 1.0 match or OF 1.0 + IPv6 extension match + private List actionsList; // OF 1.0 actions + private int actionsLength; + private boolean isIPv6; + + public FlowConverter(OFMatch ofMatch, List actionsList) { + this.ofMatch = ofMatch; + this.actionsList = actionsList; + this.actionsLength = 0; + this.flow = null; + this.isIPv6 = ofMatch instanceof V6Match; + } + + public FlowConverter(Flow flow) { + this.ofMatch = null; + this.actionsList = null; + this.actionsLength = 0; + this.flow = flow; + this.isIPv6 = flow.isIPv6(); + } + + /** + * Returns the match in OF 1.0 (OFMatch) form or OF 1.0 + IPv6 extensions + * form (V6Match) + * + * @return + */ + public OFMatch getOFMatch() { + if (ofMatch == null) { + Match match = flow.getMatch(); + ofMatch = (isIPv6) ? new V6Match() : new OFMatch(); + + int wildcards = OFMatch.OFPFW_ALL; + if (match.isPresent(MatchType.IN_PORT)) { + short port = (Short) ((NodeConnector) match.getField( + MatchType.IN_PORT).getValue()).getID(); + if (!isIPv6) { + ofMatch.setInputPort(port); + wildcards &= ~OFMatch.OFPFW_IN_PORT; + } else { + ((V6Match) ofMatch).setInputPort(port, (short) 0); + } + } + if (match.isPresent(MatchType.DL_SRC)) { + byte[] srcMac = (byte[]) match.getField(MatchType.DL_SRC) + .getValue(); + if (!isIPv6) { + ofMatch.setDataLayerSource(srcMac.clone()); + wildcards &= ~OFMatch.OFPFW_DL_SRC; + } else { + ((V6Match) ofMatch).setDataLayerSource(srcMac, null); + } + } + if (match.isPresent(MatchType.DL_DST)) { + byte[] dstMac = (byte[]) match.getField(MatchType.DL_DST) + .getValue(); + if (!isIPv6) { + ofMatch.setDataLayerDestination(dstMac.clone()); + wildcards &= ~OFMatch.OFPFW_DL_DST; + } else { + ((V6Match) ofMatch).setDataLayerDestination(dstMac, null); + } + } + if (match.isPresent(MatchType.DL_VLAN)) { + short vlan = (Short) match.getField(MatchType.DL_VLAN) + .getValue(); + if (vlan == MatchType.DL_VLAN_NONE) { + vlan = OFP_VLAN_NONE; + } + if (!isIPv6) { + ofMatch.setDataLayerVirtualLan(vlan); + wildcards &= ~OFMatch.OFPFW_DL_VLAN; + } else { + ((V6Match) ofMatch).setDataLayerVirtualLan(vlan, (short) 0); + } + } + if (match.isPresent(MatchType.DL_VLAN_PR)) { + byte vlanPr = (Byte) match.getField(MatchType.DL_VLAN_PR) + .getValue(); + if (!isIPv6) { + ofMatch.setDataLayerVirtualLanPriorityCodePoint(vlanPr); + wildcards &= ~OFMatch.OFPFW_DL_VLAN_PCP; + } else { + ((V6Match) ofMatch) + .setDataLayerVirtualLanPriorityCodePoint(vlanPr, + (byte) 0); + } + } + if (match.isPresent(MatchType.DL_TYPE)) { + short ethType = (Short) match.getField(MatchType.DL_TYPE) + .getValue(); + if (!isIPv6) { + ofMatch.setDataLayerType(ethType); + wildcards &= ~OFMatch.OFPFW_DL_TYPE; + } else { + ((V6Match) ofMatch).setDataLayerType(ethType, (short) 0); + } + } + if (match.isPresent(MatchType.NW_TOS)) { + /* + * OF 1.0 switch expects the TOS as the 6 msb in the byte. it is + * actually the DSCP field followed by a zero ECN + */ + byte tos = (Byte) match.getField(MatchType.NW_TOS).getValue(); + byte dscp = (byte) (tos << 2); + if (!isIPv6) { + ofMatch.setNetworkTypeOfService(dscp); + wildcards &= ~OFMatch.OFPFW_NW_TOS; + } else { + ((V6Match) ofMatch).setNetworkTypeOfService(dscp, (byte) 0); + } + } + if (match.isPresent(MatchType.NW_PROTO)) { + byte proto = (Byte) match.getField(MatchType.NW_PROTO) + .getValue(); + if (!isIPv6) { + ofMatch.setNetworkProtocol(proto); + wildcards &= ~OFMatch.OFPFW_NW_PROTO; + } else { + ((V6Match) ofMatch).setNetworkProtocol(proto, (byte) 0); + } + } + if (match.isPresent(MatchType.NW_SRC)) { + InetAddress address = (InetAddress) match.getField(MatchType.NW_SRC).getValue(); + InetAddress mask = (InetAddress) match.getField(MatchType.NW_SRC).getMask(); + if (!isIPv6) { + ofMatch.setNetworkSource(NetUtils.byteArray4ToInt(address.getAddress())); + int maskLength = (mask == null) ? 32 : NetUtils.getSubnetMaskLength(mask); + wildcards = (wildcards & ~OFMatch.OFPFW_NW_SRC_MASK) | ((32 - maskLength) << OFMatch.OFPFW_NW_SRC_SHIFT); + } else { + ((V6Match) ofMatch).setNetworkSource(address, mask); + } + } + if (match.isPresent(MatchType.NW_DST)) { + InetAddress address = (InetAddress) match.getField(MatchType.NW_DST).getValue(); + InetAddress mask = (InetAddress) match.getField(MatchType.NW_DST).getMask(); + if (!isIPv6) { + ofMatch.setNetworkDestination(NetUtils.byteArray4ToInt(address.getAddress())); + int maskLength = (mask == null) ? 32 : NetUtils.getSubnetMaskLength(mask); + wildcards = (wildcards & ~OFMatch.OFPFW_NW_DST_MASK) | ((32 - maskLength) << OFMatch.OFPFW_NW_DST_SHIFT); + } else { + ((V6Match) ofMatch).setNetworkDestination(address, mask); + } + } + if (match.isPresent(MatchType.TP_SRC)) { + short port = (Short) match.getField(MatchType.TP_SRC) + .getValue(); + if (!isIPv6) { + ofMatch.setTransportSource(port); + wildcards &= ~OFMatch.OFPFW_TP_SRC; + } else { + ((V6Match) ofMatch).setTransportSource(port, (short) 0); + } + } + if (match.isPresent(MatchType.TP_DST)) { + short port = (Short) match.getField(MatchType.TP_DST) + .getValue(); + if (!isIPv6) { + ofMatch.setTransportDestination(port); + wildcards &= ~OFMatch.OFPFW_TP_DST; + } else { + ((V6Match) ofMatch) + .setTransportDestination(port, (short) 0); + } + } + + if (!isIPv6) { + ofMatch.setWildcards(U32.t(Long.valueOf(wildcards))); + } + } + logger.trace("SAL Match: {} Openflow Match: {}", flow.getMatch(), + ofMatch); + return ofMatch; + } + + /** + * Returns the list of actions in OF 1.0 form + * + * @return + */ + public List getOFActions() { + if (this.actionsList == null) { + actionsList = new ArrayList(); + for (Action action : flow.getActions()) { + if (action.getType() == ActionType.OUTPUT) { + Output a = (Output) action; + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setMaxLength((short) 0xffff); + ofAction.setPort(PortConverter.toOFPort(a.getPort())); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.DROP) { + continue; + } + if (action.getType() == ActionType.LOOPBACK) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_IN_PORT.getValue()); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.FLOOD) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_FLOOD.getValue()); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.FLOOD_ALL) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_ALL.getValue()); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.CONTROLLER) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_CONTROLLER.getValue()); + // We want the whole frame hitting the match be sent to the + // controller + ofAction.setMaxLength((short) 0xffff); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SW_PATH) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_LOCAL.getValue()); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.HW_PATH) { + OFActionOutput ofAction = new OFActionOutput(); + ofAction.setPort(OFPort.OFPP_NORMAL.getValue()); + actionsList.add(ofAction); + actionsLength += OFActionOutput.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_VLAN_ID) { + SetVlanId a = (SetVlanId) action; + OFActionVirtualLanIdentifier ofAction = new OFActionVirtualLanIdentifier(); + ofAction.setVirtualLanIdentifier((short) a.getVlanId()); + actionsList.add(ofAction); + actionsLength += OFActionVirtualLanIdentifier.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_VLAN_PCP) { + SetVlanPcp a = (SetVlanPcp) action; + OFActionVirtualLanPriorityCodePoint ofAction = new OFActionVirtualLanPriorityCodePoint(); + ofAction.setVirtualLanPriorityCodePoint(Integer.valueOf( + a.getPcp()).byteValue()); + actionsList.add(ofAction); + actionsLength += OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.POP_VLAN) { + OFActionStripVirtualLan ofAction = new OFActionStripVirtualLan(); + actionsList.add(ofAction); + actionsLength += OFActionStripVirtualLan.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_DL_SRC) { + SetDlSrc a = (SetDlSrc) action; + OFActionDataLayerSource ofAction = new OFActionDataLayerSource(); + ofAction.setDataLayerAddress(a.getDlAddress()); + actionsList.add(ofAction); + actionsLength += OFActionDataLayer.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_DL_DST) { + SetDlDst a = (SetDlDst) action; + OFActionDataLayerDestination ofAction = new OFActionDataLayerDestination(); + ofAction.setDataLayerAddress(a.getDlAddress()); + actionsList.add(ofAction); + actionsLength += OFActionDataLayer.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_NW_SRC) { + SetNwSrc a = (SetNwSrc) action; + OFActionNetworkLayerSource ofAction = new OFActionNetworkLayerSource(); + ofAction.setNetworkAddress(NetUtils.byteArray4ToInt(a + .getAddress().getAddress())); + actionsList.add(ofAction); + actionsLength += OFActionNetworkLayerAddress.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_NW_DST) { + SetNwDst a = (SetNwDst) action; + OFActionNetworkLayerDestination ofAction = new OFActionNetworkLayerDestination(); + ofAction.setNetworkAddress(NetUtils.byteArray4ToInt(a + .getAddress().getAddress())); + actionsList.add(ofAction); + actionsLength += OFActionNetworkLayerAddress.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_NW_TOS) { + SetNwTos a = (SetNwTos) action; + OFActionNetworkTypeOfService ofAction = new OFActionNetworkTypeOfService(); + ofAction.setNetworkTypeOfService(Integer.valueOf( + a.getNwTos()).byteValue()); + actionsList.add(ofAction); + actionsLength += OFActionNetworkTypeOfService.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_TP_SRC) { + SetTpSrc a = (SetTpSrc) action; + OFActionTransportLayerSource ofAction = new OFActionTransportLayerSource(); + ofAction.setTransportPort(Integer.valueOf(a.getPort()) + .shortValue()); + actionsList.add(ofAction); + actionsLength += OFActionTransportLayer.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_TP_DST) { + SetTpDst a = (SetTpDst) action; + OFActionTransportLayerDestination ofAction = new OFActionTransportLayerDestination(); + ofAction.setTransportPort(Integer.valueOf(a.getPort()) + .shortValue()); + actionsList.add(ofAction); + actionsLength += OFActionTransportLayer.MINIMUM_LENGTH; + continue; + } + if (action.getType() == ActionType.SET_NEXT_HOP) { + logger.info("Unsupported action: {}", action); + continue; + } + } + } + logger.trace("SAL Actions: {} Openflow Actions: {}", flow.getActions(), + actionsList); + return actionsList; + } + + /** + * Utility to convert a SAL flow to an OF 1.0 (OFFlowMod) or to an OF 1.0 + + * IPv6 extension (V6FlowMod) Flow modifier Message + * + * @param sw + * @param command + * @param port + * @return + */ + public OFMessage getOFFlowMod(short command, OFPort port) { + OFMessage fm = (isIPv6) ? new V6FlowMod() : new OFFlowMod(); + if (this.ofMatch == null) { + getOFMatch(); + } + if (this.actionsList == null) { + getOFActions(); + } + if (!isIPv6) { + ((OFFlowMod) fm).setMatch(this.ofMatch); + ((OFFlowMod) fm).setActions(this.actionsList); + ((OFFlowMod) fm).setPriority(flow.getPriority()); + ((OFFlowMod) fm).setCookie(flow.getId()); + ((OFFlowMod) fm).setBufferId(OFPacketOut.BUFFER_ID_NONE); + ((OFFlowMod) fm).setLength(U16.t(OFFlowMod.MINIMUM_LENGTH + + actionsLength)); + ((OFFlowMod) fm).setIdleTimeout(flow.getIdleTimeout()); + ((OFFlowMod) fm).setHardTimeout(flow.getHardTimeout()); + ((OFFlowMod) fm).setCommand(command); + if (port != null) { + ((OFFlowMod) fm).setOutPort(port); + } + if (command == OFFlowMod.OFPFC_ADD + || command == OFFlowMod.OFPFC_MODIFY + || command == OFFlowMod.OFPFC_MODIFY_STRICT) { + if (flow.getIdleTimeout() != 0 || flow.getHardTimeout() != 0) { + // Instruct switch to let controller know when flow expires + ((OFFlowMod) fm).setFlags((short) 1); + } + } + } else { + ((V6FlowMod) fm).setVendor(); + ((V6FlowMod) fm).setMatch((V6Match) ofMatch); + ((V6FlowMod) fm).setActions(this.actionsList); + ((V6FlowMod) fm).setPriority(flow.getPriority()); + ((V6FlowMod) fm).setCookie(flow.getId()); + ((V6FlowMod) fm).setLength(U16.t(OFVendor.MINIMUM_LENGTH + + ((V6Match) ofMatch).getIPv6ExtMinHdrLen() + + ((V6Match) ofMatch).getIPv6MatchLen() + + ((V6Match) ofMatch).getPadSize() + actionsLength)); + ((V6FlowMod) fm).setIdleTimeout(flow.getIdleTimeout()); + ((V6FlowMod) fm).setHardTimeout(flow.getHardTimeout()); + ((V6FlowMod) fm).setCommand(command); + if (port != null) { + ((V6FlowMod) fm).setOutPort(port); + } + if (command == OFFlowMod.OFPFC_ADD + || command == OFFlowMod.OFPFC_MODIFY + || command == OFFlowMod.OFPFC_MODIFY_STRICT) { + if (flow.getIdleTimeout() != 0 || flow.getHardTimeout() != 0) { + // Instruct switch to let controller know when flow expires + ((V6FlowMod) fm).setFlags((short) 1); + } + } + } + logger.trace("Openflow Match: {} Openflow Actions: {}", ofMatch, + actionsList); + logger.trace("Openflow Mod Message: {}", fm); + return fm; + } + + public Flow getFlow(Node node) { + if (this.flow == null) { + Match salMatch = new Match(); + + /* + * Installed flow may not have a Match defined like in case of a + * drop all flow + */ + if (ofMatch != null) { + if (!isIPv6) { + // Compute OF1.0 Match + if (ofMatch.getInputPort() != 0 && ofMatch.getInputPort() != OFPort.OFPP_LOCAL.getValue()) { + salMatch.setField(new MatchField(MatchType.IN_PORT, + NodeConnectorCreator.createNodeConnector( + ofMatch.getInputPort(), node))); + } + if (ofMatch.getDataLayerSource() != null + && !NetUtils + .isZeroMAC(ofMatch.getDataLayerSource())) { + byte srcMac[] = ofMatch.getDataLayerSource(); + salMatch.setField(new MatchField(MatchType.DL_SRC, + srcMac.clone())); + } + if (ofMatch.getDataLayerDestination() != null + && !NetUtils.isZeroMAC(ofMatch + .getDataLayerDestination())) { + byte dstMac[] = ofMatch.getDataLayerDestination(); + salMatch.setField(new MatchField(MatchType.DL_DST, + dstMac.clone())); + } + if (ofMatch.getDataLayerType() != 0) { + salMatch.setField(new MatchField(MatchType.DL_TYPE, + ofMatch.getDataLayerType())); + } + short vlan = ofMatch.getDataLayerVirtualLan(); + if (vlan != 0) { + if (vlan == OFP_VLAN_NONE) { + vlan = MatchType.DL_VLAN_NONE; + } + salMatch.setField(new MatchField(MatchType.DL_VLAN, + vlan)); + } + if (ofMatch.getDataLayerVirtualLanPriorityCodePoint() != 0) { + salMatch.setField(MatchType.DL_VLAN_PR, ofMatch + .getDataLayerVirtualLanPriorityCodePoint()); + } + if (ofMatch.getNetworkSource() != 0) { + salMatch.setField(MatchType.NW_SRC, NetUtils + .getInetAddress(ofMatch.getNetworkSource()), + NetUtils.getInetNetworkMask( + ofMatch.getNetworkSourceMaskLen(), + false)); + } + if (ofMatch.getNetworkDestination() != 0) { + salMatch.setField(MatchType.NW_DST, + NetUtils.getInetAddress(ofMatch + .getNetworkDestination()), + NetUtils.getInetNetworkMask( + ofMatch.getNetworkDestinationMaskLen(), + false)); + } + if (ofMatch.getNetworkTypeOfService() != 0) { + int dscp = NetUtils.getUnsignedByte(ofMatch + .getNetworkTypeOfService()); + byte tos = (byte) (dscp >> 2); + salMatch.setField(MatchType.NW_TOS, tos); + } + if (ofMatch.getNetworkProtocol() != 0) { + salMatch.setField(MatchType.NW_PROTO, + ofMatch.getNetworkProtocol()); + } + if (ofMatch.getTransportSource() != 0) { + salMatch.setField(MatchType.TP_SRC, + ofMatch.getTransportSource()); + } + if (ofMatch.getTransportDestination() != 0) { + salMatch.setField(MatchType.TP_DST, + ofMatch.getTransportDestination()); + } + } else { + // Compute OF1.0 + IPv6 extensions Match + V6Match v6Match = (V6Match) ofMatch; + if (v6Match.getInputPort() != 0 && v6Match.getInputPort() != OFPort.OFPP_LOCAL.getValue()) { + // Mask on input port is not defined + salMatch.setField(new MatchField(MatchType.IN_PORT, + NodeConnectorCreator.createOFNodeConnector( + v6Match.getInputPort(), node))); + } + if (v6Match.getDataLayerSource() != null + && !NetUtils + .isZeroMAC(ofMatch.getDataLayerSource())) { + byte srcMac[] = v6Match.getDataLayerSource(); + salMatch.setField(new MatchField(MatchType.DL_SRC, + srcMac.clone())); + } + if (v6Match.getDataLayerDestination() != null + && !NetUtils.isZeroMAC(ofMatch + .getDataLayerDestination())) { + byte dstMac[] = v6Match.getDataLayerDestination(); + salMatch.setField(new MatchField(MatchType.DL_DST, + dstMac.clone())); + } + if (v6Match.getDataLayerType() != 0) { + salMatch.setField(new MatchField(MatchType.DL_TYPE, + v6Match.getDataLayerType())); + } + short vlan = v6Match.getDataLayerVirtualLan(); + if (vlan != 0) { + if (vlan == OFP_VLAN_NONE) { + vlan = MatchType.DL_VLAN_NONE; + } + salMatch.setField(new MatchField(MatchType.DL_VLAN, + vlan)); + } + if (v6Match.getDataLayerVirtualLanPriorityCodePoint() != 0) { + salMatch.setField(MatchType.DL_VLAN_PR, v6Match + .getDataLayerVirtualLanPriorityCodePoint()); + } + // V6Match may carry IPv4 address + if (v6Match.getNetworkSrc() != null) { + salMatch.setField(MatchType.NW_SRC, + v6Match.getNetworkSrc(), + v6Match.getNetworkSourceMask()); + } else if (v6Match.getNetworkSource() != 0) { + salMatch.setField(MatchType.NW_SRC, NetUtils + .getInetAddress(v6Match.getNetworkSource()), + NetUtils.getInetNetworkMask( + v6Match.getNetworkSourceMaskLen(), + false)); + } + // V6Match may carry IPv4 address + if (v6Match.getNetworkDest() != null) { + salMatch.setField(MatchType.NW_DST, + v6Match.getNetworkDest(), + v6Match.getNetworkDestinationMask()); + } else if (v6Match.getNetworkDestination() != 0) { + salMatch.setField(MatchType.NW_DST, + NetUtils.getInetAddress(v6Match + .getNetworkDestination()), + NetUtils.getInetNetworkMask( + v6Match.getNetworkDestinationMaskLen(), + false)); + } + if (v6Match.getNetworkTypeOfService() != 0) { + int dscp = NetUtils.getUnsignedByte(v6Match + .getNetworkTypeOfService()); + byte tos = (byte) (dscp >> 2); + salMatch.setField(MatchType.NW_TOS, tos); + } + if (v6Match.getNetworkProtocol() != 0) { + salMatch.setField(MatchType.NW_PROTO, + v6Match.getNetworkProtocol()); + } + if (v6Match.getTransportSource() != 0) { + salMatch.setField(MatchType.TP_SRC, + (v6Match.getTransportSource())); + } + if (v6Match.getTransportDestination() != 0) { + salMatch.setField(MatchType.TP_DST, + (v6Match.getTransportDestination())); + } + } + } + + // Convert actions + Action salAction = null; + List salActionList = new ArrayList(); + if (actionsList == null) { + salActionList.add(new Drop()); + } else { + for (OFAction ofAction : actionsList) { + if (ofAction instanceof OFActionOutput) { + short ofPort = ((OFActionOutput) ofAction).getPort(); + if (ofPort == OFPort.OFPP_CONTROLLER.getValue()) { + salAction = new Controller(); + } else if (ofPort == OFPort.OFPP_NONE.getValue()) { + salAction = new Drop(); + } else if (ofPort == OFPort.OFPP_IN_PORT.getValue()) { + salAction = new Loopback(); + } else if (ofPort == OFPort.OFPP_FLOOD.getValue()) { + salAction = new Flood(); + } else if (ofPort == OFPort.OFPP_ALL.getValue()) { + salAction = new FloodAll(); + } else if (ofPort == OFPort.OFPP_LOCAL.getValue()) { + salAction = new SwPath(); + } else if (ofPort == OFPort.OFPP_NORMAL.getValue()) { + salAction = new HwPath(); + } else if (ofPort == OFPort.OFPP_TABLE.getValue()) { + salAction = new HwPath(); // TODO: we do not handle + // table in sal for now + } else { + salAction = new Output( + NodeConnectorCreator.createOFNodeConnector( + ofPort, node)); + } + } else if (ofAction instanceof OFActionVirtualLanIdentifier) { + salAction = new SetVlanId( + ((OFActionVirtualLanIdentifier) ofAction) + .getVirtualLanIdentifier()); + } else if (ofAction instanceof OFActionStripVirtualLan) { + salAction = new PopVlan(); + } else if (ofAction instanceof OFActionVirtualLanPriorityCodePoint) { + salAction = new SetVlanPcp( + ((OFActionVirtualLanPriorityCodePoint) ofAction) + .getVirtualLanPriorityCodePoint()); + } else if (ofAction instanceof OFActionDataLayerSource) { + salAction = new SetDlSrc( + ((OFActionDataLayerSource) ofAction) + .getDataLayerAddress().clone()); + } else if (ofAction instanceof OFActionDataLayerDestination) { + salAction = new SetDlDst( + ((OFActionDataLayerDestination) ofAction) + .getDataLayerAddress().clone()); + } else if (ofAction instanceof OFActionNetworkLayerSource) { + byte addr[] = BigInteger.valueOf( + ((OFActionNetworkLayerSource) ofAction) + .getNetworkAddress()).toByteArray(); + InetAddress ip = null; + try { + ip = InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + logger.error("", e); + } + salAction = new SetNwSrc(ip); + } else if (ofAction instanceof OFActionNetworkLayerDestination) { + byte addr[] = BigInteger.valueOf( + ((OFActionNetworkLayerDestination) ofAction) + .getNetworkAddress()).toByteArray(); + InetAddress ip = null; + try { + ip = InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + logger.error("", e); + } + salAction = new SetNwDst(ip); + } else if (ofAction instanceof OFActionNetworkTypeOfService) { + salAction = new SetNwTos( + ((OFActionNetworkTypeOfService) ofAction) + .getNetworkTypeOfService()); + } else if (ofAction instanceof OFActionTransportLayerSource) { + Short port = ((OFActionTransportLayerSource) ofAction) + .getTransportPort(); + int intPort = NetUtils.getUnsignedShort(port); + salAction = new SetTpSrc(intPort); + } else if (ofAction instanceof OFActionTransportLayerDestination) { + Short port = ((OFActionTransportLayerDestination) ofAction) + .getTransportPort(); + int intPort = NetUtils.getUnsignedShort(port); + salAction = new SetTpDst(intPort); + } + salActionList.add(salAction); + } + } + // Create Flow + flow = new Flow(salMatch, salActionList); + } + logger.trace("Openflow Match: {} Openflow Actions: {}", ofMatch, + actionsList); + logger.trace("SAL Flow: {}", flow); + return flow; + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerNotifier.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerNotifier.java new file mode 100644 index 0000000000..4db569bd87 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerNotifier.java @@ -0,0 +1,117 @@ +/* + * 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.internal; + +import org.apache.felix.dm.Component; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.flowprogrammer.IPluginOutFlowProgrammerService; +import org.opendaylight.openflowplugin.openflow.IFlowProgrammerNotifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Flow Programmer Notifier class for relaying asynchronous messages received + * from the network node to the listeners on the proper container + */ +public class FlowProgrammerNotifier implements IFlowProgrammerNotifier { + protected static final Logger logger = LoggerFactory + .getLogger(FlowProgrammerNotifier.class); + private IPluginOutFlowProgrammerService salNotifier; + private IPluginOutConnectionService connectionOutService; + + public FlowProgrammerNotifier() { + salNotifier = null; + } + + void init(Component c) { + logger.debug("INIT called!"); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + logger.debug("DESTROY called!"); + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + logger.debug("START called!"); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + logger.debug("STOP called!"); + } + + public void setPluginOutFlowProgrammerService( + IPluginOutFlowProgrammerService s) { + this.salNotifier = s; + } + + public void unsetPluginOutFlowProgrammerService( + IPluginOutFlowProgrammerService s) { + if (this.salNotifier == s) { + this.salNotifier = null; + } + } + + @Override + public void flowRemoved(Node node, Flow flow) { + if (!connectionOutService.isLocal(node)) { + logger.debug("flow removed will not be notified in a non-master controller for node "+node); + return; + } + + if (salNotifier != null) { + salNotifier.flowRemoved(node, flow); + } else { + logger.warn("Unable to relay switch message to upper layer"); + } + } + + @Override + public void flowErrorReported(Node node, long rid, Object err) { + if (!connectionOutService.isLocal(node)) { + logger.debug("flow error will not be notified in a non-master controller for node "+node); + return; + } + + if (salNotifier != null) { + salNotifier.flowErrorReported(node, rid, err); + } else { + logger.warn("Unable to relay switch error message to upper layer"); + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerService.java new file mode 100644 index 0000000000..594e6ce8e5 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowProgrammerService.java @@ -0,0 +1,803 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.Node.NodeIDType; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.flowprogrammer.IPluginInFlowProgrammerService; +import org.opendaylight.controller.sal.match.Match; +import org.opendaylight.controller.sal.match.MatchType; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.opendaylight.controller.sal.utils.Status; +import org.opendaylight.controller.sal.utils.StatusCode; +import org.opendaylight.openflowplugin.openflow.IFlowProgrammerNotifier; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageListener; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFFlowRemoved; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFType; +import org.openflow.protocol.action.OFAction; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents the openflow plugin component in charge of programming the flows + * the flow programming and relay them to functional modules above SAL. + */ +public class FlowProgrammerService implements IPluginInFlowProgrammerService, + IMessageListener, IContainerListener, IInventoryShimExternalListener, + CommandProvider { + private static final Logger log = LoggerFactory + .getLogger(FlowProgrammerService.class); + private IController controller; + private ConcurrentMap flowProgrammerNotifiers; + private Map> containerToNc; + private ConcurrentMap> xid2rid; + private int barrierMessagePriorCount = getBarrierMessagePriorCount(); + private IPluginOutConnectionService connectionOutService; + + public FlowProgrammerService() { + controller = null; + flowProgrammerNotifiers = new ConcurrentHashMap(); + containerToNc = new HashMap>(); + xid2rid = new ConcurrentHashMap>(); + } + + public void setController(IController core) { + this.controller = core; + } + + public void unsetController(IController core) { + if (this.controller == core) { + this.controller = null; + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + + public void setFlowProgrammerNotifier(Map props, + IFlowProgrammerNotifier s) { + if (props == null || props.get("containerName") == null) { + log.error("Didn't receive the service correct properties"); + return; + } + String containerName = (String) props.get("containerName"); + this.flowProgrammerNotifiers.put(containerName, s); + } + + public void unsetFlowProgrammerNotifier(Map props, + IFlowProgrammerNotifier s) { + if (props == null || props.get("containerName") == null) { + log.error("Didn't receive the service correct properties"); + return; + } + String containerName = (String) props.get("containerName"); + if (this.flowProgrammerNotifiers != null + && this.flowProgrammerNotifiers.containsKey(containerName) + && this.flowProgrammerNotifiers.get(containerName) == s) { + this.flowProgrammerNotifiers.remove(containerName); + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + this.controller.addMessageListener(OFType.FLOW_REMOVED, this); + this.controller.addMessageListener(OFType.ERROR, this); + registerWithOSGIConsole(); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + } + + @Override + public Status addFlow(Node node, Flow flow) { + if (!connectionOutService.isLocal(node)) { + log.debug("Add flow will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return addFlowInternal(node, flow, 0); + } + + @Override + public Status modifyFlow(Node node, Flow oldFlow, Flow newFlow) { + if (!connectionOutService.isLocal(node)) { + log.debug("Modify flow will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return modifyFlowInternal(node, oldFlow, newFlow, 0); + } + + @Override + public Status removeFlow(Node node, Flow flow) { + if (!connectionOutService.isLocal(node)) { + log.debug("Remove flow will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return removeFlowInternal(node, flow, 0); + } + + @Override + public Status addFlowAsync(Node node, Flow flow, long rid) { + if (!connectionOutService.isLocal(node)) { + log.debug("Add flow Async will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return addFlowInternal(node, flow, rid); + } + + @Override + public Status modifyFlowAsync(Node node, Flow oldFlow, Flow newFlow, + long rid) { + if (!connectionOutService.isLocal(node)) { + log.debug("Modify flow async will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return modifyFlowInternal(node, oldFlow, newFlow, rid); + } + + @Override + public Status removeFlowAsync(Node node, Flow flow, long rid) { + if (!connectionOutService.isLocal(node)) { + log.debug("Remove flow async will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return removeFlowInternal(node, flow, rid); + } + + private Status addFlowInternal(Node node, Flow flow, long rid) { + String action = "add"; + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + return new Status(StatusCode.NOTACCEPTABLE, errorString("send", + action, "Invalid node type")); + } + + if (controller != null) { + ISwitch sw = controller.getSwitch((Long) node.getID()); + if (sw != null) { + FlowConverter x = new FlowConverter(flow); + OFMessage msg = x.getOFFlowMod(OFFlowMod.OFPFC_ADD, null); + + Object result; + if (rid == 0) { + /* + * Synchronous message send. Each message is followed by a + * Barrier message. + */ + result = sw.syncSend(msg); + } else { + /* + * Message will be sent asynchronously. A Barrier message + * will be inserted automatically to synchronize the + * progression. + */ + result = asyncMsgSend(node, sw, msg, rid); + } + return getStatusInternal(result, action, rid); + } else { + return new Status(StatusCode.GONE, errorString("send", action, + "Switch is not available")); + } + } + return new Status(StatusCode.INTERNALERROR, errorString("send", action, + "Internal plugin error")); + } + + private Status modifyFlowInternal(Node node, Flow oldFlow, Flow newFlow, long rid) { + String action = "modify"; + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + return new Status(StatusCode.NOTACCEPTABLE, errorString("send", + action, "Invalid node type")); + } + if (controller != null) { + ISwitch sw = controller.getSwitch((Long) node.getID()); + if (sw != null) { + OFMessage msg1 = null, msg2 = null; + + // If priority and match portion are the same, send a + // modification message + if (oldFlow.getPriority() != newFlow.getPriority() + || !oldFlow.getMatch().equals(newFlow.getMatch())) { + msg1 = new FlowConverter(oldFlow).getOFFlowMod( + OFFlowMod.OFPFC_DELETE_STRICT, OFPort.OFPP_NONE); + msg2 = new FlowConverter(newFlow).getOFFlowMod( + OFFlowMod.OFPFC_ADD, null); + } else { + msg1 = new FlowConverter(newFlow).getOFFlowMod( + OFFlowMod.OFPFC_MODIFY_STRICT, null); + } + /* + * Synchronous message send + */ + action = (msg2 == null) ? "modify" : "delete"; + Object result; + if (rid == 0) { + /* + * Synchronous message send. Each message is followed by a + * Barrier message. + */ + result = sw.syncSend(msg1); + } else { + /* + * Message will be sent asynchronously. A Barrier message + * will be inserted automatically to synchronize the + * progression. + */ + result = asyncMsgSend(node, sw, msg1, rid); + } + + Status rv = getStatusInternal(result, action, rid); + if ((msg2 == null) || !rv.isSuccess()) { + return rv; + } + + action = "add"; + if (rid == 0) { + /* + * Synchronous message send. Each message is followed by a + * Barrier message. + */ + result = sw.syncSend(msg2); + } else { + /* + * Message will be sent asynchronously. A Barrier message + * will be inserted automatically to synchronize the + * progression. + */ + result = asyncMsgSend(node, sw, msg2, rid); + } + return getStatusInternal(result, action, rid); + } else { + return new Status(StatusCode.GONE, errorString("send", action, + "Switch is not available")); + } + } + return new Status(StatusCode.INTERNALERROR, errorString("send", action, + "Internal plugin error")); + } + + private Status removeFlowInternal(Node node, Flow flow, long rid) { + String action = "remove"; + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + return new Status(StatusCode.NOTACCEPTABLE, errorString("send", + action, "Invalid node type")); + } + if (controller != null) { + ISwitch sw = controller.getSwitch((Long) node.getID()); + if (sw != null) { + OFMessage msg = new FlowConverter(flow).getOFFlowMod( + OFFlowMod.OFPFC_DELETE_STRICT, OFPort.OFPP_NONE); + Object result; + if (rid == 0) { + /* + * Synchronous message send. Each message is followed by a + * Barrier message. + */ + result = sw.syncSend(msg); + } else { + /* + * Message will be sent asynchronously. A Barrier message + * will be inserted automatically to synchronize the + * progression. + */ + result = asyncMsgSend(node, sw, msg, rid); + } + return getStatusInternal(result, action, rid); + } else { + return new Status(StatusCode.GONE, errorString("send", action, + "Switch is not available")); + } + } + return new Status(StatusCode.INTERNALERROR, errorString("send", action, + "Internal plugin error")); + } + + @Override + public Status removeAllFlows(Node node) { + if (!connectionOutService.isLocal(node)) { + log.debug("Remove all flows will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + return new Status(StatusCode.SUCCESS); + } + + private String errorString(String phase, String action, String cause) { + return "Failed to " + + ((phase != null) ? phase + " the " + action + + " flow message: " : action + " the flow: ") + cause; + } + + @Override + public void receive(ISwitch sw, OFMessage msg) { + if (msg instanceof OFFlowRemoved) { + handleFlowRemovedMessage(sw, (OFFlowRemoved) msg); + } else if (msg instanceof OFError) { + handleErrorMessage(sw, (OFError) msg); + } + } + + private void handleFlowRemovedMessage(ISwitch sw, OFFlowRemoved msg) { + Node node = NodeCreator.createOFNode(sw.getId()); + Flow flow = new FlowConverter(msg.getMatch(), + new ArrayList(0)).getFlow(node); + flow.setPriority(msg.getPriority()); + flow.setIdleTimeout(msg.getIdleTimeout()); + flow.setId(msg.getCookie()); + + Match match = flow.getMatch(); + NodeConnector inPort = match.isPresent(MatchType.IN_PORT) ? (NodeConnector) match + .getField(MatchType.IN_PORT).getValue() : null; + + for (Map.Entry containerNotifier : flowProgrammerNotifiers + .entrySet()) { + String container = containerNotifier.getKey(); + IFlowProgrammerNotifier notifier = containerNotifier.getValue(); + /* + * Switch only provide us with the match information. For now let's + * try to identify the container membership only from the input port + * match field. In any case, upper layer consumers can derive + * whether the notification was not for them. More sophisticated + * filtering can be added later on. + */ + if (inPort == null + || container.equals(GlobalConstants.DEFAULT.toString()) + || this.containerToNc.get(container).contains(inPort)) { + notifier.flowRemoved(node, flow); + } + } + } + + private void handleErrorMessage(ISwitch sw, OFError errorMsg) { + Node node = NodeCreator.createOFNode(sw.getId()); + OFMessage offendingMsg = errorMsg.getOffendingMsg(); + Integer xid; + if (offendingMsg != null) { + xid = offendingMsg.getXid(); + } else { + xid = errorMsg.getXid(); + } + + Long rid = getMessageRid(sw.getId(), xid); + /* + * Null or zero requestId indicates that the error message is meant for + * a sync message. It will be handled by the sync message worker thread. + * Hence we are done here. + */ + if ((rid == null) || (rid == 0)) { + return; + } + + /* + * Notifies the caller that error has been reported for a previous flow + * programming request + */ + for (Map.Entry containerNotifier : flowProgrammerNotifiers + .entrySet()) { + IFlowProgrammerNotifier notifier = containerNotifier.getValue(); + notifier.flowErrorReported(node, rid, errorMsg); + } + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, + short newTag, UpdateType t) { + + } + + @Override + public void containerFlowUpdated(String containerName, + ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) { + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector p, + UpdateType type) { + Set target = null; + + switch (type) { + case ADDED: + if (!containerToNc.containsKey(containerName)) { + containerToNc.put(containerName, new HashSet()); + } + containerToNc.get(containerName).add(p); + break; + case CHANGED: + break; + case REMOVED: + target = containerToNc.get(containerName); + if (target != null) { + target.remove(p); + } + break; + default: + } + } + + @Override + public void containerModeUpdated(UpdateType t) { + + } + + @Override + public Status syncSendBarrierMessage(Node node) { + if (!connectionOutService.isLocal(node)) { + log.debug("Sync Send Barrier will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + return new Status(StatusCode.NOTACCEPTABLE, + "The node does not support Barrier message."); + } + + if (controller != null) { + long swid = (Long) node.getID(); + ISwitch sw = controller.getSwitch(swid); + if (sw != null) { + sw.syncSendBarrierMessage(); + clearXid2Rid(swid); + return (new Status(StatusCode.SUCCESS)); + } else { + return new Status(StatusCode.GONE, + "The node does not have a valid Switch reference."); + } + } + return new Status(StatusCode.INTERNALERROR, + "Failed to send Barrier message."); + } + + @Override + public Status asyncSendBarrierMessage(Node node) { + if (!connectionOutService.isLocal(node)) { + log.debug("ASync Send Barrier will not be processed in a non-master controller for node " + node); + return new Status(StatusCode.NOTALLOWED, "This is not the master controller for " + node); + } + + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + return new Status(StatusCode.NOTACCEPTABLE, + "The node does not support Barrier message."); + } + + if (controller != null) { + long swid = (Long) node.getID(); + ISwitch sw = controller.getSwitch(swid); + if (sw != null) { + sw.asyncSendBarrierMessage(); + clearXid2Rid(swid); + return (new Status(StatusCode.SUCCESS)); + } else { + return new Status(StatusCode.GONE, + "The node does not have a valid Switch reference."); + } + } + return new Status(StatusCode.INTERNALERROR, + "Failed to send Barrier message."); + } + + /** + * This method sends the message asynchronously until the number of messages + * sent reaches a threshold. Then a Barrier message is sent automatically + * for sync purpose. An unique Request ID associated with the message is + * passed down by the caller. The Request ID will be returned to the caller + * when an error message is received from the switch. + * + * @param node + * The node + * @param msg + * The switch + * @param msg + * The OF message to be sent + * @param rid + * The Request Id + * @return result + */ + private Object asyncMsgSend(Node node, ISwitch sw, OFMessage msg, long rid) { + Object result = Boolean.TRUE; + long swid = (Long) node.getID(); + int xid; + + xid = sw.asyncSend(msg); + addXid2Rid(swid, xid, rid); + + Map swxid2rid = this.xid2rid.get(swid); + if (swxid2rid == null) { + return result; + } + + int size = swxid2rid.size(); + if (size % barrierMessagePriorCount == 0) { + result = asyncSendBarrierMessage(node); + } + + return result; + } + + /** + * A number of async messages are sent followed by a synchronous Barrier + * message. This method returns the maximum async messages that can be sent + * before the Barrier message. + * + * @return The max count of async messages sent prior to Barrier message + */ + private int getBarrierMessagePriorCount() { + String count = System.getProperty("of.barrierMessagePriorCount"); + int rv = 100; + + if (count != null) { + try { + rv = Integer.parseInt(count); + } catch (Exception e) { + } + } + + return rv; + } + + /** + * This method returns the message Request ID previously assigned by the + * caller for a given OF message xid + * + * @param swid + * The switch id + * @param xid + * The OF message xid + * @return The Request ID + */ + private Long getMessageRid(long swid, Integer xid) { + Long rid = null; + + if (xid == null) { + return rid; + } + + Map swxid2rid = this.xid2rid.get(swid); + if (swxid2rid != null) { + rid = swxid2rid.get(xid); + } + return rid; + } + + /** + * This method returns a copy of outstanding xid to rid mappings.for a given + * switch + * + * @param swid + * The switch id + * @return a copy of xid2rid mappings + */ + public Map getSwXid2Rid(long swid) { + Map swxid2rid = this.xid2rid.get(swid); + + if (swxid2rid != null) { + return new HashMap(swxid2rid); + } else { + return new HashMap(); + } + } + + /** + * Adds xid to rid mapping to the local DB + * + * @param swid + * The switch id + * @param xid + * The OF message xid + * @param rid + * The message Request ID + */ + private void addXid2Rid(long swid, int xid, long rid) { + Map swxid2rid = this.xid2rid.get(swid); + if (swxid2rid != null) { + swxid2rid.put(xid, rid); + } + } + + /** + * When an Error message is received, this method will be invoked to remove + * the offending xid from the local DB. + * + * @param swid + * The switch id + * @param xid + * The OF message xid + */ + private void removeXid2Rid(long swid, int xid) { + Map swxid2rid = this.xid2rid.get(swid); + if (swxid2rid != null) { + swxid2rid.remove(xid); + } + } + + /** + * Convert various result into Status + * + * @param result + * The returned result from previous action + * @param action + * add/modify/delete flow action + * @param rid + * The Request ID associated with the flow message + * @return Status + */ + private Status getStatusInternal(Object result, String action, long rid) { + if (result instanceof Boolean) { + return ((Boolean) result == Boolean.TRUE) ? new Status( + StatusCode.SUCCESS, rid) : new Status( + StatusCode.TIMEOUT, errorString(null, action, + "Request Timed Out")); + } else if (result instanceof Status) { + return (Status) result; + } else if (result instanceof OFError) { + OFError res = (OFError) result; + return new Status(StatusCode.INTERNALERROR, errorString( + "program", action, Utils.getOFErrorString(res))); + } else { + return new Status(StatusCode.INTERNALERROR, errorString( + "send", action, "Internal Error")); + } + } + + /** + * When a Barrier reply is received, this method will be invoked to clear + * the local DB + * + * @param swid + * The switch id + */ + private void clearXid2Rid(long swid) { + Map swxid2rid = this.xid2rid.get(swid); + if (swxid2rid != null) { + swxid2rid.clear(); + } + } + + @Override + public void updateNode(Node node, UpdateType type, Set props) { + long swid = (Long)node.getID(); + + switch (type) { + case ADDED: + Map swxid2rid = new HashMap(); + this.xid2rid.put(swid, swxid2rid); + break; + case CHANGED: + break; + case REMOVED: + this.xid2rid.remove(swid); + break; + default: + } + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props) { + } + + private void registerWithOSGIConsole() { + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()) + .getBundleContext(); + bundleContext.registerService(CommandProvider.class.getName(), this, + null); + } + + @Override + public String getHelp() { + StringBuffer help = new StringBuffer(); + help.append("-- Flow Programmer Service --\n"); + help.append("\t px2r - Print outstanding xid2rid mappings for a given node id\n"); + help.append("\t px2rc - Print max num of async msgs prior to the Barrier\n"); + return help.toString(); + } + + public void _px2r(CommandInterpreter ci) { + String st = ci.nextArgument(); + if (st == null) { + ci.println("Please enter a valid node id"); + return; + } + + long sid; + try { + sid = HexEncode.stringToLong(st); + } catch (NumberFormatException e) { + ci.println("Please enter a valid node id"); + return; + } + + Map swxid2rid = this.xid2rid.get(sid); + if (swxid2rid == null) { + ci.println("The node id entered does not exist"); + return; + } + + ci.println("xid rid"); + + Set xidSet = swxid2rid.keySet(); + if (xidSet == null) { + return; + } + + for (Integer xid : xidSet) { + ci.println(xid + " " + swxid2rid.get(xid)); + } + } + + public void _px2rc(CommandInterpreter ci) { + ci.println("Max num of async messages sent prior to the Barrier message is " + + barrierMessagePriorCount); + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowStatisticsConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowStatisticsConverter.java new file mode 100644 index 0000000000..f7cc79b139 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/FlowStatisticsConverter.java @@ -0,0 +1,94 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.reader.FlowOnNode; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6StatsReply; +import org.openflow.protocol.statistics.OFFlowStatisticsReply; +import org.openflow.protocol.statistics.OFStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts an openflow list of flow statistics in a SAL list of FlowOnNode + * objects + * + * + * + */ +public class FlowStatisticsConverter { + private static final Logger log = LoggerFactory + .getLogger(FlowStatisticsConverter.class); + private List ofStatsList; + private List flowOnNodeList; + + public FlowStatisticsConverter(List statsList) { + if (statsList == null) {// || statsList.isEmpty()) { + this.ofStatsList = new ArrayList(1); // dummy list + } else { + this.ofStatsList = statsList; // new + // ArrayList(statsList); + } + this.flowOnNodeList = null; + } + + public List getFlowOnNodeList(Node node) { + if (ofStatsList != null && flowOnNodeList == null) { + flowOnNodeList = new ArrayList(); + FlowConverter flowConverter = null; + OFFlowStatisticsReply ofFlowStat; + V6StatsReply v6StatsReply; + for (OFStatistics ofStat : ofStatsList) { + FlowOnNode flowOnNode = null; + if (ofStat instanceof OFFlowStatisticsReply) { + ofFlowStat = (OFFlowStatisticsReply) ofStat; + flowConverter = new FlowConverter(ofFlowStat.getMatch(), + ofFlowStat.getActions()); + Flow flow = flowConverter.getFlow(node); + flow.setPriority(ofFlowStat.getPriority()); + flow.setIdleTimeout(ofFlowStat.getIdleTimeout()); + flow.setHardTimeout(ofFlowStat.getHardTimeout()); + flowOnNode = new FlowOnNode(flow); + flowOnNode.setByteCount(ofFlowStat.getByteCount()); + flowOnNode.setPacketCount(ofFlowStat.getPacketCount()); + flowOnNode.setDurationSeconds(ofFlowStat + .getDurationSeconds()); + flowOnNode.setDurationNanoseconds(ofFlowStat + .getDurationNanoseconds()); + } else if (ofStat instanceof V6StatsReply) { + v6StatsReply = (V6StatsReply) ofStat; + flowConverter = new FlowConverter(v6StatsReply.getMatch(), + v6StatsReply.getActions()); + Flow flow = flowConverter.getFlow(node); + flow.setPriority(v6StatsReply.getPriority()); + flow.setIdleTimeout(v6StatsReply.getIdleTimeout()); + flow.setHardTimeout(v6StatsReply.getHardTimeout()); + flowOnNode = new FlowOnNode(flow); + flowOnNode.setByteCount(v6StatsReply.getByteCount()); + flowOnNode.setPacketCount(v6StatsReply.getPacketCount()); + flowOnNode.setDurationSeconds(v6StatsReply + .getDurationSeconds()); + flowOnNode.setDurationNanoseconds(v6StatsReply + .getDurationNanoseconds()); + } else { + continue; + } + flowOnNodeList.add(flowOnNode); + } + } + log.trace("OFStatistics: {} FlowOnNode: {}", ofStatsList, + flowOnNodeList); + return flowOnNodeList; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryService.java new file mode 100644 index 0000000000..df02b52af7 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryService.java @@ -0,0 +1,296 @@ +/* + * 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.internal; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.felix.dm.Component; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.inventory.IPluginInInventoryService; +import org.opendaylight.controller.sal.inventory.IPluginOutInventoryService; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.openflowplugin.openflow.IInventoryProvider; +import org.opendaylight.openflowplugin.openflow.IInventoryShimInternalListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The class describes inventory service protocol plugin. One instance per + * container of the network. Each instance gets container specific inventory + * events from InventoryServiceShim. It interacts with SAL to pass inventory + * data to the upper application. + * + * + */ +public class InventoryService implements IInventoryShimInternalListener, + IPluginInInventoryService, IInventoryProvider { + protected static final Logger logger = LoggerFactory + .getLogger(InventoryService.class); + private Set pluginOutInventoryServices; + private IController controller = null; + private ConcurrentMap> nodeProps; // properties are maintained in global container only + private ConcurrentMap> nodeConnectorProps; // properties are maintained in global container only + private boolean isDefaultContainer = false; + private String containerName = null; + + void setController(IController s) { + this.controller = s; + } + + void unsetController(IController s) { + if (this.controller == s) { + this.controller = null; + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + @SuppressWarnings("rawtypes") + void init(Component c) { + logger.trace("INIT called!"); + + Dictionary props = c.getServiceProperties(); + if (props != null) { + containerName = (String) props.get("containerName"); + if (containerName != null) { + isDefaultContainer = containerName.equals(GlobalConstants.DEFAULT + .toString()); + } + } + + nodeProps = new ConcurrentHashMap>(); + nodeConnectorProps = new ConcurrentHashMap>(); + pluginOutInventoryServices = new CopyOnWriteArraySet(); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + logger.trace("DESTROY called!"); + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + logger.trace("START called!"); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + logger.trace("STOP called!"); + } + + public void setPluginOutInventoryServices(IPluginOutInventoryService service) { + logger.trace("Got a service set request {}", service); + if (this.pluginOutInventoryServices != null) { + this.pluginOutInventoryServices.add(service); + } + } + + public void unsetPluginOutInventoryServices( + IPluginOutInventoryService service) { + logger.trace("Got a service UNset request"); + if (this.pluginOutInventoryServices != null) { + this.pluginOutInventoryServices.remove(service); + } + } + + /** + * Retrieve nodes from openflow + */ + @Override + public ConcurrentMap> getNodeProps() { + logger.debug("getNodePros for container {}", containerName); + return nodeProps; + } + + @Override + public ConcurrentMap> getNodeConnectorProps( + Boolean refresh) { + if (nodeConnectorProps == null) { + return null; + } + + if (isDefaultContainer && refresh) { + Map switches = controller.getSwitches(); + for (ISwitch sw : switches.values()) { + Map> ncProps = InventoryServiceHelper + .OFSwitchToProps(sw); + for (Map.Entry> entry : ncProps + .entrySet()) { + updateNodeConnector(entry.getKey(), UpdateType.ADDED, + entry.getValue()); + } + } + } + + return nodeConnectorProps; + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props) { + logger.trace("updateNodeConnector {} type {}", nodeConnector, + type.getName()); + if (nodeConnectorProps == null) { + logger.trace("nodeConnectorProps is null"); + return; + } + + Map propMap = nodeConnectorProps.get(nodeConnector); + switch (type) { + case ADDED: + case CHANGED: + if (propMap == null) { + propMap = new HashMap(); + } + if (props != null) { + for (Property prop : props) { + propMap.put(prop.getName(), prop); + } + } + nodeConnectorProps.put(nodeConnector, propMap); + break; + case REMOVED: + nodeConnectorProps.remove(nodeConnector); + break; + default: + return; + } + + // update sal and discovery + for (IPluginOutInventoryService service : pluginOutInventoryServices) { + service.updateNodeConnector(nodeConnector, type, props); + } + + } + + private void addNode(Node node, Set props) { + if (nodeProps == null) { + return; + } + + logger.trace("addNode: {} added, props: {} for container {}", + new Object[] { node, props, containerName }); + + // update local cache + Map propMap = nodeProps.get(node); + if (propMap == null) { + propMap = new HashMap(); + } + + if (props != null) { + for (Property prop : props) { + propMap.put(prop.getName(), prop); + } + } + nodeProps.put(node, propMap); + + // update sal + for (IPluginOutInventoryService service : pluginOutInventoryServices) { + service.updateNode(node, UpdateType.ADDED, props); + } + } + + private void removeNode(Node node) { + logger.trace("{} removed", node); + if (nodeProps == null) + return; + + // update local cache + nodeProps.remove(node); + + Set removeSet = new HashSet(); + for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) { + if (nodeConnector.getNode().equals(node)) { + removeSet.add(nodeConnector); + } + } + for (NodeConnector nodeConnector : removeSet) { + nodeConnectorProps.remove(nodeConnector); + } + + // update sal + for (IPluginOutInventoryService service : pluginOutInventoryServices) { + service.updateNode(node, UpdateType.REMOVED, null); + } + } + + private void updateNode(Node node, Set properties) { + logger.trace("{} updated, props: {}", node, properties); + if (nodeProps == null || !nodeProps.containsKey(node) || + properties == null || properties.isEmpty()) { + return; + } + + // Update local cache with new properties + Set newProperties = new HashSet(properties.size()); + Map propertyMap = nodeProps.get(node); + for (Property property : properties) { + String name = property.getName(); + Property currentProperty = propertyMap.get(name); + if (!property.equals(currentProperty)) { + propertyMap.put(name, property); + newProperties.add(property); + } + } + + // Update SAL if we got new properties + if (!newProperties.isEmpty()) { + for (IPluginOutInventoryService service : pluginOutInventoryServices) { + service.updateNode(node, UpdateType.CHANGED, newProperties); + } + } + } + + @Override + public void updateNode(Node node, UpdateType type, Set props) { + switch (type) { + case ADDED: + addNode(node, props); + break; + case REMOVED: + removeNode(node); + break; + case CHANGED: + updateNode(node, props); + break; + default: + break; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceHelper.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceHelper.java new file mode 100644 index 0000000000..99f6b9bf9b --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceHelper.java @@ -0,0 +1,167 @@ + +/* + * 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.internal; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.opendaylight.controller.sal.core.Bandwidth; +import org.opendaylight.controller.sal.core.AdvertisedBandwidth; +import org.opendaylight.controller.sal.core.SupportedBandwidth; +import org.opendaylight.controller.sal.core.PeerBandwidth; +import org.opendaylight.controller.sal.core.Config; +import org.opendaylight.controller.sal.core.Name; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.State; + +import org.opendaylight.controller.sal.utils.NodeCreator; + +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.openflow.protocol.OFPhysicalPort; +import org.openflow.protocol.OFPhysicalPort.OFPortConfig; +import org.openflow.protocol.OFPhysicalPort.OFPortFeatures; +import org.openflow.protocol.OFPhysicalPort.OFPortState; + +/** + * The class provides helper functions to retrieve inventory properties from + * OpenFlow messages + */ +public class InventoryServiceHelper { + /* + * Returns BandWidth property from OpenFlow OFPhysicalPort features + */ + public static Bandwidth OFPortToBandWidth(int portFeatures) { + Bandwidth bw = null; + int value = portFeatures + & (OFPortFeatures.OFPPF_10MB_FD.getValue() + | OFPortFeatures.OFPPF_10MB_HD.getValue() + | OFPortFeatures.OFPPF_100MB_FD.getValue() + | OFPortFeatures.OFPPF_100MB_HD.getValue() + | OFPortFeatures.OFPPF_1GB_FD.getValue() + | OFPortFeatures.OFPPF_1GB_HD.getValue() | OFPortFeatures.OFPPF_10GB_FD + .getValue()); + + switch (value) { + case 1: + case 2: + bw = new Bandwidth(Bandwidth.BW10Mbps); + break; + case 4: + case 8: + bw = new Bandwidth(Bandwidth.BW100Mbps); + break; + case 16: + case 32: + bw = new Bandwidth(Bandwidth.BW1Gbps); + break; + case 64: + bw = new Bandwidth(Bandwidth.BW10Gbps); + break; + default: + break; + } + return bw; + } + + /* + * Returns Config property from OpenFLow OFPhysicalPort config + */ + public static Config OFPortToConfig(int portConfig) { + Config config; + if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & portConfig) != 0) + config = new Config(Config.ADMIN_DOWN); + else + config = new Config(Config.ADMIN_UP); + return config; + } + + /* + * Returns State property from OpenFLow OFPhysicalPort state + */ + public static State OFPortToState(int portState) { + State state; + if ((OFPortState.OFPPS_LINK_DOWN.getValue() & portState) != 0) + state = new State(State.EDGE_DOWN); + else + state = new State(State.EDGE_UP); + return state; + } + + /* + * Returns set of properties from OpenFLow OFPhysicalPort + */ + public static Set OFPortToProps(OFPhysicalPort port) { + Set props = new HashSet(); + Bandwidth bw = InventoryServiceHelper.OFPortToBandWidth(port + .getCurrentFeatures()); + if (bw != null) { + props.add(bw); + } + + Bandwidth abw = InventoryServiceHelper.OFPortToBandWidth(port.getAdvertisedFeatures()); + if (abw != null) { + AdvertisedBandwidth a = new AdvertisedBandwidth(abw.getValue()); + if (a != null) { + props.add(a); + } + } + Bandwidth sbw = InventoryServiceHelper.OFPortToBandWidth(port.getSupportedFeatures()); + if (sbw != null) { + SupportedBandwidth s = new SupportedBandwidth(sbw.getValue()); + if (s != null) { + props.add(s); + } + } + Bandwidth pbw = InventoryServiceHelper.OFPortToBandWidth(port.getPeerFeatures()); + if (pbw != null) { + PeerBandwidth p = new PeerBandwidth(pbw.getValue()); + if (p != null) { + props.add(p); + } + } + props.add(new Name(port.getName())); + props.add(InventoryServiceHelper.OFPortToConfig(port.getConfig())); + props.add(InventoryServiceHelper.OFPortToState(port.getState())); + return props; + } + + /* + * Returns set of properties for each nodeConnector in an OpenFLow switch + */ + public static Map> OFSwitchToProps(ISwitch sw) { + Map> ncProps = new HashMap>(); + + if (sw == null) { + return ncProps; + } + + Node node = NodeCreator.createOFNode(sw.getId()); + if (node == null) { + return ncProps; + } + + Set props; + NodeConnector nodeConnector; + OFPhysicalPort port; + Map ports = sw.getPhysicalPorts(); + for (Map.Entry entry : ports.entrySet()) { + nodeConnector = PortConverter.toNodeConnector(entry.getKey(), node); + port = entry.getValue(); + props = InventoryServiceHelper.OFPortToProps(port); + ncProps.put(nodeConnector, props); + } + + return ncProps; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceShim.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceShim.java new file mode 100644 index 0000000000..ab090c9d3e --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/InventoryServiceShim.java @@ -0,0 +1,576 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Actions; +import org.opendaylight.controller.sal.core.Buffers; +import org.opendaylight.controller.sal.core.Capabilities; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.Description; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.MacAddress; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.Tables; +import org.opendaylight.controller.sal.core.TimeStamp; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.IInventoryShimInternalListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsListener; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.IMessageListener; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.opendaylight.openflowplugin.openflow.core.ISwitchStateListener; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPortStatus; +import org.openflow.protocol.OFPortStatus.OFPortReason; +import org.openflow.protocol.OFType; +import org.openflow.protocol.statistics.OFDescriptionStatistics; +import org.openflow.protocol.statistics.OFStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The class describes a shim layer that bridges inventory events from Openflow + * core to various listeners. The notifications are filtered based on container + * configurations. + * + * + */ +public class InventoryServiceShim implements IContainerListener, + IMessageListener, ISwitchStateListener, IOFStatisticsListener { + protected static final Logger logger = LoggerFactory + .getLogger(InventoryServiceShim.class); + private IController controller = null; + private final ConcurrentMap inventoryShimInternalListeners = new ConcurrentHashMap(); + private final Set globalInventoryShimInternalListeners = new HashSet(); + private final List inventoryShimExternalListeners = new CopyOnWriteArrayList(); + private final ConcurrentMap> nodeConnectorContainerMap = new ConcurrentHashMap>(); + private final ConcurrentMap> nodeContainerMap = new ConcurrentHashMap>(); + private final ConcurrentMap> nodeConnectorProps = new ConcurrentHashMap>(); + private final ConcurrentMap> nodeProps = new ConcurrentHashMap>(); + private IPluginOutConnectionService connectionOutService; + + void setController(IController s) { + this.controller = s; + } + + void unsetController(IController s) { + if (this.controller == s) { + this.controller = null; + } + } + + void setInventoryShimGlobalInternalListener(Map props, + IInventoryShimInternalListener s) { + if ((this.globalInventoryShimInternalListeners != null)) { + this.globalInventoryShimInternalListeners.add(s); + } + } + + void unsetInventoryShimGlobalInternalListener(Map props, + IInventoryShimInternalListener s) { + if ((this.globalInventoryShimInternalListeners != null)) { + this.globalInventoryShimInternalListeners.remove(s); + } + } + + void setInventoryShimInternalListener(Map props, + IInventoryShimInternalListener s) { + if (props == null) { + logger.error("setInventoryShimInternalListener property is null"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("setInventoryShimInternalListener containerName not supplied"); + return; + } + if ((this.inventoryShimInternalListeners != null) + && !this.inventoryShimInternalListeners.containsValue(s)) { + this.inventoryShimInternalListeners.put(containerName, s); + logger.trace( + "Added inventoryShimInternalListener for container {}", + containerName); + } + } + + void unsetInventoryShimInternalListener(Map props, + IInventoryShimInternalListener s) { + if (props == null) { + logger.error("unsetInventoryShimInternalListener property is null"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("setInventoryShimInternalListener containerName not supplied"); + return; + } + if ((this.inventoryShimInternalListeners != null) + && this.inventoryShimInternalListeners.get(containerName) != null + && this.inventoryShimInternalListeners.get(containerName) + .equals(s)) { + this.inventoryShimInternalListeners.remove(containerName); + logger.trace( + "Removed inventoryShimInternalListener for container {}", + containerName); + } + } + + void setInventoryShimExternalListener(IInventoryShimExternalListener s) { + logger.trace("Set inventoryShimExternalListener"); + if ((this.inventoryShimExternalListeners != null) + && !this.inventoryShimExternalListeners.contains(s)) { + this.inventoryShimExternalListeners.add(s); + } + } + + void unsetInventoryShimExternalListener(IInventoryShimExternalListener s) { + if ((this.inventoryShimExternalListeners != null) + && this.inventoryShimExternalListeners.contains(s)) { + this.inventoryShimExternalListeners.remove(s); + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + this.controller.addMessageListener(OFType.PORT_STATUS, this); + this.controller.addSwitchStateListener(this); + } + + /** + * Function called after registering the service in OSGi service registry. + */ + void started() { + /* Start with existing switches */ + startService(); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + this.controller.removeMessageListener(OFType.PACKET_IN, this); + this.controller.removeSwitchStateListener(this); + + this.inventoryShimInternalListeners.clear(); + this.nodeConnectorContainerMap.clear(); + this.nodeContainerMap.clear(); + this.globalInventoryShimInternalListeners.clear(); + this.controller = null; + } + + @Override + public void receive(ISwitch sw, OFMessage msg) { + if (msg instanceof OFPortStatus) { + handlePortStatusMessage(sw, (OFPortStatus) msg); + } + return; + } + + protected void handlePortStatusMessage(ISwitch sw, OFPortStatus m) { + Node node = NodeCreator.createOFNode(sw.getId()); + NodeConnector nodeConnector = PortConverter.toNodeConnector( + m.getDesc().getPortNumber(), node); + // get node connector properties + Set props = InventoryServiceHelper.OFPortToProps(m.getDesc()); + + UpdateType type = null; + if (m.getReason() == (byte) OFPortReason.OFPPR_ADD.ordinal()) { + type = UpdateType.ADDED; + nodeConnectorProps.put(nodeConnector, props); + } else if (m.getReason() == (byte) OFPortReason.OFPPR_DELETE.ordinal()) { + type = UpdateType.REMOVED; + nodeConnectorProps.remove(nodeConnector); + } else if (m.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) { + type = UpdateType.CHANGED; + nodeConnectorProps.put(nodeConnector, props); + } + + logger.trace("handlePortStatusMessage {} type {}", nodeConnector, type); + + if (type != null) { + notifyInventoryShimListener(nodeConnector, type, props); + } + } + + @Override + public void switchAdded(ISwitch sw) { + if (sw == null) { + return; + } + + // Add all the nodeConnectors of this switch + Map> ncProps = InventoryServiceHelper + .OFSwitchToProps(sw); + for (Map.Entry> entry : ncProps.entrySet()) { + Set props = new HashSet(); + Set prop = entry.getValue(); + if (prop != null) { + props.addAll(prop); + } + nodeConnectorProps.put(entry.getKey(), props); + notifyInventoryShimListener(entry.getKey(), UpdateType.ADDED, + entry.getValue()); + } + + // Add this node + addNode(sw); + } + + @Override + public void switchDeleted(ISwitch sw) { + if (sw == null) { + return; + } + + removeNode(sw); + } + + @Override + public void containerModeUpdated(UpdateType t) { + // do nothing + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, + short newTag, UpdateType t) { + logger.debug("tagUpdated: {} type {} for container {}", new Object[] { + n, t, containerName }); + } + + @Override + public void containerFlowUpdated(String containerName, + ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) { + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector nc, UpdateType t) { + logger.debug("nodeConnectorUpdated: {} type {} for container {}", new Object[] { nc, t, containerName }); + Node node = nc.getNode(); + Set ncContainers = this.nodeConnectorContainerMap.get(nc); + Set nodeContainers = this.nodeContainerMap.get(node); + if (ncContainers == null) { + ncContainers = new CopyOnWriteArraySet(); + } + if (nodeContainers == null) { + nodeContainers = new CopyOnWriteArraySet(); + } + boolean notifyNodeUpdate = false; + + switch (t) { + case ADDED: + if (ncContainers.add(containerName)) { + this.nodeConnectorContainerMap.put(nc, ncContainers); + } + if (nodeContainers.add(containerName)) { + this.nodeContainerMap.put(node, nodeContainers); + notifyNodeUpdate = true; + } + break; + case REMOVED: + if (ncContainers.remove(containerName)) { + if (ncContainers.isEmpty()) { + // Do cleanup to reduce memory footprint if no + // elements to be tracked + this.nodeConnectorContainerMap.remove(nc); + } else { + this.nodeConnectorContainerMap.put(nc, ncContainers); + } + } + boolean nodeContainerUpdate = true; + for (NodeConnector ncContainer : nodeConnectorContainerMap.keySet()) { + if ((ncContainer.getNode().equals(node)) && (nodeConnectorContainerMap.get(ncContainer).contains(containerName))) { + nodeContainerUpdate = false; + break; + } + } + if (nodeContainerUpdate) { + nodeContainers.remove(containerName); + notifyNodeUpdate = true; + if (nodeContainers.isEmpty()) { + this.nodeContainerMap.remove(node); + } else { + this.nodeContainerMap.put(node, nodeContainers); + } + } + break; + case CHANGED: + break; + } + + Set nodeProp = nodeProps.get(node); + if (nodeProp == null) { + return; + } + Set ncProp = nodeConnectorProps.get(nc); + // notify InventoryService + notifyInventoryShimInternalListener(containerName, nc, t, ncProp); + + if (notifyNodeUpdate) { + notifyInventoryShimInternalListener(containerName, node, t, nodeProp); + } + } + + private void notifyInventoryShimExternalListener(Node node, + UpdateType type, Set props) { + for (IInventoryShimExternalListener s : this.inventoryShimExternalListeners) { + s.updateNode(node, type, props); + } + } + + private void notifyInventoryShimExternalListener( + NodeConnector nodeConnector, UpdateType type, Set props) { + for (IInventoryShimExternalListener s : this.inventoryShimExternalListeners) { + s.updateNodeConnector(nodeConnector, type, props); + } + } + + private void notifyInventoryShimInternalListener(String container, + NodeConnector nodeConnector, UpdateType type, Set props) { + IInventoryShimInternalListener inventoryShimInternalListener = inventoryShimInternalListeners + .get(container); + if (inventoryShimInternalListener != null) { + inventoryShimInternalListener.updateNodeConnector(nodeConnector, + type, props); + logger.trace( + "notifyInventoryShimInternalListener {} type {} for container {}", + new Object[] { nodeConnector, type, container }); + } + } + + /* + * Notify all internal and external listeners + */ + private void notifyInventoryShimListener(NodeConnector nodeConnector, UpdateType type, Set props) { + notifyGlobalInventoryShimInternalListener(nodeConnector, type, props); + /* + * isLocal is intentionally moved after the GlobalInventory listener call. + * The above notification to GlobalInventory will make sure that the connectionOutService be ready + * to reply to isLocal query. + */ + if (!connectionOutService.isLocal(nodeConnector.getNode())) { + logger.debug("Connection service dropped the inventory notification for {} {}", nodeConnector.toString(), type); + return; + } else { + logger.debug("Connection service accepted the inventory notification for {} {}", nodeConnector.toString(), type); + } + + // notify other containers + Set containers = (nodeConnectorContainerMap.get(nodeConnector) == null) ? new HashSet() + : new HashSet(nodeConnectorContainerMap.get(nodeConnector)); + containers.add(GlobalConstants.DEFAULT.toString()); + for (String container : containers) { + notifyInventoryShimInternalListener(container, nodeConnector, type, props); + } + + // Notify DiscoveryService + notifyInventoryShimExternalListener(nodeConnector, type, props); + } + + /* + * Notify all internal and external listeners + */ + private void notifyInventoryShimListener(Node node, UpdateType type, Set props) { + notifyGlobalInventoryShimInternalListener(node, type, props); + /* + * isLocal is intentionally moved after the GlobalInventory listener call. + * The above notification to GlobalInventory will make sure that the connectionOutService be ready + * to reply to isLocal query. + */ + if (!connectionOutService.isLocal(node)) { + logger.debug("Connection service dropped the inventory notification for {} {}", node.toString(), type); + return; + } else { + logger.debug("Connection service accepted the inventory notification for {} {}", node.toString(), type); + } + // Now notify other containers + Set containers = (nodeContainerMap.get(node) == null) ? new HashSet() : new HashSet( + nodeContainerMap.get(node)); + containers.add(GlobalConstants.DEFAULT.toString()); + for (String container : containers) { + notifyInventoryShimInternalListener(container, node, type, props); + } + + // Notify external listener + notifyInventoryShimExternalListener(node, type, props); + } + + private void notifyGlobalInventoryShimInternalListener(Node node, UpdateType type, Set props) { + for (IInventoryShimInternalListener globalListener : globalInventoryShimInternalListeners) { + globalListener.updateNode(node, type, props); + logger.trace( + "notifyGlobalInventoryShimInternalListener {} type {}", + new Object[] { node, type }); + } + } + + private void notifyGlobalInventoryShimInternalListener(NodeConnector nodeConnector, UpdateType type, Set props) { + for (IInventoryShimInternalListener globalListener : globalInventoryShimInternalListeners) { + globalListener.updateNodeConnector(nodeConnector, type, props); + logger.trace( + "notifyGlobalInventoryShimInternalListener {} type {}", + new Object[] { nodeConnector, type }); + } + } + + private void notifyInventoryShimInternalListener(String container, + Node node, UpdateType type, Set props) { + IInventoryShimInternalListener inventoryShimInternalListener = inventoryShimInternalListeners + .get(container); + if (inventoryShimInternalListener != null) { + inventoryShimInternalListener.updateNode(node, type, props); + logger.trace( + "notifyInventoryShimInternalListener {} type {} for container {}", + new Object[] { node, type, container }); + } + } + + private void addNode(ISwitch sw) { + Node node = NodeCreator.createOFNode(sw.getId()); + UpdateType type = UpdateType.ADDED; + + Set props = new HashSet(); + Long sid = (Long) node.getID(); + + Date connectedSince = controller.getSwitches().get(sid) + .getConnectedDate(); + Long connectedSinceTime = (connectedSince == null) ? 0 : connectedSince + .getTime(); + props.add(new TimeStamp(connectedSinceTime, "connectedSince")); + props.add(new MacAddress(deriveMacAddress(sid))); + + byte tables = sw.getTables(); + Tables t = new Tables(tables); + if (t != null) { + props.add(t); + } + int cap = sw.getCapabilities(); + Capabilities c = new Capabilities(cap); + if (c != null) { + props.add(c); + } + int act = sw.getActions(); + Actions a = new Actions(act); + if (a != null) { + props.add(a); + } + int buffers = sw.getBuffers(); + Buffers b = new Buffers(buffers); + if (b != null) { + props.add(b); + } + + nodeProps.put(node, props); + // Notify all internal and external listeners + notifyInventoryShimListener(node, type, props); + } + + private void removeNode(ISwitch sw) { + Node node = NodeCreator.createOFNode(sw.getId()); + if(node == null) { + return; + } + removeNodeConnectorProps(node); + nodeProps.remove(node); + UpdateType type = UpdateType.REMOVED; + // Notify all internal and external listeners + notifyInventoryShimListener(node, type, null); + } + + private void startService() { + // Get a snapshot of all the existing switches + Map switches = this.controller.getSwitches(); + for (ISwitch sw : switches.values()) { + switchAdded(sw); + } + } + + private void removeNodeConnectorProps(Node node) { + List ncList = new ArrayList(); + for (NodeConnector nc : nodeConnectorProps.keySet()) { + if (nc.getNode().equals(node)) { + ncList.add(nc); + } + } + for (NodeConnector nc : ncList) { + nodeConnectorProps.remove(nc); + } + } + + @Override + public void descriptionStatisticsRefreshed(Long switchId, List descriptionStats) { + Node node = NodeCreator.createOFNode(switchId); + Set properties = new HashSet(1); + OFDescriptionStatistics ofDesc = (OFDescriptionStatistics) descriptionStats.get(0); + Description desc = new Description(ofDesc.getDatapathDescription()); + properties.add(desc); + + // Notify all internal and external listeners + notifyInventoryShimListener(node, UpdateType.CHANGED, properties); + } + + private byte[] deriveMacAddress(long dpid) { + byte[] mac = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + for (short i = 0; i < 6; i++) { + mac[5 - i] = (byte) dpid; + dpid >>= 8; + } + + return mac; + } + + @Override + public void flowStatisticsRefreshed(Long switchId, List flows) { + // Nothing to do + } + + @Override + public void portStatisticsRefreshed(Long switchId, List ports) { + // Nothing to do + } + + @Override + public void tableStatisticsRefreshed(Long switchId, List tables) { + // Nothing to do + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/OFStatisticsManager.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/OFStatisticsManager.java new file mode 100644 index 0000000000..b32e8e83db --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/OFStatisticsManager.java @@ -0,0 +1,1214 @@ +/* + * 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.internal; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsManager; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.opendaylight.openflowplugin.openflow.core.ISwitch; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6Match; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6StatsReply; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6StatsRequest; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.statistics.OFAggregateStatisticsRequest; +import org.openflow.protocol.statistics.OFFlowStatisticsReply; +import org.openflow.protocol.statistics.OFFlowStatisticsRequest; +import org.openflow.protocol.statistics.OFPortStatisticsReply; +import org.openflow.protocol.statistics.OFPortStatisticsRequest; +import org.openflow.protocol.statistics.OFQueueStatisticsRequest; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFStatisticsType; +import org.openflow.protocol.statistics.OFTableStatistics; +import org.openflow.protocol.statistics.OFVendorStatistics; +import org.openflow.util.HexString; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * It periodically polls the different OF statistics from the OF switches and + * caches them for quick retrieval for the above layers' modules It also + * provides an API to directly query the switch about the statistics + */ +public class OFStatisticsManager implements IOFStatisticsManager, +IInventoryShimExternalListener, CommandProvider { + private static final Logger log = LoggerFactory.getLogger(OFStatisticsManager.class); + private static final int INITIAL_SIZE = 64; + private static final long FLOW_STATS_PERIOD = 10000; + private static final long DESC_STATS_PERIOD = 60000; + private static final long PORT_STATS_PERIOD = 5000; + private static final long TABLE_STATS_PERIOD = 10000; + private static final long TICK = 1000; + private static short statisticsTickNumber = (short) (FLOW_STATS_PERIOD / TICK); + private static short descriptionTickNumber = (short) (DESC_STATS_PERIOD / TICK); + private static short portTickNumber = (short) (PORT_STATS_PERIOD / TICK); + private static short tableTickNumber = (short) (TABLE_STATS_PERIOD / TICK); + private static short factoredSamples = (short) 2; + private static short counter = 1; + private IController controller = null; + private ConcurrentMap> flowStatistics; + private ConcurrentMap> descStatistics; + private ConcurrentMap> portStatistics; + private ConcurrentMap> tableStatistics; + private List dummyList; + private ConcurrentMap statisticsTimerTicks; + protected BlockingQueue pendingStatsRequests; + protected BlockingQueue switchPortStatsUpdated; + private Thread statisticsCollector; + private Thread txRatesUpdater; + private Timer statisticsTimer; + private TimerTask statisticsTimerTask; + private ConcurrentMap switchSupportsVendorExtStats; + // Per port sampled (every portStatsPeriod) transmit rate + private Map> txRates; + private Set statisticsListeners; + + /** + * The object containing the latest factoredSamples tx rate samples for a + * given switch port + */ + protected class TxRates { + // contains the latest factoredSamples sampled transmitted bytes + Deque sampledTxBytes; + + public TxRates() { + sampledTxBytes = new LinkedBlockingDeque(); + } + + public void update(Long txBytes) { + /* + * Based on how many samples our average works on, we might have to + * remove the oldest sample + */ + if (sampledTxBytes.size() == factoredSamples) { + sampledTxBytes.removeLast(); + } + + // Add the latest sample to the top of the queue + sampledTxBytes.addFirst(txBytes); + } + + /** + * Returns the average transmit rate in bps + * + * @return the average transmit rate [bps] + */ + public long getAverageTxRate() { + long average = 0; + /* + * If we cannot provide the value for the time window length set + */ + if (sampledTxBytes.size() < factoredSamples) { + return average; + } + long increment = sampledTxBytes.getFirst() - sampledTxBytes + .getLast(); + long timePeriod = factoredSamples * PORT_STATS_PERIOD / TICK; + average = (8L * increment) / timePeriod; + return average; + } + } + + public void setController(IController core) { + this.controller = core; + } + + public void unsetController(IController core) { + if (this.controller == core) { + this.controller = null; + } + } + + private short getStatsQueueSize() { + String statsQueueSizeStr = System.getProperty("of.statsQueueSize"); + short statsQueueSize = INITIAL_SIZE; + if (statsQueueSizeStr != null) { + try { + statsQueueSize = Short.parseShort(statsQueueSizeStr); + if (statsQueueSize <= 0) { + statsQueueSize = INITIAL_SIZE; + } + } catch (Exception e) { + } + } + return statsQueueSize; + } + + IPluginOutConnectionService connectionPluginOutService; + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionPluginOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionPluginOutService == s) { + connectionPluginOutService = null; + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + flowStatistics = new ConcurrentHashMap>(); + descStatistics = new ConcurrentHashMap>(); + portStatistics = new ConcurrentHashMap>(); + tableStatistics = new ConcurrentHashMap>(); + dummyList = new ArrayList(1); + pendingStatsRequests = new LinkedBlockingQueue(getStatsQueueSize()); + statisticsTimerTicks = new ConcurrentHashMap(INITIAL_SIZE); + switchPortStatsUpdated = new LinkedBlockingQueue(INITIAL_SIZE); + switchSupportsVendorExtStats = new ConcurrentHashMap(INITIAL_SIZE); + txRates = new HashMap>(INITIAL_SIZE); + statisticsListeners = new CopyOnWriteArraySet(); + + configStatsPollIntervals(); + + // Initialize managed timers + statisticsTimer = new Timer(); + statisticsTimerTask = new TimerTask() { + @Override + public void run() { + decrementTicks(); + } + }; + + // Initialize Statistics collector thread + statisticsCollector = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + StatsRequest req = pendingStatsRequests.take(); + queryStatisticsInternal(req.switchId, req.type); + } catch (InterruptedException e) { + log.warn("Flow Statistics Collector thread " + + "interrupted", e); + } + } + } + }, "Statistics Collector"); + + // Initialize Tx Rate Updater thread + txRatesUpdater = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + long switchId = switchPortStatsUpdated.take(); + updatePortsTxRate(switchId); + } catch (InterruptedException e) { + log.warn("TX Rate Updater thread interrupted", e); + } + } + } + }, "TX Rate Updater"); + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + // Start managed timers + statisticsTimer.scheduleAtFixedRate(statisticsTimerTask, 0, TICK); + + // Start statistics collector thread + statisticsCollector.start(); + + // Start bandwidth utilization computer thread + txRatesUpdater.start(); + + // OSGI console + registerWithOSGIConsole(); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + // Stop managed timers + statisticsTimer.cancel(); + } + + public void setStatisticsListener(IOFStatisticsListener s) { + this.statisticsListeners.add(s); + } + + public void unsetStatisticsListener(IOFStatisticsListener s) { + if (s != null) { + this.statisticsListeners.remove(s); + } + } + + private void registerWithOSGIConsole() { + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext(); + bundleContext.registerService(CommandProvider.class.getName(), this, null); + } + + private static class StatsRequest { + protected Long switchId; + protected OFStatisticsType type; + + public StatsRequest(Long d, OFStatisticsType t) { + switchId = d; + type = t; + } + + @Override + public String toString() { + return "SReq = {switchId=" + switchId + ", type=" + type + "}"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((switchId == null) ? 0 : switchId.hashCode()); + result = prime * result + ((type == null) ? 0 : type.ordinal()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + StatsRequest other = (StatsRequest) obj; + if (switchId == null) { + if (other.switchId != null) { + return false; + } + } else if (!switchId.equals(other.switchId)) { + return false; + } + if (type != other.type) { + return false; + } + return true; + } + } + + private void addStatisticsTicks(Long switchId) { + switchSupportsVendorExtStats.put(switchId, Boolean.TRUE); // Assume + // switch + // supports + // Vendor + // extension + // stats + statisticsTimerTicks.put(switchId, new StatisticsTicks(true)); + log.debug("Added Switch {} to target pool", + HexString.toHexString(switchId.longValue())); + } + + protected static class StatisticsTicks { + private short flowStatisticsTicks; + private short descriptionTicks; + private short portStatisticsTicks; + private short tableStatisticsTicks; + + public StatisticsTicks(boolean scattered) { + if (scattered) { + // scatter bursts by statisticsTickPeriod + if (++counter < 0) { + counter = 0; + } // being paranoid here + flowStatisticsTicks = (short) (1 + counter + % statisticsTickNumber); + descriptionTicks = (short) (1 + counter % descriptionTickNumber); + portStatisticsTicks = (short) (1 + counter % portTickNumber); + tableStatisticsTicks = (short) (1 + counter % tableTickNumber); + } else { + flowStatisticsTicks = statisticsTickNumber; + descriptionTicks = descriptionTickNumber; + portStatisticsTicks = portTickNumber; + tableStatisticsTicks = tableTickNumber; + } + } + + public boolean decrementFlowTicksIsZero() { + // Please ensure no code is inserted between the if check and the + // flowStatisticsTicks reset + if (--flowStatisticsTicks == 0) { + flowStatisticsTicks = statisticsTickNumber; + return true; + } + return false; + } + + public boolean decrementDescTicksIsZero() { + // Please ensure no code is inserted between the if check and the + // descriptionTicks reset + if (--descriptionTicks == 0) { + descriptionTicks = descriptionTickNumber; + return true; + } + return false; + } + + public boolean decrementPortTicksIsZero() { + // Please ensure no code is inserted between the if check and the + // descriptionTicks reset + if (--portStatisticsTicks == 0) { + portStatisticsTicks = portTickNumber; + return true; + } + return false; + } + + public boolean decrementTableTicksIsZero() { + // Please ensure no code is inserted between the if check and the + // descriptionTicks reset + if(--tableStatisticsTicks == 0) { + tableStatisticsTicks = tableTickNumber; + return true; + } + return false; + } + + @Override + public String toString() { + return "{fT=" + flowStatisticsTicks + ",dT=" + descriptionTicks + + ",pT=" + portStatisticsTicks + ",tT=" + tableStatisticsTicks + "}"; + } + } + + private void printInfoMessage(String type, StatsRequest request) { + log.info("{} stats request not inserted for switch: {}. Queue size: {}. Collector state: {}.", + new Object[] {type, HexString.toHexString(request.switchId), pendingStatsRequests.size(), + statisticsCollector.getState().toString() }); + } + + protected void decrementTicks() { + StatsRequest request = null; + for (Map.Entry entry : statisticsTimerTicks + .entrySet()) { + StatisticsTicks clock = entry.getValue(); + Long switchId = entry.getKey(); + if (clock.decrementFlowTicksIsZero()) { + request = (switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) ? + new StatsRequest(switchId, OFStatisticsType.VENDOR) : + new StatsRequest(switchId, OFStatisticsType.FLOW); + // If a request for this switch is already in the queue, skip to + // add this new request + if (!pendingStatsRequests.contains(request) + && false == pendingStatsRequests.offer(request)) { + printInfoMessage("Flow", request); + } + } + + if (clock.decrementDescTicksIsZero()) { + request = new StatsRequest(switchId, OFStatisticsType.DESC); + // If a request for this switch is already in the queue, skip to + // add this new request + if (!pendingStatsRequests.contains(request) + && false == pendingStatsRequests.offer(request)) { + printInfoMessage("Description", request); + } + } + + if (clock.decrementPortTicksIsZero()) { + request = new StatsRequest(switchId, OFStatisticsType.PORT); + // If a request for this switch is already in the queue, skip to + // add this new request + if (!pendingStatsRequests.contains(request) + && false == pendingStatsRequests.offer(request)) { + printInfoMessage("Port", request); + } + } + + if(clock.decrementTableTicksIsZero()) { + request = new StatsRequest(switchId, OFStatisticsType.TABLE); + // If a request for this switch is already in the queue, skip to + // add this new request + if (!pendingStatsRequests.contains(request) + && false == pendingStatsRequests.offer(request)) { + printInfoMessage("Table", request); + } + } + } + } + + private void removeStatsRequestTasks(Long switchId) { + log.debug("Cleaning Statistics database for switch {}", + HexEncode.longToHexString(switchId)); + // To be safe, let's attempt removal of both VENDOR and FLOW request. It + // does not hurt + pendingStatsRequests.remove(new StatsRequest(switchId, + OFStatisticsType.VENDOR)); + pendingStatsRequests.remove(new StatsRequest(switchId, + OFStatisticsType.FLOW)); + pendingStatsRequests.remove(new StatsRequest(switchId, + OFStatisticsType.DESC)); + pendingStatsRequests.remove(new StatsRequest(switchId, + OFStatisticsType.PORT)); + pendingStatsRequests.remove(new StatsRequest(switchId, + OFStatisticsType.TABLE)); + // Take care of the TX rate databases + switchPortStatsUpdated.remove(switchId); + txRates.remove(switchId); + } + + private void clearFlowStatsAndTicks(Long switchId) { + statisticsTimerTicks.remove(switchId); + removeStatsRequestTasks(switchId); + flowStatistics.remove(switchId); + log.debug("Statistics removed for switch {}", + HexString.toHexString(switchId)); + } + + private void queryStatisticsInternal(Long switchId, OFStatisticsType statType) { + + // Query the switch on all matches + List values = this.fetchStatisticsFromSwitch(switchId, statType, null); + + // If got a valid response update local cache and notify listeners + if (values != null && !values.isEmpty()) { + switch (statType) { + case FLOW: + case VENDOR: + flowStatistics.put(switchId, values); + notifyFlowUpdate(switchId, values); + break; + case DESC: + // Overwrite cache + descStatistics.put(switchId, values); + // Notify who may be interested in a description change + notifyDescriptionUpdate(switchId, values); + break; + case PORT: + // Overwrite cache with new port statistics for this switch + portStatistics.put(switchId, values); + + // Wake up the thread which maintains the TX byte counters for + // each port + switchPortStatsUpdated.offer(switchId); + notifyPortUpdate(switchId, values); + break; + case TABLE: + // Overwrite cache + tableStatistics.put(switchId, values); + notifyTableUpdate(switchId, values); + break; + default: + } + } + } + + private void notifyDescriptionUpdate(Long switchId, List values) { + for (IOFStatisticsListener l : this.statisticsListeners) { + l.descriptionStatisticsRefreshed(switchId, values); + } + } + + private void notifyFlowUpdate(Long switchId, List values) { + if (values.get(0) instanceof OFVendorStatistics) { + values = this.v6StatsListToOFStatsList(values); + } + + for (IOFStatisticsListener l : this.statisticsListeners) { + l.flowStatisticsRefreshed(switchId, values); + } + + } + + private void notifyPortUpdate(Long switchId, List values) { + for (IOFStatisticsListener l : this.statisticsListeners) { + l.portStatisticsRefreshed(switchId, values); + } + } + + private void notifyTableUpdate(Long switchId, List values) { + for (IOFStatisticsListener l : this.statisticsListeners) { + l.tableStatisticsRefreshed(switchId, values); + } + } + + /* + * Generic function to get the statistics form an OF switch + */ + @SuppressWarnings("unchecked") + private List fetchStatisticsFromSwitch(Long switchId, + OFStatisticsType statsType, Object target) { + List values = null; + String type = null; + ISwitch sw = controller.getSwitch(switchId); + + if (sw != null) { + OFStatisticsRequest req = new OFStatisticsRequest(); + req.setStatisticType(statsType); + int requestLength = req.getLengthU(); + + if (statsType == OFStatisticsType.FLOW) { + OFMatch match = null; + if (target == null) { + // All flows request + match = new OFMatch(); + match.setWildcards(0xffffffff); + } else if (!(target instanceof OFMatch)) { + // Malformed request + log.warn("Invalid target type for Flow stats request: {}", + target.getClass()); + return null; + } else { + // Specific flow request + match = (OFMatch) target; + } + OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest(); + specificReq.setMatch(match); + specificReq.setOutPort(OFPort.OFPP_NONE.getValue()); + specificReq.setTableId((byte) 0xff); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + type = "FLOW"; + } else if (statsType == OFStatisticsType.VENDOR) { + V6StatsRequest specificReq = new V6StatsRequest(); + specificReq.setOutPort(OFPort.OFPP_NONE.getValue()); + specificReq.setTableId((byte) 0xff); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + type = "VENDOR"; + } else if (statsType == OFStatisticsType.AGGREGATE) { + OFAggregateStatisticsRequest specificReq = new OFAggregateStatisticsRequest(); + OFMatch match = new OFMatch(); + match.setWildcards(0xffffffff); + specificReq.setMatch(match); + specificReq.setOutPort(OFPort.OFPP_NONE.getValue()); + specificReq.setTableId((byte) 0xff); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + type = "AGGREGATE"; + } else if (statsType == OFStatisticsType.PORT) { + short targetPort; + if (target == null) { + // All ports request + targetPort = OFPort.OFPP_NONE.getValue(); + } else if (!(target instanceof Short)) { + // Malformed request + log.warn("Invalid target type for Port stats request: {}", + target.getClass()); + return null; + } else { + // Specific port request + targetPort = (Short) target; + } + OFPortStatisticsRequest specificReq = new OFPortStatisticsRequest(); + specificReq.setPortNumber(targetPort); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + type = "PORT"; + } else if (statsType == OFStatisticsType.QUEUE) { + OFQueueStatisticsRequest specificReq = new OFQueueStatisticsRequest(); + specificReq.setPortNumber(OFPort.OFPP_ALL.getValue()); + specificReq.setQueueId(0xffffffff); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + type = "QUEUE"; + } else if (statsType == OFStatisticsType.DESC) { + type = "DESC"; + } else if (statsType == OFStatisticsType.TABLE) { + if(target != null){ + if (!(target instanceof Byte)) { + // Malformed request + log.warn("Invalid table id for table stats request: {}", + target.getClass()); + return null; + } + byte targetTable = (Byte) target; + OFTableStatistics specificReq = new OFTableStatistics(); + specificReq.setTableId(targetTable); + req.setStatistics(Collections + .singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + } + type = "TABLE"; + } + req.setLengthU(requestLength); + Object result = sw.getStatistics(req); + + if (result == null) { + log.warn("Request Timed Out for ({}) from switch {}", type, + HexString.toHexString(switchId)); + } else if (result instanceof OFError) { + log.warn("Switch {} failed to handle ({}) stats request: {}", + new Object[] { HexString.toHexString(switchId), type, + Utils.getOFErrorString((OFError) result) }); + if (this.switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) { + log.warn( + "Switching back to regular Flow stats requests for switch {}", + HexString.toHexString(switchId)); + this.switchSupportsVendorExtStats.put(switchId, + Boolean.FALSE); + } + } else { + values = (List) result; + } + } + return values; + } + + @Override + public List getOFFlowStatistics(Long switchId) { + List list = flowStatistics.get(switchId); + + /* + * Check on emptiness as interference between add and get is still + * possible on the inner list (the concurrentMap entry's value) + */ + return (list == null || list.isEmpty()) ? this.dummyList + : (list.get(0) instanceof OFVendorStatistics) ? this + .v6StatsListToOFStatsList(list) : list; + } + + @Override + public List getOFFlowStatistics(Long switchId, OFMatch ofMatch, short priority) { + List statsList = flowStatistics.get(switchId); + + /* + * Check on emptiness as interference between add and get is still + * possible on the inner list (the concurrentMap entry's value) + */ + if (statsList == null || statsList.isEmpty()) { + return this.dummyList; + } + + if (statsList.get(0) instanceof OFVendorStatistics) { + /* + * Caller could provide regular OF match when we instead pull the + * vendor statistics from this node Caller is not supposed to know + * whether this switch supports vendor extensions statistics + * requests + */ + V6Match targetMatch = (ofMatch instanceof V6Match) ? (V6Match) ofMatch + : new V6Match(ofMatch); + + List targetList = v6StatsListToOFStatsList(statsList); + for (OFStatistics stats : targetList) { + V6StatsReply v6Stats = (V6StatsReply) stats; + V6Match v6Match = v6Stats.getMatch(); + if (v6Stats.getPriority() == priority && v6Match.equals(targetMatch)) { + List list = new ArrayList(); + list.add(stats); + return list; + } + } + } else { + for (OFStatistics stats : statsList) { + OFFlowStatisticsReply flowStats = (OFFlowStatisticsReply) stats; + if (flowStats.getPriority() == priority && flowStats.getMatch().equals(ofMatch)) { + List list = new ArrayList(); + list.add(stats); + return list; + } + } + } + return this.dummyList; + } + + /* + * Converts the v6 vendor statistics to the OFStatistics + */ + private List v6StatsListToOFStatsList( + List statistics) { + List v6statistics = new ArrayList(); + if (statistics != null && !statistics.isEmpty()) { + for (OFStatistics stats : statistics) { + if (stats instanceof OFVendorStatistics) { + List r = getV6ReplyStatistics((OFVendorStatistics) stats); + if (r != null) { + v6statistics.addAll(r); + } + } + } + } + return v6statistics; + } + + private static List getV6ReplyStatistics( + OFVendorStatistics stat) { + int length = stat.getLength(); + List results = new ArrayList(); + if (length < 12) + return null; // Nicira Hdr is 12 bytes. We need atleast that much + ByteBuffer data = ByteBuffer.allocate(length); + stat.writeTo(data); + data.rewind(); + if (log.isTraceEnabled()) { + log.trace("getV6ReplyStatistics: Buffer BYTES ARE {}", + HexString.toHexString(data.array())); + } + + int vendor = data.getInt(); // first 4 bytes is vendor id. + if (vendor != V6StatsRequest.NICIRA_VENDOR_ID) { + log.warn("Unexpected vendor id: 0x{}", Integer.toHexString(vendor)); + return null; + } else { + // go ahead by 8 bytes which is 8 bytes of 0 + data.getLong(); // should be all 0's + length -= 12; // 4 bytes Nicira Hdr + 8 bytes from above line have + // been consumed + } + + V6StatsReply v6statsreply; + int min_len; + while (length > 0) { + v6statsreply = new V6StatsReply(); + min_len = v6statsreply.getLength(); + if (length < v6statsreply.getLength()) + break; + v6statsreply.setActionFactory(stat.getActionFactory()); + v6statsreply.readFrom(data); + if (v6statsreply.getLength() < min_len) + break; + v6statsreply.setVendorId(vendor); + log.trace("V6StatsReply: {}", v6statsreply); + length -= v6statsreply.getLength(); + results.add(v6statsreply); + } + return results; + } + + @Override + public List queryStatistics(Long switchId, + OFStatisticsType statType, Object target) { + /* + * Caller does not know and it is not supposed to know whether this + * switch supports vendor extension. We adjust the target for him + */ + if (statType == OFStatisticsType.FLOW) { + if (switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) { + statType = OFStatisticsType.VENDOR; + } + } + + List list = this.fetchStatisticsFromSwitch(switchId, statType, + target); + + return (list == null) ? null : + (statType == OFStatisticsType.VENDOR) ? v6StatsListToOFStatsList(list) : list; + } + + @Override + public List getOFDescStatistics(Long switchId) { + if (!descStatistics.containsKey(switchId)) + return this.dummyList; + + return descStatistics.get(switchId); + } + + @Override + public List getOFPortStatistics(Long switchId) { + if (!portStatistics.containsKey(switchId)) { + return this.dummyList; + } + + return portStatistics.get(switchId); + } + + @Override + public List getOFPortStatistics(Long switchId, short portId) { + if (!portStatistics.containsKey(switchId)) { + return this.dummyList; + } + List list = new ArrayList(1); + for (OFStatistics stats : portStatistics.get(switchId)) { + if (((OFPortStatisticsReply) stats).getPortNumber() == portId) { + list.add(stats); + break; + } + } + return list; + } + + @Override + public List getOFTableStatistics(Long switchId) { + if (!tableStatistics.containsKey(switchId)) { + return this.dummyList; + } + + return tableStatistics.get(switchId); + } + + @Override + public List getOFTableStatistics(Long switchId, Byte tableId) { + if (!tableStatistics.containsKey(switchId)) { + return this.dummyList; + } + + List list = new ArrayList(1); + for (OFStatistics stats : tableStatistics.get(switchId)) { + if (((OFTableStatistics) stats).getTableId() == tableId) { + list.add(stats); + break; + } + } + return list; + } + + @Override + public int getFlowsNumber(long switchId) { + return this.flowStatistics.get(switchId).size(); + } + + /* + * InventoryShim replay for us all the switch addition which happened before + * we were brought up + */ + @Override + public void updateNode(Node node, UpdateType type, Set props) { + Long switchId = (Long) node.getID(); + switch (type) { + case ADDED: + addStatisticsTicks(switchId); + break; + case REMOVED: + clearFlowStatsAndTicks(switchId); + default: + } + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props) { + // No action + } + + /** + * Update the cached port rates for this switch with the latest retrieved + * port transmit byte count + * + * @param switchId + */ + private synchronized void updatePortsTxRate(long switchId) { + List newPortStatistics = this.portStatistics.get(switchId); + if (newPortStatistics == null) { + return; + } + Map rates = this.txRates.get(switchId); + if (rates == null) { + // First time rates for this switch are added + rates = new HashMap(); + txRates.put(switchId, rates); + } + for (OFStatistics stats : newPortStatistics) { + OFPortStatisticsReply newPortStat = (OFPortStatisticsReply) stats; + short port = newPortStat.getPortNumber(); + TxRates portRatesHolder = rates.get(port); + if (portRatesHolder == null) { + // First time rates for this port are added + portRatesHolder = new TxRates(); + rates.put(port, portRatesHolder); + } + // Get and store the number of transmitted bytes for this port + // And handle the case where agent does not support the counter + long transmitBytes = newPortStat.getTransmitBytes(); + long value = (transmitBytes < 0) ? 0 : transmitBytes; + portRatesHolder.update(value); + } + } + + @Override + public synchronized long getTransmitRate(Long switchId, Short port) { + long average = 0; + if (switchId == null || port == null) { + return average; + } + Map perSwitch = txRates.get(switchId); + if (perSwitch == null) { + return average; + } + TxRates portRates = perSwitch.get(port); + if (portRates == null) { + return average; + } + return portRates.getAverageTxRate(); + } + + /* + * Manual switch name configuration code + */ + @Override + public String getHelp() { + StringBuffer help = new StringBuffer(); + help.append("---OF Statistics Manager utilities---\n"); + help.append("\t ofdumpstatsmgr - " + + "Print Internal Stats Mgr db\n"); + help.append("\t ofstatsmgrintervals (all in seconds) - " + + "Set/Show flow/port/dedscription stats poll intervals\n"); + return help.toString(); + } + + private boolean isValidSwitchId(String switchId) { + String regexDatapathID = "^([0-9a-fA-F]{1,2}[:-]){7}[0-9a-fA-F]{1,2}$"; + String regexDatapathIDLong = "^[0-9a-fA-F]{1,16}$"; + + return (switchId != null && (switchId.matches(regexDatapathID) || switchId + .matches(regexDatapathIDLong))); + } + + public long getSwitchIDLong(String switchId) { + int radix = 16; + String switchString = "0"; + + if (isValidSwitchId(switchId)) { + if (switchId.contains(":")) { + // Handle the 00:00:AA:BB:CC:DD:EE:FF notation + switchString = switchId.replace(":", ""); + } else if (switchId.contains("-")) { + // Handle the 00-00-AA-BB-CC-DD-EE-FF notation + switchString = switchId.replace("-", ""); + } else { + // Handle the 0123456789ABCDEF notation + switchString = switchId; + } + } + return Long.parseLong(switchString, radix); + } + + /* + * Internal information dump code + */ + private String prettyPrintSwitchMap(ConcurrentMap map) { + StringBuffer buffer = new StringBuffer(); + buffer.append("{"); + for (Entry entry : map.entrySet()) { + buffer.append(HexString.toHexString(entry.getKey()) + "=" + + entry.getValue().toString() + " "); + } + buffer.append("}"); + return buffer.toString(); + } + + public void _ofdumpstatsmgr(CommandInterpreter ci) { + ci.println("Global Counter: " + counter); + ci.println("Timer Ticks: " + prettyPrintSwitchMap(statisticsTimerTicks)); + ci.println("PendingStatsQueue: " + pendingStatsRequests); + ci.println("PendingStatsQueue size: " + pendingStatsRequests.size()); + ci.println("Stats Collector alive: " + statisticsCollector.isAlive()); + ci.println("Stats Collector State: " + + statisticsCollector.getState().toString()); + ci.println("StatsTimer: " + statisticsTimer.toString()); + ci.println("Flow Stats Period: " + statisticsTickNumber + " s"); + ci.println("Desc Stats Period: " + descriptionTickNumber + " s"); + ci.println("Port Stats Period: " + portTickNumber + " s"); + ci.println("Table Stats Period: " + tableTickNumber + " s"); + } + + public void _resetSwitchCapability(CommandInterpreter ci) { + String sidString = ci.nextArgument(); + Long sid = null; + if (sidString == null) { + ci.println("Insert the switch id (numeric value)"); + return; + } + try { + sid = Long.valueOf(sidString); + this.switchSupportsVendorExtStats.put(sid, Boolean.TRUE); + ci.println("Vendor capability for switch " + sid + " set to " + + this.switchSupportsVendorExtStats.get(sid)); + } catch (NumberFormatException e) { + ci.println("Invalid switch id. Has to be numeric."); + } + + } + + public void _ofbw(CommandInterpreter ci) { + String sidString = ci.nextArgument(); + Long sid = null; + if (sidString == null) { + ci.println("Insert the switch id (numeric value)"); + return; + } + try { + sid = Long.valueOf(sidString); + } catch (NumberFormatException e) { + ci.println("Invalid switch id. Has to be numeric."); + } + if (sid != null) { + Map thisSwitchRates = txRates.get(sid); + ci.println("Bandwidth utilization (" + factoredSamples + * portTickNumber + " sec average) for switch " + + HexEncode.longToHexString(sid) + ":"); + if (thisSwitchRates == null) { + ci.println("Not available"); + } else { + for (Entry entry : thisSwitchRates.entrySet()) { + ci.println("Port: " + entry.getKey() + ": " + + entry.getValue().getAverageTxRate() + " bps"); + } + } + } + } + + public void _txratewindow(CommandInterpreter ci) { + String averageWindow = ci.nextArgument(); + short seconds = 0; + if (averageWindow == null) { + ci.println("Insert the length in seconds of the median " + + "window for tx rate"); + ci.println("Current: " + factoredSamples * portTickNumber + " secs"); + return; + } + try { + seconds = Short.valueOf(averageWindow); + } catch (NumberFormatException e) { + ci.println("Invalid period."); + } + OFStatisticsManager.factoredSamples = (short) (seconds / portTickNumber); + ci.println("New: " + factoredSamples * portTickNumber + " secs"); + } + + public void _ofstatsmgrintervals(CommandInterpreter ci) { + String flowStatsInterv = ci.nextArgument(); + String portStatsInterv = ci.nextArgument(); + String descStatsInterv = ci.nextArgument(); + String tableStatsInterv = ci.nextArgument(); + + if (flowStatsInterv == null || portStatsInterv == null + || descStatsInterv == null) { + ci.println("Usage: ofstatsmgrintervals (all in seconds)"); + ci.println("Current Values: fP=" + statisticsTickNumber + "sec pP=" + + portTickNumber + "sec dP=" + descriptionTickNumber + "sec tP=" + tableTickNumber + " sec"); + return; + } + Short fP, pP, dP, tP; + try { + fP = Short.parseShort(flowStatsInterv); + pP = Short.parseShort(portStatsInterv); + dP = Short.parseShort(descStatsInterv); + tP = Short.parseShort(tableStatsInterv); + } catch (Exception e) { + ci.println("Invalid format values: " + e.getMessage()); + return; + } + + if (pP <= 1 || fP <= 1 || dP <= 1 || tP <= 1) { + ci.println("Invalid values. fP, pP, dP, tP have to be greater than 1."); + return; + } + + statisticsTickNumber = fP; + portTickNumber = pP; + descriptionTickNumber = dP; + tableTickNumber = tP; + + ci.println("New Values: fP=" + statisticsTickNumber + "s pP=" + + portTickNumber + "s dP=" + descriptionTickNumber + "s tP=" + + tableTickNumber + "s"); + } + + /** + * This method retrieves user configurations from config.ini and updates + * statisticsTickNumber/portTickNumber/descriptionTickNumber accordingly. + */ + private void configStatsPollIntervals() { + String fsStr = System.getProperty("of.flowStatsPollInterval"); + String psStr = System.getProperty("of.portStatsPollInterval"); + String dsStr = System.getProperty("of.descStatsPollInterval"); + String tsStr = System.getProperty("of.tableStatsPollInterval"); + Short fs, ps, ds, ts; + + if (fsStr != null) { + try { + fs = Short.parseShort(fsStr); + if (fs > 0) { + statisticsTickNumber = fs; + } + } catch (Exception e) { + } + } + + if (psStr != null) { + try { + ps = Short.parseShort(psStr); + if (ps > 0) { + portTickNumber = ps; + } + } catch (Exception e) { + } + } + + if (dsStr != null) { + try { + ds = Short.parseShort(dsStr); + if (ds > 0) { + descriptionTickNumber = ds; + } + } catch (Exception e) { + } + } + + if (tsStr != null) { + try{ + ts = Short.parseShort(tsStr); + if (ts > 0) { + tableTickNumber = ts; + } + } catch (Exception e) { + } + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortConverter.java new file mode 100644 index 0000000000..028d643944 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortConverter.java @@ -0,0 +1,73 @@ +/* + * 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.internal; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType; +import org.opendaylight.controller.sal.utils.NetUtils; +import org.opendaylight.controller.sal.utils.NodeConnectorCreator; +import org.openflow.protocol.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class which provides the utilities for converting the Openflow port + * number to the equivalent NodeConnector and vice versa + * + * + * + */ +public abstract class PortConverter { + private static final Logger log = LoggerFactory + .getLogger(PortConverter.class); + private static final int maxOFPhysicalPort = NetUtils + .getUnsignedShort(OFPort.OFPP_MAX.getValue()); + + /** + * Converts the Openflow port number to the equivalent NodeConnector. + */ + public static NodeConnector toNodeConnector(short ofPort, Node node) { + // Restore original OF unsigned 16 bits value for the comparison + int unsignedOFPort = NetUtils.getUnsignedShort(ofPort); + log.trace("Openflow port number signed: {} unsigned: {}", ofPort, + unsignedOFPort); + if (unsignedOFPort > maxOFPhysicalPort) { + if (ofPort == OFPort.OFPP_LOCAL.getValue()) { + return NodeConnectorCreator.createNodeConnector( + NodeConnectorIDType.SWSTACK, + NodeConnector.SPECIALNODECONNECTORID, node); + } else if (ofPort == OFPort.OFPP_NORMAL.getValue()) { + return NodeConnectorCreator.createNodeConnector( + NodeConnectorIDType.HWPATH, + NodeConnector.SPECIALNODECONNECTORID, node); + } else if (ofPort == OFPort.OFPP_CONTROLLER.getValue()) { + return NodeConnectorCreator.createNodeConnector( + NodeConnectorIDType.CONTROLLER, + NodeConnector.SPECIALNODECONNECTORID, node); + } + } + return NodeConnectorCreator.createNodeConnector(ofPort, node); + } + + /** + * Converts the NodeConnector to the equivalent Openflow port number + */ + public static short toOFPort(NodeConnector salPort) { + log.trace("SAL Port", salPort); + if (salPort.getType().equals(NodeConnectorIDType.SWSTACK)) { + return OFPort.OFPP_LOCAL.getValue(); + } else if (salPort.getType().equals(NodeConnectorIDType.HWPATH)) { + return OFPort.OFPP_NORMAL.getValue(); + } else if (salPort.getType().equals(NodeConnectorIDType.CONTROLLER)) { + return OFPort.OFPP_CONTROLLER.getValue(); + } + return (Short) salPort.getID(); + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortStatisticsConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortStatisticsConverter.java new file mode 100644 index 0000000000..e731c69d82 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/PortStatisticsConverter.java @@ -0,0 +1,78 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.reader.NodeConnectorStatistics; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.openflow.protocol.statistics.OFPortStatisticsReply; +import org.openflow.protocol.statistics.OFStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts an openflow list of port statistics in a SAL list of + * NodeConnectorStatistics objects + * + * + * + */ +public class PortStatisticsConverter { + private static final Logger log = LoggerFactory + .getLogger(PortStatisticsConverter.class); + private long switchId; + private List ofStatsList; + private List ncStatsList; + + public PortStatisticsConverter(long switchId, List statsList) { + this.switchId = switchId; + if (statsList == null || statsList.isEmpty()) { + this.ofStatsList = new ArrayList(1); // dummy list + } else { + this.ofStatsList = new ArrayList(statsList); + } + this.ncStatsList = null; + } + + public List getNodeConnectorStatsList() { + if (this.ofStatsList != null && this.ncStatsList == null) { + this.ncStatsList = new ArrayList(); + OFPortStatisticsReply ofPortStat; + Node node = NodeCreator.createOFNode(switchId); + for (OFStatistics ofStat : this.ofStatsList) { + ofPortStat = (OFPortStatisticsReply) ofStat; + NodeConnectorStatistics NCStat = new NodeConnectorStatistics(); + NCStat.setNodeConnector(PortConverter.toNodeConnector( + ofPortStat.getPortNumber(), node)); + NCStat.setReceivePacketCount(ofPortStat.getreceivePackets()); + NCStat.setTransmitPacketCount(ofPortStat.getTransmitPackets()); + NCStat.setReceiveByteCount(ofPortStat.getReceiveBytes()); + NCStat.setTransmitByteCount(ofPortStat.getTransmitBytes()); + NCStat.setReceiveDropCount(ofPortStat.getReceiveDropped()); + NCStat.setTransmitDropCount(ofPortStat.getTransmitDropped()); + NCStat.setReceiveErrorCount(ofPortStat.getreceiveErrors()); + NCStat.setTransmitErrorCount(ofPortStat.getTransmitErrors()); + NCStat.setReceiveFrameErrorCount(ofPortStat + .getReceiveFrameErrors()); + NCStat.setReceiveOverRunErrorCount(ofPortStat + .getReceiveOverrunErrors()); + NCStat.setReceiveCRCErrorCount(ofPortStat.getReceiveCRCErrors()); + NCStat.setCollisionCount(ofPortStat.getCollisions()); + this.ncStatsList.add(NCStat); + } + } + log.trace("OFStatistics: {} NodeConnectorStatistics: {}", ofStatsList, + ncStatsList); + return this.ncStatsList; + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadService.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadService.java new file mode 100644 index 0000000000..1b86caadf4 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadService.java @@ -0,0 +1,285 @@ + +/* + * 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.internal; + +import java.util.Dictionary; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.felix.dm.Component; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.Node.NodeIDType; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.NodeTable; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.reader.FlowOnNode; +import org.opendaylight.controller.sal.reader.IPluginInReadService; +import org.opendaylight.controller.sal.reader.IPluginOutReadService; +import org.opendaylight.controller.sal.reader.NodeConnectorStatistics; +import org.opendaylight.controller.sal.reader.NodeDescription; +import org.opendaylight.controller.sal.reader.NodeTableStatistics; +import org.opendaylight.openflowplugin.openflow.IReadFilterInternalListener; +import org.opendaylight.openflowplugin.openflow.IReadServiceFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Container Instance of IPluginInReadService implementation class + */ +public class ReadService implements IPluginInReadService, IReadFilterInternalListener { + private static final Logger logger = LoggerFactory + .getLogger(ReadService.class); + private IReadServiceFilter filter; + private Set pluginOutReadServices; + private String containerName; + private IPluginOutConnectionService connectionOutService; + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + @SuppressWarnings("unchecked") + void init(Component c) { + Dictionary props = c.getServiceProperties(); + containerName = (props != null) ? (String) props.get("containerName") + : null; + pluginOutReadServices = new CopyOnWriteArraySet(); + } + + /** + * Function called by the dependency manager when at least one + * dependency become unsatisfied or when the component is shutting + * down because for example bundle is being stopped. + * + */ + void destroy() { + } + + /** + * Function called by dependency manager after "init ()" is called + * and after the services provided by the class are registered in + * the service registry + * + */ + void start() { + } + + /** + * Function called by the dependency manager before the services + * exported by the component are unregistered, this will be + * followed by a "destroy ()" calls + * + */ + void stop() { + } + + public void setService(IReadServiceFilter filter) { + this.filter = filter; + } + + public void unsetService(IReadServiceFilter filter) { + this.filter = null; + } + + public void setPluginOutReadServices(IPluginOutReadService service) { + logger.trace("Got a service set request {}", service); + if (this.pluginOutReadServices != null) { + this.pluginOutReadServices.add(service); + } + } + + public void unsetPluginOutReadServices( + IPluginOutReadService service) { + logger.trace("Got a service UNset request"); + if (this.pluginOutReadServices != null) { + this.pluginOutReadServices.remove(service); + } + } + @Override + public FlowOnNode readFlow(Node node, Flow flow, boolean cached) { + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for the node : " + node); + return null; + } + return filter.readFlow(containerName, node, flow, cached); + } + + @Override + public List readAllFlow(Node node, boolean cached) { + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for the node : " + node); + return null; + } + + return filter.readAllFlow(containerName, node, cached); + } + + @Override + public NodeDescription readDescription(Node node, boolean cached) { + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for the node : " + node); + return null; + } + + return filter.readDescription(node, cached); + } + + @Override + public NodeConnectorStatistics readNodeConnector(NodeConnector connector, + boolean cached) { + if (!connector.getNode().getType() + .equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(connector.getNode())) { + logger.debug("This Controller is not the master for connector : "+connector); + return null; + } + + return filter.readNodeConnector(containerName, connector, cached); + } + + @Override + public List readAllNodeConnector(Node node, + boolean cached) { + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return null; + } + + return filter.readAllNodeConnector(containerName, node, cached); + } + + @Override + public long getTransmitRate(NodeConnector connector) { + if (!connector.getNode().getType() + .equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return 0; + } + + if (!connectionOutService.isLocal(connector.getNode())) { + logger.debug("This Controller is not the master for connector : "+connector); + return 0; + } + + return filter.getTransmitRate(containerName, connector); + } + + @Override + public NodeTableStatistics readNodeTable(NodeTable table, boolean cached) { + if (!table.getNode().getType() + .equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(table.getNode())) { + logger.debug("This Controller is not the master for connector : "+table); + return null; + } + + return filter.readNodeTable(containerName, table, cached); + } + + @Override + public List readAllNodeTable(Node node, boolean cached) { + if (!node.getType().equals(NodeIDType.OPENFLOW)) { + logger.error("Invalid node type"); + return null; + } + + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return null; + } + + return filter.readAllNodeTable(containerName, node, cached); + } + + @Override + public void nodeFlowStatisticsUpdated(Node node, List flowStatsList) { + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return; + } + for (IPluginOutReadService service : pluginOutReadServices) { + service.nodeFlowStatisticsUpdated(node, flowStatsList); + } + } + + @Override + public void nodeConnectorStatisticsUpdated(Node node, List ncStatsList) { + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return; + } + for (IPluginOutReadService service : pluginOutReadServices) { + service.nodeConnectorStatisticsUpdated(node, ncStatsList); + } + } + + @Override + public void nodeTableStatisticsUpdated(Node node, List tableStatsList) { + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return; + } + for (IPluginOutReadService service : pluginOutReadServices) { + service.nodeTableStatisticsUpdated(node, tableStatsList); + } + } + + @Override + public void nodeDescriptionStatisticsUpdated(Node node, NodeDescription nodeDescription) { + if (!connectionOutService.isLocal(node)) { + logger.debug("This Controller is not the master for node : " + node); + return; + } + for (IPluginOutReadService service : pluginOutReadServices) { + service.descriptionStatisticsUpdated(node, nodeDescription); + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadServiceFilter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadServiceFilter.java new file mode 100644 index 0000000000..7bf6ac403e --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/ReadServiceFilter.java @@ -0,0 +1,646 @@ + +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.opendaylight.controller.sal.action.Action; +import org.opendaylight.controller.sal.action.ActionType; +import org.opendaylight.controller.sal.action.Output; +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.NodeTable; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.match.Match; +import org.opendaylight.controller.sal.match.MatchType; +import org.opendaylight.controller.sal.reader.FlowOnNode; +import org.opendaylight.controller.sal.reader.NodeConnectorStatistics; +import org.opendaylight.controller.sal.reader.NodeDescription; +import org.opendaylight.controller.sal.reader.NodeTableStatistics; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.controller.sal.utils.NodeConnectorCreator; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.opendaylight.controller.sal.utils.NodeTableCreator; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsManager; +import org.opendaylight.openflowplugin.openflow.IReadFilterInternalListener; +import org.opendaylight.openflowplugin.openflow.IReadServiceFilter; +import org.opendaylight.openflowplugin.openflow.core.IController; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.statistics.OFFlowStatisticsReply; +import org.openflow.protocol.statistics.OFPortStatisticsReply; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFStatisticsType; +import org.openflow.protocol.statistics.OFTableStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * Read Service shim layer which is in charge of filtering the flow statistics + * based on container. It is a Global instance. + */ +public class ReadServiceFilter implements IReadServiceFilter, IContainerListener, IOFStatisticsListener { + private static final Logger logger = LoggerFactory + .getLogger(ReadServiceFilter.class); + private IController controller = null; + private IOFStatisticsManager statsMgr = null; + private ConcurrentMap> containerToNc; + private ConcurrentMap> containerToNode; + private ConcurrentMap> containerToNt; + private ConcurrentMap> containerFlows; + private ConcurrentMap readFilterInternalListeners; + + public void setController(IController core) { + this.controller = core; + } + + public void unsetController(IController core) { + if (this.controller == core) { + this.controller = null; + } + } + + public void setReadFilterInternalListener(Map props, IReadFilterInternalListener s) { + if (props == null) { + logger.error("Failed setting Read Filter Listener, property map is null."); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("Failed setting Read Filter Listener, container name not supplied."); + return; + } + if ((this.readFilterInternalListeners != null) && !this.readFilterInternalListeners.containsValue(s)) { + this.readFilterInternalListeners.put(containerName, s); + logger.trace("Added Read Filter Listener for container {}", containerName); + } + } + + public void unsetReadFilterInternalListener(Map props, IReadFilterInternalListener s) { + if (props == null) { + logger.error("Failed unsetting Read Filter Listener, property map is null."); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("Failed unsetting Read Filter Listener, containerName not supplied"); + return; + } + if ((this.readFilterInternalListeners != null) && this.readFilterInternalListeners.get(containerName) != null + && this.readFilterInternalListeners.get(containerName).equals(s)) { + this.readFilterInternalListeners.remove(containerName); + logger.trace("Removed Read Filter Listener for container {}", containerName); + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + containerToNc = new ConcurrentHashMap>(); + containerToNt = new ConcurrentHashMap>(); + containerToNode = new ConcurrentHashMap>(); + containerFlows = new ConcurrentHashMap>(); + readFilterInternalListeners = new ConcurrentHashMap(); + } + + /** + * Function called by the dependency manager when at least one + * dependency become unsatisfied or when the component is shutting + * down because for example bundle is being stopped. + * + */ + void destroy() { + } + + /** + * Function called by dependency manager after "init ()" is called + * and after the services provided by the class are registered in + * the service registry + * + */ + void start() { + } + + /** + * Function called by the dependency manager before the services + * exported by the component are unregistered, this will be + * followed by a "destroy ()" calls + * + */ + void stop() { + } + + public void setService(IOFStatisticsManager service) { + this.statsMgr = service; + } + + public void unsetService(IOFStatisticsManager service) { + this.statsMgr = null; + } + + IPluginOutConnectionService connectionPluginOutService; + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionPluginOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionPluginOutService == s) { + connectionPluginOutService = null; + } + } + + @Override + public FlowOnNode readFlow(String container, Node node, Flow flow, boolean cached) { + + if (controller == null) { + // Avoid to provide cached statistics if controller went down. + // They are not valid anymore anyway + logger.error("Internal plugin error"); + return null; + } + + long sid = (Long) node.getID(); + OFMatch ofMatch = new FlowConverter(flow).getOFMatch(); + List ofList; + if (cached == true){ + ofList = statsMgr.getOFFlowStatistics(sid, ofMatch, flow.getPriority()); + } else { + ofList = statsMgr.queryStatistics(sid, OFStatisticsType.FLOW, ofMatch); + for (OFStatistics ofStat : ofList) { + if (((OFFlowStatisticsReply)ofStat).getPriority() == flow.getPriority()){ + ofList = new ArrayList(1); + ofList.add(ofStat); + break; + } + } + } + + // Convert and filter the statistics per container + List flowOnNodeList = new FlowStatisticsConverter(ofList).getFlowOnNodeList(node); + List filteredList = filterFlowListPerContainer(container, node, flowOnNodeList); + + return (filteredList == null || filteredList.isEmpty()) ? null : filteredList.get(0); + } + + @Override + public List readAllFlow(String container, Node node, + boolean cached) { + + long sid = (Long) node.getID(); + List ofList = (cached == true) ? statsMgr + .getOFFlowStatistics(sid) : statsMgr.queryStatistics(sid, + OFStatisticsType.FLOW, null); + + // Convert and filter the statistics per container + List flowOnNodeList = new FlowStatisticsConverter(ofList).getFlowOnNodeList(node); + List filteredList = filterFlowListPerContainer(container, node, flowOnNodeList); + + return (filteredList == null) ? null : filteredList; + + } + + @Override + public NodeDescription readDescription(Node node, boolean cached) { + + if (controller == null) { + logger.error("Internal plugin error"); + return null; + } + + long sid = (Long) node.getID(); + List ofList = (cached == true) ? statsMgr + .getOFDescStatistics(sid) : statsMgr.queryStatistics(sid, + OFStatisticsType.DESC, null); + + return new DescStatisticsConverter(ofList).getHwDescription(); + } + + /** + * Filters a list of FlowOnNode elements based on the container + * + * @param container + * @param nodeId + * @param list + * @return + */ + public List filterFlowListPerContainer(String container, + Node nodeId, List list) { + if (list == null) { + return null; + } + + // Create new filtered list of flows + List newList = new ArrayList(); + + for (FlowOnNode target : list) { + // Check whether the described flow (match + actions) belongs to this container + if (flowBelongToContainer(container, nodeId, target.getFlow())) { + newList.add(target); + } + } + + return newList; + } + + /** + * Filters a list of OFStatistics elements based on the container + * + * @param container + * @param nodeId + * @param list + * @return + */ + public List filterPortListPerContainer(String container, long switchId, List list) { + if (list == null) { + return null; + } + + // Create new filtered list of flows + List newList = new ArrayList(); + + for (OFStatistics stat : list) { + OFPortStatisticsReply target = (OFPortStatisticsReply) stat; + NodeConnector nc = NodeConnectorCreator.createOFNodeConnector( + target.getPortNumber(), NodeCreator.createOFNode(switchId)); + if (containerOwnsNodeConnector(container, nc)) { + newList.add(target); + } + } + + return newList; + } + + + public List filterTableListPerContainer( + String container, long switchId, List list) { + if (list == null) { + return null; + } + + // Create new filtered list of node tables + List newList = new ArrayList(); + + for (OFStatistics stat : list) { + OFTableStatistics target = (OFTableStatistics) stat; + NodeTable nt = NodeTableCreator.createOFNodeTable(target.getTableId(), NodeCreator.createOFNode(switchId)); + if (containerOwnsNodeTable(container, nt)) { + newList.add(target); + } + } + + return newList; + } + + /** + * Returns whether the specified flow (flow match + actions) + * belongs to the container + * + * @param container + * @param node + * @param flow + * @return true if it belongs + */ + public boolean flowBelongToContainer(String container, Node node, Flow flow) { + // All flows belong to the default container + if (container.equals(GlobalConstants.DEFAULT.toString())) { + return true; + } + return (flowPortsBelongToContainer(container, node, flow) && + flowVlanBelongsToContainer(container, node, flow) && + isFlowAllowedByContainer(container, flow)); + } + + /** + * Returns whether the passed NodeConnector belongs to the container + * + * @param container container name + * @param p node connector to test + * @return true if belongs false otherwise + */ + public boolean containerOwnsNodeConnector(String container, NodeConnector p) { + // All node connectors belong to the default container + if (container.equals(GlobalConstants.DEFAULT.toString())) { + return true; + } + Set portSet = containerToNc.get(container); + return (portSet == null) ? false : portSet.contains(p); + } + + /** + * Returns whether the passed NodeConnector belongs to the container + * + * @param container container name + * @param table node table to test + * @return true if belongs false otherwise + */ + public boolean containerOwnsNodeTable(String container, NodeTable table) { + // All node table belong to the default container + if (container.equals(GlobalConstants.DEFAULT.toString())) { + return true; + } + Set tableSet = containerToNt.get(container); + return (tableSet == null) ? false : tableSet.contains(table); + } + + /** + * Returns whether the container flows allow the passed flow + * + * @param container + * @param match + * @return + */ + private boolean isFlowAllowedByContainer(String container, Flow flow) { + Set cFlowSet = this.containerFlows.get(container); + if (cFlowSet == null || cFlowSet.isEmpty()) { + return true; + } + for (ContainerFlow cFlow : cFlowSet) { + if (cFlow.allowsFlow(flow)) { + return true; + } + } + return false; + } + + /** + * Check whether the vlan field in the flow match is the same + * of the static vlan configured for the container + * + * @param container + * @param node + * @param flow + * @return + */ + private boolean flowVlanBelongsToContainer(String container, Node node, Flow flow) { + return true; // Always true for now + } + + /** + * Check whether the ports in the flow match and flow actions for + * the specified node belong to the container + * + * @param container + * @param node + * @param flow + * @return + */ + private boolean flowPortsBelongToContainer(String container, Node node, + Flow flow) { + Match m = flow.getMatch(); + if (m.isPresent(MatchType.IN_PORT)) { + NodeConnector inPort = (NodeConnector) m.getField(MatchType.IN_PORT).getValue(); + // If the incoming port is specified, check if it belongs to + if (!containerOwnsNodeConnector(container, inPort)) { + return false; + } + } + + // If an outgoing port is specified, it must belong to this container + for (Action action : flow.getActions()) { + if (action.getType() == ActionType.OUTPUT) { + NodeConnector outPort = ((Output) action).getPort(); + if (!containerOwnsNodeConnector(container, outPort)) { + return false; + } + } + } + return true; + } + + @Override + public void containerFlowUpdated(String containerName, ContainerFlow previousFlow, + ContainerFlow currentFlow, UpdateType t) { + Set cFlowSet = containerFlows.get(containerName); + switch (t) { + case ADDED: + if (cFlowSet == null) { + cFlowSet = new HashSet(); + containerFlows.put(containerName, cFlowSet); + } + cFlowSet.add(currentFlow); + case CHANGED: + break; + case REMOVED: + if (cFlowSet != null) { + cFlowSet.remove(currentFlow); + } + break; + default: + break; + } + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector p, UpdateType type) { + + switch (type) { + case ADDED: + if (!containerToNc.containsKey(containerName)) { + containerToNc.put(containerName, + Collections.newSetFromMap(new ConcurrentHashMap())); + } + containerToNc.get(containerName).add(p); + if (!containerToNode.containsKey(containerName)) { + containerToNode.put(containerName, new HashSet()); + } + containerToNode.get(containerName).add(p.getNode()); + break; + case REMOVED: + Set ncSet = containerToNc.get(containerName); + if (ncSet != null) { + //remove this nc from container map + ncSet.remove(p); + + //check if there are still ports of this node in this container + //and if not, remove its mapping + boolean nodeInContainer = false; + Node node = p.getNode(); + for (NodeConnector nodeConnector : ncSet) { + if (nodeConnector.getNode().equals(node)){ + nodeInContainer = true; + break; + } + } + if (! nodeInContainer) { + Set nodeSet = containerToNode.get(containerName); + if (nodeSet != null) { + nodeSet.remove(node); + } + } + } + break; + case CHANGED: + default: + } + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, short newTag, UpdateType t) { + // Not interested in this event + } + + @Override + public void containerModeUpdated(UpdateType t) { + // Not interested in this event + } + + @Override + public NodeConnectorStatistics readNodeConnector(String containerName, NodeConnector connector, boolean cached) { + if (!containerOwnsNodeConnector(containerName, connector)) { + return null; + } + Node node = connector.getNode(); + long sid = (Long) node.getID(); + short portId = (Short) connector.getID(); + List ofList = (cached == true) ? statsMgr + .getOFPortStatistics(sid, portId) : statsMgr.queryStatistics( + sid, OFStatisticsType.PORT, portId); + + List ncStatistics = new PortStatisticsConverter(sid, ofList) + .getNodeConnectorStatsList(); + return (ncStatistics.isEmpty()) ? new NodeConnectorStatistics() : ncStatistics.get(0); + } + + @Override + public List readAllNodeConnector(String containerName, Node node, boolean cached) { + + long sid = (Long) node.getID(); + List ofList = (cached == true) ? statsMgr + .getOFPortStatistics(sid) : statsMgr.queryStatistics(sid, + OFStatisticsType.FLOW, null); + + List filteredList = filterPortListPerContainer(containerName, sid, ofList); + + return new PortStatisticsConverter(sid, filteredList).getNodeConnectorStatsList(); + } + + @Override + public long getTransmitRate(String containerName, NodeConnector connector) { + if (!containerOwnsNodeConnector(containerName, connector)) { + return 0; + } + + long switchId = (Long) connector.getNode().getID(); + short port = (Short) connector.getID(); + + return statsMgr.getTransmitRate(switchId, port); + } + + @Override + public NodeTableStatistics readNodeTable(String containerName, + NodeTable table, boolean cached) { + if (!containerOwnsNodeTable(containerName, table)) { + return null; + } + Node node = table.getNode(); + long sid = (Long) node.getID(); + Byte tableId = (Byte) table.getID(); + List ofList = (cached == true) ? statsMgr.getOFTableStatistics(sid, tableId) : + statsMgr.queryStatistics(sid, OFStatisticsType.TABLE, tableId); + + List ntStatistics = new TableStatisticsConverter(sid, ofList).getNodeTableStatsList(); + + return (ntStatistics.isEmpty()) ? new NodeTableStatistics() : ntStatistics.get(0); + } + + @Override + public List readAllNodeTable(String containerName, Node node, boolean cached) { + long sid = (Long) node.getID(); + List ofList = (cached == true) ? + statsMgr.getOFTableStatistics(sid) : statsMgr.queryStatistics(sid, OFStatisticsType.FLOW, null); + + List filteredList = filterTableListPerContainer(containerName, sid, ofList); + + return new TableStatisticsConverter(sid, filteredList).getNodeTableStatsList(); + } + + @Override + public void descriptionStatisticsRefreshed(Long switchId, List description) { + String container; + IReadFilterInternalListener listener; + Node node = NodeCreator.createOFNode(switchId); + NodeDescription nodeDescription = new DescStatisticsConverter(description).getHwDescription(); + for (Map.Entry l : readFilterInternalListeners.entrySet()) { + container = l.getKey(); + listener = l.getValue(); + if (container == GlobalConstants.DEFAULT.toString() + || (containerToNode.containsKey(container) && containerToNode.get(container).contains(node))) { + listener.nodeDescriptionStatisticsUpdated(node, nodeDescription); + } + } + } + + @Override + public void flowStatisticsRefreshed(Long switchId, List flows) { + String container; + IReadFilterInternalListener listener; + Node node = NodeCreator.createOFNode(switchId); + for (Map.Entry l : readFilterInternalListeners.entrySet()) { + container = l.getKey(); + listener = l.getValue(); + + // Convert and filter the statistics per container + List flowOnNodeList = new FlowStatisticsConverter(flows).getFlowOnNodeList(node); + flowOnNodeList = filterFlowListPerContainer(container, node, flowOnNodeList); + + // notify listeners + listener.nodeFlowStatisticsUpdated(node, flowOnNodeList); + } + } + + @Override + public void portStatisticsRefreshed(Long switchId, List ports) { + String container; + IReadFilterInternalListener listener; + Node node = NodeCreator.createOFNode(switchId); + for (Map.Entry l : readFilterInternalListeners.entrySet()) { + container = l.getKey(); + listener = l.getValue(); + + // Convert and filter the statistics per container + List filteredPorts = filterPortListPerContainer(container, switchId, ports); + List ncStatsList = new PortStatisticsConverter(switchId, filteredPorts) + .getNodeConnectorStatsList(); + + // notify listeners + listener.nodeConnectorStatisticsUpdated(node, ncStatsList); + } + } + + @Override + public void tableStatisticsRefreshed(Long switchId, List tables) { + String container; + Node node = NodeCreator.createOFNode(switchId); + for (Map.Entry l : readFilterInternalListeners.entrySet()) { + container = l.getKey(); + + // Convert and filter the statistics per container + List filteredList = filterTableListPerContainer(container, switchId, tables); + List tableStatsList = new TableStatisticsConverter(switchId, filteredList) + .getNodeTableStatsList(); + + // notify listeners + l.getValue().nodeTableStatisticsUpdated(node, tableStatsList); + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableConverter.java new file mode 100644 index 0000000000..08cd502172 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 Big Switch Networks, Inc. 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.internal; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeTable; +import org.opendaylight.controller.sal.utils.NodeTableCreator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TableConverter { + private static final Logger log = LoggerFactory + .getLogger(TableConverter.class); + + public static NodeTable toNodeTable(byte tableId, Node node) { + log.trace("Openflow table ID: {}", Byte.toString(tableId)); + return NodeTableCreator.createNodeTable(tableId, node); + } + + public static byte toOFTable(NodeTable salTable) { + log.trace("SAL Table: {}", salTable); + return (Byte) salTable.getID(); + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableStatisticsConverter.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableStatisticsConverter.java new file mode 100644 index 0000000000..21117cb573 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TableStatisticsConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 Big Switch Networks, Inc. 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.reader.NodeTableStatistics; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFTableStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts an openflow list of table statistics in a SAL list of + * NodeTableStatistics objects + */ +public class TableStatisticsConverter { + private static final Logger log = LoggerFactory + .getLogger(TableStatisticsConverter.class); + + private final long switchId; + private List ofStatsList; + private List ntStatsList; + + public TableStatisticsConverter(long switchId, List statsList) { + this.switchId = switchId; + if (statsList == null || statsList.isEmpty()) { + this.ofStatsList = new ArrayList(1); // dummy list + } else { + this.ofStatsList = new ArrayList(statsList); + } + this.ntStatsList = null; + } + + public List getNodeTableStatsList() { + if (this.ofStatsList != null && this.ntStatsList == null) { + this.ntStatsList = new ArrayList(); + OFTableStatistics ofTableStat; + Node node = NodeCreator.createOFNode(switchId); + for (OFStatistics ofStat : this.ofStatsList) { + ofTableStat = (OFTableStatistics) ofStat; + NodeTableStatistics ntStat = new NodeTableStatistics(); + ntStat.setNodeTable(TableConverter.toNodeTable( + ofTableStat.getTableId(), node)); + ntStat.setActiveCount(ofTableStat.getActiveCount()); + ntStat.setLookupCount(ofTableStat.getLookupCount()); + ntStat.setMatchedCount(ofTableStat.getMatchedCount()); + this.ntStatsList.add(ntStat); + } + } + log.trace("OFStatistics: {} NodeTableStatistics: {}", ofStatsList, + ntStatsList); + return this.ntStatsList; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServiceShim.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServiceShim.java new file mode 100644 index 0000000000..5fdc55cd65 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServiceShim.java @@ -0,0 +1,833 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Bandwidth; +import org.opendaylight.controller.sal.core.Config; +import org.opendaylight.controller.sal.core.ContainerFlow; +import org.opendaylight.controller.sal.core.Edge; +import org.opendaylight.controller.sal.core.IContainerListener; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.core.Property; +import org.opendaylight.controller.sal.core.State; +import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.topology.TopoEdgeUpdate; +import org.opendaylight.controller.sal.utils.GlobalConstants; +import org.opendaylight.openflowplugin.openflow.IDiscoveryListener; +import org.opendaylight.openflowplugin.openflow.IInventoryShimExternalListener; +import org.opendaylight.openflowplugin.openflow.IOFStatisticsManager; +import org.opendaylight.openflowplugin.openflow.IRefreshInternalProvider; +import org.opendaylight.openflowplugin.openflow.ITopologyServiceShimListener; + +/** + * The class describes a shim layer that relays the topology events from + * OpenFlow core to various listeners. The notifications are filtered based on + * container configurations. + */ +public class TopologyServiceShim implements IDiscoveryListener, + IContainerListener, CommandProvider, IRefreshInternalProvider, + IInventoryShimExternalListener { + protected static final Logger logger = LoggerFactory + .getLogger(TopologyServiceShim.class); + private ConcurrentMap topologyServiceShimListeners = new ConcurrentHashMap(); + private ConcurrentMap> containerMap = new ConcurrentHashMap>(); + private ConcurrentMap>>> edgeMap = new ConcurrentHashMap>>>(); + + private BlockingQueue notifyQ; + private Thread notifyThread; + private BlockingQueue bulkNotifyQ; + private Thread ofPluginTopoBulkUpdate; + private volatile Boolean shuttingDown = false; + private IOFStatisticsManager statsMgr; + private Timer pollTimer; + private TimerTask txRatePoller; + private Thread bwUtilNotifyThread; + private BlockingQueue bwUtilNotifyQ; + private List connectorsOverUtilized; + private float bwThresholdFactor = (float) 0.8; // Threshold = 80% of link + // bandwidth + + class NotifyEntry { + String container; + List teuList; + + public NotifyEntry(String container, TopoEdgeUpdate teu) { + this.container = container; + this.teuList = new ArrayList(); + if (teu != null) { + this.teuList.add(teu); + } + } + + public NotifyEntry(String container, List teuList) { + this.container = container; + this.teuList = new ArrayList(); + if (teuList != null) { + this.teuList.addAll(teuList); + } + } + } + + class TopologyNotify implements Runnable { + private final BlockingQueue notifyQ; + private NotifyEntry entry; + private Map> teuMap = new HashMap>(); + private List teuList; + private boolean notifyListeners; + + TopologyNotify(BlockingQueue notifyQ) { + this.notifyQ = notifyQ; + } + + public void run() { + while (true) { + try { + teuMap.clear(); + notifyListeners = false; + while (!notifyQ.isEmpty()) { + entry = notifyQ.take(); + teuList = teuMap.get(entry.container); + if (teuList == null) { + teuList = new ArrayList(); + } + // group all the updates together + teuList.addAll(entry.teuList); + teuMap.put(entry.container, teuList); + notifyListeners = true; + } + + if (notifyListeners) { + for (String container : teuMap.keySet()) { + // notify the listener + topologyServiceShimListeners.get(container) + .edgeUpdate(teuMap.get(container)); + } + } + + Thread.sleep(100); + } catch (InterruptedException e1) { + logger.warn("TopologyNotify interrupted {}", + e1.getMessage()); + if (shuttingDown) { + return; + } + } catch (Exception e2) { + logger.error("", e2); + } + } + } + } + + class UtilizationUpdate { + NodeConnector connector; + UpdateType type; + + UtilizationUpdate(NodeConnector connector, UpdateType type) { + this.connector = connector; + this.type = type; + } + } + + class BwUtilizationNotify implements Runnable { + private final BlockingQueue notifyQ; + + BwUtilizationNotify(BlockingQueue notifyQ) { + this.notifyQ = notifyQ; + } + + public void run() { + while (true) { + try { + UtilizationUpdate update = notifyQ.take(); + NodeConnector connector = update.connector; + Set containerList = edgeMap.keySet(); + for (String container : containerList) { + Map>> edgePropsMap = edgeMap + .get(container); + Edge edge = edgePropsMap.get(connector).getLeft(); + if (edge.getTailNodeConnector().equals(connector)) { + ITopologyServiceShimListener topologServiceShimListener = topologyServiceShimListeners + .get(container); + if (update.type == UpdateType.ADDED) { + topologServiceShimListener + .edgeOverUtilized(edge); + } else { + topologServiceShimListener + .edgeUtilBackToNormal(edge); + } + } + } + } catch (InterruptedException e1) { + logger.warn( + "Edge Bandwidth Utilization Notify Thread interrupted {}", + e1.getMessage()); + if (shuttingDown) { + return; + } + } catch (Exception e2) { + logger.error("", e2); + } + } + } + } + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + void init() { + logger.trace("Init called"); + connectorsOverUtilized = new ArrayList(); + notifyQ = new LinkedBlockingQueue(); + notifyThread = new Thread(new TopologyNotify(notifyQ)); + bwUtilNotifyQ = new LinkedBlockingQueue(); + bwUtilNotifyThread = new Thread(new BwUtilizationNotify(bwUtilNotifyQ)); + bulkNotifyQ = new LinkedBlockingQueue(); + ofPluginTopoBulkUpdate = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + String containerName = bulkNotifyQ.take(); + logger.debug("Bulk Notify container:{}", containerName); + TopologyBulkUpdate(containerName); + } catch (InterruptedException e) { + logger.warn("Topology Bulk update thread interrupted"); + if (shuttingDown) { + return; + } + } + } + } + }, "Topology Bulk Update"); + + // Initialize node connector tx bit rate poller timer + pollTimer = new Timer(); + txRatePoller = new TimerTask() { + @Override + public void run() { + pollTxBitRates(); + } + }; + + registerWithOSGIConsole(); + } + + /** + * Continuously polls the transmit bit rate for all the node connectors from + * statistics manager and trigger the warning notification upward when the + * transmit rate is above a threshold which is a percentage of the edge + * bandwidth + */ + protected void pollTxBitRates() { + Map>> globalContainerEdges = edgeMap + .get(GlobalConstants.DEFAULT.toString()); + if (globalContainerEdges == null) { + return; + } + + for (NodeConnector connector : globalContainerEdges.keySet()) { + // Skip if node connector belongs to production switch + if (connector.getType().equals( + NodeConnector.NodeConnectorIDType.PRODUCTION)) { + continue; + } + + // Get edge for which this node connector is head + Pair> props = this.edgeMap.get( + GlobalConstants.DEFAULT.toString()).get(connector); + // On switch mgr restart the props get reset + if (props == null) { + continue; + } + Set propSet = props.getRight(); + if (propSet == null) { + continue; + } + + float bw = 0; + for (Property prop : propSet) { + if (prop instanceof Bandwidth) { + bw = ((Bandwidth) prop).getValue(); + break; + } + } + + // Skip if agent did not provide a bandwidth info for the edge + if (bw == 0) { + continue; + } + + // Compare bandwidth usage + Long switchId = (Long) connector.getNode().getID(); + Short port = (Short) connector.getID(); + float rate = statsMgr.getTransmitRate(switchId, port); + if (rate > bwThresholdFactor * bw) { + if (!connectorsOverUtilized.contains(connector)) { + connectorsOverUtilized.add(connector); + this.bwUtilNotifyQ.add(new UtilizationUpdate(connector, + UpdateType.ADDED)); + } + } else { + if (connectorsOverUtilized.contains(connector)) { + connectorsOverUtilized.remove(connector); + this.bwUtilNotifyQ.add(new UtilizationUpdate(connector, + UpdateType.REMOVED)); + } + } + } + + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + logger.trace("DESTROY called!"); + notifyQ = null; + notifyThread = null; + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + logger.trace("START called!"); + notifyThread.start(); + bwUtilNotifyThread.start(); + ofPluginTopoBulkUpdate.start(); + pollTimer.scheduleAtFixedRate(txRatePoller, 10000, 5000); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + logger.trace("STOP called!"); + shuttingDown = true; + notifyThread.interrupt(); + } + + void setTopologyServiceShimListener(Map props, + ITopologyServiceShimListener s) { + if (props == null) { + logger.error("Didn't receive the service properties"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("containerName not supplied"); + return; + } + if ((this.topologyServiceShimListeners != null) + && !this.topologyServiceShimListeners + .containsKey(containerName)) { + this.topologyServiceShimListeners.put(containerName, s); + logger.trace("Added topologyServiceShimListener for container: {}", + containerName); + } + } + + void unsetTopologyServiceShimListener(Map props, + ITopologyServiceShimListener s) { + if (props == null) { + logger.error("Didn't receive the service properties"); + return; + } + String containerName = (String) props.get("containerName"); + if (containerName == null) { + logger.error("containerName not supplied"); + return; + } + if ((this.topologyServiceShimListeners != null) + && this.topologyServiceShimListeners.containsKey(containerName) + && this.topologyServiceShimListeners.get(containerName).equals( + s)) { + this.topologyServiceShimListeners.remove(containerName); + logger.trace( + "Removed topologyServiceShimListener for container: {}", + containerName); + } + } + + void setStatisticsManager(IOFStatisticsManager s) { + this.statsMgr = s; + } + + void unsetStatisticsManager(IOFStatisticsManager s) { + if (this.statsMgr == s) { + this.statsMgr = null; + } + } + + IPluginOutConnectionService connectionPluginOutService; + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionPluginOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionPluginOutService == s) { + connectionPluginOutService = null; + } + } + + private void removeNodeConnector(String container, + NodeConnector nodeConnector) { + List teuList = new ArrayList(); + Map>> edgePropsMap = edgeMap + .get(container); + if (edgePropsMap == null) { + return; + } + + // Remove edge in one direction + Pair> edgeProps = edgePropsMap.get(nodeConnector); + if (edgeProps == null) { + return; + } + teuList.add(new TopoEdgeUpdate(edgeProps.getLeft(), null, + UpdateType.REMOVED)); + + // Remove edge in another direction + edgeProps = edgePropsMap + .get(edgeProps.getLeft().getHeadNodeConnector()); + if (edgeProps == null) { + return; + } + teuList.add(new TopoEdgeUpdate(edgeProps.getLeft(), null, + UpdateType.REMOVED)); + + // Update in one shot + notifyEdge(container, teuList); + } + + /** + * Update local cache and return true if it needs to notify upper layer + * Topology listeners. + * + * @param container + * The network container + * @param edge + * The edge + * @param type + * The update type + * @param props + * The edge properties + * @return true if it needs to notify upper layer Topology listeners + */ + private boolean updateLocalEdgeMap(String container, Edge edge, + UpdateType type, Set props) { + ConcurrentMap>> edgePropsMap = edgeMap + .get(container); + NodeConnector src = edge.getTailNodeConnector(); + Pair> edgeProps = new ImmutablePair>( + edge, props); + boolean rv = false; + + switch (type) { + case ADDED: + case CHANGED: + if (edgePropsMap == null) { + edgePropsMap = new ConcurrentHashMap>>(); + rv = true; + } else { + if (edgePropsMap.containsKey(src) + && edgePropsMap.get(src).equals(edgeProps)) { + // Entry already exists. No update. + rv = false; + } else { + rv = true; + } + } + if (rv) { + edgePropsMap.put(src, edgeProps); + edgeMap.put(container, edgePropsMap); + } + break; + case REMOVED: + if ((edgePropsMap != null) && edgePropsMap.containsKey(src)) { + edgePropsMap.remove(src); + if (edgePropsMap.isEmpty()) { + edgeMap.remove(container); + } else { + edgeMap.put(container, edgePropsMap); + } + rv = true; + } + break; + default: + logger.debug( + "notifyLocalEdgeMap: invalid {} for Edge {} in container {}", + new Object[] { type.getName(), edge, container }); + } + + if (rv) { + logger.debug( + "notifyLocalEdgeMap: {} for Edge {} in container {}", + new Object[] { type.getName(), edge, container }); + } + + return rv; + } + + private void notifyEdge(String container, Edge edge, UpdateType type, + Set props) { + boolean notifyListeners; + + // Update local cache + notifyListeners = updateLocalEdgeMap(container, edge, type, props); + + // Prepare to update TopologyService + if (notifyListeners) { + notifyQ.add(new NotifyEntry(container, new TopoEdgeUpdate(edge, props, + type))); + logger.debug("notifyEdge: {} Edge {} in container {}", + new Object[] { type.getName(), edge, container }); + } + } + + private void notifyEdge(String container, List etuList) { + if (etuList == null) { + return; + } + + Edge edge; + UpdateType type; + List etuNotifyList = new ArrayList(); + boolean notifyListeners = false, rv; + + for (TopoEdgeUpdate etu : etuList) { + edge = etu.getEdge(); + type = etu.getUpdateType(); + + // Update local cache + rv = updateLocalEdgeMap(container, edge, type, etu.getProperty()); + if (rv) { + if (!notifyListeners) { + notifyListeners = true; + } + etuNotifyList.add(etu); + logger.debug( + "notifyEdge(TopoEdgeUpdate): {} Edge {} in container {}", + new Object[] { type.getName(), edge, container }); + } + } + + // Prepare to update TopologyService + if (notifyListeners) { + notifyQ.add(new NotifyEntry(container, etuNotifyList)); + logger.debug("notifyEdge(TopoEdgeUpdate): add notifyQ"); + } + } + + @Override + public void notifyEdge(Edge edge, UpdateType type, Set props) { + if ((edge == null) || (type == null)) { + return; + } + + // Notify default container + notifyEdge(GlobalConstants.DEFAULT.toString(), edge, type, props); + + // Notify the corresponding containers + List containers = getEdgeContainers(edge); + if (containers != null) { + for (String container : containers) { + notifyEdge(container, edge, type, props); + } + } + } + + /* + * Return a list of containers the edge associated with + */ + private List getEdgeContainers(Edge edge) { + NodeConnector src = edge.getTailNodeConnector(), dst = edge + .getHeadNodeConnector(); + + if (!src.getType().equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) { + /* Find the common containers for both ends */ + List srcContainers = this.containerMap.get(src), dstContainers = this.containerMap + .get(dst), cmnContainers = null; + if ((srcContainers != null) && (dstContainers != null)) { + cmnContainers = new ArrayList(srcContainers); + cmnContainers.retainAll(dstContainers); + } + return cmnContainers; + } else { + /* + * If the neighbor is part of a monitored production network, get + * the containers that the edge port belongs to + */ + return this.containerMap.get(dst); + } + } + + @Override + public void tagUpdated(String containerName, Node n, short oldTag, + short newTag, UpdateType t) { + } + + @Override + public void containerFlowUpdated(String containerName, + ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) { + } + + @Override + public void nodeConnectorUpdated(String containerName, NodeConnector p, + UpdateType t) { + if (this.containerMap == null) { + logger.error("containerMap is NULL"); + return; + } + List containers = this.containerMap.get(p); + if (containers == null) { + containers = new CopyOnWriteArrayList(); + } + boolean updateMap = false; + switch (t) { + case ADDED: + if (!containers.contains(containerName)) { + containers.add(containerName); + updateMap = true; + } + break; + case REMOVED: + if (containers.contains(containerName)) { + containers.remove(containerName); + updateMap = true; + removeNodeConnector(containerName, p); + } + break; + case CHANGED: + break; + } + if (updateMap) { + if (containers.isEmpty()) { + // Do cleanup to reduce memory footprint if no + // elements to be tracked + this.containerMap.remove(p); + } else { + this.containerMap.put(p, containers); + } + } + } + + @Override + public void containerModeUpdated(UpdateType t) { + // do nothing + } + + private void registerWithOSGIConsole() { + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()) + .getBundleContext(); + bundleContext.registerService(CommandProvider.class.getName(), this, + null); + } + + @Override + public String getHelp() { + StringBuffer help = new StringBuffer(); + help.append("---Topology Service Shim---\n"); + help.append("\t pem [container] - Print edgeMap entries"); + help.append(" for a given container\n"); + return help.toString(); + } + + public void _pem(CommandInterpreter ci) { + String container = ci.nextArgument(); + if (container == null) { + container = GlobalConstants.DEFAULT.toString(); + } + + ci.println("Container: " + container); + ci.println(" Edge Bandwidth"); + + Map>> edgePropsMap = edgeMap + .get(container); + if (edgePropsMap == null) { + return; + } + int count = 0; + for (Pair> edgeProps : edgePropsMap.values()) { + if (edgeProps == null) { + continue; + } + + long bw = 0; + Set props = edgeProps.getRight(); + if (props != null) { + for (Property prop : props) { + if (prop.getName().equals(Bandwidth.BandwidthPropName)) { + bw = ((Bandwidth) prop).getValue(); + } + } + } + count++; + ci.println(edgeProps.getLeft() + " " + bw); + } + ci.println("Total number of Edges: " + count); + } + + public void _bwfactor(CommandInterpreter ci) { + String factorString = ci.nextArgument(); + if (factorString == null) { + ci.println("Bw threshold: " + this.bwThresholdFactor); + ci.println("Insert a non null bw threshold"); + return; + } + bwThresholdFactor = Float.parseFloat(factorString); + ci.println("New Bw threshold: " + this.bwThresholdFactor); + } + + /** + * This method will trigger topology updates to be sent toward SAL. SAL then + * pushes the updates to ALL the applications that have registered as + * listeners for this service. SAL has no way of knowing which application + * requested for the refresh. + * + * As an example of this case, is stopping and starting the Topology + * Manager. When the topology Manager is stopped, and restarted, it will no + * longer have the latest topology. Hence, a request is sent here. + * + * @param containerName + * @return void + */ + @Override + public void requestRefresh(String containerName) { + // wake up a bulk update thread and exit + // the thread will execute the bulkUpdate() + bulkNotifyQ.add(containerName); + } + + /** + * Reading the current topology database, the method will replay all the + * edge updates for the ITopologyServiceShimListener instance in the given + * container, which will in turn publish them toward SAL. + * + * @param containerName + */ + private void TopologyBulkUpdate(String containerName) { + Map>> edgePropMap = null; + + logger.debug("Try bulk update for container:{}", containerName); + edgePropMap = edgeMap.get(containerName); + if (edgePropMap == null) { + logger.debug("No edges known for container:{}", containerName); + return; + } + ITopologyServiceShimListener topologServiceShimListener = topologyServiceShimListeners + .get(containerName); + if (topologServiceShimListener == null) { + logger.debug("No topology service shim listener for container:{}", + containerName); + return; + } + int i = 0; + List teuList = new ArrayList(); + for (Pair> edgeProps : edgePropMap.values()) { + if (edgeProps != null) { + i++; + teuList.add(new TopoEdgeUpdate(edgeProps.getLeft(), edgeProps + .getRight(), UpdateType.ADDED)); + logger.trace("Add edge {}", edgeProps.getLeft()); + } + } + if (i > 0) { + topologServiceShimListener.edgeUpdate(teuList); + } + logger.debug("Sent {} updates", i); + } + + @Override + public void updateNode(Node node, UpdateType type, Set props) { + } + + @Override + public void updateNodeConnector(NodeConnector nodeConnector, + UpdateType type, Set props) { + List containers = new ArrayList(); + List conList = this.containerMap.get(nodeConnector); + + containers.add(GlobalConstants.DEFAULT.toString()); + if (conList != null) { + containers.addAll(conList); + } + + switch (type) { + case ADDED: + break; + case CHANGED: + if (props == null) { + break; + } + + boolean rmEdge = false; + for (Property prop : props) { + if (((prop instanceof Config) && (((Config) prop).getValue() != Config.ADMIN_UP)) + || ((prop instanceof State) && (((State) prop) + .getValue() != State.EDGE_UP))) { + /* + * If port admin down or link down, remove the edges + * associated with the port + */ + rmEdge = true; + break; + } + } + + if (rmEdge) { + for (String cName : containers) { + removeNodeConnector(cName, nodeConnector); + } + } + break; + case REMOVED: + for (String cName : containers) { + removeNodeConnector(cName, nodeConnector); + } + break; + default: + break; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServices.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServices.java new file mode 100644 index 0000000000..63e79b3996 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/TopologyServices.java @@ -0,0 +1,165 @@ +/* + * 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.internal; + +import java.util.Dictionary; +import java.util.List; +import org.apache.felix.dm.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.opendaylight.controller.sal.connection.IPluginOutConnectionService; +import org.opendaylight.controller.sal.core.Edge; +import org.opendaylight.controller.sal.topology.IPluginInTopologyService; +import org.opendaylight.controller.sal.topology.IPluginOutTopologyService; +import org.opendaylight.controller.sal.topology.TopoEdgeUpdate; +import org.opendaylight.openflowplugin.openflow.IRefreshInternalProvider; +import org.opendaylight.openflowplugin.openflow.ITopologyServiceShimListener; + +public class TopologyServices implements ITopologyServiceShimListener, + IPluginInTopologyService { + protected static final Logger logger = LoggerFactory + .getLogger(TopologyServices.class); + private IPluginOutTopologyService salTopoService = null; + private IRefreshInternalProvider topoRefreshService = null; + private IPluginOutConnectionService connectionOutService; + private String containerName; + + /** + * Function called by the dependency manager when all the required + * dependencies are satisfied + * + */ + @SuppressWarnings("unchecked") + void init(Component c) { + logger.trace("INIT called!"); + Dictionary props = c.getServiceProperties(); + containerName = (props != null) ? (String) props.get("containerName") + : null; + } + + /** + * Function called by the dependency manager when at least one dependency + * become unsatisfied or when the component is shutting down because for + * example bundle is being stopped. + * + */ + void destroy() { + logger.trace("DESTROY called!"); + } + + /** + * Function called by dependency manager after "init ()" is called and after + * the services provided by the class are registered in the service registry + * + */ + void start() { + logger.trace("START called!"); + } + + /** + * Function called by the dependency manager before the services exported by + * the component are unregistered, this will be followed by a "destroy ()" + * calls + * + */ + void stop() { + logger.trace("STOP called!"); + } + + /** + * Retrieve SAL service IPluginOutTopologyService + * + * @param s + * Called by Dependency Manager as soon as the SAL service is + * available + */ + public void setPluginOutTopologyService(IPluginOutTopologyService s) { + logger.trace("Setting IPluginOutTopologyService to: {}", s); + this.salTopoService = s; + } + + /** + * called when SAL service IPluginOutTopologyService is no longer available + * + * @param s + * Called by Dependency Manager as soon as the SAL service is + * unavailable + */ + public void unsetPluginOutTopologyService(IPluginOutTopologyService s) { + if (this.salTopoService == s) { + logger.trace("UNSetting IPluginOutTopologyService from: {}", s); + this.salTopoService = null; + } + } + + /** + * Retrieve OF protocol_plugin service IRefreshInternalProvider + * + * @param s + * Called by Dependency Manager as soon as the SAL service is + * available + */ + public void setRefreshInternalProvider(IRefreshInternalProvider s) { + logger.trace("Setting IRefreshInternalProvider to: {}", s); + this.topoRefreshService = s; + } + + /** + * called when OF protocol_plugin service IRefreshInternalProvider is no + * longer available + * + * @param s + * Called by Dependency Manager as soon as the SAL service is + * unavailable + */ + public void unsetRefreshInternalProvider(IRefreshInternalProvider s) { + if (this.topoRefreshService == s) { + logger.trace("UNSetting IRefreshInternalProvider from: {}", s); + this.topoRefreshService = null; + } + } + + @Override + public void edgeUpdate(List topoedgeupdateList) { + if (this.salTopoService != null) { + this.salTopoService.edgeUpdate(topoedgeupdateList); + } + } + + @Override + public void sollicitRefresh() { + logger.debug("Got a request to refresh topology"); + topoRefreshService.requestRefresh(containerName); + } + + @Override + public void edgeOverUtilized(Edge edge) { + if (this.salTopoService != null) { + this.salTopoService.edgeOverUtilized(edge); + } + } + + @Override + public void edgeUtilBackToNormal(Edge edge) { + if (this.salTopoService != null) { + this.salTopoService.edgeUtilBackToNormal(edge); + } + } + + void setIPluginOutConnectionService(IPluginOutConnectionService s) { + connectionOutService = s; + } + + void unsetIPluginOutConnectionService(IPluginOutConnectionService s) { + if (connectionOutService == s) { + connectionOutService = null; + } + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Utils.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Utils.java new file mode 100644 index 0000000000..53ad83dfd5 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/internal/Utils.java @@ -0,0 +1,80 @@ + +/* + * 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.internal; + +import java.nio.ByteBuffer; + +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6Error; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFError.OFBadActionCode; +import org.openflow.protocol.OFError.OFBadRequestCode; +import org.openflow.protocol.OFError.OFErrorType; +import org.openflow.protocol.OFError.OFFlowModFailedCode; +import org.openflow.protocol.OFError.OFHelloFailedCode; +import org.openflow.protocol.OFError.OFPortModFailedCode; +import org.openflow.protocol.OFError.OFQueueOpFailedCode; + +public final class Utils { + + private Utils() { //prevent instantiation + throw new AssertionError(); + } + static String getOFErrorString(OFError error) { + // Handle VENDOR extension errors here + if (error.getErrorType() == V6Error.NICIRA_VENDOR_ERRORTYPE) { + V6Error er = new V6Error(error); + byte[] b = error.getError(); + ByteBuffer bb = ByteBuffer.allocate(b.length); + bb.put(b); + bb.rewind(); + er.readFrom(bb); + return er.toString(); + } + + // Handle OF1.0 errors here + OFErrorType et = OFErrorType.values()[0xffff & error.getErrorType()]; + String errorStr = "Error : " + et.toString(); + switch (et) { + case OFPET_HELLO_FAILED: + OFHelloFailedCode hfc = OFHelloFailedCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + hfc.toString(); + break; + case OFPET_BAD_REQUEST: + OFBadRequestCode brc = OFBadRequestCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + brc.toString(); + break; + case OFPET_BAD_ACTION: + OFBadActionCode bac = OFBadActionCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + bac.toString(); + break; + case OFPET_FLOW_MOD_FAILED: + OFFlowModFailedCode fmfc = OFFlowModFailedCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + fmfc.toString(); + break; + case OFPET_PORT_MOD_FAILED: + OFPortModFailedCode pmfc = OFPortModFailedCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + pmfc.toString(); + break; + case OFPET_QUEUE_OP_FAILED: + OFQueueOpFailedCode qofc = OFQueueOpFailedCode.values()[0xffff & error + .getErrorCode()]; + errorStr += " " + qofc.toString(); + break; + default: + break; + } + return errorStr; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Error.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Error.java new file mode 100644 index 0000000000..ec2a29a99d --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Error.java @@ -0,0 +1,106 @@ +package org.opendaylight.openflowplugin.openflow.vendorextension.v6extension; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.openflow.protocol.OFError; + +public class V6Error extends OFError { + private static final long serialVersionUID = 1L; + public static int MINIMUM_LENGTH = 20;//OfHdr(8) + NXET_VENDOR(2) + NXEC_VENDOR_ERROR(2) + struct nx_vendor_error(8) + public static final short NICIRA_VENDOR_ERRORTYPE = (short)0xb0c2; + protected int V6VendorId; + protected short V6VendorErrorType; + protected short V6VendorErrorCode; + protected byte[] V6ErrorData; + + public V6Error(OFError e) { + this.length = (short)e.getLengthU(); + this.errorType = e.getErrorType(); + this.errorCode = e.getErrorCode(); + this.xid = e.getXid(); + } + + @Override + public void readFrom(ByteBuffer data) { + this.V6VendorId = data.getInt(); + this.V6VendorErrorType = data.getShort(); + this.V6VendorErrorCode = data.getShort(); + int dataLength = this.getLengthU() - MINIMUM_LENGTH; + if (dataLength > 0) { + this.V6ErrorData = new byte[dataLength]; + data.get(this.V6ErrorData); + } + } + + /** + * @return the V6VendorId + */ + public int getVendorId() { + return V6VendorId; + } + + /** + * @return the V6VendorErrorType + */ + public short getVendorErrorType() { + return V6VendorErrorType; + } + + /** + * @return the VendorErrorType + */ + public short getVendorErrorCode() { + return V6VendorErrorCode; + } + + /** + * @return the Error Bytes + */ + public byte[] getError() { + return V6ErrorData; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(V6ErrorData); + result = prime * result + V6VendorErrorCode; + result = prime * result + V6VendorErrorType; + result = prime * result + V6VendorId; + return result; + } + + @Override + public String toString() { + return "V6Error [V6VendorId=" + V6VendorId + ", V6VendorErrorType=" + + V6VendorErrorType + ", V6VendorErrorCode=" + + V6VendorErrorCode + ", V6ErrorData=" + + Arrays.toString(V6ErrorData) + ", errorType=" + errorType + + ", errorCode=" + errorCode + ", factory=" + factory + + ", error=" + Arrays.toString(error) + ", errorIsAscii=" + + errorIsAscii + ", version=" + version + ", type=" + type + + ", length=" + length + ", xid=" + xid + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + V6Error other = (V6Error) obj; + if (!Arrays.equals(V6ErrorData, other.V6ErrorData)) + return false; + if (V6VendorErrorCode != other.V6VendorErrorCode) + return false; + if (V6VendorErrorType != other.V6VendorErrorType) + return false; + if (V6VendorId != other.V6VendorId) + return false; + return true; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6FlowMod.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6FlowMod.java new file mode 100644 index 0000000000..b4cf17cec1 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6FlowMod.java @@ -0,0 +1,242 @@ + +/* + * 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.vendorextension.v6extension; + +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +import org.openflow.protocol.OFPacketOut; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFVendor; +import org.openflow.protocol.action.OFAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is used to create IPv6 Vendor Extension messages. Specfically, It + * defines the methods used in creation of Vendor specific IPv6 Flow Mod message. + * + * + */ +public class V6FlowMod extends OFVendor implements Cloneable { + private static final Logger logger = LoggerFactory + .getLogger(V6FlowMod.class); + private static final long serialVersionUID = 1L; + protected V6Match match; + protected long cookie; + protected short command; + protected short idleTimeout; + protected short hardTimeout; + protected short priority; + protected int bufferId; + protected short outPort; + protected short flags; + protected List actions; + short match_len; + short actions_len; + short pad_size; + + private static int IPV6EXT_ADD_FLOW_MSG_TYPE = 13; + private static int IPV6_EXT_MIN_HDR_LEN = 36; + + /** + * Constructor for the V6FlowMod class. Initializes OFVendor (parent class) + * fields by calling the parent class' constructor. + */ + public V6FlowMod() { + super(); + } + + /** + * This method sets the match fields of V6FlowMod object + * @param match V6Match object for this V6FlowMod message + */ + public void setMatch(V6Match match) { + this.match = match; + } + + /** + * Sets the list of actions V6FlowMod message + * @param actions a list of ordered OFAction objects + */ + public void setActions(List actions) { + this.actions = actions; + } + + /** + * Sets the priority field of V6FlowMod message + * @param priority Priority of the message + */ + public void setPriority(short priority) { + this.priority = priority; + } + + /** + * Sets the cookie field of V6FlowMod message + * @param cookie Cookie of the message + */ + public void setCookie(long cookie) { + this.cookie = cookie; + } + + /** + * Sets the command field of V6FlowMod message + * @param command Command type of the message (ADD or DELETE) + */ + public V6FlowMod setCommand(short command) { + this.command = command; + return this; + } + + /** + * Sets the outPort field of V6FlowMod message + * @param outPort outPort of the message + */ + public V6FlowMod setOutPort(OFPort port) { + this.outPort = port.getValue(); + return this; + } + + /** + * Sets the idle_timeout of V6FlowMod message + * @param idleTimeout idle timeout for this message + */ + public void setIdleTimeout(short idleTimeout) { + this.idleTimeout = idleTimeout; + } + + /** + * Sets the hardTimeout field of V6FlowMod message + * @param hardTimeout hard timeout of the message + */ + public void setHardTimeout(short hardTimeout) { + this.hardTimeout = hardTimeout; + } + + /** + * Returns the Flow Mod message subtype for V6FlowMod message + * @return message subtype + */ + private int getIPv6ExtensionFlowModAddSubType() { + return IPV6EXT_ADD_FLOW_MSG_TYPE; + } + + /** + * Returns the minimum header size for V6Flow Message type + * @return minimum header size + */ + + public int getV6FlowModMinHdrSize() { + return IPV6_EXT_MIN_HDR_LEN; + } + + /** + * Sets the Vendor type in OFVendor message + */ + + public void setVendor() { + super.setVendor(V6StatsRequest.NICIRA_VENDOR_ID); + } + + /** + * Get flags + * @return + */ + public short getFlags() { + return flags; + } + + /** + * Set flags + * @param flags + */ + public void setFlags(short flags) { + this.flags = flags; + } + + /** + * This method forms the Vendor extension IPv6 Flow Mod message.It uses the + * fields in V6FlowMod class, and writes the data according to vendor + * extension format. The fields include flow properties (cookie, timeout, + * priority, etc), flow match, and action list. It also takes care of + * required padding. + */ + + @Override + public void writeTo(ByteBuffer data) { + super.writeTo(data); + data.putInt(getIPv6ExtensionFlowModAddSubType()); + data.putLong(this.cookie); + data.putShort(command); /* should be OFPFC_ADD, OFPFC_DELETE_STRICT, etc*/ + data.putShort(this.idleTimeout); + data.putShort(this.hardTimeout); + data.putShort(this.priority); + data.putInt(OFPacketOut.BUFFER_ID_NONE); + data.putShort(outPort); /* output_port */ + data.putShort(flags); /* flags */ + match_len = this.match.getIPv6MatchLen(); + data.putShort(match_len); + byte[] pad = new byte[6]; + data.put(pad); + this.match.writeTo(data); + + pad_size = (short) (((match_len + 7) / 8) * 8 - match_len); + + /* + * action list should be preceded by a padding of 0 to 7 bytes based upon + * above formula. + */ + + byte[] pad2 = new byte[pad_size]; + data.put(pad2); + if (actions != null) { + for (OFAction action : actions) { + actions_len += action.getLength(); + action.writeTo(data); + } + } + logger.trace("{}", this); + } + + /** + * Forms the clone of V6FlowMod Object. If Object is returned + * successfully, then returns the cloned object. Throws an + * exception if cloning is not supported. + */ + @Override + public V6FlowMod clone() { + try { + V6Match neoMatch = match.clone(); + V6FlowMod v6flowMod = (V6FlowMod) super.clone(); + v6flowMod.setMatch(neoMatch); + List neoActions = new LinkedList(); + for (OFAction action : this.actions) + neoActions.add((OFAction) action.clone()); + v6flowMod.setActions(neoActions); + return v6flowMod; + } catch (CloneNotSupportedException e) { + // Won't happen + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "V6FlowMod [match=" + match + ", cookie=" + cookie + + ", command=" + command + ", idleTimeout=" + idleTimeout + + ", hardTimeout=" + hardTimeout + ", priority=" + priority + + ", bufferId=" + bufferId + ", outPort=" + outPort + + ", flags=" + flags + ", actions=" + actions + ", match_len=" + + match_len + ", actions_len=" + actions_len + ", pad_size=" + + pad_size + "]"; + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Match.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Match.java new file mode 100644 index 0000000000..85e48e27e3 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6Match.java @@ -0,0 +1,1614 @@ +/* + * 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.vendorextension.v6extension; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.controller.sal.utils.NetUtils; +import org.openflow.protocol.OFMatch; +import org.openflow.util.U16; +import org.openflow.util.U8; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This Class forms the vendor specific IPv6 Flow Match messages as well as + * processes the vendor specific IPv6 Stats Reply message. + * + * For message creation, it parses the user entered IPv6 match fields, creates a + * sub-message for each field which are later used to form the complete message. + * + * For message processing, it parses the incoming message and reads each field + * of the message and stores in appropriate field of V6Match object. + * + * + */ +public class V6Match extends OFMatch implements Cloneable { + private static final Logger logger = LoggerFactory.getLogger(V6Match.class); + private static final long serialVersionUID = 1L; + protected Inet6Address nwSrc; + protected Inet6Address nwDst; + protected short inputPortMask; + protected byte[] dataLayerSourceMask; + protected byte[] dataLayerDestinationMask; + protected int dataLayerVirtualLanTCIMask; + protected short dataLayerTypeMask; + protected byte networkTypeOfServiceMask; + protected byte networkProtocolMask; + protected short transportSourceMask; + protected short transportDestinationMask; + protected short srcIPv6SubnetMaskbits; + protected short dstIPv6SubnetMaskbits; + + protected MatchFieldState inputPortState; + protected MatchFieldState dlSourceState; + protected MatchFieldState dlDestState; + protected MatchFieldState dlVlanIDState; + protected MatchFieldState dlVlanPCPState; + protected MatchFieldState dlVlanTCIState; + protected MatchFieldState ethTypeState; + protected MatchFieldState nwTosState; + protected MatchFieldState nwProtoState; + protected MatchFieldState nwSrcState; + protected MatchFieldState nwDstState; + protected MatchFieldState tpSrcState; + protected MatchFieldState tpDstState; + protected short match_len = 0; + protected short pad_size = 0; + + private static int IPV6_EXT_MIN_HDR_LEN = 36; + + private enum MatchFieldState { + MATCH_ABSENT, MATCH_FIELD_ONLY, MATCH_FIELD_WITH_MASK + } + + private enum OF_Match_Types { + MATCH_OF_IN_PORT(0), MATCH_OF_ETH_DST(1), MATCH_OF_ETH_SRC(2), MATCH_OF_ETH_TYPE( + 3), MATCH_OF_VLAN_TCI(4), MATCH_OF_IP_TOS(5), MATCH_OF_IP_PROTO( + 6), MATCH_OF_IP_SRC(7), MATCH_OF_IP_DST(8), MATCH_OF_TCP_SRC(9), MATCH_OF_TCP_DST( + 10), MATCH_OF_UDP_SRC(11), MATCH_OF_UDP_DST(12), MATCH_OF_ICMTP_TYPE( + 13), MATCH_OF_ICMP_CODE(14), MATCH_OF_ARP_OP(15); + + private int value; + + private OF_Match_Types(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + } + + private enum IPv6Extension_Match_Types { + MATCH_IPV6EXT_TUN_ID(16), MATCH_IPV6EXT_ARP_SHA(17), MATCH_IPV6EXT_ARP_THA( + 18), MATCH_IPV6EXT_IPV6_SRC(19), MATCH_IPV6EXT_IPV6_DST(20); + + private int value; + + private IPv6Extension_Match_Types(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public enum Extension_Types { + OF_10(0), IPV6EXT(1); + + protected int value; + + private Extension_Types(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public V6Match() { + super(); + + this.nwSrc = null; + this.nwDst = null; + + this.inputPortMask = 0; + this.dataLayerSourceMask = null; + this.dataLayerDestinationMask = null; + this.dataLayerTypeMask = 0; + this.dataLayerVirtualLanTCIMask = 0; + this.networkTypeOfServiceMask = 0; + this.networkProtocolMask = 0; + this.transportSourceMask = 0; + this.transportDestinationMask = 0; + + this.inputPortState = MatchFieldState.MATCH_ABSENT; + this.dlSourceState = MatchFieldState.MATCH_ABSENT; + this.dlDestState = MatchFieldState.MATCH_ABSENT; + this.dlVlanIDState = MatchFieldState.MATCH_ABSENT; + this.dlVlanPCPState = MatchFieldState.MATCH_ABSENT; + this.dlVlanTCIState = MatchFieldState.MATCH_ABSENT; + this.ethTypeState = MatchFieldState.MATCH_ABSENT; + this.nwTosState = MatchFieldState.MATCH_ABSENT; + this.nwProtoState = MatchFieldState.MATCH_ABSENT; + this.nwSrcState = MatchFieldState.MATCH_ABSENT; + this.nwDstState = MatchFieldState.MATCH_ABSENT; + this.tpSrcState = MatchFieldState.MATCH_ABSENT; + this.tpDstState = MatchFieldState.MATCH_ABSENT; + + this.match_len = 0; + this.pad_size = 0; + } + + public V6Match(OFMatch match) { + super(); + this.match_len = 0; + this.pad_size = 0; + + if (match.getNetworkSource() != 0) { + InetAddress address = NetUtils.getInetAddress(match.getNetworkSource()); + InetAddress mask = NetUtils.getInetNetworkMask(match.getNetworkSourceMaskLen(), false); + this.setNetworkSource(address, mask); + } else { + this.nwSrcState = MatchFieldState.MATCH_ABSENT; + } + + if (match.getNetworkDestination() != 0) { + InetAddress address = NetUtils.getInetAddress(match.getNetworkDestination()); + InetAddress mask = NetUtils.getInetNetworkMask(match.getNetworkDestinationMaskLen(), false); + this.setNetworkDestination(address, mask); + } else { + this.nwDstState = MatchFieldState.MATCH_ABSENT; + } + + this.inputPortMask = 0; + if (match.getInputPort() != 0) { + this.setInputPort(match.getInputPort(), (short) 0); + } else { + this.inputPortMask = 0; + this.inputPortState = MatchFieldState.MATCH_ABSENT; + } + + this.dataLayerSourceMask = null; + if (match.getDataLayerSource() != null + && !NetUtils.isZeroMAC(match.getDataLayerSource())) { + this.setDataLayerSource(match.getDataLayerSource(), null); + } else { + this.dlSourceState = MatchFieldState.MATCH_ABSENT; + } + this.dataLayerDestinationMask = null; + if (match.getDataLayerDestination() != null + && !NetUtils.isZeroMAC(match.getDataLayerDestination())) { + this.setDataLayerDestination(match.getDataLayerDestination(), null); + } else { + this.dlDestState = MatchFieldState.MATCH_ABSENT; + } + + this.dataLayerTypeMask = 0; + if (match.getDataLayerType() != 0) { + this.setDataLayerType(match.getDataLayerType(), (short) 0); + } else { + this.dataLayerType = 0; + this.ethTypeState = MatchFieldState.MATCH_ABSENT; + } + + this.dataLayerVirtualLanTCIMask = 0; + this.dlVlanTCIState = MatchFieldState.MATCH_ABSENT; + if (match.getDataLayerVirtualLan() != 0) { + this.setDataLayerVirtualLan(match.getDataLayerVirtualLan(), + (short) 0); + } else { + this.dataLayerVirtualLan = 0; + this.dlVlanIDState = MatchFieldState.MATCH_ABSENT; + } + + if (match.getDataLayerVirtualLanPriorityCodePoint() != 0) { + this.setDataLayerVirtualLanPriorityCodePoint( + match.getDataLayerVirtualLanPriorityCodePoint(), (byte) 0); + } else { + this.dataLayerVirtualLanPriorityCodePoint = 0; + this.dlVlanPCPState = MatchFieldState.MATCH_ABSENT; + } + + this.networkProtocolMask = 0; + if (match.getNetworkProtocol() != 0) { + this.setNetworkProtocol( + this.networkProtocol = match.getNetworkProtocol(), (byte) 0); + } else { + this.networkProtocol = 0; + this.nwProtoState = MatchFieldState.MATCH_ABSENT; + } + + this.networkTypeOfServiceMask = 0; + if (match.getNetworkTypeOfService() != 0) { + this.setNetworkTypeOfService( + this.networkTypeOfService = match.getNetworkTypeOfService(), + (byte) 0); + } else { + this.networkTypeOfService = match.getNetworkTypeOfService(); + this.nwTosState = MatchFieldState.MATCH_ABSENT; + } + + this.transportSourceMask = 0; + if (match.getTransportSource() != 0) { + this.setTransportSource(match.getTransportSource(), (short) 0); + } else { + this.transportSource = 0; + this.tpSrcState = MatchFieldState.MATCH_ABSENT; + } + + this.transportDestinationMask = 0; + if (match.getTransportDestination() != 0) { + this.setTransportDestination(match.getTransportDestination(), + (short) 0); + } else { + this.transportDestination = 0; + this.tpDstState = MatchFieldState.MATCH_ABSENT; + } + + this.setWildcards(match.getWildcards()); + } + + private enum IPProtocols { + ICMP(1), TCP(6), UDP(17), ICMPV6(58); + + private int protocol; + + private IPProtocols(int value) { + this.protocol = value; + } + + private byte getValue() { + return (byte) this.protocol; + } + } + + public short getIPv6MatchLen() { + return match_len; + } + + public int getIPv6ExtMinHdrLen() { + return IPV6_EXT_MIN_HDR_LEN; + } + + public short getPadSize() { + return (short) (((match_len + 7) / 8) * 8 - match_len); + } + + private int getIPv6ExtensionMatchHeader(Extension_Types extType, int field, + int has_mask, int length) { + return (((extType.getValue() & 0x0000ffff) << 16) + | ((field & 0x0000007f) << 9) | ((has_mask & 0x00000001) << 8) | (length & 0x000000ff)); + } + + private byte[] getIPv6ExtensionPortMatchMsg(short port) { + ByteBuffer ipv6ext_port_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_IN_PORT.getValue(), 0, 2); + ipv6ext_port_msg.putInt(nxm_header); + ipv6ext_port_msg.putShort(port); + return (ipv6ext_port_msg.array()); + } + + private byte[] getIPv6ExtensionDestMacMatchMsg(byte[] destMac) { + ByteBuffer ipv6ext_destmac_msg = ByteBuffer.allocate(10); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_ETH_DST.getValue(), 0, 6); + ipv6ext_destmac_msg.putInt(nxm_header); + ipv6ext_destmac_msg.put(destMac); + return (ipv6ext_destmac_msg.array()); + } + + private byte[] getIPv6ExtensionSrcMacMatchMsg(byte[] srcMac) { + ByteBuffer ipv6ext_srcmac_msg = ByteBuffer.allocate(10); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_ETH_SRC.getValue(), 0, 6); + ipv6ext_srcmac_msg.putInt(nxm_header); + ipv6ext_srcmac_msg.put(srcMac); + return (ipv6ext_srcmac_msg.array()); + } + + private byte[] getIPv6ExtensionEtherTypeMatchMsg(short EtherType) { + ByteBuffer ipv6ext_etype_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_ETH_TYPE.getValue(), 0, 2); + ipv6ext_etype_msg.putInt(nxm_header); + ipv6ext_etype_msg.putShort(EtherType); + return (ipv6ext_etype_msg.array()); + } + + private byte[] getVlanTCI(short dataLayerVirtualLanID, + byte dataLayerVirtualLanPriorityCodePoint) { + ByteBuffer vlan_tci = ByteBuffer.allocate(2); + int cfi = 1 << 12; // the cfi bit is in position 12 + int pcp = dataLayerVirtualLanPriorityCodePoint << 13; // the pcp fields + // have to move by + // 13 + int vlan_tci_int = pcp + cfi + dataLayerVirtualLanID; + vlan_tci.put((byte) (vlan_tci_int >> 8)); // bits 8 to 15 + vlan_tci.put((byte) vlan_tci_int); // bits 0 to 7 + return vlan_tci.array(); + } + + private byte[] getIPv6ExtensionVlanTCIMatchMsg(short dataLayerVirtualLanID, + byte dataLayerVirtualLanPriorityCodePoint) { + ByteBuffer ipv6ext_vlan_tci_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_VLAN_TCI.getValue(), 0, 2); + ipv6ext_vlan_tci_msg.putInt(nxm_header); + ipv6ext_vlan_tci_msg.put(getVlanTCI(dataLayerVirtualLanID, + dataLayerVirtualLanPriorityCodePoint)); + return (ipv6ext_vlan_tci_msg.array()); + } + + private byte[] getIPv6ExtensionVlanTCIMatchWithMaskMsg( + short dataLayerVirtualLan, + byte dataLayerVirtualLanPriorityCodePoint, + int dataLayerVirtualLanTCIMask) { + ByteBuffer ipv6ext_vlan_tci_msg = ByteBuffer.allocate(8); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_VLAN_TCI.getValue(), 1, 4); + ipv6ext_vlan_tci_msg.putInt(nxm_header); + ipv6ext_vlan_tci_msg.put(getVlanTCI(dataLayerVirtualLan, + dataLayerVirtualLanPriorityCodePoint)); + ipv6ext_vlan_tci_msg.put((byte) (dataLayerVirtualLanTCIMask >> 8)); // bits + // 8 + // to + // 15 + ipv6ext_vlan_tci_msg.put((byte) (dataLayerVirtualLanTCIMask)); // bits 0 + // to 7 + return (ipv6ext_vlan_tci_msg.array()); + } + + private byte[] getIPv6ExtensionSrcIPv6MatchMsg(byte[] srcIpv6) { + ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(20); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT, + IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC.getValue(), 0, + 16); + ipv6ext_ipv6_msg.putInt(nxm_header); + ipv6ext_ipv6_msg.put(srcIpv6); + return (ipv6ext_ipv6_msg.array()); + } + + private byte[] getIPv6ExtensionSrcIPv6MatchwithMaskMsg(byte[] srcIpv6, + short masklen) { + ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(36); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT, + IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC.getValue(), 1, + 32); + ipv6ext_ipv6_msg.putInt(nxm_header); + ipv6ext_ipv6_msg.put(srcIpv6); + byte[] ipv6_mask = getIPv6NetworkMaskinBytes(masklen); + ipv6ext_ipv6_msg.put(ipv6_mask); + return (ipv6ext_ipv6_msg.array()); + } + + private byte[] getIPv6ExtensionDstIPv6MatchMsg(byte[] dstIpv6) { + ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(20); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT, + IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST.getValue(), 0, + 16); + ipv6ext_ipv6_msg.putInt(nxm_header); + ipv6ext_ipv6_msg.put(dstIpv6); + return (ipv6ext_ipv6_msg.array()); + } + + private byte[] getIPv6ExtensionDstIPv6MatchwithMaskMsg(byte[] dstIpv6, + short masklen) { + ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(36); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT, + IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST.getValue(), 1, + 32); + ipv6ext_ipv6_msg.putInt(nxm_header); + ipv6ext_ipv6_msg.put(dstIpv6); + byte[] ipv6_mask = getIPv6NetworkMaskinBytes(masklen); + ipv6ext_ipv6_msg.put(ipv6_mask); + return (ipv6ext_ipv6_msg.array()); + } + + private byte[] getIPv6ExtensionProtocolMatchMsg(byte protocol) { + ByteBuffer ipv6ext_proto_msg = ByteBuffer.allocate(5); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_IP_PROTO.getValue(), 0, 1); + if (protocol == 0) { + return null; + } + ipv6ext_proto_msg.putInt(nxm_header); + if (protocol == IPProtocols.ICMP.getValue()) { + /* + * The front end passes the same protocol type values for IPv4 and + * IPv6 flows. For the Protocol types we allow in our GUI (ICMP, + * TCP, UDP), ICMP is the only one which is different for IPv6. It + * is 1 for v4 and 58 for v6 Therefore, we overwrite it here. + */ + protocol = IPProtocols.ICMPV6.getValue(); + } + ipv6ext_proto_msg.put(protocol); + return (ipv6ext_proto_msg.array()); + } + + private byte[] getIPv6ExtensionTOSMatchMsg(byte tos) { + ByteBuffer ipv6ext_tos_msg = ByteBuffer.allocate(5); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_IP_TOS.getValue(), 0, 1); + ipv6ext_tos_msg.putInt(nxm_header); + ipv6ext_tos_msg.put(tos); + return (ipv6ext_tos_msg.array()); + } + + private byte[] getIPv6ExtensionTCPSrcPortMatchMsg(short src_port) { + ByteBuffer ipv6ext_tcp_srcport_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_TCP_SRC.getValue(), 0, 2); + ipv6ext_tcp_srcport_msg.putInt(nxm_header); + ipv6ext_tcp_srcport_msg.putShort(src_port); + return (ipv6ext_tcp_srcport_msg.array()); + } + + private byte[] getIPv6ExtensionTCPDstPortMatchMsg(short dst_port) { + ByteBuffer ipv6ext_tcp_dstport_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_TCP_DST.getValue(), 0, 2); + ipv6ext_tcp_dstport_msg.putInt(nxm_header); + ipv6ext_tcp_dstport_msg.putShort(dst_port); + return (ipv6ext_tcp_dstport_msg.array()); + } + + private byte[] getIPv6ExtensionUDPSrcPortMatchMsg(short src_port) { + ByteBuffer ipv6ext_udp_srcport_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_UDP_SRC.getValue(), 0, 2); + ipv6ext_udp_srcport_msg.putInt(nxm_header); + ipv6ext_udp_srcport_msg.putShort(src_port); + return (ipv6ext_udp_srcport_msg.array()); + } + + private byte[] getIPv6ExtensionUDPDstPortMatchMsg(short dst_port) { + ByteBuffer ipv6ext_udp_dstport_msg = ByteBuffer.allocate(6); + int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10, + OF_Match_Types.MATCH_OF_UDP_DST.getValue(), 0, 2); + ipv6ext_udp_dstport_msg.putInt(nxm_header); + ipv6ext_udp_dstport_msg.putShort(dst_port); + return (ipv6ext_udp_dstport_msg.array()); + } + + /** + * Sets this (V6Match) object's member variables based on a comma-separated + * key=value pair similar to OFMatch's fromString. + * + * @param match + * a key=value comma separated string. + */ + @Override + public void fromString(String match) throws IllegalArgumentException { + if (match.equals("") || match.equalsIgnoreCase("any") + || match.equalsIgnoreCase("all") || match.equals("[]")) { + match = "OFMatch[]"; + } + String[] tokens = match.split("[\\[,\\]]"); + String[] values; + int initArg = 0; + if (tokens[0].equals("OFMatch")) { + initArg = 1; + } + this.wildcards = OFPFW_ALL; + int i; + for (i = initArg; i < tokens.length; i++) { + values = tokens[i].split("="); + if (values.length != 2) { + throw new IllegalArgumentException("Token " + tokens[i] + + " does not have form 'key=value' parsing " + match); + } + values[0] = values[0].toLowerCase(); // try to make this case insens + if (values[0].equals(STR_IN_PORT) || values[0].equals("input_port")) { + this.inputPort = U16.t(Integer.valueOf(values[1])); + inputPortState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 6; + } else if (values[0].equals(STR_DL_DST) + || values[0].equals("eth_dst")) { + this.dataLayerDestination = HexEncode + .bytesFromHexString(values[1]); + dlDestState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 10; + } else if (values[0].equals(STR_DL_SRC) + || values[0].equals("eth_src")) { + this.dataLayerSource = HexEncode.bytesFromHexString(values[1]); + dlSourceState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 10; + this.wildcards &= ~OFPFW_DL_SRC; + } else if (values[0].equals(STR_DL_TYPE) + || values[0].equals("eth_type")) { + if (values[1].startsWith("0x")) { + this.dataLayerType = U16.t(Integer.valueOf(values[1] + .replaceFirst("0x", ""), 16)); + } else { + + this.dataLayerType = U16.t(Integer.valueOf(values[1])); + } + ethTypeState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 6; + } else if (values[0].equals(STR_DL_VLAN)) { + this.dataLayerVirtualLan = U16.t(Integer.valueOf(values[1])); + this.dlVlanIDState = MatchFieldState.MATCH_FIELD_ONLY; + // the variable dlVlanIDState is not really used as a flag + // for serializing and deserializing. Rather it is used as a + // flag + // to check if the vlan id is being set so that we can set the + // dlVlanTCIState appropriately. + if (this.dlVlanPCPState != MatchFieldState.MATCH_ABSENT) { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_ONLY; + match_len -= 2; + } else { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.dataLayerVirtualLanTCIMask = 0x1fff; + match_len += 8; + } + this.wildcards &= ~OFPFW_DL_VLAN; + } else if (values[0].equals(STR_DL_VLAN_PCP)) { + this.dataLayerVirtualLanPriorityCodePoint = U8.t(Short + .valueOf(values[1])); + this.dlVlanPCPState = MatchFieldState.MATCH_FIELD_ONLY; + // the variable dlVlanPCPState is not really used as a flag + // for serializing and deserializing. Rather it is used as a + // flag + // to check if the vlan pcp is being set so that we can set the + // dlVlanTCIState appropriately. + if (this.dlVlanIDState != MatchFieldState.MATCH_ABSENT) { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_ONLY; + match_len -= 2; + } else { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.dataLayerVirtualLanTCIMask = 0xf000; + match_len += 8; + } + this.wildcards &= ~OFPFW_DL_VLAN_PCP; + } else if (values[0].equals(STR_NW_DST) + || values[0].equals("ip_dst")) { + try { + InetAddress address = null; + InetAddress mask = null; + if (values[1].contains("/")) { + String addressString[] = values[1].split("/"); + address = InetAddress.getByName(addressString[0]); + int masklen = Integer.valueOf(addressString[1]); + mask = NetUtils.getInetNetworkMask(masklen, address instanceof Inet6Address); + } else { + address = InetAddress.getByName(values[1]); + } + this.setNetworkDestination(address, mask); + } catch (UnknownHostException e) { + logger.error("", e); + } + } else if (values[0].equals(STR_NW_SRC) + || values[0].equals("ip_src")) { + try { + InetAddress address = null; + InetAddress mask = null; + if (values[1].contains("/")) { + String addressString[] = values[1].split("/"); + address = InetAddress.getByName(addressString[0]); + int masklen = Integer.valueOf(addressString[1]); + mask = NetUtils.getInetNetworkMask(masklen, address instanceof Inet6Address); + } else { + address = InetAddress.getByName(values[1]); + } + this.setNetworkSource(address, mask); + } catch (UnknownHostException e) { + logger.error("", e); + } + } else if (values[0].equals(STR_NW_PROTO)) { + this.networkProtocol = U8.t(Short.valueOf(values[1])); + if (!(this.networkProtocol == 0)) { + /* + * if user selects proto 0, don't use it + */ + nwProtoState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 5; + } + } else if (values[0].equals(STR_NW_TOS)) { + this.networkTypeOfService = U8.t(Short.valueOf(values[1])); + nwTosState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 5; + } else if (values[0].equals(STR_TP_DST)) { + this.transportDestination = U16.t(Integer.valueOf(values[1])); + tpDstState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 6; + } else if (values[0].equals(STR_TP_SRC)) { + this.transportSource = U16.t(Integer.valueOf(values[1])); + tpSrcState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 6; + } else { + throw new IllegalArgumentException("unknown token " + tokens[i] + + " parsing " + match); + } + } + + /* + * In a V6 extension message action list should be preceded by a padding + * of 0 to 7 bytes based upon following formula. + */ + + pad_size = (short) (((match_len + 7) / 8) * 8 - match_len); + + } + + /** + * Write this message's binary format to the specified ByteBuffer + * + * @param data + */ + @Override + public void writeTo(ByteBuffer data) { + if (inputPortState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_ingress_port_msg = getIPv6ExtensionPortMatchMsg(this.inputPort); + data.put(ipv6ext_ingress_port_msg); + } + if (ethTypeState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_ether_type_msg = getIPv6ExtensionEtherTypeMatchMsg(this.dataLayerType); + data.put(ipv6ext_ether_type_msg); + } + if (dlDestState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_destmac_msg = getIPv6ExtensionDestMacMatchMsg(this.dataLayerDestination); + data.put(ipv6ext_destmac_msg); + } + if (dlSourceState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_srcmac_msg = getIPv6ExtensionSrcMacMatchMsg(this.dataLayerSource); + data.put(ipv6ext_srcmac_msg); + } + if (dlVlanTCIState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_vlan_tci_msg = getIPv6ExtensionVlanTCIMatchMsg( + this.dataLayerVirtualLan, + this.dataLayerVirtualLanPriorityCodePoint); + data.put(ipv6ext_vlan_tci_msg); + } else if (dlVlanTCIState == MatchFieldState.MATCH_FIELD_WITH_MASK) { + byte[] ipv6ext_vlan_tci_msg_with_mask = getIPv6ExtensionVlanTCIMatchWithMaskMsg( + this.dataLayerVirtualLan, + this.dataLayerVirtualLanPriorityCodePoint, + this.dataLayerVirtualLanTCIMask); + data.put(ipv6ext_vlan_tci_msg_with_mask); + } + if (nwSrcState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_src_ipv6_msg = getIPv6ExtensionSrcIPv6MatchMsg(this.nwSrc + .getAddress()); + data.put(ipv6ext_src_ipv6_msg); + } else if (nwSrcState == MatchFieldState.MATCH_FIELD_WITH_MASK) { + byte[] ipv6ext_src_ipv6_with_mask_msg = getIPv6ExtensionSrcIPv6MatchwithMaskMsg( + this.nwSrc.getAddress(), this.srcIPv6SubnetMaskbits); + data.put(ipv6ext_src_ipv6_with_mask_msg); + } + if (nwDstState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_dst_ipv6_msg = getIPv6ExtensionDstIPv6MatchMsg(this.nwDst + .getAddress()); + data.put(ipv6ext_dst_ipv6_msg); + } else if (nwDstState == MatchFieldState.MATCH_FIELD_WITH_MASK) { + byte[] ipv6ext_dst_ipv6_with_mask_msg = getIPv6ExtensionDstIPv6MatchwithMaskMsg( + this.nwDst.getAddress(), this.dstIPv6SubnetMaskbits); + data.put(ipv6ext_dst_ipv6_with_mask_msg); + } + if (nwProtoState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_protocol_msg = getIPv6ExtensionProtocolMatchMsg(this.networkProtocol); + if (ipv6ext_protocol_msg != null) { + data.put(ipv6ext_protocol_msg); + } + } + if (nwTosState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_tos_msg = getIPv6ExtensionTOSMatchMsg(this.networkTypeOfService); + data.put(ipv6ext_tos_msg); + } + if (tpSrcState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_srcport_msg = null; + if (this.networkProtocol == IPProtocols.TCP.getValue()) { + ipv6ext_srcport_msg = getIPv6ExtensionTCPSrcPortMatchMsg(this.transportSource); + } else if (this.networkProtocol == IPProtocols.UDP.getValue()) { + ipv6ext_srcport_msg = getIPv6ExtensionUDPSrcPortMatchMsg(this.transportSource); + } + if (ipv6ext_srcport_msg != null) { + data.put(ipv6ext_srcport_msg); + } + } + if (tpDstState == MatchFieldState.MATCH_FIELD_ONLY) { + byte[] ipv6ext_dstport_msg = null; + if (this.networkProtocol == IPProtocols.TCP.getValue()) { + ipv6ext_dstport_msg = getIPv6ExtensionTCPDstPortMatchMsg(this.transportDestination); + } else if (this.networkProtocol == IPProtocols.UDP.getValue()) { + ipv6ext_dstport_msg = getIPv6ExtensionUDPDstPortMatchMsg(this.transportDestination); + } + if (ipv6ext_dstport_msg != null) { + data.put(ipv6ext_dstport_msg); + } + } + logger.trace("{}", this); + } + + private void readInPort(ByteBuffer data, int nxmLen, boolean hasMask) { + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + /* + * mask is not allowed for inport port + */ + return; + } + super.setInputPort(data.getShort()); + this.inputPortState = MatchFieldState.MATCH_FIELD_ONLY; + this.wildcards ^= (1 << 0); // Sync with 0F 1.0 Match + this.match_len += 6; + } + + private void readDataLinkDestination(ByteBuffer data, int nxmLen, + boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 6) || (data.remaining() < 2 * 6)) { + return; + } else { + byte[] bytes = new byte[6]; + data.get(bytes); + super.setDataLayerDestination(bytes); + this.dataLayerDestinationMask = new byte[6]; + data.get(this.dataLayerDestinationMask); + this.dlDestState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 16; + } + } else { + if ((nxmLen != 6) || (data.remaining() < 6)) { + return; + } else { + byte[] bytes = new byte[6]; + data.get(bytes); + super.setDataLayerDestination(bytes); + this.dlDestState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 10; + } + } + this.wildcards ^= (1 << 3); // Sync with 0F 1.0 Match + } + + private void readDataLinkSource(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in data link source + */ + if ((nxmLen != 6) || (data.remaining() < 6) || (hasMask)) { + return; + } + byte[] bytes = new byte[6]; + data.get(bytes); + super.setDataLayerSource(bytes); + this.dlSourceState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 10; + this.wildcards ^= (1 << 2); // Sync with 0F 1.0 Match + } + + private void readEtherType(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in ethertype + */ + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + return; + } + super.setDataLayerType(data.getShort()); + this.ethTypeState = MatchFieldState.MATCH_FIELD_ONLY; + this.wildcards ^= (1 << 4); // Sync with 0F 1.0 Match + this.match_len += 6; + } + + private short getVlanID(byte firstByte, byte secondByte) { + short vlan_id_mask_firstByte = 0x0f;// this is the mask for the first + // byte + short vlan_id_mask_secondByte = 0xff;// this is the mask for the second + // byte + int vlanPart1 = (firstByte & vlan_id_mask_firstByte) << 8; + int vlanPart2 = secondByte & vlan_id_mask_secondByte; + return (short) (vlanPart1 + vlanPart2); + } + + private byte getVlanPCP(byte pcpByte) { + short vlan_pcp_mask = 0xe0;// this is the vlan pcp mask + int pcp_int = pcpByte & vlan_pcp_mask; + return (byte) (pcp_int >> 5); + } + + private void readVlanTci(ByteBuffer data, int nxmLen, boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 2) || (data.remaining() < 2 * 2)) { + return; + } + else { + byte firstByte = data.get(); + byte secondByte = data.get(); + this.dataLayerVirtualLanTCIMask = data.getShort() & 0xffff; // we + // need + // the + // last + // 16 + // bits + // check the mask now + if ((this.dataLayerVirtualLanTCIMask & 0x0fff) != 0) { + // if its a vlan id mask + // extract the vlan id + super.setDataLayerVirtualLan(getVlanID(firstByte, + secondByte)); + } else { + this.wildcards ^= (1 << 1); // Sync with 0F 1.0 Match + } + if ((this.dataLayerVirtualLanTCIMask & 0xe000) != 0) { + // else if its a vlan pcp mask + // extract the vlan pcp + super.setDataLayerVirtualLanPriorityCodePoint(getVlanPCP(firstByte)); + } else { + this.wildcards ^= (1 << 20); + } + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 8; + } + } else { + if ((nxmLen != 2) || (data.remaining() < 2)) { + return; + } + else { + // get the vlan pcp + byte firstByte = data.get(); + byte secondByte = data.get(); + super.setDataLayerVirtualLanPriorityCodePoint(getVlanPCP(firstByte)); + super.setDataLayerVirtualLan(getVlanID(firstByte, secondByte)); + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + } + } + } + + private void readIpTos(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in IP TOS + */ + if ((nxmLen != 1) || (data.remaining() < 1) || (hasMask)) { + return; + } + super.setNetworkTypeOfService(data.get()); + this.nwTosState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 5; + this.wildcards ^= (1 << 21); // Sync with 0F 1.0 Match + } + + private void readIpProto(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in IP protocol + */ + if ((nxmLen != 1) || (data.remaining() < 1) || (hasMask)) { + return; + } + super.setNetworkProtocol(data.get()); + this.nwProtoState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 5; + this.wildcards ^= (1 << 5); // Sync with 0F 1.0 Match + } + + private void readIpv4Src(ByteBuffer data, int nxmLen, boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 4) || (data.remaining() < 2 * 4)) { + return; + } else { + byte[] sbytes = new byte[4]; + data.get(sbytes); + // For compatibility, let's set the IPv4 in the parent OFMatch + int address = NetUtils.byteArray4ToInt(sbytes); + super.setNetworkSource(address); + byte[] mbytes = new byte[4]; + data.get(mbytes); + this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 12; + int prefixlen = getNetworkMaskPrefixLength(mbytes); + this.wildcards ^= (((1 << 6) - 1) << 8); // Sync with 0F 1.0 Match + this.wildcards |= ((32 - prefixlen) << 8); // Sync with 0F 1.0 Match + } + } else { + if ((nxmLen != 4) || (data.remaining() < 4)) { + return; + } else { + byte[] sbytes = new byte[4]; + data.get(sbytes); + // For compatibility, let's also set the IPv4 in the parent OFMatch + int address = NetUtils.byteArray4ToInt(sbytes); + super.setNetworkSource(address); + this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 8; + this.wildcards ^= (((1 << 6) - 1) << 8); // Sync with 0F 1.0 + // Match + } + } + } + + private void readIpv4Dst(ByteBuffer data, int nxmLen, boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 4) || (data.remaining() < 2 * 4)) { + return; + } else { + byte[] dbytes = new byte[4]; + data.get(dbytes); + // For compatibility, let's also set the IPv4 in the parent OFMatch + int address = NetUtils.byteArray4ToInt(dbytes); + super.setNetworkDestination(address); + byte[] mbytes = new byte[4]; + data.get(mbytes); + this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 12; + int prefixlen = getNetworkMaskPrefixLength(mbytes); + this.wildcards ^= (((1 << 6) - 1) << 14); // Sync with 0F 1.0 + // Match + this.wildcards |= ((32 - prefixlen) << 14); // Sync with 0F 1.0 + // Match + } + } else { + if ((nxmLen != 4) || (data.remaining() < 4)) { + return; + } else { + byte[] dbytes = new byte[4]; + data.get(dbytes); + int address = NetUtils.byteArray4ToInt(dbytes); + super.setNetworkDestination(address); + this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.wildcards ^= (((1 << 6) - 1) << 14); // Sync with 0F 1.0 + // Match + this.match_len += 8; + } + } + } + + private void readTcpSrc(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in TCP SRC + */ + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + return; + } + super.setTransportSource(data.getShort()); + this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + this.wildcards ^= (1 << 6); // Sync with 0F 1.0 Match + } + + private void readTcpDst(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in TCP DST + */ + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + return; + } + super.setTransportDestination(data.getShort()); + this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + this.wildcards ^= (1 << 7); // Sync with 0F 1.0 Match + } + + private void readUdpSrc(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in UDP SRC + */ + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + return; + } + super.setTransportSource(data.getShort()); + this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + this.wildcards ^= (1 << 6); // Sync with 0F 1.0 Match + } + + private void readUdpDst(ByteBuffer data, int nxmLen, boolean hasMask) { + /* + * mask is not allowed in UDP DST + */ + if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask)) { + return; + } + super.setTransportDestination(data.getShort()); + this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + this.wildcards ^= (1 << 7); // Sync with 0F 1.0 Match + } + + private void readIpv6Src(ByteBuffer data, int nxmLen, boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 16) || (data.remaining() < 2 * 16)) { + return; + } else { + byte[] sbytes = new byte[16]; + data.get(sbytes); + try { + this.nwSrc = (Inet6Address) InetAddress.getByAddress(sbytes); + } catch (UnknownHostException e) { + return; + } + byte[] mbytes = new byte[16]; + data.get(mbytes); + this.srcIPv6SubnetMaskbits = (short)NetUtils.getSubnetMaskLength(mbytes); + this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 36; + } + } else { + if ((nxmLen != 16) || (data.remaining() < 16)) { + return; + } else { + byte[] sbytes = new byte[16]; + data.get(sbytes); + try { + this.nwSrc = (Inet6Address) InetAddress.getByAddress(sbytes); + } catch (UnknownHostException e) { + return; + } + this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 20; + } + } + } + + private void readIpv6Dst(ByteBuffer data, int nxmLen, boolean hasMask) { + if (hasMask) { + if ((nxmLen != 2 * 16) || (data.remaining() < 2 * 16)) { + return; + } else { + byte[] dbytes = new byte[16]; + data.get(dbytes); + try { + this.nwDst = (Inet6Address) InetAddress.getByAddress(dbytes); + } catch (UnknownHostException e) { + return; + } + byte[] mbytes = new byte[16]; + data.get(mbytes); + this.dstIPv6SubnetMaskbits = (short)NetUtils.getSubnetMaskLength(mbytes); + this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 36; + } + } else { + if ((nxmLen != 16) || (data.remaining() < 16)) { + return; + } else { + byte[] dbytes = new byte[16]; + data.get(dbytes); + try { + this.nwDst = (Inet6Address) InetAddress.getByAddress(dbytes); + } catch (UnknownHostException e) { + return; + } + this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 20; + } + } + } + + @Override + public String toString() { + return "V6Match [nwSrc=" + nwSrc + ", nwDst=" + nwDst + + ", inputPortMask=" + inputPortMask + ", dataLayerSourceMask=" + + HexEncode.bytesToHexStringFormat(dataLayerSourceMask) + + ", dataLayerDestinationMask=" + + HexEncode.bytesToHexStringFormat(dataLayerDestinationMask) + + ", dataLayerVirtualLanTCIMask=" + dataLayerVirtualLanTCIMask + + ", dataLayerTypeMask=" + dataLayerTypeMask + + ", networkTypeOfServiceMask=" + networkTypeOfServiceMask + + ", networkProtocolMask=" + networkProtocolMask + + ", transportSourceMask=" + transportSourceMask + + ", transportDestinationMask=" + transportDestinationMask + + ", srcIPv6SubnetMaskbits=" + srcIPv6SubnetMaskbits + + ", dstIPv6SubnetMaskbits=" + dstIPv6SubnetMaskbits + + ", inputPortState=" + inputPortState + ", dlSourceState=" + + dlSourceState + ", dlDestState=" + dlDestState + + ", dlVlanTCIState=" + dlVlanTCIState + ", ethTypeState=" + + ethTypeState + ", nwTosState=" + nwTosState + + ", nwProtoState=" + nwProtoState + ", nwSrcState=" + + nwSrcState + ", nwDstState=" + nwDstState + ", tpSrcState=" + + tpSrcState + ", tpDstState=" + tpDstState + ", match_len=" + + match_len + ", pad_size=" + pad_size + "]"; + } + + /** + * Read the data corresponding to the match field (received from the wire) + * Input: data: match field(s). Since match field is of variable length, the + * whole data that are passed in are assumed to fem0tbd.be the match fields. + * + * @param data + */ + @Override + public void readFrom(ByteBuffer data) { + readFromInternal(data); + postprocessWildCardInfo(); + } + + private void readFromInternal(ByteBuffer data) { + this.match_len = 0; + while (data.remaining() > 0) { + if (data.remaining() < 4) { + /* + * at least 4 bytes for each match header + */ + logger.error("Invalid Vendor Extension Header. Size {}", + data.remaining()); + return; + } + /* + * read the 4 byte match header + */ + int nxmVendor = data.getShort(); + int b = data.get(); + int nxmField = b >> 1; + boolean hasMask = ((b & 0x01) == 1) ? true : false; + int nxmLen = data.get(); + if (nxmVendor == Extension_Types.OF_10.getValue()) { + if (nxmField == OF_Match_Types.MATCH_OF_IN_PORT.getValue()) { + readInPort(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_DST + .getValue()) { + readDataLinkDestination(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_SRC + .getValue()) { + readDataLinkSource(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_TYPE + .getValue()) { + readEtherType(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_VLAN_TCI + .getValue()) { + readVlanTci(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_IP_TOS + .getValue()) { + readIpTos(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_IP_PROTO + .getValue()) { + readIpProto(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_IP_SRC + .getValue()) { + readIpv4Src(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_IP_DST + .getValue()) { + readIpv4Dst(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_TCP_SRC + .getValue()) { + readTcpSrc(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_TCP_DST + .getValue()) { + readTcpDst(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_UDP_SRC + .getValue()) { + readUdpSrc(data, nxmLen, hasMask); + } else if (nxmField == OF_Match_Types.MATCH_OF_UDP_DST + .getValue()) { + readUdpDst(data, nxmLen, hasMask); + } else { + // unexpected nxmField + return; + } + } else if (nxmVendor == Extension_Types.IPV6EXT.getValue()) { + if (nxmField == IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC + .getValue()) { + readIpv6Src(data, nxmLen, hasMask); + } else if (nxmField == IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST + .getValue()) { + readIpv6Dst(data, nxmLen, hasMask); + } else { + // unexpected nxmField + return; + } + } else { + // invalid nxmVendor + return; + } + } + } + + private void postprocessWildCardInfo() { + // Sync with 0F 1.0 Match + if (super.getDataLayerType() == 0x800) { + if (((this.wildcards >> 8) & 0x3f) == 0x3f) { + // ipv4 src processing + this.wildcards ^= (((1 << 5) - 1) << 8); + } + if (((this.wildcards >> 14) & 0x3f) == 0x3f) { + // ipv4 dest processing + this.wildcards ^= (((1 << 5) - 1) << 14); + } + } else { + this.wildcards = 0; + } + } + + @Override + public V6Match clone() { + + V6Match ret = (V6Match) super.clone(); + try { + if (this.nwSrc != null) { + ret.nwSrc = (Inet6Address) InetAddress.getByAddress(this.nwSrc.getAddress()); + } + if (this.nwDst != null) { + ret.nwDst = (Inet6Address) InetAddress.getByAddress(this.nwDst.getAddress()); + } + return ret; + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + /** + * Get nw_dst + * + * @return + */ + + public Inet6Address getNetworkDest() { + return this.nwDst; + } + + /** + * Set nw_src + * + * @return + */ + + public Inet6Address getNetworkSrc() { + return this.nwSrc; + } + + private int getNetworkMaskPrefixLength(byte[] netMask) { + ByteBuffer nm = ByteBuffer.wrap(netMask); + int trailingZeros = Integer.numberOfTrailingZeros(nm.getInt()); + return 32 - trailingZeros; + } + + public short getInputPortMask() { + return inputPortMask; + } + + public void setInputPort(short port, short mask) { + super.inputPort = port; + this.inputPortState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 6; + // Looks like mask is not allowed for input port. Will discard it + } + + public byte[] getDataLayerSourceMask() { + return dataLayerSourceMask; + } + + public void setDataLayerSource(byte[] mac, byte[] mask) { + if (mac != null) { + System.arraycopy(mac, 0, super.dataLayerSource, 0, mac.length); + } + if (mask == null) { + this.dlSourceState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 10; + } else { + if (this.dataLayerSourceMask == null) { + this.dataLayerSourceMask = new byte[mask.length]; + } + System.arraycopy(mask, 0, this.dataLayerSourceMask, 0, mask.length); + this.dlSourceState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 16; + } + } + + public byte[] getDataLayerDestinationMask() { + return dataLayerDestinationMask; + } + + public void setDataLayerDestination(byte[] mac, byte[] mask) { + if (mac != null) { + System.arraycopy(mac, 0, super.dataLayerDestination, 0, mac.length); + } + if (mask == null) { + this.dlDestState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 10; + } else { + if (this.dataLayerDestinationMask == null) { + this.dataLayerDestinationMask = new byte[mask.length]; + } + System.arraycopy(mask, 0, this.dataLayerDestinationMask, 0, + mask.length); + this.dlDestState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 16; + } + } + + public void setDataLayerVirtualLan(short vlan, short mask) { + // mask is ignored as the code sets the appropriate mask + super.dataLayerVirtualLan = vlan; + this.dlVlanIDState = MatchFieldState.MATCH_FIELD_ONLY; + // the variable dlVlanIDState is not really used as a flag + // for serializing and deserializing. Rather it is used as a flag + // to check if the vlan id is being set so that we can set the + // dlVlanTCIState appropriately. + if (this.dlVlanPCPState != MatchFieldState.MATCH_ABSENT) { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_ONLY; + match_len -= 2; + } else { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.dataLayerVirtualLanTCIMask = 0x1fff; + match_len += 8; + } + } + + public void setDataLayerVirtualLanPriorityCodePoint(byte pcp, byte mask) { + // mask is ignored as the code sets the appropriate mask + super.dataLayerVirtualLanPriorityCodePoint = pcp; + this.dlVlanPCPState = MatchFieldState.MATCH_FIELD_ONLY; + // the variable dlVlanPCPState is not really used as a flag + // for serializing and deserializing. Rather it is used as a flag + // to check if the vlan pcp is being set so that we can set the + // dlVlanTCIState appropriately. + if (this.dlVlanIDState != MatchFieldState.MATCH_ABSENT) { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_ONLY; + match_len -= 2; + } else { + this.dlVlanTCIState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.dataLayerVirtualLanTCIMask = 0xf000; + match_len += 8; + } + } + + public void setDataLayerType(short ethType, short mask) { + // mask not allowed + super.dataLayerType = ethType; + this.ethTypeState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + } + + public void setNetworkTypeOfService(byte tos, byte mask) { + // mask not allowed + super.networkTypeOfService = tos; + this.nwTosState = MatchFieldState.MATCH_FIELD_ONLY; + match_len += 5; + } + + public void setNetworkProtocol(byte ipProto, byte mask) { + // mask not allowed + super.networkProtocol = ipProto; + this.nwProtoState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 5; + } + + public Inet6Address getNetworkSourceMask() { + return (this.nwSrcState == MatchFieldState.MATCH_FIELD_WITH_MASK) ? (Inet6Address) NetUtils.getInetNetworkMask( + this.srcIPv6SubnetMaskbits, true) : null; + } + + public void setNetworkSource(InetAddress address, InetAddress mask) { + if (address instanceof Inet6Address) { + this.nwSrc = (Inet6Address) address; + if (mask == null) { + this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += (address instanceof Inet6Address) ? 20 : 8; + } else { + this.srcIPv6SubnetMaskbits = (short)NetUtils.getSubnetMaskLength(mask); + this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += (address instanceof Inet6Address) ? 36 : 12; + } + } else { + super.setNetworkSource(NetUtils.byteArray4ToInt(address.getAddress())); + this.wildcards ^= (((1 << 6) - 1) << 8); + if (mask == null) { + this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 8; + } else { + this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 12; + this.wildcards |= ((32 - NetUtils.getSubnetMaskLength(mask)) << 8); + } + } + } + + public Inet6Address getNetworkDestinationMask() { + return (this.nwDstState == MatchFieldState.MATCH_FIELD_WITH_MASK) ? (Inet6Address) NetUtils.getInetNetworkMask( + this.dstIPv6SubnetMaskbits, true) : null; + } + + public void setNetworkDestination(InetAddress address, InetAddress mask) { + if (address instanceof Inet6Address) { + this.nwDst = (Inet6Address) address; + if (mask == null) { + this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += (address instanceof Inet6Address) ? 20 : 8; + } else { + this.dstIPv6SubnetMaskbits = (short)NetUtils.getSubnetMaskLength(mask); + this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += (address instanceof Inet6Address) ? 36 : 12; + } + } else { + this.setNetworkDestination(NetUtils.byteArray4ToInt(address.getAddress())); + this.wildcards ^= (((1 << 6) - 1) << 14); + if (mask == null) { + this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 8; + } else { + this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK; + this.match_len += 12; + this.wildcards |= ((32 - NetUtils.getSubnetMaskLength(mask)) << 14); + } + } + } + + public void setTransportSource(short tpSrc, short mask) { + // mask not allowed + super.transportSource = tpSrc; + this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + } + + public short getTransportDestinationMask() { + return transportDestinationMask; + } + + public void setTransportDestination(short tpDst, short mask) { + // mask not allowed + super.transportDestination = tpDst; + this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY; + this.match_len += 6; + } + + private byte[] getIPv6NetworkMaskinBytes(short num) { + byte[] nbytes = new byte[16]; + int quot = num / 8; + int bits = num % 8; + int i; + + for (i = 0; i < quot; i++) { + nbytes[i] = (byte) 0xff; + } + + if (bits > 0) { + nbytes[i] = (byte) 0xff; + nbytes[i] <<= 8 - bits; + } + return nbytes; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(dataLayerDestinationMask); + result = prime * result + Arrays.hashCode(dataLayerSourceMask); + result = prime * result + dataLayerTypeMask; + result = prime * result + dataLayerVirtualLanTCIMask; + result = prime * result + + ((dlDestState == null) ? 0 : dlDestState.hashCode()); + result = prime * result + + ((dlSourceState == null) ? 0 : dlSourceState.hashCode()); + result = prime * result + + ((dlVlanTCIState == null) ? 0 : dlVlanTCIState.hashCode()); + result = prime * result + dstIPv6SubnetMaskbits; + result = prime * result + + ((ethTypeState == null) ? 0 : ethTypeState.hashCode()); + result = prime * result + inputPortMask; + result = prime * result + + ((inputPortState == null) ? 0 : inputPortState.hashCode()); + result = prime * result + match_len; + result = prime * result + networkProtocolMask; + result = prime * result + networkTypeOfServiceMask; + result = prime * result + ((nwDst == null) ? 0 : nwDst.hashCode()); + result = prime * result + + ((nwDstState == null) ? 0 : nwDstState.hashCode()); + result = prime * result + + ((nwProtoState == null) ? 0 : nwProtoState.hashCode()); + result = prime * result + ((nwSrc == null) ? 0 : nwSrc.hashCode()); + result = prime * result + + ((nwSrcState == null) ? 0 : nwSrcState.hashCode()); + result = prime * result + + ((nwTosState == null) ? 0 : nwTosState.hashCode()); + result = prime * result + pad_size; + result = prime * result + srcIPv6SubnetMaskbits; + result = prime * result + + ((tpDstState == null) ? 0 : tpDstState.hashCode()); + result = prime * result + + ((tpSrcState == null) ? 0 : tpSrcState.hashCode()); + result = prime * result + transportDestinationMask; + result = prime * result + transportSourceMask; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + V6Match other = (V6Match) obj; + if (!Arrays.equals(dataLayerDestinationMask, other.dataLayerDestinationMask)) { + return false; + } + if (!Arrays.equals(dataLayerSourceMask, other.dataLayerSourceMask)) { + return false; + } + if (dataLayerTypeMask != other.dataLayerTypeMask) { + return false; + } + if (dataLayerVirtualLanTCIMask != other.dataLayerVirtualLanTCIMask) { + return false; + } + if (dlVlanTCIState != other.dlVlanTCIState) { + return false; + } + if (dlSourceState != other.dlSourceState) { + return false; + } + if (dstIPv6SubnetMaskbits != other.dstIPv6SubnetMaskbits) { + return false; + } + if (ethTypeState != other.ethTypeState) { + return false; + } + if (inputPortMask != other.inputPortMask) { + return false; + } + if (inputPortState != other.inputPortState) { + return false; + } + if (match_len != other.match_len) { + return false; + } + if (networkProtocolMask != other.networkProtocolMask) { + return false; + } + if (networkTypeOfServiceMask != other.networkTypeOfServiceMask) { + return false; + } + if (nwDst == null) { + if (other.nwDst != null) { + return false; + } + } else if (!nwDst.equals(other.nwDst)) { + return false; + } + if (nwDstState != other.nwDstState) { + return false; + } + if (nwProtoState != other.nwProtoState) { + return false; + } + if (nwSrc == null) { + if (other.nwSrc != null) { + return false; + } + } else if (!nwSrc.equals(other.nwSrc)) { + return false; + } + if (nwSrcState != other.nwSrcState) { + return false; + } + if (nwTosState != other.nwTosState) { + return false; + } + if (pad_size != other.pad_size) { + return false; + } + if (srcIPv6SubnetMaskbits != other.srcIPv6SubnetMaskbits) { + return false; + } + if (tpDstState != other.tpDstState) { + return false; + } + if (tpSrcState != other.tpSrcState) { + return false; + } + if (transportDestinationMask != other.transportDestinationMask) { + return false; + } + if (transportSourceMask != other.transportSourceMask) { + return false; + } + return true; + } +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsReply.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsReply.java new file mode 100644 index 0000000000..d0c852a576 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsReply.java @@ -0,0 +1,394 @@ + +/* + * 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.vendorextension.v6extension; + +import java.nio.ByteBuffer; +import java.util.List; + +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.statistics.OFVendorStatistics; +import org.openflow.util.U16; + +/** + * This Class processes the OpenFlow Vendor Extension Reply message of a Stats + * Request. It parses the reply message and initializes fields of V6StatsReply + * object. Multiple instances of this class objects are created and used by + * OpenDaylight's Troubleshooting Application. + * + */ + +public class V6StatsReply extends OFVendorStatistics { + private static final long serialVersionUID = 1L; + + public static int MINIMUM_LENGTH = 48; //48 for nx_flow_stats + + protected short length = (short) MINIMUM_LENGTH; + protected byte tableId; + protected int durationSeconds; + protected int durationNanoseconds; + protected short priority; + protected short idleTimeout; + protected short hardTimeout; + protected short match_len; + protected short idleAge; + protected short hardAge; + protected long cookie; + protected long packetCount; + protected long byteCount; + protected V6Match match; + protected List actions; + + /** + * @return vendor id + */ + public int getVendorId() { + return vendor; + } + + /** + * @param vendor the vendor to set + */ + public void setVendorId(int vendor) { + this.vendor = vendor; + } + + /** + * @return the tableId + */ + public byte getTableId() { + return tableId; + } + + /** + * @param tableId the tableId to set + */ + public void setTableId(byte tableId) { + this.tableId = tableId; + } + + /** + * @return the durationSeconds + */ + public int getDurationSeconds() { + return durationSeconds; + } + + /** + * @param durationSeconds the durationSeconds to set + */ + public void setDurationSeconds(int durationSeconds) { + this.durationSeconds = durationSeconds; + } + + /** + * @return the durationNanoseconds + */ + public int getDurationNanoseconds() { + return durationNanoseconds; + } + + /** + * @param durationNanoseconds the durationNanoseconds to set + */ + public void setDurationNanoseconds(int durationNanoseconds) { + this.durationNanoseconds = durationNanoseconds; + } + + /** + * @return the priority + */ + public short getPriority() { + return priority; + } + + /** + * @param priority the priority to set + */ + public void setPriority(short priority) { + this.priority = priority; + } + + /** + * @return the idleTimeout + */ + public short getIdleTimeout() { + return idleTimeout; + } + + /** + * @param idleTimeout the idleTimeout to set + */ + public void setIdleTimeout(short idleTimeout) { + this.idleTimeout = idleTimeout; + } + + /** + * @return the hardTimeout + */ + public short getHardTimeout() { + return hardTimeout; + } + + /** + * @param hardTimeout the hardTimeout to set + */ + public void setHardTimeout(short hardTimeout) { + this.hardTimeout = hardTimeout; + } + + /** + * @param match_len the match_len to set + */ + public void setMatchLen(short match_len) { + this.match_len = match_len; + } + + /** + * @return the match_len + */ + public short getMatchLen() { + return match_len; + } + + /** + * @return the idleAge + */ + public short getIdleAge() { + return idleAge; + } + + /** + * @param idleAge the idleAge to set + */ + public void setIdleAge(short idleAge) { + this.idleAge = idleAge; + } + + /** + * @return the hardAge + */ + public short getHardAge() { + return hardAge; + } + + /** + * @param hardAge the hardAge to set + */ + public void setHardAge(short hardAge) { + this.hardAge = hardAge; + } + + /** + * @return the cookie + */ + public long getCookie() { + return cookie; + } + + /** + * @param cookie the cookie to set + */ + public void setCookie(long cookie) { + this.cookie = cookie; + } + + /** + * @return the packetCount + */ + public long getPacketCount() { + return packetCount; + } + + /** + * @param packetCount the packetCount to set + */ + public void setPacketCount(long packetCount) { + this.packetCount = packetCount; + } + + /** + * @return the byteCount + */ + public long getByteCount() { + return byteCount; + } + + /** + * @param byteCount the byteCount to set + */ + public void setByteCount(long byteCount) { + this.byteCount = byteCount; + } + + /** + * @param length the length to set + */ + public void setLength(short length) { + this.length = length; + } + + @Override + public int getLength() { + return U16.f(length); + } + + /** + * @return the match + */ + public V6Match getMatch() { + return match; + } + + /** + * @return the actions + */ + public List getActions() { + return actions; + } + + /** + * @param actions the actions to set + */ + public void setActions(List actions) { + this.actions = actions; + } + + @Override + public void readFrom(ByteBuffer data) { + short i; + this.length = data.getShort(); + if (length < MINIMUM_LENGTH) + return; //TBD - Spurious Packet? + this.tableId = data.get(); + data.get(); // pad + this.durationSeconds = data.getInt(); + this.durationNanoseconds = data.getInt(); + this.priority = data.getShort(); + this.idleTimeout = data.getShort(); + this.hardTimeout = data.getShort(); + this.match_len = data.getShort(); + this.idleAge = data.getShort(); + this.hardAge = data.getShort(); + this.cookie = data.getLong(); + this.packetCount = data.getLong(); + this.byteCount = data.getLong(); + if (this.length == MINIMUM_LENGTH) { + return; //TBD - can this happen?? + } + if (this.match == null) + this.match = new V6Match(); + ByteBuffer mbuf = ByteBuffer.allocate(match_len); + for (i = 0; i < match_len; i++) { + mbuf.put(data.get()); + } + mbuf.rewind(); + this.match.readFrom(mbuf); + if (this.actionFactory == null) + throw new RuntimeException("OFActionFactory not set"); + /* + * action list may be preceded by a padding of 0 to 7 bytes based upon this: + */ + short pad_size = (short) (((match_len + 7) / 8) * 8 - match_len); + for (i = 0; i < pad_size; i++) + data.get(); + int action_len = this.length - MINIMUM_LENGTH - (match_len + pad_size); + if (action_len > 0) + this.actions = this.actionFactory.parseActions(data, action_len); + } + + @Override + public void writeTo(ByteBuffer data) { + super.writeTo(data);//TBD. This Fn needs work. Should never get called though. + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((actions == null) ? 0 : actions.hashCode()); + result = prime * result + (int) (byteCount ^ (byteCount >>> 32)); + result = prime * result + (int) (cookie ^ (cookie >>> 32)); + result = prime * result + durationNanoseconds; + result = prime * result + durationSeconds; + result = prime * result + hardAge; + result = prime * result + hardTimeout; + result = prime * result + idleAge; + result = prime * result + idleTimeout; + result = prime * result + length; + result = prime * result + ((match == null) ? 0 : match.hashCode()); + result = prime * result + match_len; + result = prime * result + (int) (packetCount ^ (packetCount >>> 32)); + result = prime * result + priority; + result = prime * result + tableId; + return result; + } + + @Override + public String toString() { + return "V6StatsReply [length=" + length + ", tableId=" + tableId + + ", durationSeconds=" + durationSeconds + + ", durationNanoseconds=" + durationNanoseconds + + ", priority=" + priority + ", idleTimeout=" + idleTimeout + + ", hardTimeout=" + hardTimeout + ", match_len=" + match_len + + ", idleAge=" + idleAge + ", hardAge=" + hardAge + ", cookie=" + + cookie + ", packetCount=" + packetCount + ", byteCount=" + + byteCount + ", match=" + match + ", actions=" + actions + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + V6StatsReply other = (V6StatsReply) obj; + if (actions == null) { + if (other.actions != null) + return false; + } else if (!actions.equals(other.actions)) + return false; + if (byteCount != other.byteCount) + return false; + if (cookie != other.cookie) + return false; + if (durationNanoseconds != other.durationNanoseconds) + return false; + if (durationSeconds != other.durationSeconds) + return false; + if (hardAge != other.hardAge) + return false; + if (hardTimeout != other.hardTimeout) + return false; + if (idleAge != other.idleAge) + return false; + if (idleTimeout != other.idleTimeout) + return false; + if (length != other.length) + return false; + if (match == null) { + if (other.match != null) + return false; + } else if (!match.equals(other.match)) + return false; + if (match_len != other.match_len) + return false; + if (packetCount != other.packetCount) + return false; + if (priority != other.priority) + return false; + if (tableId != other.tableId) + return false; + return true; + } + +} diff --git a/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsRequest.java b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsRequest.java new file mode 100644 index 0000000000..928e0dabb5 --- /dev/null +++ b/openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/vendorextension/v6extension/V6StatsRequest.java @@ -0,0 +1,176 @@ + +/* + * 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.vendorextension.v6extension; + +import java.nio.ByteBuffer; + +import org.openflow.protocol.statistics.OFVendorStatistics; + + +/** + * This Class creates the OpenFlow Vendor Extension IPv6 Flow Stats Request + * messages and also reads the Reply of a stats request message. + * + */ + +public class V6StatsRequest extends OFVendorStatistics { + private static final long serialVersionUID = 1L; + protected int msgsubtype; + protected short outPort; + protected short match_len; + protected byte tableId; + + public static final int NICIRA_VENDOR_ID = 0x00002320; //Nicira ID + private static final int NXST_FLOW = 0x0; //Nicira Flow Stats Request Id + + public V6StatsRequest() { + this.vendor = NICIRA_VENDOR_ID; + this.msgsubtype = NXST_FLOW; + this.match_len = 0; + } + + /** + * @param None. Being set with local variable (TBD). + */ + public void setVendorId() { + this.vendor = NICIRA_VENDOR_ID; + } + + /** + * @return vendor id + */ + public int getVendorId() { + return vendor; + } + + /** + * @param None. Being set with local variable (TBD). + */ + public void setMsgtype() { + this.msgsubtype = NXST_FLOW; + } + + /** + * @return vendor_msgtype + */ + public int getMsgtype() { + return msgsubtype; + } + + /** + * @param outPort the outPort to set + */ + public void setOutPort(short outPort) { + this.outPort = outPort; + } + + /** + * @return the outPort + */ + public short getOutPort() { + return outPort; + } + + /** + * @param match_len the match_len to set + */ + public void setMatchLen(short match_len) { + this.match_len = match_len; + } + + /** + * @return the match_len + */ + public short getMatchLen() { + return match_len; + } + + /** + * @param tableId the tableId to set + */ + public void setTableId(byte tableId) { + this.tableId = tableId; + } + + /** + * @return the tableId + */ + public byte getTableId() { + return tableId; + } + + @Override + public int getLength() { + return 20;// 4(vendor)+4(msgsubtype)+4(pad)+2(outPort)+2(match_len)+1(tableid)+3(pad) + } + + @Override + public void readFrom(ByteBuffer data) { + this.vendor = data.getInt(); + this.msgsubtype = data.getInt(); + data.getInt();//pad 4 bytes + this.outPort = data.getShort(); + this.match_len = data.getShort(); + this.tableId = data.get(); + for (int i = 0; i < 3; i++) + data.get();//pad byte + + } + + @Override + public void writeTo(ByteBuffer data) { + data.putInt(this.vendor); + data.putInt(this.msgsubtype); + data.putInt((int) 0x0);//pad0 + data.putShort(this.outPort); + data.putShort(this.match_len); + data.put(this.tableId); + for (int i = 0; i < 3; i++) + data.put((byte) 0x0);//pad byte + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + match_len; + result = prime * result + msgsubtype; + result = prime * result + outPort; + result = prime * result + tableId; + return result; + } + + @Override + public String toString() { + return "V6StatsRequest [msgsubtype=" + msgsubtype + ", outPort=" + + outPort + ", match_len=" + match_len + ", tableId=" + tableId + + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + V6StatsRequest other = (V6StatsRequest) obj; + if (match_len != other.match_len) + return false; + if (msgsubtype != other.msgsubtype) + return false; + if (outPort != other.outPort) + return false; + if (tableId != other.tableId) + return false; + return true; + } +} diff --git a/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java b/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java new file mode 100644 index 0000000000..e023ce6396 --- /dev/null +++ b/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java @@ -0,0 +1,408 @@ + +/* + * 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.controller.protocol_plugin.openflow.internal; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.action.OFAction; + +import org.opendaylight.controller.sal.action.Action; +import org.opendaylight.controller.sal.action.Flood; +import org.opendaylight.controller.sal.action.FloodAll; +import org.opendaylight.controller.sal.action.HwPath; +import org.opendaylight.controller.sal.action.Loopback; +import org.opendaylight.controller.sal.action.Output; +import org.opendaylight.controller.sal.action.PopVlan; +import org.opendaylight.controller.sal.action.SetDlDst; +import org.opendaylight.controller.sal.action.SetDlSrc; +import org.opendaylight.controller.sal.action.SetNwDst; +import org.opendaylight.controller.sal.action.SetNwSrc; +import org.opendaylight.controller.sal.action.SetNwTos; +import org.opendaylight.controller.sal.action.SetTpDst; +import org.opendaylight.controller.sal.action.SetTpSrc; +import org.opendaylight.controller.sal.action.SetVlanId; +import org.opendaylight.controller.sal.action.SwPath; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.flowprogrammer.Flow; +import org.opendaylight.controller.sal.match.Match; +import org.opendaylight.controller.sal.match.MatchType; +import org.opendaylight.controller.sal.utils.EtherTypes; +import org.opendaylight.controller.sal.utils.IPProtocols; +import org.opendaylight.controller.sal.utils.NodeConnectorCreator; +import org.opendaylight.controller.sal.utils.NodeCreator; +import org.opendaylight.openflowplugin.openflow.internal.FlowConverter; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6Match; + +public class FlowProgrammerServiceTest { + + @Test + public void testSALtoOFFlowConverter() throws UnknownHostException { + Node node = NodeCreator.createOFNode(1000l); + NodeConnector port = NodeConnectorCreator.createNodeConnector( + (short) 24, node); + NodeConnector oport = NodeConnectorCreator.createNodeConnector( + (short) 30, node); + byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, + (byte) 0x9a, (byte) 0xbc }; + byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d, + (byte) 0x5e, (byte) 0x6f }; + InetAddress srcIP = InetAddress.getByName("172.28.30.50"); + InetAddress dstIP = InetAddress.getByName("171.71.9.52"); + InetAddress ipMask = InetAddress.getByName("255.255.255.0"); + short ethertype = EtherTypes.IPv4.shortValue(); + short vlan = (short) 27; + byte vlanPr = 3; + Byte tos = 4; + byte proto = IPProtocols.TCP.byteValue(); + short src = (short) 55000; + short dst = 80; + + /* + * Create a SAL Flow aFlow + */ + Match match = new Match(); + match.setField(MatchType.IN_PORT, port); + match.setField(MatchType.DL_SRC, srcMac); + match.setField(MatchType.DL_DST, dstMac); + match.setField(MatchType.DL_TYPE, ethertype); + match.setField(MatchType.DL_VLAN, vlan); + match.setField(MatchType.DL_VLAN_PR, vlanPr); + match.setField(MatchType.NW_SRC, srcIP, ipMask); + match.setField(MatchType.NW_DST, dstIP, ipMask); + match.setField(MatchType.NW_TOS, tos); + match.setField(MatchType.NW_PROTO, proto); + match.setField(MatchType.TP_SRC, src); + match.setField(MatchType.TP_DST, dst); + + Assert.assertTrue(match.isIPv4()); + + List actions = new ArrayList(); + // Setting all the actions supported by of + actions.add(new PopVlan()); + actions.add(new Output(oport)); + actions.add(new Flood()); + actions.add(new FloodAll()); + actions.add(new SwPath()); + actions.add(new HwPath()); + actions.add(new Loopback()); + byte mac[] = { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 }; + actions.add(new SetDlSrc(mac)); + actions.add(new SetDlDst(mac)); + actions.add(new SetNwSrc(dstIP)); + actions.add(new SetNwDst(srcIP)); + actions.add(new SetNwTos(3)); + actions.add(new SetTpSrc(10)); + actions.add(new SetTpDst(20)); + actions.add(new SetVlanId(200)); + + Flow aFlow = new Flow(match, actions); + + /* + * Convert the SAL aFlow to OF Flow + */ + FlowConverter salToOF = new FlowConverter(aFlow); + OFMatch ofMatch = salToOF.getOFMatch(); + List ofActions = salToOF.getOFActions(); + + /* + * Convert the OF Flow to SAL Flow bFlow + */ + FlowConverter ofToSal = new FlowConverter(ofMatch, ofActions); + Flow bFlow = ofToSal.getFlow(node); + Match bMatch = bFlow.getMatch(); + List bActions = bFlow.getActions(); + + /* + * Verify the converted SAL flow bFlow is equivalent to the original SAL Flow + */ + Assert.assertTrue(((NodeConnector) match.getField(MatchType.IN_PORT) + .getValue()).equals(((NodeConnector) bMatch.getField( + MatchType.IN_PORT).getValue()))); + Assert.assertTrue(Arrays.equals((byte[]) match.getField( + MatchType.DL_SRC).getValue(), (byte[]) bMatch.getField( + MatchType.DL_SRC).getValue())); + Assert.assertTrue(Arrays.equals((byte[]) match.getField( + MatchType.DL_DST).getValue(), (byte[]) bMatch.getField( + MatchType.DL_DST).getValue())); + Assert + .assertTrue(((Short) match.getField(MatchType.DL_TYPE) + .getValue()).equals((Short) bMatch.getField( + MatchType.DL_TYPE).getValue())); + Assert + .assertTrue(((Short) match.getField(MatchType.DL_VLAN) + .getValue()).equals((Short) bMatch.getField( + MatchType.DL_VLAN).getValue())); + Assert.assertTrue(((Byte) match.getField(MatchType.DL_VLAN_PR) + .getValue()).equals((Byte) bMatch + .getField(MatchType.DL_VLAN_PR).getValue())); + Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_SRC) + .getValue()).equals((InetAddress) bMatch.getField( + MatchType.NW_SRC).getValue())); + Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_SRC) + .getMask()).equals((InetAddress) bMatch.getField( + MatchType.NW_SRC).getMask())); + Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_DST) + .getValue()).equals((InetAddress) bMatch.getField( + MatchType.NW_DST).getValue())); + Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_DST) + .getMask()).equals((InetAddress) bMatch.getField( + MatchType.NW_DST).getMask())); + Assert + .assertTrue(((Byte) match.getField(MatchType.NW_PROTO) + .getValue()).equals((Byte) bMatch.getField( + MatchType.NW_PROTO).getValue())); + Assert.assertTrue(((Byte) match.getField(MatchType.NW_TOS).getValue()) + .equals((Byte) bMatch.getField(MatchType.NW_TOS).getValue())); + Assert.assertTrue(((Short) match.getField(MatchType.TP_SRC).getValue()) + .equals((Short) bMatch.getField(MatchType.TP_SRC).getValue())); + Assert.assertTrue(((Short) match.getField(MatchType.TP_DST).getValue()) + .equals((Short) bMatch.getField(MatchType.TP_DST).getValue())); + + // FlowConverter parses and sets the actions in the same order for sal match and of match + for (short i = 0; i < actions.size(); i++) { + Assert.assertTrue(actions.get(i).equals(bActions.get(i))); + } + } + + @Test + public void testVlanNoneIdFlowConversion() throws Exception { + Node node = NodeCreator.createOFNode(1000l); + + /* + * The value 0 is used to indicate that no VLAN ID is set + * for SAL Flow. + */ + short vlan = (short) 0; + + /* + * Create a SAL Flow aFlow + */ + Match match = new Match(); + match.setField(MatchType.DL_VLAN, vlan); + + List actions = new ArrayList(); + + Flow aFlow = new Flow(match, actions); + + /* + * Convert the SAL aFlow to OF Flow + */ + FlowConverter salToOF = new FlowConverter(aFlow); + OFMatch ofMatch = salToOF.getOFMatch(); + List ofActions = salToOF.getOFActions(); + + /* + * The value 0xffff (OFP_VLAN_NONE) is used to indicate + * that no VLAN ID is set for OF Flow. + */ + Assert.assertEquals((short) 0xffff, ofMatch.getDataLayerVirtualLan()); + + /* + * Convert the OF Flow to SAL Flow bFlow + */ + FlowConverter ofToSal = new FlowConverter(ofMatch, ofActions); + Flow bFlow = ofToSal.getFlow(node); + Match bMatch = bFlow.getMatch(); + + /* + * Verify the converted SAL flow bFlow is equivalent to the original SAL Flow + */ + Assert + .assertTrue(((Short) match.getField(MatchType.DL_VLAN) + .getValue()).equals((Short) bMatch.getField( + MatchType.DL_VLAN).getValue())); + } + + @Test + public void testV6toSALFlowConversion() throws Exception { + Node node = NodeCreator.createOFNode(12l); + NodeConnector port = NodeConnectorCreator.createNodeConnector( + (short) 34, node); + NodeConnector oport = NodeConnectorCreator.createNodeConnector( + (short) 30, node); + byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, + (byte) 0x9a, (byte) 0xbc }; + byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d, + (byte) 0x5e, (byte) 0x6f }; + InetAddress srcIP = InetAddress + .getByName("2001:420:281:1004:407a:57f4:4d15:c355"); + InetAddress dstIP = InetAddress + .getByName("2001:420:281:1004:e123:e688:d655:a1b0"); + InetAddress ipMask = InetAddress + .getByName("ffff:ffff:ffff:ffff:0:0:0:0"); + short ethertype = EtherTypes.IPv6.shortValue(); + short vlan = (short) 27; + byte vlanPr = 3; + Byte tos = 4; + byte proto = IPProtocols.TCP.byteValue(); + short src = (short) 55000; + short dst = 80; + + /* + * Create a SAL Flow aFlow + */ + Match aMatch = new Match(); + + aMatch.setField(MatchType.IN_PORT, port); + aMatch.setField(MatchType.DL_SRC, srcMac); + aMatch.setField(MatchType.DL_DST, dstMac); + aMatch.setField(MatchType.DL_TYPE, ethertype); + aMatch.setField(MatchType.DL_VLAN, vlan); + aMatch.setField(MatchType.DL_VLAN_PR, vlanPr); + aMatch.setField(MatchType.NW_SRC, srcIP, ipMask); + aMatch.setField(MatchType.NW_DST, dstIP, ipMask); + aMatch.setField(MatchType.NW_TOS, tos); + aMatch.setField(MatchType.NW_PROTO, proto); + aMatch.setField(MatchType.TP_SRC, src); + aMatch.setField(MatchType.TP_DST, dst); + + Assert.assertTrue(aMatch.isIPv6()); + + List actions = new ArrayList(); + // Setting all the actions supported by of for v6 + actions.add(new PopVlan()); + actions.add(new Output(oport)); + actions.add(new Flood()); + actions.add(new FloodAll()); + actions.add(new SwPath()); + actions.add(new HwPath()); + actions.add(new Loopback()); + byte mac[] = { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 }; + actions.add(new SetDlSrc(mac)); + actions.add(new SetDlDst(mac)); + //actions.add(new SetNwSrc(dstIP)); Nicira extensions do not provide IPv6 match addresses change + //actions.add(new SetNwDst(srcIP)); + actions.add(new SetNwTos(3)); + actions.add(new SetTpSrc(10)); + actions.add(new SetTpDst(65535)); + actions.add(new SetVlanId(200)); + + Flow aFlow = new Flow(aMatch, actions); + + /* + * Convert the SAL aFlow to OF Flow + */ + FlowConverter salToOF = new FlowConverter(aFlow); + V6Match v6Match = (V6Match) salToOF.getOFMatch(); + List ofActions = salToOF.getOFActions(); + + /* + * Convert the OF Flow to SAL Flow bFlow + */ + FlowConverter ofToSal = new FlowConverter(v6Match, ofActions); + Flow bFlow = ofToSal.getFlow(node); + Match bMatch = bFlow.getMatch(); + List bActions = bFlow.getActions(); + + /* + * Verify the converted SAL flow bFlow is equivalent to the original SAL Flow + */ + Assert.assertTrue(((NodeConnector) aMatch.getField(MatchType.IN_PORT) + .getValue()).equals(((NodeConnector) bMatch.getField( + MatchType.IN_PORT).getValue()))); + Assert.assertTrue(Arrays.equals((byte[]) aMatch.getField( + MatchType.DL_SRC).getValue(), (byte[]) bMatch.getField( + MatchType.DL_SRC).getValue())); + Assert.assertTrue(Arrays.equals((byte[]) aMatch.getField( + MatchType.DL_DST).getValue(), (byte[]) bMatch.getField( + MatchType.DL_DST).getValue())); + Assert.assertTrue(((Short) aMatch.getField(MatchType.DL_TYPE) + .getValue()).equals((Short) bMatch.getField(MatchType.DL_TYPE) + .getValue())); + Assert.assertTrue(((Short) aMatch.getField(MatchType.DL_VLAN) + .getValue()).equals((Short) bMatch.getField(MatchType.DL_VLAN) + .getValue())); + Assert.assertTrue(((Byte) aMatch.getField(MatchType.DL_VLAN_PR) + .getValue()).equals((Byte) bMatch + .getField(MatchType.DL_VLAN_PR).getValue())); + Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_SRC) + .getValue()).equals((InetAddress) bMatch.getField( + MatchType.NW_SRC).getValue())); + Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_SRC) + .getMask()).equals((InetAddress) bMatch.getField( + MatchType.NW_SRC).getMask())); + Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_DST) + .getValue()).equals((InetAddress) bMatch.getField( + MatchType.NW_DST).getValue())); + Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_DST) + .getMask()).equals((InetAddress) bMatch.getField( + MatchType.NW_DST).getMask())); + Assert.assertTrue(((Byte) aMatch.getField(MatchType.NW_PROTO) + .getValue()).equals((Byte) bMatch.getField(MatchType.NW_PROTO) + .getValue())); + Assert.assertTrue(((Byte) aMatch.getField(MatchType.NW_TOS).getValue()) + .equals((Byte) bMatch.getField(MatchType.NW_TOS).getValue())); + Assert + .assertTrue(((Short) aMatch.getField(MatchType.TP_SRC) + .getValue()).equals((Short) bMatch.getField( + MatchType.TP_SRC).getValue())); + Assert + .assertTrue(((Short) aMatch.getField(MatchType.TP_DST) + .getValue()).equals((Short) bMatch.getField( + MatchType.TP_DST).getValue())); + + // FlowConverter parses and sets the actions in the same order for sal match and of match + for (short i = 0; i < actions.size(); i++) { + Assert.assertTrue(actions.get(i).equals(bActions.get(i))); + } + } + + @Test + public void testV6MatchToSALMatchToV6MatchConversion() + throws UnknownHostException { + NodeConnector port = NodeConnectorCreator.createNodeConnector( + (short) 24, NodeCreator.createOFNode(6l)); + byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, + (byte) 0x9a, (byte) 0xbc }; + byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d, + (byte) 0x5e, (byte) 0x6f }; + InetAddress srcIP = InetAddress + .getByName("2001:420:281:1004:407a:57f4:4d15:c355"); + InetAddress dstIP = InetAddress + .getByName("2001:420:281:1004:e123:e688:d655:a1b0"); + InetAddress ipMask = null;//InetAddress.getByName("ffff:ffff:ffff:ffff:0:0:0:0"); + short ethertype = EtherTypes.IPv6.shortValue(); + short vlan = (short) 27; + byte vlanPr = 3; + Byte tos = 4; + byte proto = IPProtocols.TCP.byteValue(); + short src = (short) 55000; + short dst = 80; + + /* + * Create a SAL Flow aFlow + */ + Match aMatch = new Match(); + + aMatch.setField(MatchType.IN_PORT, port); + aMatch.setField(MatchType.DL_SRC, srcMac); + aMatch.setField(MatchType.DL_DST, dstMac); + aMatch.setField(MatchType.DL_TYPE, ethertype); + aMatch.setField(MatchType.DL_VLAN, vlan); + aMatch.setField(MatchType.DL_VLAN_PR, vlanPr); + aMatch.setField(MatchType.NW_SRC, srcIP, ipMask); + aMatch.setField(MatchType.NW_DST, dstIP, ipMask); + aMatch.setField(MatchType.NW_TOS, tos); + aMatch.setField(MatchType.NW_PROTO, proto); + aMatch.setField(MatchType.TP_SRC, src); + aMatch.setField(MatchType.TP_DST, dst); + + Assert.assertTrue(aMatch.isIPv6()); + + } +} diff --git a/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6ExtensionTest.java b/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6ExtensionTest.java new file mode 100644 index 0000000000..da83800429 --- /dev/null +++ b/openflowplugin/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6ExtensionTest.java @@ -0,0 +1,224 @@ +package org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension; + +import static org.junit.Assert.fail; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; +import org.opendaylight.openflowplugin.openflow.vendorextension.v6extension.V6Match; +import org.openflow.protocol.OFMatch; + +public class V6ExtensionTest { + + @Test + public void testFromString() throws UnknownHostException { + // This tests creating V6Match using fromString and OFMatch by comparing + // the results to each other + V6Match match = new V6Match(); + V6Match match2 = new V6Match(); + + OFMatch ofm = new OFMatch(); + V6Match match4 = new V6Match(ofm); + + match.fromString(""); + Assert.assertTrue(match.equals(match2)); + match.fromString("any"); + Assert.assertTrue(match.equals(match2)); + Assert.assertTrue(match.equals(match4)); + try { + match.fromString("invalidArgument"); + + fail("Did not throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // passed test for throwing exception. + } + try { + match.fromString("invalidParameter=abcdefg"); + fail("Did not throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // passed test for throwing exception. + } + + match.fromString("input_port=1"); + match.fromString("dl_dst=20:A0:11:10:00:99"); + match.fromString("dl_src=00:10:08:22:12:75"); + + match.fromString("ip_src=10.1.1.1"); + match.fromString("ip_dst=1.2.3.4"); + match.fromString("eth_type=0x800"); + match.fromString("dl_vlan=10"); + match.fromString("dl_vpcp=1"); + match.fromString("nw_proto=6"); + match.fromString("nw_tos=100"); + match.fromString("tp_dst=8080"); + match.fromString("tp_src=60"); + + Assert.assertTrue(match.getInputPort() == 1); + // Assert.assertTrue(match.getIPv6MatchLen()==6); + + ofm.setInputPort((short) 1); + // V6Match is meant for IPv6, but if using OFMatch, it will be set to + // IPv4 values, as OF1.0 doesn't support IPv6. + InetAddress addr = InetAddress.getByName("10.1.1.1"); + int ipsrc = ByteBuffer.wrap(addr.getAddress()).getInt(); + ofm.setNetworkSource(ipsrc); + + addr = InetAddress.getByName("1.2.3.4"); + int ipdst = ByteBuffer.wrap(addr.getAddress()).getInt(); + ofm.setNetworkDestination(ipdst); + + byte[] macSrc = { 0x00, 0x10, 0x08, 0x22, 0x12, 0x75 }; + ofm.setDataLayerSource(macSrc); + byte[] macDst = { 0x20, (byte) 0xA0, 0x11, 0x10, 0x00, (byte) 0x99 }; + ofm.setDataLayerDestination(macDst); + ofm.setDataLayerType((short) 0x800); + ofm.setDataLayerVirtualLan((short) 10); + ofm.setDataLayerVirtualLanPriorityCodePoint((byte) 1); + ofm.setNetworkProtocol((byte) 6); + ofm.setNetworkTypeOfService((byte) 100); + ofm.setTransportSource((short) 60); + ofm.setTransportDestination((short) 8080); + + V6Match match3 = new V6Match(ofm); + + Assert.assertTrue(match.getInputPort() == match3.getInputPort()); + Assert.assertTrue(Arrays.equals(match.getDataLayerSource(), + match3.getDataLayerSource())); + Assert.assertTrue(Arrays.equals(match.getDataLayerDestination(), + match3.getDataLayerDestination())); + Assert.assertNull(match.getNetworkSrc()); + Assert.assertNull(match3.getNetworkSrc()); + Assert.assertNull(match.getNetworkDest()); + Assert.assertNull(match3.getNetworkDest()); + Assert.assertTrue(match.getDataLayerVirtualLan() == match3 + .getDataLayerVirtualLan()); + Assert.assertTrue(match.getDataLayerVirtualLanPriorityCodePoint() == match3 + .getDataLayerVirtualLanPriorityCodePoint()); + Assert.assertTrue(match.getNetworkProtocol() == match3 + .getNetworkProtocol()); + Assert.assertTrue(match.getNetworkTypeOfService() == match3 + .getNetworkTypeOfService()); + Assert.assertTrue(match.getTransportSource() == match3 + .getTransportSource()); + Assert.assertTrue(match.getTransportDestination() == match3 + .getTransportDestination()); + Assert.assertTrue(match.getWildcards() == match3.getWildcards()); + + } + + @Test + public void testReadWriteBuffer() { + V6Match match = new V6Match(); + match.fromString("input_port=1"); + match.fromString("dl_dst=20:A0:11:10:00:99"); + match.fromString("dl_src=00:10:08:22:12:75"); + // writeTo(ByteBuffer) will only write IPv6 + match.fromString("ip_src=2001:ddd:3e1:1234:0000:1111:2222:3333/64"); + match.fromString("ip_dst=2001:123:222:abc:111:aaa:1111:2222/64"); + match.fromString("dl_vlan=10"); + match.fromString("dl_vpcp=1"); + match.fromString("nw_proto=6"); + match.fromString("nw_tos=100"); + match.fromString("tp_dst=8080"); + match.fromString("tp_src=60"); + match.fromString("dl_type=0x800"); + + ByteBuffer data = ByteBuffer.allocateDirect(10000); + match.writeTo(data); + data.flip(); + V6Match match2 = new V6Match(); + match2.readFrom(data); + Assert.assertTrue(match.getInputPort() == match2.getInputPort()); + Assert.assertTrue(Arrays.equals(match.getDataLayerSource(), + match2.getDataLayerSource())); + Assert.assertTrue(Arrays.equals(match.getDataLayerDestination(), + match2.getDataLayerDestination())); + + Assert.assertTrue(match.getNetworkSrc().equals(match2.getNetworkSrc())); + Assert.assertTrue(match.getNetworkDest() + .equals(match2.getNetworkDest())); + + Assert.assertTrue(match.getDataLayerVirtualLan() == match2 + .getDataLayerVirtualLan()); + Assert.assertTrue(match.getDataLayerVirtualLanPriorityCodePoint() == match2 + .getDataLayerVirtualLanPriorityCodePoint()); + Assert.assertTrue(match.getNetworkProtocol() == match2 + .getNetworkProtocol()); + Assert.assertTrue(match.getNetworkTypeOfService() == match2 + .getNetworkTypeOfService()); + Assert.assertTrue(match.getTransportSource() == match2 + .getTransportSource()); + Assert.assertTrue(match.getTransportDestination() == match2 + .getTransportDestination()); + + } + + @Test + public void testClone() { + V6Match match = new V6Match(); + match.fromString("input_port=1"); + match.fromString("dl_dst=20:A0:11:10:00:99"); + match.fromString("dl_src=00:10:08:22:12:75"); + match.fromString("ip_src=2001:ddd:3e1:1234:0000:1111:2222:3333/64"); + match.fromString("ip_dst=2001:123:222:abc:111:aaa:1111:2222/64"); + match.fromString("dl_vlan=10"); + match.fromString("dl_vpcp=1"); + match.fromString("nw_proto=6"); + match.fromString("nw_tos=100"); + match.fromString("tp_dst=8080"); + match.fromString("tp_src=60"); + match.fromString("dl_type=0x800"); + + V6Match match2 = match.clone(); + Assert.assertTrue(match.getInputPort() == match2.getInputPort()); + Assert.assertTrue(Arrays.equals(match.getDataLayerSource(), + match2.getDataLayerSource())); + Assert.assertTrue(Arrays.equals(match.getDataLayerDestination(), + match2.getDataLayerDestination())); + Assert.assertTrue(match.getNetworkSrc().equals(match2.getNetworkSrc())); + Assert.assertTrue(match.getNetworkDest() + .equals(match2.getNetworkDest())); + Assert.assertTrue(match.getDataLayerVirtualLan() == match2 + .getDataLayerVirtualLan()); + Assert.assertTrue(match.getDataLayerVirtualLanPriorityCodePoint() == match2 + .getDataLayerVirtualLanPriorityCodePoint()); + Assert.assertTrue(match.getNetworkProtocol() == match2 + .getNetworkProtocol()); + Assert.assertTrue(match.getNetworkTypeOfService() == match2 + .getNetworkTypeOfService()); + Assert.assertTrue(match.getTransportSource() == match2 + .getTransportSource()); + Assert.assertTrue(match.getTransportDestination() == match2 + .getTransportDestination()); + Assert.assertTrue(match.getWildcards() == match2.getWildcards()); + } + + @Test + public void testPadding() { + // testing that matchlen+pad keeps the 8byte alignment + V6Match match = new V6Match(); + + match.fromString("input_port=1"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("dl_dst=20:A0:11:10:00:99"); + match.fromString("dl_src=00:10:08:22:12:75"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("ip_src=2001:ddd:3e1:1234:0000:1111:2222:3333"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("ip_dst=2001:123:222:abc:111:aaa:1111:2222"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("dl_vlan=10"); + match.fromString("dl_vpcp=1"); + match.fromString("nw_proto=6"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("nw_tos=100"); + match.fromString("tp_dst=8080"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + match.fromString("tp_src=60"); + Assert.assertTrue((match.getPadSize() + match.getIPv6MatchLen()) % 8 == 0); + } +} -- 2.36.6