From 68e74782556acef71d069a6cd5f9c6d1c2585e34 Mon Sep 17 00:00:00 2001 From: Rob Adams <rob.adams@bigswitch.com> Date: Fri, 17 Feb 2012 15:25:38 -0800 Subject: [PATCH] Unit tests for the parts of device manager that should be working, and some effort at fixing compile errors in other modules --- .../floodlightcontroller/core/IListener.java | 8 +- .../devicemanager/IDevice.java | 44 +- .../devicemanager/IDeviceManager.java | 61 ++ .../devicemanager/IEntityClass.java | 12 +- .../devicemanager/IEntityClassifier.java | 28 +- .../devicemanager/SwitchPort.java | 74 +++ .../internal/DefaultEntityClassifier.java | 29 +- .../devicemanager/internal/Device.java | 176 ++++- .../internal/DeviceManagerImpl.java | 626 ++++++++++++------ .../devicemanager/internal/Entity.java | 63 +- .../devicemanager/internal/IndexedEntity.java | 20 +- .../topology/ITopology.java | 16 +- .../topology/internal/TopologyImpl.java | 14 +- .../floodlightcontroller/util/MACAddress.java | 2 +- .../java/org/openflow/util/HexString.java | 10 +- .../internal/DeviceManagerImplTest.java | 288 ++++---- 16 files changed, 1066 insertions(+), 405 deletions(-) create mode 100644 src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java diff --git a/src/main/java/net/floodlightcontroller/core/IListener.java b/src/main/java/net/floodlightcontroller/core/IListener.java index cb168608f..58b922e4c 100644 --- a/src/main/java/net/floodlightcontroller/core/IListener.java +++ b/src/main/java/net/floodlightcontroller/core/IListener.java @@ -17,7 +17,13 @@ package net.floodlightcontroller.core; - +/** + * Interface for implementing an event listener with support for ordering + * based on prerequisites and postrequisites. + * @author readams + * + * @param <T> The event type + */ public interface IListener<T> { /** * The name assigned to this listener diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java index db0441a16..fa8c694c4 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java @@ -17,13 +17,55 @@ package net.floodlightcontroller.devicemanager; +import java.util.Date; + /** * Represents an independent device on the network. A device consists of a * set of entities, and all the information known about a given device comes * only from merging all associated entities for that device. * @author readams - * */ public interface IDevice { + /** + * Get the primary key for this device. + * @return the primary key + */ + public Long getDeviceKey(); + + /** + * Get the MAC address of the device as a Long value. + * @return the MAC address for the device + */ + public long getMACAddress(); + /** + * Get the MAC address of the device as a String value. + * @return the MAC address for the device + */ + public String getMACAddressString(); + + /** + * Get all VLAN IDs for the device. The array returned can have + * null entries representing untagged packets. + * @return an array containing all unique VLAN IDs for the device. + */ + public Short[] getVlanId(); + + /** + * Get all IPv4 addresses associated with the device. + * @return an array containing the unique IPv4 addresses for the device. + */ + public Integer[] getIPv4Addresses(); + + /** + * Get all attachment points associated with the device. + * @return an array containing all unique attachment points for the device + */ + public SwitchPort[] getAttachmentPoints(); + + /** + * Get the most recent timestamp for this device + * @return the last seen timestamp + */ + public Date getLastSeen(); } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManager.java b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManager.java index 508707a6e..14bed96ad 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManager.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManager.java @@ -17,11 +17,72 @@ package net.floodlightcontroller.devicemanager; +import java.util.Collection; + +import net.floodlightcontroller.core.FloodlightContextStore; + /** * Device manager allows interacting with devices on the network. Note * that under normal circumstances, {@link Device} objects should be retrieved * from the {@link FloodlightContext} rather than from {@link IDeviceManager}. */ public interface IDeviceManager { + /** + * The source device for the current packet-in, if applicable. + */ + public static final String CONTEXT_SRC_DEVICE = + "com.bigswitch.floodlight.devicemanager.srcDevice"; + + /** + * The destination device for the current packet-in, if applicable. + */ + public static final String CONTEXT_DST_DEVICE = + "com.bigswitch.floodlight.devicemanager.dstDevice"; + /** + * A FloodlightContextStore object that can be used to interact with the + * FloodlightContext information created by BVS manager. + */ + public static final FloodlightContextStore<IDevice> fcStore = + new FloodlightContextStore<IDevice>(); + + /** + * Set the entity classifier for the device manager to use to + * differentiate devices on the network. If no classifier is set, + * the {@link DefaultEntityClassifer} will be used. This should be + * registered in the application initialization phase before startup. + * @param classifier the classifier to set. + */ + public void setEntityClassifier(IEntityClassifier classifier); + + /** + * Flush and/or reclassify all entities in a class + * + * @param entityClass the class to flush. If null, flush all classes + * @param reclassify if true, begin an asynchronous task to reclassify the + * flushed entities + */ + public void flushEntityCache(IEntityClass entityClass, boolean reclassify); + + /** + * Search for a device using entity fields. Only the key fields as + * defined by the {@link IEntityClassifier} will be important in this + * search. + * @param macAddress The MAC address + * @param ipv4Address the ipv4 address + * @param vlan the VLAN + * @param switchDPID the switch DPID + * @param switchPort the switch port + * @return an {@link IDevice} or null if no device is found. + * @see IDeviceManager#setEntityClassifier(IEntityClassifier) + */ + public IDevice findDevice(long macAddress, Integer ipv4Address, + Short vlan, Long switchDPID, + Integer switchPort); + + /** + * Get an unmodifiable collection view over all devices currently known. + * @return the collection of all devices + */ + public Collection<? extends IDevice> getAllDevices(); } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java index 3af548dd0..d5412f154 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java @@ -17,7 +17,7 @@ package net.floodlightcontroller.devicemanager; -import java.util.Collection; +import java.util.Set; import net.floodlightcontroller.devicemanager.IEntityClassifier.EntityField; import net.floodlightcontroller.devicemanager.internal.Device; @@ -30,20 +30,24 @@ import net.floodlightcontroller.devicemanager.internal.Device; * for that entity class. A field is effectively wildcarded by not including * it in the list of key fields returned by {@link IEntityClassifier} and/or * {@link IEntityClass}. + * + * Note that if you're not using static objects, you'll need to override + * {@link Object#equals(Object)} and {@link Object#hashCode()}. + * * @author readams * */ public interface IEntityClass { /** - * Return the list of key fields for this entity class. Entities + * Return the set of key fields for this entity class. Entities * belonging to this class that differ in fields not included in * this collection will be considered the same device. The key * fields for an entity class must not change unless associated * with a flush of that entity class. * - * @return a collection containing the fields that should not + * @return a set containing the fields that should not * be wildcarded. May be null to indicate that all fields are key fields. */ - Collection<EntityField> getKeyFields(); + Set<EntityField> getKeyFields(); } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java index 7b448aa8e..8d4994cba 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java @@ -18,6 +18,7 @@ package net.floodlightcontroller.devicemanager; import java.util.Collection; +import java.util.Set; import net.floodlightcontroller.devicemanager.internal.Device; import net.floodlightcontroller.devicemanager.internal.Entity; @@ -36,10 +37,20 @@ public interface IEntityClassifier { } /** - * Classify the given entity into a class. + * Classify the given entity into a set of classes. It is important + * that the key fields returned by {@link IEntityClassifier#getKeyFields()} + * be sufficient for classifying entities. That is, if two entities are + * identical except for a field that is not a key field, they must be + * assigned the same class. + * + * <p>Note further that you must take steps to ensure you always return + * classes in some consistent ordering. This could be achieved by sorting + * the returned collection before returning it. * * @param entity the entity to classify - * @return the IEntityClass resulting from the classification + * @return the IEntityClass resulting from the classification. When + * iterating, must return results in a sorted order. + * @see IEntityClassifier#getKeyFields() */ Collection<IEntityClass> classifyEntity(Entity entity); @@ -51,6 +62,9 @@ public interface IEntityClassifier { * object if the entity class returned is different from the entity class for * curDevice. * + * <p>Note that you must take steps to ensure you always return classes + * in some consistent ordering. + * @param curDevice the device currently associated with the entity * @param entity the entity to reclassify * @return the IEntityClass resulting from the classification @@ -78,11 +92,15 @@ public interface IEntityClassifier { * never be considered a different device by any {@link IEntityClass} * returned by {@link IEntityClassifier#classifyEntity}. The key fields * for an entity classifier must not change unless associated with a - * flush of all entity state. + * flush of all entity state. The list of key fields must be the union + * of all key fields that could be returned by + * {@link IEntityClass#getKeyFields()}. * - * @return a collection containing the fields that should not be + * @return a set containing the fields that should not be * wildcarded. May be null to indicate that all fields are key fields. + * @see {@link IEntityClass#getKeyFields()} + * @see {@link IEntityClassifier#classifyEntity} */ - Collection<EntityField> getKeyFields(); + Set<EntityField> getKeyFields(); } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java new file mode 100644 index 000000000..2fc3c703b --- /dev/null +++ b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java @@ -0,0 +1,74 @@ +/** +* Copyright 2012 Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.devicemanager; + +/** + * A simple switch DPID/port pair + * @author readams + * + */ +public class SwitchPort { + protected long switchDPID; + protected int port; + + /** + * Simple constructor + * @param switchDPID the dpid + * @param port the port + */ + public SwitchPort(long switchDPID, int port) { + super(); + this.switchDPID = switchDPID; + this.port = port; + } + + // *************** + // Getters/Setters + // *************** + + public long getSwitchDPID() { + return switchDPID; + } + public int getPort() { + return port; + } + + // ****** + // Object + // ****** + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + port; + result = prime * result + (int) (switchDPID ^ (switchDPID >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + SwitchPort other = (SwitchPort) obj; + if (port != other.port) return false; + if (switchDPID != other.switchDPID) return false; + return true; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java index 8111aad34..8de1d2f5b 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java @@ -17,8 +17,9 @@ package net.floodlightcontroller.devicemanager.internal; -import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.IEntityClassifier; @@ -29,21 +30,27 @@ import net.floodlightcontroller.devicemanager.IEntityClassifier; * @author readams */ public class DefaultEntityClassifier implements IEntityClassifier { - protected static Collection<EntityField> keyFields; + /** + * A default fixed entity class + */ + protected static class DefaultEntityClass implements IEntityClass { + @Override + public Set<EntityField> getKeyFields() { + return keyFields; + } + } + + protected static Set<EntityField> keyFields; static { - keyFields = new ArrayList<EntityField>(2); + keyFields = new HashSet<EntityField>(2); keyFields.add(EntityField.MAC); keyFields.add(EntityField.VLAN); } - protected static IEntityClass entityClass = new IEntityClass() { - @Override - public Collection<EntityField> getKeyFields() { - return keyFields; - } - }; + protected static IEntityClass entityClass = new DefaultEntityClass(); + protected static Collection<IEntityClass> entityClasses; static { - entityClasses = new ArrayList<IEntityClass>(1); + entityClasses = new HashSet<IEntityClass>(1); entityClasses.add(entityClass); } @@ -65,7 +72,7 @@ public class DefaultEntityClassifier implements IEntityClassifier { } @Override - public Collection<EntityField> getKeyFields() { + public Set<EntityField> getKeyFields() { return keyFields; } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java index 453a827c8..96adb23fa 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java @@ -18,40 +18,194 @@ package net.floodlightcontroller.devicemanager.internal; import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.TreeSet; + +import org.openflow.util.HexString; + import net.floodlightcontroller.devicemanager.IDevice; +import net.floodlightcontroller.devicemanager.IEntityClass; +import net.floodlightcontroller.devicemanager.SwitchPort; /** * Concrete implementation of {@link IDevice} * @author readams */ -public class Device { +public class Device implements IDevice { + protected Long deviceKey; + protected Entity[] entities; + protected IEntityClass[] entityClasses; + + protected String macAddressString; + + // ************ + // Constructors + // ************ /** * Create a device from a set of entities - * @param entities the entities that make up the device + * @param the unique identifier for this device object + * @param entity the initial entity for the device + * @param entityClasses the entity classes associated with the entity */ - public Device(Entity... entities) { - this.entities = entities; + public Device(Long deviceKey, + Entity entity, + Collection<IEntityClass> entityClasses) { + this.deviceKey = deviceKey; + this.entities = new Entity[] {entity}; + this.macAddressString = + HexString.toHexString(entity.getMacAddress(), 6); + this.entityClasses = + entityClasses.toArray(new IEntityClass[entityClasses.size()]); + Arrays.sort(this.entities); } /** * Create a device consisting of all entities from another device plus * the additional entities specified. * @param device the old device - * @param entities the new entities to add + * @param entity the new entity to add to the device + * @param entityClasses the entity classes associated with the entity */ - public Device(Device device, Entity... entities) { - this.entities = new Entity[device.entities.length + entities.length]; - int i = 0; - for (; i < device.entities.length; i++) { + public Device(Device device, + Entity entity, + Collection<IEntityClass> entityClasses) { + this.deviceKey = device.deviceKey; + this.macAddressString = device.macAddressString; + + this.entities = new Entity[device.entities.length + 1]; + for (int i = 0; i < device.entities.length; i++) { this.entities[i] = device.entities[i]; } - for (int j = 0; j < entities.length; j++, i++) { - this.entities[i] = entities[j]; + this.entities[this.entities.length-1] = entity; + Arrays.sort(this.entities); + + if (entityClasses != null && + entityClasses.size() > this.entityClasses.length) { + IEntityClass[] classes = new IEntityClass[entityClasses.size()]; + this.entityClasses = + entityClasses.toArray(classes); + } else { + // same actual array, not a copy + this.entityClasses = device.entityClasses; + } + } + + // ******* + // IDevice + // ******* + + @Override + public Long getDeviceKey() { + return deviceKey; + } + + @Override + public long getMACAddress() { + // we assume only one MAC per device for now. + return entities[0].getMacAddress(); + } + + @Override + public String getMACAddressString() { + return macAddressString; + } + + @Override + public Short[] getVlanId() { + if (entities.length == 1) + return new Short[]{ entities[0].getVlan() }; + + TreeSet<Short> vals = new TreeSet<Short>(); + for (Entity e : entities) { + vals.add(e.getVlan()); + } + return vals.toArray(new Short[vals.size()]); + } + + @Override + public Integer[] getIPv4Addresses() { + Integer addr; + if (entities.length == 1 && + (addr = entities[0].getIpv4Address()) != null) + return new Integer[]{ addr }; + + TreeSet<Integer> vals = new TreeSet<Integer>(); + for (Entity e : entities) { + if (e.getIpv4Address() != null) + vals.add(e.getIpv4Address()); + } + return vals.toArray(new Integer[vals.size()]); + } + + @Override + public SwitchPort[] getAttachmentPoints() { + if (entities.length == 1) + return new SwitchPort[]{new SwitchPort(entities[0]. + getSwitchDPID(), + entities[0]. + getSwitchPort()) }; + + HashSet<SwitchPort> vals = new HashSet<SwitchPort>(); + for (Entity e : entities) { + SwitchPort sp = new SwitchPort(e.getSwitchDPID(), + e.getSwitchPort()); + vals.add(sp); } + return vals.toArray(new SwitchPort[vals.size()]); } + @Override + public Date getLastSeen() { + Date d = entities[0].getLastSeenTimestamp(); + for (int i = 1; i < entities.length; i++) { + if (entities[i].getLastSeenTimestamp().compareTo(d) < 0) + d = entities[i].getLastSeenTimestamp(); + } + return d; + } + + // *************** + // Getters/Setters + // *************** + + + public IEntityClass[] getEntityClasses() { + return entityClasses; + } + + public void setEntityClasses(IEntityClass[] entityClasses) { + this.entityClasses = entityClasses; + } + + public Entity[] getEntities() { + return entities; + } + + // *************** + // Utility Methods + // *************** + + /** + * Check whether the device contains the specified entity + * @param entity the entity to search for + * @return true if the entity is in the device, or false otherwise + */ + public boolean containsEntity(Entity entity) { + for (Entity e : entities) { + if (e.equals(entity)) + return true; + } + return false; + } + + // ****** + // Object + // ****** + @Override public int hashCode() { final int prime = 31; diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java index 3ef2adb16..dbc4760c1 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java @@ -1,5 +1,5 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011,2012 Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,21 +15,34 @@ * under the License. **/ -/** - * - */ package net.floodlightcontroller.devicemanager.internal; -import java.util.Map; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Date; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceManager; import net.floodlightcontroller.devicemanager.IEntityClass; +import net.floodlightcontroller.devicemanager.IEntityClassifier; +import net.floodlightcontroller.devicemanager.IEntityClassifier.EntityField; +import net.floodlightcontroller.packet.ARP; +import net.floodlightcontroller.packet.DHCP; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPv4; +import net.floodlightcontroller.packet.UDP; +import net.floodlightcontroller.storage.IStorageSource; import net.floodlightcontroller.storage.IStorageSourceListener; +import net.floodlightcontroller.topology.ITopology; import net.floodlightcontroller.topology.ITopologyAware; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; @@ -50,44 +63,91 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, LoggerFactory.getLogger(DeviceManagerImpl.class); protected IFloodlightProvider floodlightProvider; + protected ITopology topology; + protected IStorageSource storageSource; /** * This is the master device map that maps device IDs to {@link Device} * objects. */ - protected Map<Long, Device> deviceMap; + protected ConcurrentHashMap<Long, Device> deviceMap; + + /** + * Counter used to generate device keys + */ + protected long deviceKeyCounter = 0; + + /** + * Lock for incrementing the device key counter + */ + protected Object deviceKeyLock = new Object(); /** * This is the primary entity index that maps entities to device IDs. */ - protected Map<IndexedEntity, Long> primaryIndex; + protected ConcurrentHashMap<IndexedEntity, Long> primaryIndex; + + /** + * The primary key fields used in the primary index + */ + protected Set<EntityField> primaryKeyFields; /** * This map contains secondary indices for each of the configured {@ref IEntityClass} * that exist */ - protected Map<IEntityClass, Map<IndexedEntity, Long>> secondaryIndexMap; + protected ConcurrentHashMap<IEntityClass, + ConcurrentHashMap<IndexedEntity, + Long>> classIndexMap; + /** + * The entity classifier currently in use + */ + IEntityClassifier entityClassifier; + // ************** // IDeviceManager // ************** + + @Override + public void setEntityClassifier(IEntityClassifier classifier) { + entityClassifier = classifier; + primaryKeyFields = classifier.getKeyFields(); + } - // None for now + @Override + public void flushEntityCache(IEntityClass entityClass, + boolean reclassify) { + // TODO Auto-generated method stub + } + + @Override + public IDevice findDevice(long macAddress, Integer ipv4Address, + Short vlan, Long switchDPID, + Integer switchPort) { + return findDeviceByEntity(new Entity(macAddress, vlan, + ipv4Address, switchDPID, + switchPort, null)); + } + + @Override + public Collection<? extends IDevice> getAllDevices() { + return Collections.unmodifiableCollection(deviceMap.values()); + } // ****************** // IOFMessageListener // ****************** - + + @Override public String getName() { - // TODO Auto-generated method stub - return null; + return "devicemanager"; } @Override public int getId() { - // TODO Auto-generated method stub - return 0; + return IOFMessageListener.FlListenerID.DEVICEMANAGERIMPL; } @Override @@ -101,12 +161,22 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, // TODO Auto-generated method stub return false; } - + @Override - public Command receive(IOFSwitch sw, OFMessage msg, + public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - // TODO Auto-generated method stub - return null; + switch (msg.getType()) { + case PACKET_IN: + return this.processPacketInMessage(sw, + (OFPacketIn) msg, cntx); + case PORT_STATUS: + return this.processPortStatusMessage(sw, + (OFPortStatus) msg); + } + + logger.error("received an unexpected message {} from switch {}", + msg, sw); + return Command.CONTINUE; } // ********************** @@ -132,8 +202,7 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, @Override public void addedLink(IOFSwitch srcSw, short srcPort, int srcPortState, IOFSwitch dstSw, short dstPort, int dstPortState) { - // TODO Auto-generated method stub - + // Nothing to do } @Override @@ -141,7 +210,6 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, int srcPortState, IOFSwitch dstSw, short dstPort, int dstPortState) { // TODO Auto-generated method stub - } @Override @@ -176,16 +244,52 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, // TODO Auto-generated method stub } + // ************** + // Initialization + // ************** + + public void setFloodlightProvider(IFloodlightProvider floodlightProvider) { + this.floodlightProvider = floodlightProvider; + } + + public void setStorageSource(IStorageSource storageSource) { + this.storageSource = storageSource; + } + + public void setTopology(ITopology topology) { + this.topology = topology; + } + + public void init() { + + } + + public void startUp() { + if (entityClassifier == null) + setEntityClassifier(new DefaultEntityClassifier()); + + deviceMap = new ConcurrentHashMap<Long, Device>(); + primaryIndex = new ConcurrentHashMap<IndexedEntity, Long>(); + classIndexMap = + new ConcurrentHashMap<IEntityClass, + ConcurrentHashMap<IndexedEntity, + Long>>(); + + floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); + floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this); + + // XXX - TODO entity aging timer + } + // **************** // Internal methods // **************** - public Command processPortStatusMessage(IOFSwitch sw, OFPortStatus ps) { - return null; - + protected Command processPortStatusMessage(IOFSwitch sw, OFPortStatus ps) { + // XXX - TODO + return null; } - /** * This method is called for every packet-in and should be optimized for * performance. @@ -194,194 +298,326 @@ public class DeviceManagerImpl implements IDeviceManager, IOFMessageListener, * @param cntx * @return */ - public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx) { - return null; - /* - Command ret = Command.CONTINUE; - OFMatch match = new OFMatch(); - match.loadFromPacket(pi.getPacketData(), pi.getInPort(), sw.getId()); - // Add this packet-in to event history - evHistPktIn(match); - - // Create attachment point/update network address if required - SwitchPortTuple switchPort = new SwitchPortTuple(sw, pi.getInPort()); - // Don't learn from internal port or invalid port - if (topology.isInternal(switchPort) || - !isValidInputPort(switchPort.getPort())) { - processUpdates(); + protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, + FloodlightContext cntx) { + try { + + if (topology.isInternal(sw, pi.getInPort()) || + !isValidInputPort(pi.getInPort())) + return Command.CONTINUE; + + Ethernet eth = + IFloodlightProvider.bcStore. + get(cntx,IFloodlightProvider.CONTEXT_PI_PAYLOAD); + + Entity entity = getEntityFromPacket(eth, sw, pi.getInPort()); + if (entity == null) + return Command.CONTINUE; + + if (isGratArp(eth)) { + // XXX - TODO - Clear attachment points from other clusters + } + + Device d = learnDeviceByEntity(entity); + + fcStore.put(cntx, CONTEXT_SRC_DEVICE, d); + return Command.CONTINUE; - } - // Packet arrive at a port from where we can learn the device - // if the source is multicast/broadcast ignore it - if ((match.getDataLayerSource()[0] & 0x1) != 0) { - return Command.CONTINUE; + } finally { + processUpdates(); } - - Long dlAddr = Ethernet.toLong(match.getDataLayerSource()); - Short vlan = match.getDataLayerVirtualLan(); - if (vlan < 0) vlan = null; - Ethernet eth = IFloodlightProvider.bcStore.get( - cntx, IFloodlightProvider.CONTEXT_PI_PAYLOAD); - int nwSrc = getSrcNwAddr(eth, dlAddr); - Device device = devMgrMaps.getDeviceByDataLayerAddr(dlAddr); - Date currentDate = new Date(); // TODO, - if (device != null) { - // Write lock is expensive, check if we have an update first - boolean newAttachmentPoint = false; - boolean newNetworkAddress = false; - boolean updateAttachmentPointLastSeen = false; - boolean updateNetworkAddressLastSeen = false; - boolean updateNeworkAddressMap = false; - boolean updateDeviceVlan = false; - boolean clearAttachmentPoints = false; - boolean updateDevice = false; - - DeviceAttachmentPoint attachmentPoint = null; - DeviceNetworkAddress networkAddress = null; - - // Copy-replace of device would be too expensive here - device.setLastSeen(currentDate); - updateDevice = device.shouldWriteLastSeenToStorage(); - attachmentPoint = device.getAttachmentPoint(switchPort); - - if (isGratArp(eth)) { - clearAttachmentPoints = true; + + } + + private void processUpdates() { + // XXX - TODO + } + + /** + * Check whether the port is a physical port. We should not learn + * attachment points on "special" ports. + * @param port the port to check + * @return + */ + private boolean isValidInputPort(short port) { + return ((int)port & 0xff00) != 0xff00 || + port == (short)0xfffe; + } + + private boolean isGratArp(Ethernet eth) { + if (eth.getPayload() instanceof ARP) { + ARP arp = (ARP) eth.getPayload(); + if (arp.isGratuitous()) { + return true; } + } + return false; + } - if (attachmentPoint != null) { - updateAttachmentPointLastSeen = true; - } else { - newAttachmentPoint = true; + private int getSrcNwAddr(Ethernet eth, long dlAddr) { + if (eth.getPayload() instanceof ARP) { + ARP arp = (ARP) eth.getPayload(); + if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) && + (Ethernet.toLong(arp.getSenderHardwareAddress()) == dlAddr)) { + return IPv4.toIPv4Address(arp.getSenderProtocolAddress()); } - - if (nwSrc != 0) { - networkAddress = device.getNetworkAddress(nwSrc); - if (networkAddress != null) { - updateNetworkAddressLastSeen = true; - } else if (eth != null && (eth.getPayload() instanceof ARP)) { - networkAddress = new DeviceNetworkAddress(nwSrc, - currentDate); - newNetworkAddress = true; - } - - // Also, if this address is currently mapped to a different - // device, fix it. This should be rare, so it is OK to do update - // the storage from here. - // - // NOTE: the mapping is observed, and the decision is made based - // on that, outside a lock. So the state may change by the time - // we get to do the map update. But that is OK since the mapping - // will eventually get consistent. - Device deviceByNwaddr = this.getDeviceByIPv4Address(nwSrc); - if ((deviceByNwaddr != null) && - (deviceByNwaddr.getDataLayerAddressAsLong() != - device.getDataLayerAddressAsLong())) { - updateNeworkAddressMap = true; - Device dCopy = new Device(deviceByNwaddr); - DeviceNetworkAddress naOld = dCopy.getNetworkAddress(nwSrc); - Map<Integer, DeviceNetworkAddress> namap = - dCopy.getNetworkAddressesMap(); - if (namap.containsKey(nwSrc)) namap.remove(nwSrc); - dCopy.setNetworkAddresses(namap.values()); - this.devMgrMaps.updateMaps(dCopy); - if (naOld !=null) - removeNetworkAddressFromStorage(dCopy, naOld); - log.info( - "Network address {} moved from {} to {} due to packet {}", - new Object[] {networkAddress, - deviceByNwaddr.getDataLayerAddress(), - device.getDataLayerAddress(), - eth}); + } else if (eth.getPayload() instanceof IPv4) { + IPv4 ipv4 = (IPv4) eth.getPayload(); + if (ipv4.getPayload() instanceof UDP) { + UDP udp = (UDP)ipv4.getPayload(); + if (udp.getPayload() instanceof DHCP) { + DHCP dhcp = (DHCP)udp.getPayload(); + if (dhcp.getOpCode() == DHCP.OPCODE_REPLY) { + return ipv4.getSourceAddress(); + } } - } - if ((vlan == null && device.getVlanId() != null) || - (vlan != null && !vlan.equals(device.getVlanId()))) { - updateDeviceVlan = true; - } - - if (newAttachmentPoint || newNetworkAddress || updateDeviceVlan || - updateNeworkAddressMap) { - - Device nd = new Device(device); - - try { - // Check if we have seen this attachmentPoint recently, - // An exception is thrown if the attachmentPoint is blocked. - if (newAttachmentPoint) { - attachmentPoint = getNewAttachmentPoint(nd, switchPort); - nd.addAttachmentPoint(attachmentPoint); - evHistAttachmtPt(nd, attachmentPoint.getSwitchPort(), - EvAction.ADDED, "New AP from pkt-in"); - } - - if (clearAttachmentPoints) { - nd.clearAttachmentPoints(); - evHistAttachmtPt(nd, 0L, (short)(-1), - EvAction.CLEARED, "Grat. ARP from pkt-in"); - } + } + return 0; + } + + /** + * Parse an entity from an {@link Ethernet} packet. + * @param eth the packet to parse + * @param sw the switch on which the packet arrived + * @param pi the original packetin + * @return the entity from the packet + */ + private Entity getEntityFromPacket(Ethernet eth, + IOFSwitch sw, + int port) { + byte[] dlAddrArr = eth.getSourceMACAddress(); + long dlAddr = Ethernet.toLong(dlAddrArr); + + // Ignore broadcast/multicast source + if ((dlAddrArr[0] & 0x1) != 0) + return null; + + short vlan = eth.getVlanID(); + int nwSrc = getSrcNwAddr(eth, dlAddr); + return new Entity(dlAddr, + ((vlan >= 0) ? vlan : null), + ((nwSrc != 0) ? nwSrc : null), + sw.getId(), + port, + new Date()); + } - if (newNetworkAddress) { - // add the address - nd.addNetworkAddress(networkAddress); - log.debug("Device {} added IP {}", - nd, IPv4.fromIPv4Address(nwSrc)); + /** + * Look up a {@link Device} based on the provided {@link Entity}. + * @param entity the entity to search for + * @return The {@link Device} object if found + */ + protected Device findDeviceByEntity(Entity entity) { + IndexedEntity ie = new IndexedEntity(primaryKeyFields, entity); + Long deviceKey = primaryIndex.get(ie); + if (deviceKey == null) + return null; + return deviceMap.get(deviceKey); + } + + /** + * Look up a {@link Device} based on the provided {@link Entity}. Also learns + * based on the new entity, and will update existing devices as required. + * + * @param entity the {@link Entity} + * @return The {@link Device} object if found + */ + protected Device learnDeviceByEntity(Entity entity) { + IndexedEntity ie = new IndexedEntity(primaryKeyFields, entity); + ArrayList<Long> deleteQueue = null; + Device device = null; + + // we may need to restart the learning process if we detect + // concurrent modification. Note that we ensure that at least + // one thread should always succeed so we don't get into infinite + // starvation loops + while (true) { + // Look up the fully-qualified entity to see if it already + // exists in the primary entity index. + Long deviceKey = primaryIndex.get(ie); + Collection<IEntityClass> classes = null; + if (deviceKey == null) { + // If the entity does not exist in the primary entity index, + // use the entity classifier for find the classes for the + // entity. Look up the entity in each of the returned classes' + // class entity indexes. + classes = entityClassifier.classifyEntity(entity); + for (IEntityClass clazz : classes) { + // ensure that a class index exists for the class if + // needed + ConcurrentHashMap<IndexedEntity, Long> classIndex; + try { + classIndex = getClassIndex(clazz); + } catch (ConcurrentModificationException e) { + continue; } - - if (updateDeviceVlan) { - nd.setVlanId(vlan); - writeDeviceToStorage(nd, currentDate); + + if (classIndex != null) { + IndexedEntity sie = + new IndexedEntity(clazz.getKeyFields(), + entity); + deviceKey = classIndex.get(sie); } - - } catch (APBlockedException e) { - assert(attachmentPoint == null); - ret = Command.STOP; - // install drop flow to avoid overloading the controller - // set hard timeout to 5 seconds to avoid blocking host - // forever - ForwardingBase.blockHost(floodlightProvider, switchPort, - dlAddr, ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT); } - // Update the maps - devMgrMaps.updateMaps(nd); - // publish the update after devMgrMaps is updated. - if (newNetworkAddress) { - updateAddress(nd, networkAddress, true); + } + if (deviceKey != null) { + // If the primary or secondary index contains the entity, + // update the entity timestamp, then use resulting device + // key to look up the device in the device map, and + // use the referenced Device below. + entity.setLastSeenTimestamp(new Date()); + device = deviceMap.get(deviceKey); + if (device == null) + continue; + } else { + // If the secondary index does not contain the entity, + // create a new Device object containing the entity, and + // generate a new device ID + synchronized (deviceKeyLock) { + deviceKey = Long.valueOf(deviceKeyCounter++); } - if (updateDeviceVlan) { - updateVlan(nd); + device = new Device(deviceKey, entity, classes); + + // Add the new device to the primary map with a simple put + deviceMap.put(deviceKey, device); + + if (!updateIndices(device, deviceKey)) { + if (deleteQueue == null) + deleteQueue = new ArrayList<Long>(); + deleteQueue.add(deviceKey); + continue; } - device = nd; } - - if (updateAttachmentPointLastSeen) { - attachmentPoint.setLastSeen(currentDate); - if (attachmentPoint.shouldWriteLastSeenToStorage()) - writeAttachmentPointToStorage( - device, attachmentPoint, currentDate); + + if (device.containsEntity(entity)) { + break; + } else { + Device newDevice = new Device(device, entity, classes); + // XXX - TODO When adding an entity, any existing entities on the + // same OpenFlow switch cluster but a different attachment point + // should be removed. If an entity being removed contains an + // IP address but the new entity does not contain that IP, + // then a new entity should be added containing the IP + // (including updating the entity caches), preserving the old + // timestamp of the entity. + + // XXX - TODO Handle port channels + + // XXX - TODO Handle broadcast domains + + // XXX - TODO Prevent flapping of entities + + boolean res = deviceMap.replace(deviceKey, device, newDevice); + // If replace returns false, restart the process from the + // beginning (this implies another thread concurrently + // modified this Device). + if (!res) + continue; + + device = newDevice; + + if (!updateIndices(device, deviceKey)) { + continue; + } } - - if (updateNetworkAddressLastSeen || newNetworkAddress) { - if (updateNetworkAddressLastSeen) - networkAddress.setLastSeen(currentDate); - if (newNetworkAddress || - networkAddress.shouldWriteLastSeenToStorage()) - writeNetworkAddressToStorage( - device, networkAddress, currentDate); + } + + if (deleteQueue != null) { + for (Long l : deleteQueue) { + deviceMap.remove(l); } - - if (updateDevice) { - writeDeviceToStorage(device, currentDate); + } + return device; + } + + /** + * Get the secondary index for a class. Will return null if the + * secondary index was created concurrently in another thread. + * @param clazz the class for the index + * @return + */ + private ConcurrentHashMap<IndexedEntity, + Long> getClassIndex(IEntityClass clazz) + throws ConcurrentModificationException { + ConcurrentHashMap<IndexedEntity, Long> classIndex = + classIndexMap.get(clazz); + if (classIndex != null) return classIndex; + + if (primaryKeyFields.equals(clazz.getKeyFields())) { + return null; + } + + classIndex = + new ConcurrentHashMap<IndexedEntity, Long>(); + ConcurrentHashMap<IndexedEntity, Long> r = + classIndexMap.putIfAbsent(clazz, + classIndex); + if (r != null) { + // concurrent add; restart + throw new ConcurrentModificationException(); + } + return classIndex; + } + + /** + * Update both the primary and class indices for the provided device. + * If the update fails because of a concurrent update, will return false. + * @param device the device to update + * @param deviceKey the device key for the device + * @return true if the update succeeded, false otherwise. + */ + private boolean updateIndices(Device device, Long deviceKey) { + if (!updateIndex(device, deviceKey, + primaryIndex, primaryKeyFields)) { + return false; + } + for (IEntityClass clazz : device.getEntityClasses()) { + Set<EntityField> ef = clazz.getKeyFields(); + if (primaryKeyFields.equals(ef)) + continue; + ConcurrentHashMap<IndexedEntity, Long> classIndex; + try { + classIndex = getClassIndex(clazz); + } catch (ConcurrentModificationException e) { + return false; } - - } else { // device is null - handleNewDevice(match.getDataLayerSource(), currentDate, - switchPort, nwSrc, vlan); + if (classIndex != null && + !updateIndex(device, deviceKey, classIndex, ef)) + return false; } - processUpdates(); - return ret; - */ + // XXX - TODO handle indexed views into data + return true; + } + + /** + * Attempt to update an index with the entities in the provided + * {@link Device}. If the update fails because of a concurrent update, + * will return false. + * @param device the device to update + * @param deviceKey the device key for the device + * @param index the index to update + * @param keyFields the key fields to use for the index + * @return true if the update succeeded, false otherwise. + */ + private boolean updateIndex(Device device, + Long deviceKey, + ConcurrentHashMap<IndexedEntity, + Long> index, + Set<EntityField> keyFields) { + for (Entity e : device.entities) { + IndexedEntity ie = new IndexedEntity(keyFields, e); + Long ret = index.putIfAbsent(ie, deviceKey); + if (ret != null) { + // If the return value is non-null, then fail the insert + // (this implies that a device using this entity has + // already been created in another thread). + return false; + } + } + + return true; } + } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java index cf18ec579..136faba86 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java @@ -19,7 +19,7 @@ package net.floodlightcontroller.devicemanager.internal; import java.util.Date; -import net.floodlightcontroller.util.MACAddress; +import org.openflow.util.HexString; /** * An entity on the network is a visible trace of a device that corresponds @@ -35,11 +35,11 @@ import net.floodlightcontroller.util.MACAddress; * @author readams * */ -public class Entity { +public class Entity implements Comparable<Entity> { /** * The MAC address associated with this entity */ - protected MACAddress macAddress; + protected long macAddress; /** * The IP address associated with this entity, or null if no IP learned @@ -77,14 +77,14 @@ public class Entity { * Create a new entity * * @param macAddress - * @param ipv4Address * @param vlan + * @param ipv4Address * @param switchDPID * @param switchPort * @param lastSeenTimestamp */ - public Entity(MACAddress macAddress, Integer ipv4Address, - Short vlan, Long switchDPID, Integer switchPort, + public Entity(long macAddress, Short vlan, + Integer ipv4Address, Long switchDPID, Integer switchPort, Date lastSeenTimestamp) { super(); this.macAddress = macAddress; @@ -99,7 +99,7 @@ public class Entity { // Getters/Setters // *************** - public MACAddress getMacAddress() { + public long getMacAddress() { return macAddress; } @@ -133,8 +133,7 @@ public class Entity { int result = 1; result = prime * result + ((ipv4Address == null) ? 0 : ipv4Address.hashCode()); - result = prime * result - + ((macAddress == null) ? 0 : macAddress.hashCode()); + result = prime * result + (int) (macAddress ^ (macAddress >>> 32)); result = prime * result + ((switchDPID == null) ? 0 : switchDPID.hashCode()); result = prime * result @@ -152,9 +151,7 @@ public class Entity { if (ipv4Address == null) { if (other.ipv4Address != null) return false; } else if (!ipv4Address.equals(other.ipv4Address)) return false; - if (macAddress == null) { - if (other.macAddress != null) return false; - } else if (!macAddress.equals(other.macAddress)) return false; + if (macAddress != other.macAddress) return false; if (switchDPID == null) { if (other.switchDPID != null) return false; } else if (!switchDPID.equals(other.switchDPID)) return false; @@ -169,9 +166,49 @@ public class Entity { @Override public String toString() { - return "Entity [macAddress=" + macAddress + ", ipv4Address=" + return "Entity [macAddress=" + HexString.toHexString(macAddress) + + ", ipv4Address=" + ipv4Address + ", vlan=" + vlan + ", switchDPID=" + switchDPID + ", switchPort=" + switchPort + "]"; } + + @Override + public int compareTo(Entity o) { + if (macAddress < o.macAddress) return -1; + if (macAddress > o.macAddress) return 1; + + int r; + if (ipv4Address == null) + r = o.ipv4Address == null ? -1 : 0; + else if (o.ipv4Address == null) + r = 1; + else + r = ipv4Address.compareTo(o.ipv4Address); + if (r != 0) return r; + + if (vlan == null) + r = o.vlan == null ? -1 : 0; + else if (o.vlan == null) + r = 1; + else + r = vlan.compareTo(o.vlan); + if (r != 0) return r; + + if (switchDPID == null) + r = o.switchDPID == null ? -1 : 0; + else if (o.switchDPID == null) + r = 1; + else + r = switchDPID.compareTo(o.switchDPID); + if (r != 0) return r; + + if (switchPort == null) + r = o.switchPort == null ? -1 : 0; + else if (o.switchPort == null) + r = 1; + else + r = switchPort.compareTo(o.switchPort); + return r; + } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java index 4b301c447..d9b2fa29e 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java @@ -37,29 +37,32 @@ public class IndexedEntity { switch (f) { case MAC: hashCode = prime * hashCode - + ((entity.macAddress == null) - ? 0 - : entity.macAddress.hashCode()); + + (int)(entity.macAddress); + break; case IP: hashCode = prime * hashCode + ((entity.ipv4Address == null) ? 0 : entity.ipv4Address.hashCode()); + break; case SWITCH: hashCode = prime * hashCode + ((entity.switchDPID == null) ? 0 : entity.switchDPID.hashCode()); + break; case PORT: hashCode = prime * hashCode + ((entity.switchPort == null) ? 0 : entity.switchPort.hashCode()); + break; case VLAN: hashCode = prime * hashCode + ((entity.vlan == null) ? 0 : entity.vlan.hashCode()); + break; } } return hashCode; @@ -75,30 +78,33 @@ public class IndexedEntity { for (EntityField f : keyFields) { switch (f) { case MAC: - if (entity.macAddress == null) { - if (other.entity.macAddress != null) return false; - } else if (!entity.macAddress. - equals(other.entity.macAddress)) return false; + if (entity.macAddress != other.entity.macAddress) + return false; + break; case IP: if (entity.ipv4Address == null) { if (other.entity.ipv4Address != null) return false; } else if (!entity.ipv4Address. equals(other.entity.ipv4Address)) return false; + break; case SWITCH: if (entity.switchDPID == null) { if (other.entity.switchDPID != null) return false; } else if (!entity.switchDPID. equals(other.entity.switchDPID)) return false; + break; case PORT: if (entity.switchPort == null) { if (other.entity.switchPort != null) return false; } else if (!entity.switchPort. equals(other.entity.switchPort)) return false; + break; case VLAN: if (entity.vlan == null) { if (other.entity.vlan != null) return false; } else if (!entity.vlan. equals(other.entity.vlan)) return false; + break; } } diff --git a/src/main/java/net/floodlightcontroller/topology/ITopology.java b/src/main/java/net/floodlightcontroller/topology/ITopology.java index fcd99d0ca..0816124a0 100644 --- a/src/main/java/net/floodlightcontroller/topology/ITopology.java +++ b/src/main/java/net/floodlightcontroller/topology/ITopology.java @@ -34,13 +34,23 @@ import net.floodlightcontroller.core.IOFSwitch; */ public interface ITopology { /** - * Query to determine if the specified switch id and port tuple are + * Query to determine if the specified switch id and port are * connected to another switch or not. If so, this means the link * is passing LLDPs properly between two OpenFlow switches. - * @param idPort - * @return + * @param idPort the {@link SwitchPortTuple} to check + * @return true if the link is internal */ public boolean isInternal(SwitchPortTuple idPort); + + /** + * Query to determine if the specified switch id and port are + * connected to another switch or not. If so, this means the link + * is passing LLDPs properly between two OpenFlow switches. + * @param sw the switch to check + * @param port the port to check + * @return true if the link is internal + */ + public boolean isInternal(IOFSwitch sw, short port); /** * Get the link that either sources from the port or terminates diff --git a/src/main/java/net/floodlightcontroller/topology/internal/TopologyImpl.java b/src/main/java/net/floodlightcontroller/topology/internal/TopologyImpl.java index e079b1e94..b3ef04319 100644 --- a/src/main/java/net/floodlightcontroller/topology/internal/TopologyImpl.java +++ b/src/main/java/net/floodlightcontroller/topology/internal/TopologyImpl.java @@ -1137,14 +1137,12 @@ public class TopologyImpl implements IOFMessageListener, IOFSwitchListener, public void setFloodlightProvider(IFloodlightProvider floodlightProvider) { this.floodlightProvider = floodlightProvider; } - - /** - * Checks to see if a SwitchPortTuple is internal. A SwitchPortTuple is - * defined as internal if the switch is a core switch if only switches that - * are in the same SwitchCluster are connected to it. - * @param idPort The SwitchPortTuple to check - * @return True if it is internal, false otherwise - */ + + @Override + public boolean isInternal(IOFSwitch sw, short port) { + return isInternal(new SwitchPortTuple(sw, port)); + } + @Override public boolean isInternal(SwitchPortTuple idPort) { lock.readLock().lock(); diff --git a/src/main/java/net/floodlightcontroller/util/MACAddress.java b/src/main/java/net/floodlightcontroller/util/MACAddress.java index f80f110d5..4ba9dade8 100644 --- a/src/main/java/net/floodlightcontroller/util/MACAddress.java +++ b/src/main/java/net/floodlightcontroller/util/MACAddress.java @@ -11,7 +11,7 @@ public class MACAddress { public static final int MAC_ADDRESS_LENGTH = 6; private byte[] address = new byte[MAC_ADDRESS_LENGTH]; - private MACAddress(byte[] address) { + public MACAddress(byte[] address) { this.address = Arrays.copyOf(address, MAC_ADDRESS_LENGTH); } diff --git a/src/main/java/org/openflow/util/HexString.java b/src/main/java/org/openflow/util/HexString.java index c39ed5f12..3a2f30fa9 100644 --- a/src/main/java/org/openflow/util/HexString.java +++ b/src/main/java/org/openflow/util/HexString.java @@ -38,12 +38,12 @@ public class HexString { return ret; } - public static String toHexString(long val) { + public static String toHexString(long val, int padTo) { char arr[] = Long.toHexString(val).toCharArray(); String ret = ""; // prepend the right number of leading zeros int i = 0; - for (; i < (16 - arr.length); i++) { + for (; i < (padTo * 2 - arr.length); i++) { ret += "0"; if ((i % 2) == 1) ret += ":"; @@ -53,7 +53,11 @@ public class HexString { if ((((i + j) % 2) == 1) && (j < (arr.length - 1))) ret += ":"; } - return ret; + return ret; + } + + public static String toHexString(long val) { + return toHexString(val, 8); } diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java index c9274ae08..4e559b775 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java @@ -17,9 +17,13 @@ package net.floodlightcontroller.devicemanager.internal; +import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; @@ -31,6 +35,10 @@ import static org.easymock.EasyMock.verify; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.devicemanager.DeviceAttachmentPoint; +import net.floodlightcontroller.devicemanager.IEntityClass; +import net.floodlightcontroller.devicemanager.IEntityClassifier; +import net.floodlightcontroller.devicemanager.IEntityClassifier.EntityField; +import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPacket; @@ -41,6 +49,7 @@ import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.topology.ITopology; import net.floodlightcontroller.topology.SwitchPortTuple; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.openflow.protocol.OFPacketIn; @@ -53,12 +62,9 @@ import org.openflow.protocol.OFPhysicalPort; * @author David Erickson (daviderickson@cs.stanford.edu) */ public class DeviceManagerImplTest extends FloodlightTestCase { - protected OFPacketIn packetIn, anotherPacketIn; - protected IPacket testPacket, anotherTestPacket; - protected byte[] testPacketSerialized, anotherTestPacketSerialized; - private IPacket thirdTestPacket; - private byte[] thirdTestPacketSerialized; - private OFPacketIn thirdPacketIn; + private OFPacketIn packetIn; + private IPacket testPacket; + private byte[] testPacketSerialized; MockFloodlightProvider mockFloodlightProvider; DeviceManagerImpl deviceManager; IStorageSource storageSource; @@ -93,41 +99,6 @@ public class DeviceManagerImplTest extends FloodlightTestCase { .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); this.testPacketSerialized = testPacket.serialize(); - // Another test packet with a different source IP - this.anotherTestPacket = new Ethernet() - .setSourceMACAddress("00:44:33:22:11:01") - .setDestinationMACAddress("00:11:22:33:44:55") - .setEtherType(Ethernet.TYPE_ARP) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01")) - .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1")) - .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) - .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); - this.anotherTestPacketSerialized = anotherTestPacket.serialize(); - - this.thirdTestPacket = new Ethernet() - .setSourceMACAddress("00:44:33:22:11:01") - .setDestinationMACAddress("00:11:22:33:44:55") - .setEtherType(Ethernet.TYPE_ARP) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01")) - .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.3")) - .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) - .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); - this.thirdTestPacketSerialized = thirdTestPacket.serialize(); - // Build the PacketIn this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN)) .setBufferId(-1) @@ -135,27 +106,116 @@ public class DeviceManagerImplTest extends FloodlightTestCase { .setPacketData(this.testPacketSerialized) .setReason(OFPacketInReason.NO_MATCH) .setTotalLength((short) this.testPacketSerialized.length); - - // Build the PacketIn - this.anotherPacketIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(this.anotherTestPacketSerialized) - .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) this.anotherTestPacketSerialized.length); - - // Build the PacketIn - this.thirdPacketIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(this.thirdTestPacketSerialized) - .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) this.thirdTestPacketSerialized.length); } + + static HashSet<EntityField> testKeyFields; + static { + testKeyFields = new HashSet<EntityField>(); + testKeyFields.add(EntityField.MAC); + testKeyFields.add(EntityField.VLAN); + testKeyFields.add(EntityField.SWITCH); + testKeyFields.add(EntityField.PORT); + } + + public static class TestEntityClass implements IEntityClass { + @Override + public Set<EntityField> getKeyFields() { + return testKeyFields; + } + } + + protected static IEntityClass testEC = new TestEntityClass(); + + public static class TestEntityClassifier extends DefaultEntityClassifier { + + @Override + public Collection<IEntityClass> classifyEntity(Entity entity) { + if (entity.switchDPID >= 10L) { + ArrayList<IEntityClass> l = new ArrayList<IEntityClass>(); + l.add(testEC); + return l; + } + return DefaultEntityClassifier.entityClasses; + } + @Override + public Set<EntityField> getKeyFields() { + return testKeyFields; + } + + } + + @Test + public void testEntityLearning() throws Exception { + deviceManager.setEntityClassifier(new TestEntityClassifier()); + + Entity entity1 = new Entity(1L, null, null, 1L, 1, new Date()); + Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date()); + Entity entity3 = new Entity(1L, null, 1, 10L, 1, new Date()); + Entity entity4 = new Entity(1L, null, 1, 1L, 1, new Date()); + Entity entity5 = new Entity(2L, (short)4, 1, 5L, 2, new Date()); + Entity entity6 = new Entity(2L, (short)4, 1, 50L, 3, new Date()); + + Device d1 = deviceManager.learnDeviceByEntity(entity1); + assertSame(d1, deviceManager.learnDeviceByEntity(entity1)); + assertSame(d1, deviceManager.findDeviceByEntity(entity1)); + assertArrayEquals(new IEntityClass[]{ DefaultEntityClassifier.entityClass }, + d1.entityClasses); + + assertEquals(1, deviceManager.getAllDevices().size()); + + Device d2 = deviceManager.learnDeviceByEntity(entity2); + assertFalse(d1.equals(d2)); + assertNotSame(d1, d2); + assertArrayEquals(new IEntityClass[]{ testEC }, + d2.entityClasses); + + assertEquals(2, deviceManager.getAllDevices().size()); + + Device d3 = deviceManager.learnDeviceByEntity(entity3); + assertNotSame(d2, d3); + assertArrayEquals(new IEntityClass[]{ testEC }, + d3.entityClasses); + assertArrayEquals(new Integer[] { 1 }, + d3.getIPv4Addresses()); + assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) }, + d3.getAttachmentPoints()); + + assertEquals(2, deviceManager.getAllDevices().size()); + + + Device d4 = deviceManager.learnDeviceByEntity(entity4); + assertNotSame(d1, d4); + assertArrayEquals(new IEntityClass[]{ DefaultEntityClassifier.entityClass }, + d4.entityClasses); + assertArrayEquals(new Integer[] { 1 }, + d4.getIPv4Addresses()); + assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, + d4.getAttachmentPoints()); + + assertEquals(2, deviceManager.getAllDevices().size()); + + Device d5 = deviceManager.learnDeviceByEntity(entity5); + assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 2) }, + d5.getAttachmentPoints()); + assertArrayEquals(new Short[] { (short) 4 }, + d5.getVlanId()); + assertEquals(2L, d5.getMACAddress()); + assertEquals("00:00:00:00:00:02", d5.getMACAddressString()); + + Device d6 = deviceManager.learnDeviceByEntity(entity6); + assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) }, + d6.getAttachmentPoints()); + assertArrayEquals(new Short[] { (short) 4 }, + d6.getVlanId()); + + assertEquals(4, deviceManager.getAllDevices().size()); + } @Test public void testAddHostAttachmentPoint() throws Exception { + fail(); + /* IOFSwitch mockSwitch = createMock(IOFSwitch.class); Device d = new Device(((Ethernet)this.testPacket).getSourceMACAddress()); Date cDate = new Date(); @@ -169,6 +229,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { assertEquals(dap1, d.getAttachmentPoint(spt1)); assertEquals(dap2, d.getAttachmentPoint(spt2)); assertEquals((int)2, d.getAttachmentPoints().size()); + */ } @Test @@ -181,17 +242,21 @@ public class DeviceManagerImplTest extends FloodlightTestCase { expect(mockSwitch.getId()).andReturn(1L).anyTimes(); expect(mockSwitch.getStringId()).andReturn("00:00:00:00:00:00:00:01").anyTimes(); ITopology mockTopology = createMock(ITopology.class); - expect(mockTopology.isInternal(new SwitchPortTuple(mockSwitch, 1))).andReturn(false); + expect(mockTopology.isInternal(mockSwitch, (short)1)).andReturn(false).anyTimes(); deviceManager.setTopology(mockTopology); Date currentDate = new Date(); // build our expected Device - Device device = new Device(); - device.setDataLayerAddress(dataLayerSource); - device.addAttachmentPoint(new SwitchPortTuple(mockSwitch, (short)1), currentDate); - Integer ipaddr = IPv4.toIPv4Address("192.168.1.1"); - device.addNetworkAddress(ipaddr, currentDate); + Device device = + new Device(new Long(deviceManager.deviceKeyCounter), + new Entity(Ethernet.toLong(dataLayerSource), + (short)5, + IPv4.toIPv4Address("192.168.1.1"), + 1L, + 1, + currentDate), + DefaultEntityClassifier.entityClasses); // Start recording the replay on the mocks replay(mockSwitch, mockTopology); @@ -202,9 +267,12 @@ public class DeviceManagerImplTest extends FloodlightTestCase { verify(mockSwitch, mockTopology); // Verify the device - Device rdevice = deviceManager.getDeviceByDataLayerAddress(dataLayerSource); + Device rdevice = (Device) + deviceManager.findDevice(Ethernet.toLong(dataLayerSource), null, + (short)5, null, null); assertEquals(device, rdevice); - assertEquals(new Short((short)5), rdevice.getVlanId()); + assertEquals(new Short((short)5), rdevice.getVlanId()[0]); + /* assertEquals(device, deviceManager.getDeviceByIPv4Address(ipaddr)); // move the port on this device @@ -232,86 +300,15 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // Reset the device cache deviceManager.clearAllDeviceStateFromMemory(); + */ + fail(); } - @Test - public void testDeviceRecoverFromStorage() throws Exception { - byte[] dataLayerSource = ((Ethernet)this.anotherTestPacket).getSourceMACAddress(); - - // Mock up our expected behavior - IOFSwitch mockSwitch = createMock(IOFSwitch.class); - ITopology mockTopology = createNiceMock(ITopology.class); - - expect(mockSwitch.getId()).andReturn(1L).anyTimes(); - expect(mockSwitch.getStringId()).andReturn("00:00:00:00:00:00:00:01").anyTimes(); - expect(mockTopology.isInternal(new SwitchPortTuple(mockSwitch, 1))).andReturn(false); - deviceManager.setTopology(mockTopology); - - // Start recording the replay on the mocks - replay(mockSwitch, mockTopology); - - // Add the switch so the list isn't empty - mockFloodlightProvider.getSwitches().put(mockSwitch.getId(), mockSwitch); - - // build our expected Device - Device device = new Device(); - Date currentDate = new Date(); - Integer ipaddr = IPv4.toIPv4Address("192.168.1.1"); - Integer ipaddr2 = IPv4.toIPv4Address("192.168.1.3"); - device.setDataLayerAddress(dataLayerSource); - SwitchPortTuple spt = new SwitchPortTuple(mockSwitch, (short)1); - DeviceAttachmentPoint dap = new DeviceAttachmentPoint(spt, currentDate); - device.addAttachmentPoint(dap); - device.addNetworkAddress(ipaddr, currentDate); - device.addNetworkAddress(ipaddr2, currentDate); - - // Get the listener and trigger the packet ins - mockFloodlightProvider.dispatchMessage(mockSwitch, this.anotherPacketIn); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.thirdPacketIn); - - // Verify the device - assertEquals(device, deviceManager.getDeviceByDataLayerAddress(dataLayerSource)); - assertEquals(device, deviceManager.getDeviceByIPv4Address(ipaddr)); - assertEquals(device, deviceManager.getDeviceByIPv4Address(ipaddr2)); - assertEquals(dap, device.getAttachmentPoint(spt)); - - // Reset the device cache - deviceManager.clearAllDeviceStateFromMemory(); - - // Verify the device - assertNull(deviceManager.getDeviceByDataLayerAddress(dataLayerSource)); - assertNull(deviceManager.getDeviceByIPv4Address(ipaddr)); - assertNull(deviceManager.getDeviceByIPv4Address(ipaddr2)); - - // Load the device cache from storage - deviceManager.readAllDeviceStateFromStorage(); - - // Verify the device - Device device2 = deviceManager.getDeviceByDataLayerAddress(dataLayerSource); - assertEquals(device, device2); - assertEquals(dap, device2.getAttachmentPoint(spt)); - - deviceManager.clearAllDeviceStateFromMemory(); - mockFloodlightProvider.setSwitches(new HashMap<Long,IOFSwitch>()); - deviceManager.removedSwitch(mockSwitch); - deviceManager.readAllDeviceStateFromStorage(); - - device2 = deviceManager.getDeviceByDataLayerAddress(dataLayerSource); - assertEquals(device, device2); - - assertNull(device2.getAttachmentPoint(spt)); - // The following two asserts seems to be incorrect, need to - // replace NULL check with the correct value TODO - //assertNull(deviceManager.getDeviceByIPv4Address(ipaddr)); - //assertNull(deviceManager.getDeviceByIPv4Address(ipaddr2)); - deviceManager.addedSwitch(mockSwitch); - assertEquals(dap, device.getAttachmentPoint(spt)); - assertEquals(device, deviceManager.getDeviceByIPv4Address(ipaddr)); - assertEquals(device, deviceManager.getDeviceByIPv4Address(ipaddr2)); - } @Test public void testDeviceUpdateLastSeenToStorage() throws Exception { + fail(); + /* deviceManager.clearAllDeviceStateFromMemory(); MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider(); @@ -352,10 +349,13 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // Make sure the last seen is after our date device = deviceManager.getDeviceByDataLayerAddress(dataLayerSource); assertTrue(device.getLastSeen().after(currentDate)); + */ } @Test public void testAttachmentPointFlapping() throws Exception { + fail(); + /* OFPhysicalPort port1 = new OFPhysicalPort(); OFPhysicalPort port2 = new OFPhysicalPort(); port1.setName("port1"); @@ -401,8 +401,9 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // Reset the device cache deviceManager.clearAllDeviceStateFromMemory(); + */ } - + /* private static final Map<String, Object> pcPort1; static { pcPort1 = new HashMap<String, Object>(); @@ -435,13 +436,15 @@ public class DeviceManagerImplTest extends FloodlightTestCase { pcPort2.get(DeviceManagerImpl.PC_ID_COLUMN_NAME)); deviceManager.readPortChannelConfigFromStorage(); } - + */ /** * The same test as testAttachmentPointFlapping except for port-channel * @throws Exception */ @Test public void testPortChannel() throws Exception { + fail(); + /* OFPhysicalPort port1 = new OFPhysicalPort(); OFPhysicalPort port2 = new OFPhysicalPort(); port1.setName("port1"); @@ -490,5 +493,6 @@ public class DeviceManagerImplTest extends FloodlightTestCase { deviceManager.clearAllDeviceStateFromMemory(); teardownPortChannel(); + */ } } -- GitLab