/** * 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 * 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.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.Iterator; import java.util.TreeSet; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.openflow.util.HexString; import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField; import net.floodlightcontroller.devicemanager.web.DeviceSerializer; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.SwitchPort; import static net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus.*; import net.floodlightcontroller.topology.ITopologyService; /** * Concrete implementation of {@link IDevice} * @author readams */ @JsonSerialize(using=DeviceSerializer.class) public class Device implements IDevice { protected Long deviceKey; protected DeviceManagerImpl deviceManager; protected Entity[] entities; protected IEntityClass[] entityClasses; protected String macAddressString; // ************ // Constructors // ************ /** * Create a device from an entities * @param deviceManager the device manager for this device * @param deviceKey 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(DeviceManagerImpl deviceManager, Long deviceKey, Entity entity, Collection<IEntityClass> entityClasses) { this.deviceManager = deviceManager; 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 from a set of entities * @param deviceManager the device manager for this device * @param deviceKey the unique identifier for this device object * @param entities the initial entities for the device * @param entityClasses the entity classes associated with the entity */ public Device(DeviceManagerImpl deviceManager, Long deviceKey, Collection<Entity> entities, IEntityClass[] entityClasses) { this.deviceManager = deviceManager; this.deviceKey = deviceKey; this.entities = entities.toArray(new Entity[entities.size()]); this.macAddressString = HexString.toHexString(this.entities[0].getMacAddress(), 6); this.entityClasses = entityClasses; Arrays.sort(this.entities); } /** * Construct a new device consisting of the entities from the old device * plus an additional entity * @param device the old device object * @param newEntity the entity to add * @param entityClasses the entity classes associated with the entities */ public Device(Device device, Entity newEntity, Collection<IEntityClass> entityClasses) { this.deviceManager = device.deviceManager; this.deviceKey = device.deviceKey; this.entities = Arrays.<Entity>copyOf(device.entities, device.entities.length + 1); this.entities[this.entities.length - 1] = newEntity; Arrays.sort(this.entities); this.macAddressString = HexString.toHexString(this.entities[0].getMacAddress(), 6); if (entityClasses != null && entityClasses.size() > device.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) { if (entities[0].getVlan() != null) { return new Short[]{ entities[0].getVlan() }; } else { return new Short[] { Short.valueOf((short)-1) }; } } TreeSet<Short> vals = new TreeSet<Short>(); for (Entity e : entities) { if (e.getVlan() == null) vals.add((short)-1); else vals.add(e.getVlan()); } return vals.toArray(new Short[vals.size()]); } static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4); @Override public Integer[] getIPv4Addresses() { // XXX - TODO we can cache this result. Let's find out if this // is really a performance bottleneck first though. if (entities.length == 1) { if (entities[0].getIpv4Address() != null) { return new Integer[]{ entities[0].getIpv4Address() }; } else { return new Integer[0]; } } TreeSet<Integer> vals = new TreeSet<Integer>(); for (Entity e : entities) { if (e.getIpv4Address() == null) continue; // We have an IP address only if among the devices within the class // we have the most recent entity with that IP. boolean validIP = true; for (IEntityClass clazz : entityClasses) { Iterator<Device> devices = deviceManager.queryClassByEntity(clazz, ipv4Fields, e); while (devices.hasNext()) { Device d = devices.next(); for (Entity se : d.entities) { if (se.ipv4Address != null && se.ipv4Address.equals(e.ipv4Address) && se.lastSeenTimestamp != null && 0 < se.lastSeenTimestamp. compareTo(e.lastSeenTimestamp)) { validIP = false; break; } } if (!validIP) break; } if (!validIP) break; } if (validIP) vals.add(e.getIpv4Address()); } return vals.toArray(new Integer[vals.size()]); } @Override public SwitchPort[] getAttachmentPoints() { return getAttachmentPoints(false); } @Override public SwitchPort[] getAttachmentPoints(boolean includeError) { // XXX - TODO we can cache this result. Let's find out if this // is really a performance bottleneck first though. if (entities.length == 1) { Long dpid = entities[0].getSwitchDPID(); Integer port = entities[0].getSwitchPort(); if (dpid != null && port != null && deviceManager.isValidAttachmentPoint(dpid, port)) { SwitchPort sp = new SwitchPort(dpid, port); return new SwitchPort[] { sp }; } else { return new SwitchPort[0]; } } // Find the most recent attachment point for each cluster Entity[] clentities = Arrays.<Entity>copyOf(entities, entities.length); Arrays.sort(clentities, deviceManager.apComparator); ArrayList<SwitchPort> blocked = null; ArrayList<SwitchPort> clusterBlocked = null; if (includeError) { blocked = new ArrayList<SwitchPort>(); clusterBlocked = new ArrayList<SwitchPort>(); } ITopologyService topology = deviceManager.topology; long prevCluster = 0; int clEntIndex = -1; Entity prev = null; long latestLastSeen = 0; for (int i = 0; i < clentities.length; i++) { Entity cur = clentities[i]; Long dpid = cur.getSwitchDPID(); Integer port = cur.getSwitchPort(); if (dpid == null || port == null || !deviceManager.isValidAttachmentPoint(dpid, port) || (prev != null && topology.isConsistent(prev.getSwitchDPID().longValue(), prev.getSwitchPort().shortValue(), dpid.longValue(), port.shortValue())) ) continue; long curCluster = topology.getL2DomainId(cur.switchDPID); if (prevCluster != curCluster) { prev = null; latestLastSeen = 0; clEntIndex += 1; if (includeError) { blocked.addAll(clusterBlocked); clusterBlocked.clear(); } } if (prev != null && !(dpid.equals(prev.getSwitchDPID()) && port.equals(prev.getSwitchPort())) && !topology.isInSameBroadcastDomain(dpid.longValue(), port.shortValue(), prev.getSwitchDPID().longValue(), prev.getSwitchPort().shortValue()) && !topology.isConsistent(prev.getSwitchDPID().longValue(), prev.getSwitchPort().shortValue(), dpid.longValue(), port.shortValue())) { long curActive = deviceManager.apComparator. getEffTS(cur, cur.getActiveSince()); if (latestLastSeen > 0 && curActive > 0 && 0 < Long.valueOf(latestLastSeen).compareTo(curActive)) { // If the previous and current are both active at the same // time (i.e. the last seen timestamp of previous is // greater than active timestamp of current item, we want // to suppress rapid flapping between the two points. We // choose arbitrarily based on criteria other than // timestamp; the compareTo for entity should fit the bill. Entity block = prev; if (0 < prev.compareTo(cur)) { block = cur; cur = prev; } if (includeError) { boolean alreadyBlocked = false; for (SwitchPort bl : clusterBlocked) { if (dpid.equals(bl.getSwitchDPID()) && port.equals(bl.getPort())) { alreadyBlocked = true; break; } } if (!alreadyBlocked) { SwitchPort blap = new SwitchPort(block.getSwitchDPID(), block.getSwitchPort(), DUPLICATE_DEVICE); clusterBlocked.add(blap); } } } else { if (includeError) { clusterBlocked.clear(); } latestLastSeen = 0; } } prev = clentities[clEntIndex] = cur; prevCluster = curCluster; long prevLastSeen = deviceManager.apComparator. getEffTS(prev, prev.getLastSeenTimestamp()); if (latestLastSeen < prevLastSeen) latestLastSeen = prevLastSeen; } if (clEntIndex < 0) { return new SwitchPort[0]; } ArrayList<SwitchPort> vals = new ArrayList<SwitchPort>(clEntIndex + 1); for (int i = 0; i <= clEntIndex; i++) { Entity e = clentities[i]; if (e.getSwitchDPID() != null && e.getSwitchPort() != null) { SwitchPort sp = new SwitchPort(e.getSwitchDPID(), e.getSwitchPort()); vals.add(sp); } } if (includeError) { vals.addAll(blocked); vals.addAll(clusterBlocked); } return vals.toArray(new SwitchPort[vals.size()]); } @Override public Date getLastSeen() { Date d = null; for (int i = 0; i < entities.length; i++) { if (d == null || entities[i].getLastSeenTimestamp().compareTo(d) > 0) d = entities[i].getLastSeenTimestamp(); } return d; } // *************** // Getters/Setters // *************** public IEntityClass[] getEntityClasses() { return entityClasses; } public Entity[] getEntities() { return entities; } // *************** // Utility Methods // *************** /** * Check whether the device contains the specified entity * @param entity the entity to search for * @return the index of the entity, or <0 if not found */ protected int entityIndex(Entity entity) { return Arrays.binarySearch(entities, entity); } // ****** // Object // ****** @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(entities); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Device other = (Device) obj; if (!deviceKey.equals(other.deviceKey)) return false; if (!Arrays.equals(entities, other.entities)) return false; return true; } @Override public String toString() { return "Device [entities=" + Arrays.toString(entities) + "]"; } }