From: Amit Mandke Date: Sat, 5 Apr 2014 00:59:52 +0000 (-0700) Subject: Bug 639, Bug 641, Bug 642: This is MD-SAL based sample implementation of a learning... X-Git-Tag: autorelease-tag-v20140601202136_82eb3f9~270^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=273b2a25453e9fe24f973fd6f7bc8c65bcf372d3 Bug 639, Bug 641, Bug 642: This is MD-SAL based sample implementation of a learning switch with optimizations in how packet is forwarded. Link to detail documetation: https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:L2_Switch It has following components: PacketHandler examines Ethernet packets to find information about Mac-Port pairings. -informs AddressTracker about new Mac-Port pairings. -informs FlowWriterService about new flows, when the source & destination of a packet are known. -uses InventoryService to determine external ports and only send packets to those ports when flooding packets in the network. AddressTracker stores the Mac-Port pairings in the MD-SAL data tree. InventoryService provides information about the nodes and node connectors in the network. FlowWriterService adds packet forwarding (mac-to-mac) flows to the MD-SAL data tree. -uses NetworkGraphDijkstra to determine all the intermediate nodes along a path. TopologyLinkDataChangeHandler listens to topology updates and informs NetworkGraphDijkstra of these updates. NetworkGraphDijkstra maintains the network graph and computes the shortest path between each node. Change-Id: I33497a9e2136316de1db16e1c7f916cbc13f437f Signed-off-by: Amit Mandke --- diff --git a/opendaylight/md-sal/samples/l2switch/implementation/pom.xml b/opendaylight/md-sal/samples/l2switch/implementation/pom.xml new file mode 100644 index 0000000000..c095eee6bc --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + sal-samples + org.opendaylight.controller.samples + 1.1-SNAPSHOT + ../.. + + org.opendaylight.controller.samples.l2switch.md + l2switch-impl + bundle + + + + + org.apache.felix + maven-bundle-plugin + true + + + + org.opendaylight.controller.sample.l2switch.md.L2SwitchProvider + + ${project.build.directory}/META-INF + + + + + + + junit + junit + ${junit.version} + test + + + org.opendaylight.controller.samples.l2switch.md + l2switch-model + ${project.version} + + + + org.opendaylight.controller.model + model-inventory + + + + org.opendaylight.controller + sal-binding-api + + + org.opendaylight.yangtools + yang-common + ${yangtools.version} + + + org.opendaylight.yangtools + yang-binding + ${yangtools.version} + + + junit + junit + test + + + org.mockito + mockito-all + ${mockito.version} + test + + + org.opendaylight.controller + sal + + + org.opendaylight.controller.model + model-flow-service + + + org.opendaylight.controller.thirdparty + net.sf.jung2 + + + org.opendaylight.controller.model + model-topology + 1.1-SNAPSHOT + + + diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/L2SwitchProvider.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/L2SwitchProvider.java new file mode 100644 index 0000000000..6f31a7ec76 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/L2SwitchProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md; + +import org.opendaylight.controller.sample.l2switch.md.addresstracker.AddressTracker; +import org.opendaylight.controller.sample.l2switch.md.flow.FlowWriterService; +import org.opendaylight.controller.sample.l2switch.md.flow.FlowWriterServiceImpl; +import org.opendaylight.controller.sample.l2switch.md.inventory.InventoryService; +import org.opendaylight.controller.sample.l2switch.md.packet.PacketHandler; +import org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphDijkstra; +import org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService; +import org.opendaylight.controller.sample.l2switch.md.topology.TopologyLinkDataChangeHandler; +import org.opendaylight.controller.sal.binding.api.AbstractBindingAwareConsumer; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.NotificationService; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.yang.binding.NotificationListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * L2SwitchProvider serves as the Activator for our L2Switch OSGI bundle. + */ +public class L2SwitchProvider extends AbstractBindingAwareConsumer + implements AutoCloseable { + + private final static Logger _logger = LoggerFactory.getLogger(L2SwitchProvider.class); + + private Registration listenerRegistration; + private AddressTracker addressTracker; + private TopologyLinkDataChangeHandler topologyLinkDataChangeHandler; + + + /** + * Setup the L2Switch. + * @param consumerContext The context of the L2Switch. + */ + @Override + public void onSessionInitialized(BindingAwareBroker.ConsumerContext consumerContext) { + DataBrokerService dataService = consumerContext.getSALService(DataBrokerService.class); + addressTracker = new AddressTracker(dataService); + + NetworkGraphService networkGraphService = new NetworkGraphDijkstra(); + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataService, networkGraphService); + + NotificationService notificationService = + consumerContext.getSALService(NotificationService.class); + PacketProcessingService packetProcessingService = + consumerContext.getRpcService(PacketProcessingService.class); + PacketHandler packetHandler = new PacketHandler(); + packetHandler.setAddressTracker(addressTracker); + packetHandler.setFlowWriterService(flowWriterService); + packetHandler.setPacketProcessingService(packetProcessingService); + packetHandler.setInventoryService(new InventoryService(dataService)); + + this.listenerRegistration = notificationService.registerNotificationListener(packetHandler); + this.topologyLinkDataChangeHandler = new TopologyLinkDataChangeHandler(dataService, networkGraphService); + topologyLinkDataChangeHandler.registerAsDataChangeListener(); + } + + /** + * Cleanup the L2Switch. + * @throws Exception occurs when the NotificationListener is closed + */ + @Override + public void close() throws Exception { + if (listenerRegistration != null) + listenerRegistration.close(); + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/addresstracker/AddressTracker.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/addresstracker/AddressTracker.java new file mode 100644 index 0000000000..ae5f03110c --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/addresstracker/AddressTracker.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.addresstracker; + +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.address.tracker.rev140402.L2Addresses; +import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.address.tracker.rev140402.l2.addresses.L2Address; +import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.address.tracker.rev140402.l2.addresses.L2AddressBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.address.tracker.rev140402.l2.addresses.L2AddressKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Future; + +/** + * AddressTracker manages the MD-SAL data tree for L2Address (mac, node connector pairings) information. + */ +public class AddressTracker { + + private final static Logger _logger = LoggerFactory.getLogger(AddressTracker.class); + private DataBrokerService dataService; + + /** + * Construct an AddressTracker with the specified inputs + * @param dataService The DataBrokerService for the AddressTracker + */ + public AddressTracker(DataBrokerService dataService) { + this.dataService = dataService; + } + + /** + * Get all the L2 Addresses in the MD-SAL data tree + * @return All the L2 Addresses in the MD-SAL data tree + */ + public L2Addresses getAddresses() { + return (L2Addresses)dataService.readOperationalData(InstanceIdentifier.builder(L2Addresses.class).toInstance()); + } + + /** + * Get a specific L2 Address in the MD-SAL data tree + * @param macAddress A MacAddress associated with an L2 Address object + * @return The L2 Address corresponding to the specified macAddress + */ + public L2Address getAddress(MacAddress macAddress) { + return (L2Address) dataService.readOperationalData(createPath(macAddress)); + } + + /** + * Add L2 Address into the MD-SAL data tree + * @param macAddress The MacAddress of the new L2Address object + * @param nodeConnectorRef The NodeConnectorRef of the new L2Address object + * @return Future containing the result of the add operation + */ + public Future> addAddress(MacAddress macAddress, NodeConnectorRef nodeConnectorRef) { + if(macAddress == null || nodeConnectorRef == null) { + return null; + } + + // Create L2Address + final L2AddressBuilder builder = new L2AddressBuilder(); + builder.setKey(new L2AddressKey(macAddress)) + .setMac(macAddress) + .setNodeConnectorRef(nodeConnectorRef); + + // Add L2Address to MD-SAL data tree + final DataModificationTransaction it = dataService.beginTransaction(); + it.putOperationalData(createPath(macAddress), builder.build()); + return it.commit(); + } + + /** + * Remove L2Address from the MD-SAL data tree + * @param macAddress The MacAddress of an L2Address object + * @return Future containing the result of the remove operation + */ + public Future> removeHost(MacAddress macAddress) { + final DataModificationTransaction it = dataService.beginTransaction(); + it.removeOperationalData(createPath(macAddress)); + return it.commit(); + } + + /** + * Create InstanceIdentifier path for an L2Address in the MD-SAL data tree + * @param macAddress The MacAddress of an L2Address object + * @return InstanceIdentifier of the L2Address corresponding to the specified macAddress + */ + private InstanceIdentifier createPath(MacAddress macAddress) { + return InstanceIdentifier.builder(L2Addresses.class) + .child(L2Address.class, new L2AddressKey(macAddress)).toInstance(); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterService.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterService.java new file mode 100644 index 0000000000..2d5149e492 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterService.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.flow; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; + +/** + * Service that adds packet forwarding flows to configuration data store. + */ +public interface FlowWriterService { + + /** + * Writes a flow that forwards packets to destPort if destination mac in packet is destMac and + * source Mac in packet is sourceMac. If sourceMac is null then flow would not set any source mac, + * resulting in all packets with destMac being forwarded to destPort. + * + * @param sourceMac + * @param destMac + * @param destNodeConnectorRef + */ + public void addMacToMacFlow(MacAddress sourceMac, MacAddress destMac, NodeConnectorRef destNodeConnectorRef); + + /** + * Writes mac-to-mac flow on all ports that are in the path between given source and destination ports. + * It uses path provided by NetworkGraphService{@link org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService} to find a links{@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link} + * between given ports. And then writes appropriate flow on each port that is covered in that path. + * + * @param sourceMac + * @param sourceNodeConnectorRef + * @param destMac + * @param destNodeConnectorRef + */ + public void addMacToMacFlowsUsingShortestPath(MacAddress sourceMac, NodeConnectorRef sourceNodeConnectorRef, MacAddress destMac, NodeConnectorRef destNodeConnectorRef); + + +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImpl.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImpl.java new file mode 100644 index 0000000000..f49771a953 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImpl.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.flow; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService; +import org.opendaylight.controller.sample.l2switch.md.util.InstanceIdentifierUtils; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.OutputActionCaseBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.output.action._case.OutputActionBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; +import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActions; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.ethernet.match.fields.EthernetDestinationBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.ethernet.match.fields.EthernetSourceBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatch; +import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatchBuilder; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Implementation of FlowWriterService{@link org.opendaylight.controller.sample.l2switch.md.flow.FlowWriterService}, + * that builds required flow and writes to configuration data store using provided DataBrokerService + * {@link org.opendaylight.controller.sal.binding.api.data.DataBrokerService} + */ +public class FlowWriterServiceImpl implements FlowWriterService { + private static final Logger _logger = LoggerFactory.getLogger(FlowWriterServiceImpl.class); + private final DataBrokerService dataBrokerService; + private final NetworkGraphService networkGraphService; + private AtomicLong flowIdInc = new AtomicLong(); + private AtomicLong flowCookieInc = new AtomicLong(0x2a00000000000000L); + + + public FlowWriterServiceImpl(DataBrokerService dataBrokerService, NetworkGraphService networkGraphService) { + Preconditions.checkNotNull(dataBrokerService, "dataBrokerService should not be null."); + Preconditions.checkNotNull(networkGraphService, "networkGraphService should not be null."); + this.dataBrokerService = dataBrokerService; + this.networkGraphService = networkGraphService; + } + + /** + * Writes a flow that forwards packets to destPort if destination mac in packet is destMac and + * source Mac in packet is sourceMac. If sourceMac is null then flow would not set any source mac, + * resulting in all packets with destMac being forwarded to destPort. + * + * @param sourceMac + * @param destMac + * @param destNodeConnectorRef + */ + @Override + public void addMacToMacFlow(MacAddress sourceMac, MacAddress destMac, NodeConnectorRef destNodeConnectorRef) { + + Preconditions.checkNotNull(destMac, "Destination mac address should not be null."); + Preconditions.checkNotNull(destNodeConnectorRef, "Destination port should not be null."); + + + // do not add flow if both macs are same. + if(sourceMac != null && destMac.equals(sourceMac)) { + _logger.info("In addMacToMacFlow: No flows added. Source and Destination mac are same."); + return; + } + + // get flow table key + TableKey flowTableKey = new TableKey((short) 0); //TODO: Hard coded Table Id 0, need to get it from Configuration data. + + //build a flow path based on node connector to program flow + InstanceIdentifier flowPath = buildFlowPath(destNodeConnectorRef, flowTableKey); + + // build a flow that target given mac id + Flow flowBody = createMacToMacFlow(flowTableKey.getId(), 0, sourceMac, destMac, destNodeConnectorRef); + + // commit the flow in config data + writeFlowToConfigData(flowPath, flowBody); + } + + /** + * Writes mac-to-mac flow on all ports that are in the path between given source and destination ports. + * It uses path provided by NetworkGraphService + * {@link org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService} to find a links + * {@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link} + * between given ports. And then writes appropriate flow on each port that is covered in that path. + * + * @param sourceMac + * @param sourceNodeConnectorRef + * @param destMac + * @param destNodeConnectorRef + */ + @Override + public void addMacToMacFlowsUsingShortestPath(MacAddress sourceMac, + NodeConnectorRef sourceNodeConnectorRef, + MacAddress destMac, + NodeConnectorRef destNodeConnectorRef) { + Preconditions.checkNotNull(sourceMac, "Source mac address should not be null."); + Preconditions.checkNotNull(sourceNodeConnectorRef, "Source port should not be null."); + Preconditions.checkNotNull(destMac, "Destination mac address should not be null."); + Preconditions.checkNotNull(destNodeConnectorRef, "Destination port should not be null."); + + if(sourceNodeConnectorRef.equals(destNodeConnectorRef)) { + _logger.info("In addMacToMacFlowsUsingShortestPath: No flows added. Source and Destination ports are same."); + return; + + } + NodeId sourceNodeId = new NodeId(sourceNodeConnectorRef.getValue().firstKeyOf(Node.class, NodeKey.class).getId().getValue()); + NodeId destNodeId = new NodeId(destNodeConnectorRef.getValue().firstKeyOf(Node.class, NodeKey.class).getId().getValue()); + + // add destMac-To-sourceMac flow on source port + addMacToMacFlow(destMac, sourceMac, sourceNodeConnectorRef); + + // add sourceMac-To-destMac flow on destination port + addMacToMacFlow(sourceMac, destMac, destNodeConnectorRef); + + if(!sourceNodeId.equals(destNodeId)) { + List linksInBeween = networkGraphService.getPath(sourceNodeId, destNodeId); + + if(linksInBeween != null) { + // assumes the list order is maintained and starts with link that has source as source node + for(Link link : linksInBeween) { + // add sourceMac-To-destMac flow on source port + addMacToMacFlow(sourceMac, destMac, getSourceNodeConnectorRef(link)); + + // add destMac-To-sourceMac flow on destination port + addMacToMacFlow(destMac, sourceMac, getDestNodeConnectorRef(link)); + } + } + } + } + + private NodeConnectorRef getSourceNodeConnectorRef(Link link) { + InstanceIdentifier nodeConnectorInstanceIdentifier + = InstanceIdentifierUtils.createNodeConnectorIdentifier( + link.getSource().getSourceNode().getValue(), + link.getSource().getSourceTp().getValue()); + return new NodeConnectorRef(nodeConnectorInstanceIdentifier); + } + + private NodeConnectorRef getDestNodeConnectorRef(Link link) { + InstanceIdentifier nodeConnectorInstanceIdentifier + = InstanceIdentifierUtils.createNodeConnectorIdentifier( + link.getDestination().getDestNode().getValue(), + link.getDestination().getDestTp().getValue()); + + return new NodeConnectorRef(nodeConnectorInstanceIdentifier); + } + + /** + * @param nodeConnectorRef + * @return + */ + private InstanceIdentifier buildFlowPath(NodeConnectorRef nodeConnectorRef, TableKey flowTableKey) { + + // generate unique flow key + FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement())); + FlowKey flowKey = new FlowKey(flowId); + + return InstanceIdentifierUtils.generateFlowInstanceIdentifier(nodeConnectorRef, flowTableKey, flowKey); + } + + /** + * @param tableId + * @param priority + * @param sourceMac + * @param destMac + * @param destPort + * @return {@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder} + * builds flow that forwards all packets with destMac to given port + */ + private Flow createMacToMacFlow(Short tableId, int priority, + MacAddress sourceMac, MacAddress destMac, NodeConnectorRef destPort) { + + // start building flow + FlowBuilder macToMacFlow = new FlowBuilder() // + .setTableId(tableId) // + .setFlowName("mac2mac"); + + // use its own hash code for id. + macToMacFlow.setId(new FlowId(Long.toString(macToMacFlow.hashCode()))); + + // create a match that has mac to mac ethernet match + EthernetMatchBuilder ethernetMatchBuilder = new EthernetMatchBuilder() // + .setEthernetDestination(new EthernetDestinationBuilder() // + .setAddress(destMac) // + .build()); + // set source in the match only if present + if(sourceMac != null) { + ethernetMatchBuilder.setEthernetSource(new EthernetSourceBuilder() + .setAddress(sourceMac) + .build()); + } + EthernetMatch ethernetMatch = ethernetMatchBuilder.build(); + Match match = new MatchBuilder() + .setEthernetMatch(ethernetMatch) + .build(); + + + Uri destPortUri = destPort.getValue().firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId(); + + Action outputToControllerAction = new ActionBuilder() // + .setAction(new OutputActionCaseBuilder() // + .setOutputAction(new OutputActionBuilder() // + .setMaxLength(new Integer(0xffff)) // + .setOutputNodeConnector(destPortUri) // + .build()) // + .build()) // + .build(); + + // Create an Apply Action + ApplyActions applyActions = new ApplyActionsBuilder().setAction(ImmutableList.of(outputToControllerAction)) + .build(); + + // Wrap our Apply Action in an Instruction + Instruction applyActionsInstruction = new InstructionBuilder() // + .setInstruction(new ApplyActionsCaseBuilder()// + .setApplyActions(applyActions) // + .build()) // + .build(); + + // Put our Instruction in a list of Instructions + macToMacFlow + .setMatch(match) // + .setInstructions(new InstructionsBuilder() // + .setInstruction(ImmutableList.of(applyActionsInstruction)) // + .build()) // + .setPriority(priority) // + .setBufferId(0L) // + .setHardTimeout(0) // + .setIdleTimeout(0) // + .setCookie(BigInteger.valueOf(flowCookieInc.getAndIncrement())) + .setFlags(new FlowModFlags(false, false, false, false, false)); + + return macToMacFlow.build(); + } + + /** + * Starts and commits data change transaction which + * modifies provided flow path with supplied body. + * + * @param flowPath + * @param flowBody + * @return transaction commit + */ + private Future> writeFlowToConfigData(InstanceIdentifier flowPath, + Flow flowBody) { + DataModificationTransaction addFlowTransaction = dataBrokerService.beginTransaction(); + addFlowTransaction.putConfigurationData(flowPath, flowBody); + return addFlowTransaction.commit(); + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/inventory/InventoryService.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/inventory/InventoryService.java new file mode 100644 index 0000000000..a12f394e66 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/inventory/InventoryService.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.inventory; + +import org.opendaylight.controller.sample.l2switch.md.util.InstanceIdentifierUtils; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import java.util.*; + +/** + * InventoryService provides functions related to Nodes & NodeConnectors. + */ +public class InventoryService { + private DataBrokerService dataService; + // Key: SwitchId, Value: NodeConnectorRef that corresponds to NC between controller & switch + private HashMap controllerSwitchConnectors; + + /** + * Construct an InventoryService object with the specified inputs. + * @param dataService The DataBrokerService associated with the InventoryService. + */ + public InventoryService(DataBrokerService dataService) { + this.dataService = dataService; + controllerSwitchConnectors = new HashMap(); + } + + public HashMap getControllerSwitchConnectors() { + return controllerSwitchConnectors; + } + + // ToDo: Improve performance for thousands of switch ports + /** + * Get the External NodeConnectors of the network, which are the NodeConnectors connected to hosts. + * @return The list of external node connectors. + */ + public List getExternalNodeConnectors() { + // External NodeConnectors = All - Internal + ArrayList externalNodeConnectors = new ArrayList(); + Set internalNodeConnectors = new HashSet<>(); + + // Read Topology -- find list of switch-to-switch internal node connectors + NetworkTopology networkTopology = + (NetworkTopology)dataService.readOperationalData( + InstanceIdentifier.builder(NetworkTopology.class).toInstance()); + + for (Topology topology : networkTopology.getTopology()) { + Topology completeTopology = + (Topology)dataService.readOperationalData( + InstanceIdentifierUtils.generateTopologyInstanceIdentifier( + topology.getTopologyId().getValue())); + + for (Link link : completeTopology.getLink()) { + internalNodeConnectors.add(link.getDestination().getDestTp().getValue()); + internalNodeConnectors.add(link.getSource().getSourceTp().getValue()); + } + } + + // Read Inventory -- contains list of all nodeConnectors + InstanceIdentifier.InstanceIdentifierBuilder nodesInsIdBuilder = InstanceIdentifier.builder(Nodes.class); + Nodes nodes = (Nodes)dataService.readOperationalData(nodesInsIdBuilder.toInstance()); + if (nodes != null) { + for (Node node : nodes.getNode()) { + Node completeNode = (Node)dataService.readOperationalData(InstanceIdentifierUtils.createNodePath(node.getId())); + for (NodeConnector nodeConnector : completeNode.getNodeConnector()) { + // NodeConnector isn't switch-to-switch, so it must be controller-to-switch (internal) or external + if (!internalNodeConnectors.contains(nodeConnector.getId().getValue())) { + NodeConnectorRef ncRef = new NodeConnectorRef( + InstanceIdentifier.builder(Nodes.class).child(Node.class, node.getKey()) + .child(NodeConnector.class, nodeConnector.getKey()).toInstance()); + + // External node connectors have "-" in their name for mininet, i.e. "s1-eth1" + if (nodeConnector.getAugmentation(FlowCapableNodeConnector.class).getName().contains("-")) { + externalNodeConnectors.add(ncRef); + } + // Controller-to-switch internal node connectors + else { + controllerSwitchConnectors.put(node.getId().getValue(), ncRef); + } + } + } + } + } + + return externalNodeConnectors; + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/packet/PacketHandler.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/packet/PacketHandler.java new file mode 100644 index 0000000000..753de4aa85 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/packet/PacketHandler.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.packet; + +import org.opendaylight.controller.sample.l2switch.md.addresstracker.AddressTracker; +import org.opendaylight.controller.sample.l2switch.md.flow.FlowWriterService; +import org.opendaylight.controller.sample.l2switch.md.inventory.InventoryService; +import org.opendaylight.controller.sample.l2switch.md.util.InstanceIdentifierUtils; +import org.opendaylight.controller.sal.packet.Ethernet; +import org.opendaylight.controller.sal.packet.LLDP; +import org.opendaylight.controller.sal.packet.LinkEncap; +import org.opendaylight.controller.sal.packet.Packet; +import org.opendaylight.controller.sal.packet.RawPacket; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.opendaylight.controller.sal.utils.NetUtils; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.*; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.address.tracker.rev140402.l2.addresses.L2Address; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; + +/** + * PacketHandler examines Ethernet packets to find L2Addresses (mac, nodeConnector) pairings + * of the sender and learns them. + * It also forwards the data packets appropriately dependending upon whether it knows about the + * target or not. + */ +public class PacketHandler implements PacketProcessingListener { + + private final static Logger _logger = LoggerFactory.getLogger(PacketHandler.class); + + private PacketProcessingService packetProcessingService; + private AddressTracker addressTracker; + private FlowWriterService flowWriterService; + private InventoryService inventoryService; + + public void setAddressTracker(AddressTracker addressTracker) { + this.addressTracker = addressTracker; + } + + public void setPacketProcessingService(PacketProcessingService packetProcessingService) { + this.packetProcessingService = packetProcessingService; + } + + public void setFlowWriterService(FlowWriterService flowWriterService) { + this.flowWriterService = flowWriterService; + } + + public void setInventoryService(InventoryService inventoryService) { + this.inventoryService = inventoryService; + } + + /** + * The handler function for all incoming packets. + * @param packetReceived The incoming packet. + */ + @Override + public void onPacketReceived(PacketReceived packetReceived) { + + if(packetReceived == null) return; + + try { + byte[] payload = packetReceived.getPayload(); + RawPacket rawPacket = new RawPacket(payload); + NodeConnectorRef ingress = packetReceived.getIngress(); + + Packet packet = decodeDataPacket(rawPacket); + + if(!(packet instanceof Ethernet)) return; + + handleEthernetPacket(packet, ingress); + + } catch(Throwable _e) { + _e.printStackTrace(); + } + } + + /** + * The handler function for Ethernet packets. + * @param packet The incoming Ethernet packet. + * @param ingress The NodeConnector where the Ethernet packet came from. + */ + private void handleEthernetPacket(Packet packet, NodeConnectorRef ingress) { + byte[] srcMac = ((Ethernet) packet).getSourceMACAddress(); + byte[] destMac = ((Ethernet) packet).getDestinationMACAddress(); + + if (srcMac == null || srcMac.length == 0) return; + + Object enclosedPacket = packet.getPayload(); + + if (enclosedPacket instanceof LLDP) + return; // LLDP packets are handled by OpenFlowPlugin + + // get l2address by src mac + // if unknown, add l2address + MacAddress srcMacAddress = toMacAddress(srcMac); + L2Address src = addressTracker.getAddress(srcMacAddress); + boolean isSrcKnown = (src != null); + if (!isSrcKnown) { + addressTracker.addAddress(srcMacAddress, ingress); + } + + // get host by dest mac + // if known set dest known to true + MacAddress destMacAddress = toMacAddress(destMac); + L2Address dest = addressTracker.getAddress(destMacAddress); + boolean isDestKnown = (dest != null); + + byte[] payload = packet.getRawPayload(); + // if (src and dest known) + // sendpacket to dest and add src<->dest flow + if(isSrcKnown & isDestKnown) { + flowWriterService.addMacToMacFlowsUsingShortestPath(srcMacAddress, src.getNodeConnectorRef(), + destMacAddress, dest.getNodeConnectorRef()); + sendPacketOut(payload, getControllerNodeConnector(dest.getNodeConnectorRef()), dest.getNodeConnectorRef()); + } else { + // if (dest unknown) + // sendpacket to external links minus ingress + floodExternalPorts(payload, ingress); + } + } + + /** + * Floods the specified payload on external ports, which are ports not connected to switches. + * @param payload The payload to be flooded. + * @param ingress The NodeConnector where the payload came from. + */ + private void floodExternalPorts(byte[] payload, NodeConnectorRef ingress) { + List externalPorts = inventoryService.getExternalNodeConnectors(); + externalPorts.remove(ingress); + + for (NodeConnectorRef egress : externalPorts) { + sendPacketOut(payload, getControllerNodeConnector(egress), egress); + } + } + + /** + * Sends the specified packet on the specified port. + * @param payload The payload to be sent. + * @param ingress The NodeConnector where the payload came from. + * @param egress The NodeConnector where the payload will go. + */ + private void sendPacketOut(byte[] payload, NodeConnectorRef ingress, NodeConnectorRef egress) { + if (ingress == null || egress == null) return; + InstanceIdentifier egressNodePath = InstanceIdentifierUtils.getNodePath(egress.getValue()); + TransmitPacketInput input = new TransmitPacketInputBuilder() // + .setPayload(payload) // + .setNode(new NodeRef(egressNodePath)) // + .setEgress(egress) // + .setIngress(ingress) // + .build(); + packetProcessingService.transmitPacket(input); + } + + /** + * Decodes an incoming packet. + * @param raw The raw packet to be decoded. + * @return The decoded form of the raw packet. + */ + private Packet decodeDataPacket(RawPacket raw) { + if(raw == null) { + return null; + } + byte[] data = raw.getPacketData(); + if(data.length <= 0) { + return null; + } + if(raw.getEncap().equals(LinkEncap.ETHERNET)) { + Ethernet res = new Ethernet(); + try { + res.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte); + res.setRawPayload(raw.getPacketData()); + } catch(Exception e) { + _logger.warn("Failed to decode packet: {}", e.getMessage()); + } + return res; + } + return null; + } + + /** + * Creates a MacAddress object out of a byte array. + * @param dataLinkAddress The byte-array form of a MacAddress + * @return MacAddress of the specified dataLinkAddress. + */ + private MacAddress toMacAddress(byte[] dataLinkAddress) { + return new MacAddress(HexEncode.bytesToHexStringFormat(dataLinkAddress)); + } + + /** + * Gets the NodeConnector that connects the controller & switch for a specified switch port/node connector. + * @param nodeConnectorRef The nodeConnector of a switch. + * @return The NodeConnector that that connects the controller & switch. + */ + private NodeConnectorRef getControllerNodeConnector(NodeConnectorRef nodeConnectorRef) { + NodeConnectorRef controllerSwitchNodeConnector = null; + HashMap controllerSwitchConnectors = inventoryService.getControllerSwitchConnectors(); + InstanceIdentifier nodePath = InstanceIdentifierUtils.getNodePath(nodeConnectorRef.getValue()); + if (nodePath != null) { + NodeKey nodeKey = InstanceIdentifierUtils.getNodeKey(nodePath); + if (nodeKey != null) { + controllerSwitchNodeConnector = controllerSwitchConnectors.get(nodeKey.getId().getValue()); + } + } + return controllerSwitchNodeConnector; + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstra.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstra.java new file mode 100644 index 0000000000..a90ac5a007 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstra.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.topology; + +import com.google.common.base.Preconditions; +import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath; +import edu.uci.ics.jung.graph.DirectedSparseGraph; +import edu.uci.ics.jung.graph.Graph; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Implementation of NetworkGraphService{@link org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService}. + * It uses Jung graph library internally to maintain a graph and optimum way to return shortest path using + * Dijkstra algorithm. + */ +public class NetworkGraphDijkstra implements NetworkGraphService { + + private static final Logger _logger = LoggerFactory.getLogger(NetworkGraphDijkstra.class); + + DijkstraShortestPath shortestPath = null; + Graph networkGraph = null; + + /** + * Adds links to existing graph or creates new directed graph with given links if graph was not initialized. + * @param links + */ + @Override + public synchronized void addLinks(List links) { + if(links == null || links.isEmpty()) { + _logger.info("In addLinks: No link added as links is null or empty."); + return; + } + + if(networkGraph == null) { + networkGraph = new DirectedSparseGraph<>(); + } + + for(Link link : links) { + NodeId sourceNodeId = link.getSource().getSourceNode(); + NodeId destinationNodeId = link.getDestination().getDestNode(); + networkGraph.addVertex(sourceNodeId); + networkGraph.addVertex(destinationNodeId); + networkGraph.addEdge(link, sourceNodeId, destinationNodeId); + } + if(shortestPath == null) { + shortestPath = new DijkstraShortestPath<>(networkGraph); + } else { + shortestPath.reset(); + } + } + + /** + * removes links from existing graph. + * @param links + */ + @Override + public synchronized void removeLinks(List links) { + Preconditions.checkNotNull(networkGraph, "Graph is not initialized, add links first."); + + if(links == null || links.isEmpty()) { + _logger.info("In removeLinks: No link removed as links is null or empty."); + return; + } + + for(Link link : links) { + networkGraph.removeEdge(link); + } + + if(shortestPath == null) { + shortestPath = new DijkstraShortestPath<>(networkGraph); + } else { + shortestPath.reset(); + } + } + + /** + * returns a path between 2 nodes. Uses Dijkstra's algorithm to return shortest path. + * @param sourceNodeId + * @param destinationNodeId + * @return + */ + @Override + public synchronized List getPath(NodeId sourceNodeId, NodeId destinationNodeId) { + Preconditions.checkNotNull(shortestPath, "Graph is not initialized, add links first."); + + if(sourceNodeId == null || destinationNodeId == null) { + _logger.info("In getPath: returning null, as sourceNodeId or destinationNodeId is null."); + return null; + } + + return shortestPath.getPath(sourceNodeId, destinationNodeId); + } + + /** + * Clears the prebuilt graph, in case same service instance is required to process a new graph. + */ + @Override + public synchronized void clear() { + networkGraph = null; + shortestPath = null; + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphService.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphService.java new file mode 100644 index 0000000000..173be342c4 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphService.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.topology; + +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; + +import java.util.List; + +/** + * Service that allows to build a network graph using Topology links + * {@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link} + * and exposes operation that can be performed on such graph. + */ +public interface NetworkGraphService { + + /** + * Adds links to existing graph or creates new graph with given links if graph was not initialized. + * @param links + */ + public void addLinks(List links); + + /** + * removes links from existing graph. + * @param links + */ + public void removeLinks(List links); + + /** + * returns a path between 2 nodes. Implementation should ideally return shortest path. + * @param sourceNodeId + * @param destinationNodeId + * @return + */ + public List getPath(NodeId sourceNodeId, NodeId destinationNodeId); + + /** + * Clears the prebuilt graph, in case same service instance is required to process a new graph. + */ + public void clear(); +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandler.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandler.java new file mode 100644 index 0000000000..254ebf8b4a --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandler.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.topology; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.sample.l2switch.md.util.InstanceIdentifierUtils; +import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Listens to data change events on topology links + * {@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link} + * and maintains a topology graph using provided NetworkGraphService + * {@link org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService}. + * It refreshes the graph after a delay(default 10 sec) to accommodate burst of change events if they come in bulk. + * This is to avoid continuous refresh of graph on a series of change events in short time. + */ +public class TopologyLinkDataChangeHandler implements DataChangeListener { + private static final Logger _logger = LoggerFactory.getLogger(TopologyLinkDataChangeHandler.class); + private static final String DEFAULT_TOPOLOGY_ID = "flow:1"; + + private boolean networkGraphRefreshScheduled = false; + private final ScheduledExecutorService networkGraphRefreshScheduler = Executors.newScheduledThreadPool(1); + private final long DEFAULT_GRAPH_REFRESH_DELAY = 10; + private final long graphRefreshDelayInSec; + + private final NetworkGraphService networkGraphService; + private final DataBrokerService dataBrokerService; + + /** + * Uses default delay to refresh topology graph if this constructor is used. + * @param dataBrokerService + * @param networkGraphService + */ + public TopologyLinkDataChangeHandler(DataBrokerService dataBrokerService, NetworkGraphService networkGraphService) { + Preconditions.checkNotNull(dataBrokerService, "dataBrokerService should not be null."); + Preconditions.checkNotNull(networkGraphService, "networkGraphService should not be null."); + this.dataBrokerService = dataBrokerService; + this.networkGraphService = networkGraphService; + this.graphRefreshDelayInSec = DEFAULT_GRAPH_REFRESH_DELAY; + } + + /** + * + * @param dataBrokerService + * @param networkGraphService + * @param graphRefreshDelayInSec + */ + public TopologyLinkDataChangeHandler(DataBrokerService dataBrokerService, NetworkGraphService networkGraphService, + long graphRefreshDelayInSec) { + Preconditions.checkNotNull(dataBrokerService, "dataBrokerService should not be null."); + Preconditions.checkNotNull(networkGraphService, "networkGraphService should not be null."); + this.dataBrokerService = dataBrokerService; + this.networkGraphService = networkGraphService; + this.graphRefreshDelayInSec = graphRefreshDelayInSec; + } + + /** + * Based on if links have been added or removed in topology data store, schedules a refresh of network graph. + * @param dataChangeEvent + */ + @Override + public void onDataChanged(DataChangeEvent, DataObject> dataChangeEvent) { + if(dataChangeEvent == null) { + _logger.info("In onDataChanged: No Processing done as dataChangeEvent is null."); + } + Map, DataObject> linkOriginalData = dataChangeEvent.getOriginalOperationalData(); + Map, DataObject> linkUpdatedData = dataChangeEvent.getUpdatedOperationalData(); + // change this logic, once MD-SAL start populating DeletedOperationData Set + if(linkOriginalData != null && linkUpdatedData != null + && (linkOriginalData.size() != 0 || linkUpdatedData.size() != 0) + && !networkGraphRefreshScheduled) { + networkGraphRefreshScheduled = linkOriginalData.size() != linkUpdatedData.size(); + if(networkGraphRefreshScheduled) { + networkGraphRefreshScheduler.schedule(new NetworkGraphRefresher(), graphRefreshDelayInSec, TimeUnit.SECONDS); + } + } + + } + + /** + * Registers as a data listener to receive changes done to + * {@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link} + * under {@link org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology} + * operation data root. + */ + + public void registerAsDataChangeListener() { + InstanceIdentifier linkInstance = InstanceIdentifier.builder(NetworkTopology.class) + .child(Topology.class, new TopologyKey(new TopologyId(DEFAULT_TOPOLOGY_ID))).child(Link.class).toInstance(); + dataBrokerService.registerDataChangeListener(linkInstance, this); + } + + /** + * + */ + private class NetworkGraphRefresher implements Runnable { + /** + * + */ + @Override + public void run() { + networkGraphRefreshScheduled = false; + //TODO: it should refer to changed links only from DataChangeEvent above. + List links = getLinksFromTopology(DEFAULT_TOPOLOGY_ID); + networkGraphService.clear();// can remove this once changed links are addressed + if(links != null && !links.isEmpty()) { + networkGraphService.addLinks(links); + } + } + + /** + * @param topologyId + * @return + */ + private List getLinksFromTopology(String topologyId) { + InstanceIdentifier topologyInstanceIdentifier = InstanceIdentifierUtils.generateTopologyInstanceIdentifier(topologyId); + Topology topology = (Topology) dataBrokerService.readOperationalData(topologyInstanceIdentifier); + return topology.getLink(); + } + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/util/InstanceIdentifierUtils.java b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/util/InstanceIdentifierUtils.java new file mode 100644 index 0000000000..ea08f94ebc --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/main/java/org/opendaylight/controller/sample/l2switch/md/util/InstanceIdentifierUtils.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2014 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.sample.l2switch.md.util; + +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/* InstanceIdentifierUtils provides utility functions related to InstanceIdentifiers. + */ +public class InstanceIdentifierUtils { + + /** + * Creates an Instance Identifier (path) for node with specified id + * + * @param nodeId + * @return + */ + public static final InstanceIdentifier createNodePath(NodeId nodeId) { + return InstanceIdentifier.builder(Nodes.class) // + .child(Node.class, new NodeKey(nodeId)) // + .build(); + } + + /** + * Shorten's node child path to node path. + * + * @param nodeChild child of node, from which we want node path. + * @return + */ + public static final InstanceIdentifier getNodePath(InstanceIdentifier nodeChild) { + return nodeChild.firstIdentifierOf(Node.class); + } + + + /** + * Creates a table path by appending table specific location to node path + * + * @param nodePath + * @param tableKey + * @return + */ + public static final InstanceIdentifier createTablePath(InstanceIdentifier nodePath, TableKey tableKey) { + return InstanceIdentifier.builder(nodePath) + .augmentation(FlowCapableNode.class) + .child(Table.class, tableKey) + .build(); + } + + /** + * Creates a path for particular flow, by appending flow-specific information + * to table path. + * + * @param table + * @param flowKey + * @return + */ + public static InstanceIdentifier createFlowPath(InstanceIdentifier
table, FlowKey flowKey) { + return InstanceIdentifier.builder(table) + .child(Flow.class, flowKey) + .build(); + } + + /** + * Extract table id from table path. + * + * @param tablePath + * @return + */ + public static Short getTableId(InstanceIdentifier
tablePath) { + return tablePath.firstKeyOf(Table.class, TableKey.class).getId(); + } + + /** + * Extracts NodeConnectorKey from node connector path. + */ + public static NodeConnectorKey getNodeConnectorKey(InstanceIdentifier nodeConnectorPath) { + return nodeConnectorPath.firstKeyOf(NodeConnector.class, NodeConnectorKey.class); + } + + /** + * Extracts NodeKey from node path. + */ + public static NodeKey getNodeKey(InstanceIdentifier nodePath) { + return nodePath.firstKeyOf(Node.class, NodeKey.class); + } + + + // + public static final InstanceIdentifier createNodeConnectorIdentifier(String nodeIdValue, + String nodeConnectorIdValue) { + return InstanceIdentifier.builder(createNodePath(new NodeId(nodeIdValue))) // + .child(NodeConnector.class, new NodeConnectorKey(new NodeConnectorId(nodeConnectorIdValue))) // + .build(); + } + + /** + * @param nodeConnectorRef + * @return + */ + public static InstanceIdentifier generateNodeInstanceIdentifier(NodeConnectorRef nodeConnectorRef) { + return nodeConnectorRef.getValue().firstIdentifierOf(Node.class); + } + + /** + * @param nodeConnectorRef + * @param flowTableKey + * @return + */ + public static InstanceIdentifier
generateFlowTableInstanceIdentifier(NodeConnectorRef nodeConnectorRef, TableKey flowTableKey) { + return InstanceIdentifier.builder(generateNodeInstanceIdentifier(nodeConnectorRef)) + .augmentation(FlowCapableNode.class) + .child(Table.class, flowTableKey) + .build(); + } + + /** + * @param nodeConnectorRef + * @param flowTableKey + * @param flowKey + * @return + */ + public static InstanceIdentifier generateFlowInstanceIdentifier(NodeConnectorRef nodeConnectorRef, + TableKey flowTableKey, + FlowKey flowKey) { + return InstanceIdentifier.builder(generateFlowTableInstanceIdentifier(nodeConnectorRef, flowTableKey)) + .child(Flow.class, flowKey) + .build(); + } + + public static InstanceIdentifier generateTopologyInstanceIdentifier(String topologyId) { + return InstanceIdentifier.builder(NetworkTopology.class) + .child(Topology.class, new TopologyKey(new TopologyId(topologyId))) + .build(); + } +} + diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImplTest.java b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImplTest.java new file mode 100644 index 0000000000..3520d812d7 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/flow/FlowWriterServiceImplTest.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2014 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.sample.l2switch.md.flow; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.sample.l2switch.md.topology.NetworkGraphService; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + */ +public class FlowWriterServiceImplTest { + private DataBrokerService dataBrokerService; + private NodeConnectorRef srcNodeConnectorRef; + private NodeConnectorRef destNodeConnectorRef; + private MacAddress destMacAddress; + private MacAddress srcMacAddress; + private DataModificationTransaction dataModificationTransaction; + private NetworkGraphService networkGraphService; + + @Before + public void init() { + dataBrokerService = mock(DataBrokerService.class); + networkGraphService = mock(NetworkGraphService.class); + //build source node connector ref + InstanceIdentifier srcNodesInstanceIdentifier + = InstanceIdentifier.builder(Nodes.class) + .build(); + InstanceIdentifier srcNodeInstanceIdentifier + = InstanceIdentifier.builder(srcNodesInstanceIdentifier) + .child(Node.class, new NodeKey(new NodeId("openflow:1"))) + .build(); + InstanceIdentifier srcNodeConnectorInstanceIdentifier + = InstanceIdentifier.builder(srcNodeInstanceIdentifier) + .child(NodeConnector.class, new NodeConnectorKey(new NodeConnectorId("openflow:1:2"))) + .build(); + srcNodeConnectorRef = new NodeConnectorRef(srcNodeConnectorInstanceIdentifier); + + //build dest node connector ref + InstanceIdentifier nodesInstanceIdentifier + = InstanceIdentifier.builder(Nodes.class) + .build(); + InstanceIdentifier nodeInstanceIdentifier + = InstanceIdentifier.builder(nodesInstanceIdentifier) + .child(Node.class, new NodeKey(new NodeId("openflow:2"))) + .build(); + InstanceIdentifier nodeConnectorInstanceIdentifier + = InstanceIdentifier.builder(nodeInstanceIdentifier) + .child(NodeConnector.class, new NodeConnectorKey(new NodeConnectorId("openflow:2:2"))) + .build(); + destNodeConnectorRef = new NodeConnectorRef(nodeConnectorInstanceIdentifier); + destMacAddress = new MacAddress("00:0a:95:9d:68:16"); + srcMacAddress = new MacAddress("00:0a:95:8c:97:24"); + dataModificationTransaction = mock(DataModificationTransaction.class); + when(dataBrokerService.beginTransaction()).thenReturn(dataModificationTransaction); + } + + @Test + public void testFlowWriterServiceImpl_NPEWhenDataBrokerServiceIsNull() throws Exception { + try { + new FlowWriterServiceImpl(null, networkGraphService); + fail("Expected null pointer exception."); + } catch(NullPointerException npe) { + assertEquals("dataBrokerService should not be null.", npe.getMessage()); + } + } + + @Test + public void testAddMacToMacFlow_NPEWhenNullSourceMacDestMacAndNodeConnectorRef() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + try { + flowWriterService.addMacToMacFlow(null, null, null); + fail("Expected null pointer exception."); + } catch(NullPointerException npe) { + assertEquals("Destination mac address should not be null.", npe.getMessage()); + } + } + + @Test + public void testAddMacToMacFlow_NPEWhenSourceMacNullMac() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + try { + flowWriterService.addMacToMacFlow(null, null, destNodeConnectorRef); + fail("Expected null pointer exception."); + } catch(NullPointerException npe) { + assertEquals("Destination mac address should not be null.", npe.getMessage()); + } + } + + @Test + public void testAddMacToMacFlow_NPEWhenNullSourceMacNodeConnectorRef() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + try { + flowWriterService.addMacToMacFlow(null, destMacAddress, null); + fail("Expected null pointer exception."); + } catch(NullPointerException npe) { + assertEquals("Destination port should not be null.", npe.getMessage()); + } + } + + @Test + public void testAddMacToMacFlow_WhenNullSourceMac() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + flowWriterService.addMacToMacFlow(null, destMacAddress, destNodeConnectorRef); + verify(dataBrokerService, times(1)).beginTransaction(); + verify(dataModificationTransaction, times(1)).commit(); + } + + @Test + public void testAddMacToMacFlow_WhenSrcAndDestMacAreSame() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + flowWriterService.addMacToMacFlow(new MacAddress(destMacAddress.getValue()), destMacAddress, destNodeConnectorRef); + verify(dataBrokerService, never()).beginTransaction(); + verify(dataModificationTransaction, never()).commit(); + + } + + @Test + public void testAddMacToMacFlow_SunnyDay() throws Exception { + FlowWriterService flowWriterService = new FlowWriterServiceImpl(dataBrokerService, networkGraphService); + flowWriterService.addMacToMacFlow(srcMacAddress, destMacAddress, destNodeConnectorRef); + verify(dataBrokerService, times(1)).beginTransaction(); + verify(dataModificationTransaction, times(1)).commit(); + } + +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstraTest.java b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstraTest.java new file mode 100644 index 0000000000..3669a5c979 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/NetworkGraphDijkstraTest.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2014 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.sample.l2switch.md.topology; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.Destination; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.Source; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + */ +public class NetworkGraphDijkstraTest { + Link link1, link2, link3, link4, link5, link6, link7, link8, link9, link10,link11,link12; + Destination dest1, dest2, dest3, dest4, dest5, dest6,dest7,dest8,dest9,dest10,dest11,dest12; + Source src1, src2, src3, src4, src5, src6,src7,src8,src9,src10,src11,src12; + NodeId nodeId1 = new NodeId("openflow:1"); + NodeId nodeId2 = new NodeId("openflow:2"); + NodeId nodeId3 = new NodeId("openflow:3"); + NodeId nodeId4 = new NodeId("openflow:4"); + NodeId nodeId5 = new NodeId("openflow:5"); + NodeId nodeId6 = new NodeId("openflow:6"); + NodeId nodeId7 = new NodeId("openflow:7"); + List links = new ArrayList<>(); + + @Before + public void init() { + link1 = mock(Link.class); + link2 = mock(Link.class); + link3 = mock(Link.class); + link4 = mock(Link.class); + link5 = mock(Link.class); + link6 = mock(Link.class); + link7 = mock(Link.class); + link8 = mock(Link.class); + link9 = mock(Link.class); + link10 = mock(Link.class); + link11 = mock(Link.class); + link12 = mock(Link.class); + dest1 = mock(Destination.class); + dest2 = mock(Destination.class); + dest3 = mock(Destination.class); + dest4 = mock(Destination.class); + dest5 = mock(Destination.class); + dest6 = mock(Destination.class); + dest7 = mock(Destination.class); + dest8 = mock(Destination.class); + dest9 = mock(Destination.class); + dest10 = mock(Destination.class); + dest11 = mock(Destination.class); + dest12 = mock(Destination.class); + src1 = mock(Source.class); + src2 = mock(Source.class); + src3 = mock(Source.class); + src4 = mock(Source.class); + src5 = mock(Source.class); + src6 = mock(Source.class); + src7 = mock(Source.class); + src8 = mock(Source.class); + src9 = mock(Source.class); + src10 = mock(Source.class); + src11 = mock(Source.class); + src12 = mock(Source.class); + when(link1.getSource()).thenReturn(src1); + when(link2.getSource()).thenReturn(src2); + when(link3.getSource()).thenReturn(src3); + when(link4.getSource()).thenReturn(src4); + when(link5.getSource()).thenReturn(src5); + when(link6.getSource()).thenReturn(src6); + when(link7.getSource()).thenReturn(src7); + when(link8.getSource()).thenReturn(src8); + when(link9.getSource()).thenReturn(src9); + when(link10.getSource()).thenReturn(src10); + when(link11.getSource()).thenReturn(src11); + when(link12.getSource()).thenReturn(src12); + when(link1.getDestination()).thenReturn(dest1); + when(link2.getDestination()).thenReturn(dest2); + when(link3.getDestination()).thenReturn(dest3); + when(link4.getDestination()).thenReturn(dest4); + when(link5.getDestination()).thenReturn(dest5); + when(link6.getDestination()).thenReturn(dest6); + when(link7.getDestination()).thenReturn(dest7); + when(link8.getDestination()).thenReturn(dest8); + when(link9.getDestination()).thenReturn(dest9); + when(link10.getDestination()).thenReturn(dest10); + when(link11.getDestination()).thenReturn(dest11); + when(link12.getDestination()).thenReturn(dest12); + when(src1.getSourceNode()).thenReturn(nodeId1); + when(dest1.getDestNode()).thenReturn(nodeId2); + when(src2.getSourceNode()).thenReturn(nodeId2); + when(dest2.getDestNode()).thenReturn(nodeId1); + when(src3.getSourceNode()).thenReturn(nodeId1); + when(dest3.getDestNode()).thenReturn(nodeId3); + when(src4.getSourceNode()).thenReturn(nodeId3); + when(dest4.getDestNode()).thenReturn(nodeId1); + when(src5.getSourceNode()).thenReturn(nodeId2); + when(dest5.getDestNode()).thenReturn(nodeId4); + when(src6.getSourceNode()).thenReturn(nodeId4); + when(dest6.getDestNode()).thenReturn(nodeId2); + when(src7.getSourceNode()).thenReturn(nodeId2); + when(dest7.getDestNode()).thenReturn(nodeId5); + when(src8.getSourceNode()).thenReturn(nodeId5); + when(dest8.getDestNode()).thenReturn(nodeId2); + when(src9.getSourceNode()).thenReturn(nodeId6); + when(dest9.getDestNode()).thenReturn(nodeId3); + when(src10.getSourceNode()).thenReturn(nodeId3); + when(dest10.getDestNode()).thenReturn(nodeId6); + when(src11.getSourceNode()).thenReturn(nodeId7); + when(dest11.getDestNode()).thenReturn(nodeId3); + when(src12.getSourceNode()).thenReturn(nodeId3); + when(dest12.getDestNode()).thenReturn(nodeId7); + links.add(link1); + links.add(link2); + links.add(link3); + links.add(link4); + links.add(link5); + links.add(link6); + links.add(link7); + links.add(link8); + links.add(link9); + links.add(link10); + links.add(link11); + links.add(link12); + + } + + @Test + public void testAddLinksAndGetPath() throws Exception { + NetworkGraphService networkGraphService = new NetworkGraphDijkstra(); + networkGraphService.addLinks(links); + List path = networkGraphService.getPath(nodeId2, nodeId3); + assertEquals("path size is not as expected.", 2, path.size()); + assertEquals("link source is not as expected.", nodeId2, path.get(0).getSource().getSourceNode()); + assertEquals("link destination is not as expected.", nodeId1, path.get(0).getDestination().getDestNode()); + path = networkGraphService.getPath(nodeId3, nodeId2); + assertEquals("path size is not as expected.", 2, path.size()); + assertEquals("link source is not as expected.", nodeId3, path.get(0).getSource().getSourceNode()); + assertEquals("link destination is not as expected.", nodeId1, path.get(0).getDestination().getDestNode()); + + path = networkGraphService.getPath(nodeId4, nodeId6); + assertEquals("path size is not as expected.", 4, path.size()); + assertEquals("link source is not as expected.", nodeId4, path.get(0).getSource().getSourceNode()); + assertEquals("link destination is not as expected.", nodeId2, path.get(0).getDestination().getDestNode()); + } +} diff --git a/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandlerTest.java b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandlerTest.java new file mode 100644 index 0000000000..9ecd25651a --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/implementation/src/test/java/org/opendaylight/controller/sample/l2switch/md/topology/TopologyLinkDataChangeHandlerTest.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2014 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.sample.l2switch.md.topology; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.sample.l2switch.md.util.InstanceIdentifierUtils; +import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; +import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + */ +public class TopologyLinkDataChangeHandlerTest { + NetworkGraphService networkGraphService; + DataBrokerService dataBrokerService; + DataChangeEvent dataChangeEvent; + Topology topology; + Link link; + + @Before + public void init() { + networkGraphService = mock(NetworkGraphService.class); + dataBrokerService = mock(DataBrokerService.class); + dataChangeEvent = mock(DataChangeEvent.class); + link = mock(Link.class); + topology = mock(Topology.class); + } + + @Test + public void testOnDataChange() throws Exception { + TopologyLinkDataChangeHandler topologyLinkDataChangeHandler = new TopologyLinkDataChangeHandler(dataBrokerService, networkGraphService, 2); + Map, DataObject> original = new HashMap, DataObject>(); + InstanceIdentifier instanceIdentifier = InstanceIdentifierUtils.generateTopologyInstanceIdentifier("flow:1"); + DataObject dataObject = mock(DataObject.class); + Map, DataObject> updated = new HashMap, DataObject>(); + updated.put(instanceIdentifier, dataObject); + when(dataChangeEvent.getUpdatedOperationalData()).thenReturn(updated); + when(dataChangeEvent.getOriginalOperationalData()).thenReturn(original); + List links = new ArrayList<>(); + links.add(link); + when(dataBrokerService.readOperationalData(instanceIdentifier)).thenReturn(topology); + when(topology.getLink()).thenReturn(links); + + topologyLinkDataChangeHandler.onDataChanged(dataChangeEvent); + Thread.sleep(2100); + verify(networkGraphService, times(1)).addLinks(links); + } +} diff --git a/opendaylight/md-sal/samples/l2switch/model/pom.xml b/opendaylight/md-sal/samples/l2switch/model/pom.xml new file mode 100644 index 0000000000..d0ef2e0c65 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/model/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + sal-samples + org.opendaylight.controller.samples + 1.1-SNAPSHOT + ../.. + + org.opendaylight.controller.samples.l2switch.md + l2switch-model + bundle + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + org.opendaylight.yangtools.yang.binding.annotations, * + ${project.basedir}/META-INF + + + + + org.opendaylight.yangtools + yang-maven-plugin + ${yangtools.version} + + + + generate-sources + + + src/main/yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + + + target/generated-sources/sal + + + + org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl + target/site/models + + + org.opendaylight.yangtools.yang.wadl.generator.maven.WadlGenerator + target/site/models + + + true + + + + + + org.opendaylight.yangtools + maven-sal-api-gen-plugin + ${yangtools.version} + jar + + + org.opendaylight.yangtools + yang-binding + ${yangtools.version} + jar + + + + + + + + + + org.opendaylight.yangtools + yang-binding + + + org.opendaylight.yangtools + yang-common + + + org.opendaylight.yangtools.model + ietf-yang-types + + + org.opendaylight.controller.model + model-inventory + + + diff --git a/opendaylight/md-sal/samples/l2switch/model/src/main/yang/l2-address-tracker.yang b/opendaylight/md-sal/samples/l2switch/model/src/main/yang/l2-address-tracker.yang new file mode 100644 index 0000000000..d694c6883d --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/model/src/main/yang/l2-address-tracker.yang @@ -0,0 +1,45 @@ +module l2-address-tracker { + yang-version 1; + namespace "urn:opendaylight:l2-address-tracker"; + prefix l2-address-tracker; + + import ietf-yang-types { + prefix yang; + revision-date 2010-09-24; + } + import opendaylight-inventory { + prefix inv; + revision-date 2013-08-19; + } + + organization "Cisco Systems Inc"; + contact + "Alex Fan "; + description + "YANG version of the L2 Address Tracker Data Model"; + + revision 2014-04-02 { + description + "L2 Address Tracker module draft."; + } + + grouping l2-address { + leaf mac { + type yang:mac-address; + mandatory true; + description + "the mac address of the host."; + } + leaf node-connector-ref { + type inv:node-connector-ref; + } + } + + container l2-addresses { + config false; + list l2-address { + key "mac"; + uses l2-address; + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/samples/l2switch/pom.xml b/opendaylight/md-sal/samples/l2switch/pom.xml new file mode 100644 index 0000000000..2e2100b287 --- /dev/null +++ b/opendaylight/md-sal/samples/l2switch/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + + l2switch.aggregator + org.opendaylight.controller.samples.l2switch + 1.0.0-SNAPSHOT + pom + + + model + implementation + + + diff --git a/opendaylight/md-sal/samples/pom.xml b/opendaylight/md-sal/samples/pom.xml index 54810266a7..9f9d9d35c7 100644 --- a/opendaylight/md-sal/samples/pom.xml +++ b/opendaylight/md-sal/samples/pom.xml @@ -19,6 +19,7 @@ toaster toaster-consumer toaster-provider + l2switch