2 * Copyright (c) 2011,2012 Big Switch Networks, Inc.
4 * Licensed under the Eclipse Public License, Version 1.0 (the
5 * "License"); you may not use this file except in compliance with the
6 * License. You may obtain a copy of the License at
8 * http://www.eclipse.org/legal/epl-v10.html
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 * implied. See the License for the specific language governing
14 * permissions and limitations under the License.
16 * This file incorporates work covered by the following copyright and
19 * Originally created by David Erickson, Stanford University
21 * Licensed under the Apache License, Version 2.0 (the "License");
22 * you may not use this file except in compliance with the
23 * License. You may obtain a copy of the License at
25 * http://www.apache.org/licenses/LICENSE-2.0
27 * Unless required by applicable law or agreed to in writing,
28 * software distributed under the License is distributed on an "AS
29 * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
30 * express or implied. See the License for the specific language
31 * governing permissions and limitations under the License.
34 package org.opendaylight.controller.hosttracker.internal;
36 import java.net.InetAddress;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.Date;
42 import java.util.EnumSet;
43 import java.util.HashMap;
44 import java.util.Iterator;
45 import java.util.List;
47 import java.util.TreeSet;
49 import org.opendaylight.controller.hosttracker.Entity;
50 import org.opendaylight.controller.hosttracker.IDevice;
51 import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
52 import org.opendaylight.controller.hosttracker.IEntityClass;
53 import org.opendaylight.controller.hosttracker.SwitchPort;
54 import org.opendaylight.controller.hosttracker.SwitchPort.ErrorStatus;
55 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
56 import org.opendaylight.controller.sal.core.NodeConnector;
57 import org.opendaylight.controller.sal.utils.HexEncode;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * Concrete implementation of {@link IDevice}
66 public class Device implements IDevice {
67 protected static Logger log = LoggerFactory.getLogger(Device.class);
68 public static final short VLAN_UNTAGGED = (short) 0xffff;
70 private final Long deviceKey;
71 protected final DeviceManagerImpl deviceManager;
73 protected final Entity[] entities;
74 private final IEntityClass entityClass;
76 protected final String macAddressString;
77 // the vlan Ids from the entities of this device
78 protected final Short[] vlanIds;
79 protected volatile String dhcpClientName;
81 private boolean staticHost;
84 * These are the old attachment points for the device that were valid no
85 * more than INACTIVITY_TIME ago.
87 protected volatile List<AttachmentPoint> oldAPs;
89 * The current attachment points for the device.
91 protected volatile List<AttachmentPoint> attachmentPoints;
98 * Create a device from an entities
100 * @param deviceManager
101 * the device manager for this device
103 * the unique identifier for this device object
105 * the initial entity for the device
107 * the entity classes associated with the entity
109 public Device(DeviceManagerImpl deviceManager, Long deviceKey,
110 Entity entity, IEntityClass entityClass) {
111 this.deviceManager = deviceManager;
112 this.deviceKey = deviceKey;
113 this.entities = new Entity[] { entity };
114 this.macAddressString = HexEncode.longToHexString(entity
116 this.entityClass = entityClass;
117 Arrays.sort(this.entities);
119 this.dhcpClientName = null;
121 this.attachmentPoints = null;
123 if (entity.getPort() != null) {
124 NodeConnector port = entity.getPort();
126 if (deviceManager.isValidAttachmentPoint(port)) {
128 ap = new AttachmentPoint(port, entity.getLastSeenTimestamp()
131 this.attachmentPoints = new ArrayList<AttachmentPoint>();
132 this.attachmentPoints.add(ap);
135 vlanIds = computeVlandIds();
139 * Create a device from a set of entities
141 * @param deviceManager
142 * the device manager for this device
144 * the unique identifier for this device object
146 * the initial entities for the device
148 * the entity class associated with the entities
150 public Device(DeviceManagerImpl deviceManager, Long deviceKey,
151 String dhcpClientName, Collection<AttachmentPoint> oldAPs,
152 Collection<AttachmentPoint> attachmentPoints,
153 Collection<Entity> entities, IEntityClass entityClass) {
154 this.deviceManager = deviceManager;
155 this.deviceKey = deviceKey;
156 this.dhcpClientName = dhcpClientName;
157 this.entities = entities.toArray(new Entity[entities.size()]);
159 this.attachmentPoints = null;
160 if (oldAPs != null) {
161 this.oldAPs = new ArrayList<AttachmentPoint>(oldAPs);
163 if (attachmentPoints != null) {
164 this.attachmentPoints = new ArrayList<AttachmentPoint>(
167 this.macAddressString = HexEncode.longToHexString(this.entities[0]
169 this.entityClass = entityClass;
170 Arrays.sort(this.entities);
171 vlanIds = computeVlandIds();
175 * Construct a new device consisting of the entities from the old device
176 * plus an additional entity. The caller needs to ensure that the additional
177 * entity is not already present in the array
180 * the old device object
182 * the entity to add. newEntity must be have the same entity
184 * @param insertionpoint
185 * if positive indicates the index in the entities array were the new
186 * entity should be inserted. If negative we will compute the correct
189 public Device(Device device, Entity newEntity, int insertionpoint) {
190 this.deviceManager = device.deviceManager;
191 this.deviceKey = device.deviceKey;
192 this.dhcpClientName = device.dhcpClientName;
194 this.entities = new Entity[device.entities.length + 1];
195 if (insertionpoint < 0) {
196 insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1);
198 if (insertionpoint > 0) {
199 // insertion point is not the beginning:
200 // copy up to insertion point
201 System.arraycopy(device.entities, 0, this.entities, 0,
204 if (insertionpoint < device.entities.length) {
205 // insertion point is not the end
206 // copy from insertion point
207 System.arraycopy(device.entities, insertionpoint, this.entities,
208 insertionpoint + 1, device.entities.length - insertionpoint);
210 this.entities[insertionpoint] = newEntity;
212 * this.entities = Arrays.<Entity>copyOf(device.entities,
213 * device.entities.length + 1); this.entities[this.entities.length - 1]
214 * = newEntity; Arrays.sort(this.entities);
217 if (device.oldAPs != null) {
218 this.oldAPs = new ArrayList<AttachmentPoint>(device.oldAPs);
220 this.attachmentPoints = null;
221 if (device.attachmentPoints != null) {
222 this.attachmentPoints = new ArrayList<AttachmentPoint>(
223 device.attachmentPoints);
226 this.macAddressString = HexEncode.longToHexString(this.entities[0]
229 this.entityClass = device.entityClass;
230 vlanIds = computeVlandIds();
233 private Short[] computeVlandIds() {
234 if (entities.length == 1) {
235 if (entities[0].getVlan() != null) {
236 return new Short[] { entities[0].getVlan() };
238 return new Short[] { Short.valueOf((short) -1) };
242 TreeSet<Short> vals = new TreeSet<Short>();
243 for (Entity e : entities) {
244 if (e.getVlan() == null) {
245 vals.add((short) -1);
247 vals.add(e.getVlan());
250 return vals.toArray(new Short[vals.size()]);
254 * Given a list of attachment points (apList), the procedure would return a
255 * map of attachment points for each L2 domain. L2 domain id is the key.
260 private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
264 // ITopologyService topology = deviceManager.topology;
266 // Get the old attachment points and sort them.
267 List<AttachmentPoint> oldAP = new ArrayList<AttachmentPoint>();
269 oldAP.addAll(apList);
271 // Remove invalid attachment points before sorting.
272 List<AttachmentPoint> tempAP = new ArrayList<AttachmentPoint>();
273 for (AttachmentPoint ap : oldAP) {
274 if (deviceManager.isValidAttachmentPoint(ap.getPort())) {
280 Collections.sort(oldAP, deviceManager.apComparator);
282 // Map of attachment point by L2 domain Id.
283 Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
285 for (int i = 0; i < oldAP.size(); ++i) {
286 AttachmentPoint ap = oldAP.get(i);
287 // if this is not a valid attachment point, continue
288 if (!deviceManager.isValidAttachmentPoint(ap.getPort()))
291 // long id = topology.getL2DomainId(ap.getSw());
292 // XXX - Missing functionality
304 * Remove all attachment points that are older than INACTIVITY_INTERVAL from
310 private boolean removeExpiredAttachmentPoints(List<AttachmentPoint> apList) {
312 List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
317 for (AttachmentPoint ap : apList) {
318 if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < System.currentTimeMillis()) {
322 if (expiredAPs.size() > 0) {
323 apList.removeAll(expiredAPs);
331 * Get a list of duplicate attachment points, given a list of old attachment
332 * points and one attachment point per L2 domain. Given a true attachment
333 * point in the L2 domain, say trueAP, another attachment point in the same
334 * L2 domain, say ap, is duplicate if: 1. ap is inconsistent with trueAP,
335 * and 2. active time of ap is after that of trueAP; and 3. last seen time
336 * of ap is within the last INACTIVITY_INTERVAL
342 List<AttachmentPoint> getDuplicateAttachmentPoints(
343 List<AttachmentPoint> oldAPList, Map<Long, AttachmentPoint> apMap) {
344 // ITopologyService topology = deviceManager.topology;
345 List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
346 long timeThreshold = System.currentTimeMillis()
347 - AttachmentPoint.INACTIVITY_INTERVAL;
349 if (oldAPList == null || apMap == null)
352 for (AttachmentPoint ap : oldAPList) {
353 // XXX - Missing functionality
354 // long id = topology.getL2DomainId(ap.getSw());
356 AttachmentPoint trueAP = apMap.get(id);
360 // XXX - Missing functionality
361 // boolean c = (topology.isConsistent(trueAP.getSw(),
363 // ap.getSw(), ap.getPort()));
365 boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
366 boolean last = ap.getLastSeen() > timeThreshold;
367 if (!c && active && last) {
376 * Update the known attachment points. This method is called whenever
377 * topology changes. The method returns true if there's any change to the
378 * list of attachment points -- which indicates a possible device move.
382 protected boolean updateAttachmentPoint() {
383 boolean moved = false;
384 this.oldAPs = attachmentPoints;
385 if (attachmentPoints == null || attachmentPoints.isEmpty())
388 List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
389 if (attachmentPoints != null)
390 apList.addAll(attachmentPoints);
391 Map<Long, AttachmentPoint> newMap = getAPMap(apList);
392 if (newMap == null || newMap.size() != apList.size()) {
396 // Prepare the new attachment point list.
398 log.info("updateAttachmentPoint: ap {} newmap {} ",
399 attachmentPoints, newMap);
400 List<AttachmentPoint> newAPList = new ArrayList<AttachmentPoint>();
402 newAPList.addAll(newMap.values());
403 this.attachmentPoints = newAPList;
406 // Set the oldAPs to null.
411 * Update the list of attachment points given that a new packet-in was seen
412 * from (sw, port) at time (lastSeen). The return value is true if there was
413 * any change to the list of attachment points for the device -- which
414 * indicates a device move.
420 protected boolean updateAttachmentPoint(NodeConnector port, long lastSeen) {
421 // ITopologyService topology = deviceManager.topology;
422 List<AttachmentPoint> oldAPList;
423 List<AttachmentPoint> apList;
424 boolean oldAPFlag = false;
426 if (!deviceManager.isValidAttachmentPoint(port))
428 AttachmentPoint newAP = new AttachmentPoint(port, lastSeen);
429 // Copy the oldAP and ap list.
430 apList = new ArrayList<AttachmentPoint>();
431 if (attachmentPoints != null)
432 apList.addAll(attachmentPoints);
433 oldAPList = new ArrayList<AttachmentPoint>();
435 oldAPList.addAll(oldAPs);
437 // if the sw, port is in old AP, remove it from there
438 // and update the lastSeen in that object.
439 if (oldAPList.contains(newAP)) {
440 int index = oldAPList.indexOf(newAP);
441 newAP = oldAPList.remove(index);
442 newAP.setLastSeen(lastSeen);
443 this.oldAPs = oldAPList;
447 // newAP now contains the new attachment point.
449 // Get the APMap is null or empty.
450 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
451 if (apMap == null || apMap.isEmpty()) {
453 attachmentPoints = apList;
454 // there are no old attachment points - since the device exists,
456 // may be because the host really moved (so the old AP port went
458 // or it may be because the switch restarted (so old APs were
460 // For now we will treat both cases as host moved.
464 // XXX - Missing functionality
465 // long id = topology.getL2DomainId(sw);
467 AttachmentPoint oldAP = apMap.get(id);
469 if (oldAP == null) // No attachment on this L2 domain.
471 apList = new ArrayList<AttachmentPoint>();
472 apList.addAll(apMap.values());
474 this.attachmentPoints = apList;
475 return true; // new AP found on an L2 island.
478 // There is already a known attachment point on the same L2 island.
479 // we need to compare oldAP and newAP.
480 if (oldAP.equals(newAP)) {
481 // nothing to do here. just the last seen has to be changed.
482 if (newAP.lastSeen > oldAP.lastSeen) {
483 oldAP.setLastSeen(newAP.lastSeen);
485 this.attachmentPoints = new ArrayList<AttachmentPoint>(
487 return false; // nothing to do here.
490 int x = deviceManager.apComparator.compare(oldAP, newAP);
492 // newAP replaces oldAP.
493 apMap.put(id, newAP);
494 this.attachmentPoints = new ArrayList<AttachmentPoint>(
497 oldAPList = new ArrayList<AttachmentPoint>();
499 oldAPList.addAll(oldAPs);
500 oldAPList.add(oldAP);
501 this.oldAPs = oldAPList;
502 // XXX - Missing functionality
503 // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
505 // newAP.getSw(), newAP.getPort()))
506 // return true; // attachment point changed.
508 } else if (oldAPFlag) {
509 // retain oldAP as is. Put the newAP in oldAPs for flagging
510 // possible duplicates.
511 oldAPList = new ArrayList<AttachmentPoint>();
513 oldAPList.addAll(oldAPs);
514 // Add to oldAPList only if it was picked up from the oldAPList
515 oldAPList.add(newAP);
516 this.oldAPs = oldAPList;
517 // XXX - Missing functionality
518 // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
520 // newAP.getSw(), newAP.getPort()))
521 // return true; // attachment point changed.
528 * Delete (sw,port) from the list of list of attachment points and oldAPs.
533 public boolean deleteAttachmentPoint(NodeConnector port) {
534 AttachmentPoint ap = new AttachmentPoint(port, 0);
536 if (this.oldAPs != null) {
537 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
538 apList.addAll(this.oldAPs);
539 int index = apList.indexOf(ap);
541 apList.remove(index);
542 this.oldAPs = apList;
546 if (this.attachmentPoints != null) {
547 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
548 apList.addAll(this.attachmentPoints);
549 int index = apList.indexOf(ap);
551 apList.remove(index);
552 this.attachmentPoints = apList;
564 public SwitchPort[] getOldAP() {
565 List<SwitchPort> sp = new ArrayList<SwitchPort>();
566 SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
568 return returnSwitchPorts;
569 if (oldAPs.isEmpty())
570 return returnSwitchPorts;
573 List<AttachmentPoint> oldAPList;
574 oldAPList = new ArrayList<AttachmentPoint>();
577 oldAPList.addAll(oldAPs);
578 removeExpiredAttachmentPoints(oldAPList);
580 if (oldAPList != null) {
581 for (AttachmentPoint ap : oldAPList) {
582 SwitchPort swport = new SwitchPort(ap.getPort());
586 return sp.toArray(new SwitchPort[sp.size()]);
590 public SwitchPort[] getAttachmentPoints() {
591 return getAttachmentPoints(false);
595 public SwitchPort[] getAttachmentPoints(boolean includeError) {
596 List<SwitchPort> sp = new ArrayList<SwitchPort>();
597 SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
598 if (attachmentPoints == null)
599 return returnSwitchPorts;
600 if (attachmentPoints.isEmpty())
601 return returnSwitchPorts;
604 List<AttachmentPoint> apList = attachmentPoints;
606 if (apList != null) {
607 for (AttachmentPoint ap : apList) {
608 SwitchPort swport = new SwitchPort(ap.getPort());
614 return sp.toArray(new SwitchPort[sp.size()]);
616 List<AttachmentPoint> oldAPList;
617 oldAPList = new ArrayList<AttachmentPoint>();
620 oldAPList.addAll(oldAPs);
622 if (removeExpiredAttachmentPoints(oldAPList))
623 this.oldAPs = oldAPList;
625 List<AttachmentPoint> dupList;
627 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
628 dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
629 if (dupList != null) {
630 for (AttachmentPoint ap : dupList) {
631 SwitchPort swport = new SwitchPort(ap.getPort(),
632 ErrorStatus.DUPLICATE_DEVICE);
636 return sp.toArray(new SwitchPort[sp.size()]);
640 public Long getDeviceKey() {
645 public long getMACAddress() {
646 // we assume only one MAC per device for now.
647 return entities[0].getMacAddress();
651 public String getMACAddressString() {
652 return macAddressString;
656 public Short[] getVlanId() {
657 return Arrays.copyOf(vlanIds, vlanIds.length);
660 static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
663 public Integer[] getIPv4Addresses() {
664 // XXX - TODO we can cache this result. Let's find out if this
665 // is really a performance bottleneck first though.
667 TreeSet<Integer> vals = new TreeSet<Integer>();
668 for (Entity e : entities) {
669 if (e.getIpv4Address() == null)
672 // We have an IP address only if among the devices within the class
673 // we have the most recent entity with that IP.
674 boolean validIP = true;
675 Iterator<Device> devices = deviceManager.queryClassByEntity(
676 entityClass, ipv4Fields, e);
677 while (devices.hasNext()) {
678 Device d = devices.next();
679 if (deviceKey.equals(d.getDeviceKey()))
681 for (Entity se : d.entities) {
682 if (se.getIpv4Address() != null
683 && se.getIpv4Address().equals(e.getIpv4Address())
684 && se.getLastSeenTimestamp() != null
685 && 0 < se.getLastSeenTimestamp().compareTo(
686 e.getLastSeenTimestamp())) {
696 vals.add(e.getIpv4Address());
699 return vals.toArray(new Integer[vals.size()]);
703 public Short[] getSwitchPortVlanIds(SwitchPort swp) {
704 TreeSet<Short> vals = new TreeSet<Short>();
705 for (Entity e : entities) {
706 if (e.getPort().equals(swp.getPort())) {
707 if (e.getVlan() == null) {
708 vals.add(VLAN_UNTAGGED);
711 vals.add(e.getVlan());
715 return vals.toArray(new Short[vals.size()]);
719 public Date getLastSeen() {
721 for (int i = 0; i < entities.length; i++) {
723 || entities[i].getLastSeenTimestamp().compareTo(d) > 0)
724 d = entities[i].getLastSeenTimestamp();
734 public IEntityClass getEntityClass() {
738 public Entity[] getEntities() {
742 public String getDHCPClientName() {
743 return dhcpClientName;
751 * Check whether the device contains the specified entity
754 * the entity to search for
755 * @return the index of the entity, or <0 if not found
757 protected int entityIndex(Entity entity) {
758 return Arrays.binarySearch(entities, entity);
766 public int hashCode() {
767 final int prime = 31;
769 result = prime * result + Arrays.hashCode(entities);
774 public boolean equals(Object obj) {
779 if (getClass() != obj.getClass())
781 Device other = (Device) obj;
782 if (!deviceKey.equals(other.deviceKey))
784 if (!Arrays.equals(entities, other.entities))
789 public HostNodeConnector toHostNodeConnector() {
790 Integer[] ipv4s = this.getIPv4Addresses();
792 Entity e = this.entities[this.entities.length-1];
793 NodeConnector n = null;
796 InetAddress ip = InetAddress.getByName(ipv4s[ipv4s.length - 1]
798 byte[] macAddr = macLongToByte(this.getMACAddress());
799 HostNodeConnector nc = new HostNodeConnector(macAddr, ip, n,
801 nc.setStaticHost(this.isStaticHost());
803 } catch (Exception e) {
808 private byte[] macLongToByte(long mac) {
809 byte[] macAddr = new byte[6];
810 for (int i = 0; i < 6; i++) {
811 macAddr[5 - i] = (byte) (mac >> (8 * i));
816 public boolean isStaticHost(){
817 return this.staticHost;
820 public void setStaticHost(boolean isStatic){
821 this.staticHost = isStatic;
825 public String toString() {
826 StringBuilder builder = new StringBuilder();
827 builder.append("Device [deviceKey=");
828 builder.append(deviceKey);
829 builder.append(", entityClass=");
830 builder.append(entityClass.getName());
831 builder.append(", MAC=");
832 builder.append(macAddressString);
833 builder.append(", IPs=[");
834 boolean isFirst = true;
835 for (Integer ip : getIPv4Addresses()) {
837 builder.append(", ");
839 // builder.append(IPv4.fromIPv4Address(ip));
842 builder.append("], APs=");
843 builder.append(Arrays.toString(getAttachmentPoints(true)));
845 return builder.toString();