-
Srinivasan Ramasubramanian authored
Bug fix: prevCluster assignment is changed to reflect the L2 domain ID of the prev entity. It was possible for cur and prev to be swapped when duplicate attachment points were detected, where the prevCluster could have the wrong assignment.
Srinivasan Ramasubramanian authoredBug fix: prevCluster assignment is changed to reflect the L2 domain ID of the prev entity. It was possible for cur and prev to be swapped when duplicate attachment points were detected, where the prevCluster could have the wrong assignment.
Device.java 14.80 KiB
/**
* 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 entityClass;
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 entityClass the entity classes associated with the entity
*/
public Device(DeviceManagerImpl deviceManager,
Long deviceKey,
Entity entity,
IEntityClass entityClass) {
this.deviceManager = deviceManager;
this.deviceKey = deviceKey;
this.entities = new Entity[] {entity};
this.macAddressString =
HexString.toHexString(entity.getMacAddress(), 6);
this.entityClass = entityClass;
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 entityClass the entity class associated with the entities
*/
public Device(DeviceManagerImpl deviceManager,
Long deviceKey,
Collection<Entity> entities,
IEntityClass entityClass) {
this.deviceManager = deviceManager;
this.deviceKey = deviceKey;
this.entities = entities.toArray(new Entity[entities.size()]);
this.macAddressString =
HexString.toHexString(this.entities[0].getMacAddress(), 6);
this.entityClass = entityClass;
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. newEntity must be have the same
* entity class as device
*/
public Device(Device device,
Entity newEntity) {
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);
this.entityClass = device.entityClass;
}
// *******
// 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.
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;
Iterator<Device> devices =
deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
while (devices.hasNext()) {
Device d = devices.next();
if (deviceKey.equals(d.getDeviceKey()))
continue;
for (Entity se : d.entities) {
if (se.getIpv4Address() != null &&
se.getIpv4Address().equals(e.getIpv4Address()) &&
se.getLastSeenTimestamp() != null &&
0 < se.getLastSeenTimestamp().
compareTo(e.getLastSeenTimestamp())) {
validIP = false;
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.getSwitchDPID());
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 = topology.getL2DomainId(prev.getSwitchDPID());
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
// ***************
@Override
public IEntityClass getEntityClass() {
return entityClass;
}
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 [entityClass=" + entityClass.getName() +
" entities=" + Arrays.toString(entities) + "]";
}
}