X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fadsal%2Fhosttracker_new%2Fimplementation%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fhosttracker%2Finternal%2FDevice.java;fp=opendaylight%2Fadsal%2Fhosttracker_new%2Fimplementation%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fhosttracker%2Finternal%2FDevice.java;h=8fcb988b6cd43a63530873c025a23d92cd2f067b;hb=42c32160bfd41de57189bb246fec5ffb48ed8e9e;hp=0000000000000000000000000000000000000000;hpb=edf5bfcee83c750853253ccfd991ba7000f5f65b;p=controller.git diff --git a/opendaylight/adsal/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java b/opendaylight/adsal/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java new file mode 100755 index 0000000000..8fcb988b6c --- /dev/null +++ b/opendaylight/adsal/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2011,2012 Big Switch Networks, Inc. + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html + * + * 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. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 org.opendaylight.controller.hosttracker.internal; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import org.opendaylight.controller.hosttracker.Entity; +import org.opendaylight.controller.hosttracker.IDevice; +import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField; +import org.opendaylight.controller.hosttracker.IEntityClass; +import org.opendaylight.controller.hosttracker.SwitchPort; +import org.opendaylight.controller.hosttracker.SwitchPort.ErrorStatus; +import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector; +import org.opendaylight.controller.sal.core.NodeConnector; +import org.opendaylight.controller.sal.utils.HexEncode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Concrete implementation of {@link IDevice} + * + * @author readams + */ +public class Device implements IDevice { + protected static Logger log = LoggerFactory.getLogger(Device.class); + public static final short VLAN_UNTAGGED = (short) 0xffff; + + private final Long deviceKey; + protected final DeviceManagerImpl deviceManager; + + protected final Entity[] entities; + private final IEntityClass entityClass; + + protected final String macAddressString; + // the vlan Ids from the entities of this device + protected final Short[] vlanIds; + protected volatile String dhcpClientName; + + private boolean staticHost; + + /** + * These are the old attachment points for the device that were valid no + * more than INACTIVITY_TIME ago. + */ + protected volatile List oldAPs; + /** + * The current attachment points for the device. + */ + protected volatile List attachmentPoints; + + // ************ + // 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 = HexEncode.longToHexString(entity + .getMacAddress()); + this.entityClass = entityClass; + Arrays.sort(this.entities); + + this.dhcpClientName = null; + this.oldAPs = null; + this.attachmentPoints = null; + + if (entity.getPort() != null) { + NodeConnector port = entity.getPort(); + + if (deviceManager.isValidAttachmentPoint(port)) { + AttachmentPoint ap; + ap = new AttachmentPoint(port, entity.getLastSeenTimestamp() + .getTime()); + + this.attachmentPoints = new ArrayList(); + this.attachmentPoints.add(ap); + } + } + vlanIds = computeVlandIds(); + } + + /** + * 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, + String dhcpClientName, Collection oldAPs, + Collection attachmentPoints, + Collection entities, IEntityClass entityClass) { + this.deviceManager = deviceManager; + this.deviceKey = deviceKey; + this.dhcpClientName = dhcpClientName; + this.entities = entities.toArray(new Entity[entities.size()]); + this.oldAPs = null; + this.attachmentPoints = null; + if (oldAPs != null) { + this.oldAPs = new ArrayList(oldAPs); + } + if (attachmentPoints != null) { + this.attachmentPoints = new ArrayList( + attachmentPoints); + } + this.macAddressString = HexEncode.longToHexString(this.entities[0] + .getMacAddress()); + this.entityClass = entityClass; + Arrays.sort(this.entities); + vlanIds = computeVlandIds(); + } + + /** + * Construct a new device consisting of the entities from the old device + * plus an additional entity. The caller needs to ensure that the additional + * entity is not already present in the array + * + * @param device + * the old device object + * @param newEntity + * the entity to add. newEntity must be have the same entity + * class as device + * @param insertionpoint + * if positive indicates the index in the entities array were the new + * entity should be inserted. If negative we will compute the correct + * insertion point + */ + public Device(Device device, Entity newEntity, int insertionpoint) { + this.deviceManager = device.deviceManager; + this.deviceKey = device.deviceKey; + this.dhcpClientName = device.dhcpClientName; + + this.entities = new Entity[device.entities.length + 1]; + if (insertionpoint < 0) { + insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1); + } + if (insertionpoint > 0) { + // insertion point is not the beginning: + // copy up to insertion point + System.arraycopy(device.entities, 0, this.entities, 0, + insertionpoint); + } + if (insertionpoint < device.entities.length) { + // insertion point is not the end + // copy from insertion point + System.arraycopy(device.entities, insertionpoint, this.entities, + insertionpoint + 1, device.entities.length - insertionpoint); + } + this.entities[insertionpoint] = newEntity; + /* + * this.entities = Arrays.copyOf(device.entities, + * device.entities.length + 1); this.entities[this.entities.length - 1] + * = newEntity; Arrays.sort(this.entities); + */ + this.oldAPs = null; + if (device.oldAPs != null) { + this.oldAPs = new ArrayList(device.oldAPs); + } + this.attachmentPoints = null; + if (device.attachmentPoints != null) { + this.attachmentPoints = new ArrayList( + device.attachmentPoints); + } + + this.macAddressString = HexEncode.longToHexString(this.entities[0] + .getMacAddress()); + + this.entityClass = device.entityClass; + vlanIds = computeVlandIds(); + } + + private Short[] computeVlandIds() { + if (entities.length == 1) { + if (entities[0].getVlan() != null) { + return new Short[] { entities[0].getVlan() }; + } else { + return new Short[] { Short.valueOf((short) -1) }; + } + } + + TreeSet vals = new TreeSet(); + for (Entity e : entities) { + if (e.getVlan() == null) { + vals.add((short) -1); + } else { + vals.add(e.getVlan()); + } + } + return vals.toArray(new Short[vals.size()]); + } + + /** + * Given a list of attachment points (apList), the procedure would return a + * map of attachment points for each L2 domain. L2 domain id is the key. + * + * @param apList + * @return + */ + private Map getAPMap(List apList) { + + if (apList == null) + return null; + // ITopologyService topology = deviceManager.topology; + + // Get the old attachment points and sort them. + List oldAP = new ArrayList(); + if (apList != null) + oldAP.addAll(apList); + + // Remove invalid attachment points before sorting. + List tempAP = new ArrayList(); + for (AttachmentPoint ap : oldAP) { + if (deviceManager.isValidAttachmentPoint(ap.getPort())) { + tempAP.add(ap); + } + } + oldAP = tempAP; + + Collections.sort(oldAP, deviceManager.apComparator); + + // Map of attachment point by L2 domain Id. + Map apMap = new HashMap(); + + for (int i = 0; i < oldAP.size(); ++i) { + AttachmentPoint ap = oldAP.get(i); + // if this is not a valid attachment point, continue + if (!deviceManager.isValidAttachmentPoint(ap.getPort())) + continue; + + // long id = topology.getL2DomainId(ap.getSw()); + // XXX - Missing functionality + long id = 0; + + apMap.put(id, ap); + } + + if (apMap.isEmpty()) + return null; + return apMap; + } + + /** + * Remove all attachment points that are older than INACTIVITY_INTERVAL from + * the list. + * + * @param apList + * @return + */ + private boolean removeExpiredAttachmentPoints(List apList) { + + List expiredAPs = new ArrayList(); + + if (apList == null) + return false; + + for (AttachmentPoint ap : apList) { + if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < System.currentTimeMillis()) { + expiredAPs.add(ap); + } + } + if (expiredAPs.size() > 0) { + apList.removeAll(expiredAPs); + return true; + } else { + return false; + } + } + + /** + * Get a list of duplicate attachment points, given a list of old attachment + * points and one attachment point per L2 domain. Given a true attachment + * point in the L2 domain, say trueAP, another attachment point in the same + * L2 domain, say ap, is duplicate if: 1. ap is inconsistent with trueAP, + * and 2. active time of ap is after that of trueAP; and 3. last seen time + * of ap is within the last INACTIVITY_INTERVAL + * + * @param oldAPList + * @param apMap + * @return + */ + List getDuplicateAttachmentPoints( + List oldAPList, Map apMap) { + List dupAPs = new ArrayList(); + long timeThreshold = System.currentTimeMillis() + - AttachmentPoint.INACTIVITY_INTERVAL; + + if (oldAPList == null || apMap == null) + return dupAPs; + + for (AttachmentPoint ap : oldAPList) { + long id = 0; + AttachmentPoint trueAP = apMap.get(id); + + if (trueAP == null) + continue; + boolean c = true; + boolean active = (ap.getActiveSince() > trueAP.getActiveSince()); + boolean last = ap.getLastSeen() > timeThreshold; + if (!c && active && last) { + dupAPs.add(ap); + } + } + + return dupAPs; + } + + /** + * Update the known attachment points. This method is called whenever + * topology changes. The method returns true if there's any change to the + * list of attachment points -- which indicates a possible device move. + * + * @return + */ + protected boolean updateAttachmentPoint() { + boolean moved = false; + this.oldAPs = attachmentPoints; + if (attachmentPoints == null || attachmentPoints.isEmpty()) + return false; + + List apList = new ArrayList(); + if (attachmentPoints != null) + apList.addAll(attachmentPoints); + Map newMap = getAPMap(apList); + if (newMap == null || newMap.size() != apList.size()) { + moved = true; + } + + // Prepare the new attachment point list. + if (moved) { + log.info("updateAttachmentPoint: ap {} newmap {} ", + attachmentPoints, newMap); + List newAPList = new ArrayList(); + if (newMap != null) + newAPList.addAll(newMap.values()); + this.attachmentPoints = newAPList; + } + + // Set the oldAPs to null. + return moved; + } + + /** + * Update the list of attachment points given that a new packet-in was seen + * from (sw, port) at time (lastSeen). The return value is true if there was + * any change to the list of attachment points for the device -- which + * indicates a device move. + * + * @param port + * @param lastSeen + * @return + */ + protected boolean updateAttachmentPoint(NodeConnector port, long lastSeen) { + // ITopologyService topology = deviceManager.topology; + List oldAPList; + List apList; + boolean oldAPFlag = false; + + if (!deviceManager.isValidAttachmentPoint(port)) + return false; + AttachmentPoint newAP = new AttachmentPoint(port, lastSeen); + // Copy the oldAP and ap list. + apList = new ArrayList(); + if (attachmentPoints != null) + apList.addAll(attachmentPoints); + oldAPList = new ArrayList(); + if (oldAPs != null) + oldAPList.addAll(oldAPs); + + // if the sw, port is in old AP, remove it from there + // and update the lastSeen in that object. + if (oldAPList.contains(newAP)) { + int index = oldAPList.indexOf(newAP); + newAP = oldAPList.remove(index); + newAP.setLastSeen(lastSeen); + this.oldAPs = oldAPList; + oldAPFlag = true; + } + + // newAP now contains the new attachment point. + + // Get the APMap is null or empty. + Map apMap = getAPMap(apList); + if (apMap == null || apMap.isEmpty()) { + apList.add(newAP); + attachmentPoints = apList; + // there are no old attachment points - since the device exists, + // this + // may be because the host really moved (so the old AP port went + // down); + // or it may be because the switch restarted (so old APs were + // nullified). + // For now we will treat both cases as host moved. + return true; + } + + // XXX - Missing functionality + long id = 0; + AttachmentPoint oldAP = apMap.get(id); + + if (oldAP == null) // No attachment on this L2 domain. + { + apList = new ArrayList(); + apList.addAll(apMap.values()); + apList.add(newAP); + this.attachmentPoints = apList; + return true; // new AP found on an L2 island. + } + + // There is already a known attachment point on the same L2 island. + // we need to compare oldAP and newAP. + if (oldAP.equals(newAP)) { + // nothing to do here. just the last seen has to be changed. + if (newAP.lastSeen > oldAP.lastSeen) { + oldAP.setLastSeen(newAP.lastSeen); + } + this.attachmentPoints = new ArrayList( + apMap.values()); + return false; // nothing to do here. + } + + int x = deviceManager.apComparator.compare(oldAP, newAP); + if (x < 0) { + // newAP replaces oldAP. + apMap.put(id, newAP); + this.attachmentPoints = new ArrayList( + apMap.values()); + + oldAPList = new ArrayList(); + if (oldAPs != null) + oldAPList.addAll(oldAPs); + oldAPList.add(oldAP); + this.oldAPs = oldAPList; + return true; + } else if (oldAPFlag) { + // retain oldAP as is. Put the newAP in oldAPs for flagging + // possible duplicates. + oldAPList = new ArrayList(); + if (oldAPs != null) + oldAPList.addAll(oldAPs); + // Add to oldAPList only if it was picked up from the oldAPList + oldAPList.add(newAP); + this.oldAPs = oldAPList; + return true; + } + return false; + } + + /** + * Delete (sw,port) from the list of list of attachment points and oldAPs. + * + * @param port + * @return + */ + public boolean deleteAttachmentPoint(NodeConnector port) { + AttachmentPoint ap = new AttachmentPoint(port, 0); + + if (this.oldAPs != null) { + ArrayList apList = new ArrayList(); + apList.addAll(this.oldAPs); + int index = apList.indexOf(ap); + if (index > 0) { + apList.remove(index); + this.oldAPs = apList; + } + } + + if (this.attachmentPoints != null) { + ArrayList apList = new ArrayList(); + apList.addAll(this.attachmentPoints); + int index = apList.indexOf(ap); + if (index > 0) { + apList.remove(index); + this.attachmentPoints = apList; + return true; + } + } + return false; + } + + // ******* + // IDevice + // ******* + + @Override + public SwitchPort[] getOldAP() { + List sp = new ArrayList(); + SwitchPort[] returnSwitchPorts = new SwitchPort[] {}; + if (oldAPs == null) + return returnSwitchPorts; + if (oldAPs.isEmpty()) + return returnSwitchPorts; + + // copy ap list. + List oldAPList; + oldAPList = new ArrayList(); + + if (oldAPs != null) + oldAPList.addAll(oldAPs); + removeExpiredAttachmentPoints(oldAPList); + + if (oldAPList != null) { + for (AttachmentPoint ap : oldAPList) { + SwitchPort swport = new SwitchPort(ap.getPort()); + sp.add(swport); + } + } + return sp.toArray(new SwitchPort[sp.size()]); + } + + @Override + public SwitchPort[] getAttachmentPoints() { + return getAttachmentPoints(false); + } + + @Override + public SwitchPort[] getAttachmentPoints(boolean includeError) { + List sp = new ArrayList(); + SwitchPort[] returnSwitchPorts = new SwitchPort[] {}; + if (attachmentPoints == null) + return returnSwitchPorts; + if (attachmentPoints.isEmpty()) + return returnSwitchPorts; + + // copy ap list. + List apList = attachmentPoints; + + if (apList != null) { + for (AttachmentPoint ap : apList) { + SwitchPort swport = new SwitchPort(ap.getPort()); + sp.add(swport); + } + } + + if (!includeError) + return sp.toArray(new SwitchPort[sp.size()]); + + List oldAPList; + oldAPList = new ArrayList(); + + if (oldAPs != null) + oldAPList.addAll(oldAPs); + + if (removeExpiredAttachmentPoints(oldAPList)) + this.oldAPs = oldAPList; + + List dupList; + // get AP map. + Map apMap = getAPMap(apList); + dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap); + if (dupList != null) { + for (AttachmentPoint ap : dupList) { + SwitchPort swport = new SwitchPort(ap.getPort(), + ErrorStatus.DUPLICATE_DEVICE); + sp.add(swport); + } + } + return sp.toArray(new SwitchPort[sp.size()]); + } + + @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() { + return Arrays.copyOf(vlanIds, vlanIds.length); + } + + static final EnumSet 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 vals = new TreeSet(); + 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 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 Short[] getSwitchPortVlanIds(SwitchPort swp) { + TreeSet vals = new TreeSet(); + for (Entity e : entities) { + if (e.getPort().equals(swp.getPort())) { + if (e.getVlan() == null) { + vals.add(VLAN_UNTAGGED); + } + else { + vals.add(e.getVlan()); + } + } + } + return vals.toArray(new Short[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; + } + + public String getDHCPClientName() { + return dhcpClientName; + } + + // *************** + // 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; + } + + public HostNodeConnector toHostNodeConnector() { + Integer[] ipv4s = this.getIPv4Addresses(); + try { + Entity e = this.entities[this.entities.length-1]; + NodeConnector n = null; + if(e!=null) + n = e.getPort(); + InetAddress ip = InetAddress.getByName(ipv4s[ipv4s.length - 1] + .toString()); + byte[] macAddr = macLongToByte(this.getMACAddress()); + HostNodeConnector nc = new HostNodeConnector(macAddr, ip, n, + (short) 0); + nc.setStaticHost(this.isStaticHost()); + return nc; + } catch (Exception e) { + return null; + } + } + + private byte[] macLongToByte(long mac) { + byte[] macAddr = new byte[6]; + for (int i = 0; i < 6; i++) { + macAddr[5 - i] = (byte) (mac >> (8 * i)); + } + return macAddr; + } + + public boolean isStaticHost(){ + return this.staticHost; + } + + public void setStaticHost(boolean isStatic){ + this.staticHost = isStatic; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Device [deviceKey="); + builder.append(deviceKey); + builder.append(", entityClass="); + builder.append(entityClass.getName()); + builder.append(", MAC="); + builder.append(macAddressString); + builder.append(", IPs=["); + boolean isFirst = true; + for (Integer ip : getIPv4Addresses()) { + if (!isFirst) + builder.append(", "); + isFirst = false; + builder.append(ip); + } + builder.append("], APs="); + builder.append(Arrays.toString(getAttachmentPoints(true))); + builder.append("]"); + return builder.toString(); + } +}