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.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.Date;
41 import java.util.EnumSet;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
46 import java.util.TreeSet;
48 import org.opendaylight.controller.hosttracker.Entity;
49 import org.opendaylight.controller.hosttracker.IDevice;
50 import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
51 import org.opendaylight.controller.hosttracker.IEntityClass;
52 import org.opendaylight.controller.hosttracker.SwitchPort;
53 import org.opendaylight.controller.hosttracker.SwitchPort.ErrorStatus;
54 import org.opendaylight.controller.sal.core.NodeConnector;
55 import org.opendaylight.controller.sal.utils.HexEncode;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
60 * Concrete implementation of {@link IDevice}
64 public class Device implements IDevice {
65 protected static Logger log = LoggerFactory.getLogger(Device.class);
66 public static final short VLAN_UNTAGGED = (short) 0xffff;
68 private final Long deviceKey;
69 protected final DeviceManagerImpl deviceManager;
71 protected final Entity[] entities;
72 private final IEntityClass entityClass;
74 protected final String macAddressString;
75 // the vlan Ids from the entities of this device
76 protected final Short[] vlanIds;
77 protected volatile String dhcpClientName;
80 * These are the old attachment points for the device that were valid no
81 * more than INACTIVITY_TIME ago.
83 protected volatile List<AttachmentPoint> oldAPs;
85 * The current attachment points for the device.
87 protected volatile List<AttachmentPoint> attachmentPoints;
94 * Create a device from an entities
96 * @param deviceManager
97 * the device manager for this device
99 * the unique identifier for this device object
101 * the initial entity for the device
103 * the entity classes associated with the entity
105 public Device(DeviceManagerImpl deviceManager, Long deviceKey,
106 Entity entity, IEntityClass entityClass) {
107 this.deviceManager = deviceManager;
108 this.deviceKey = deviceKey;
109 this.entities = new Entity[] { entity };
110 this.macAddressString = HexEncode.longToHexString(entity
112 this.entityClass = entityClass;
113 Arrays.sort(this.entities);
115 this.dhcpClientName = null;
117 this.attachmentPoints = null;
119 if (entity.getPort() != null) {
120 NodeConnector port = entity.getPort();
122 if (deviceManager.isValidAttachmentPoint(port)) {
124 ap = new AttachmentPoint(port, entity.getLastSeenTimestamp()
127 this.attachmentPoints = new ArrayList<AttachmentPoint>();
128 this.attachmentPoints.add(ap);
131 vlanIds = computeVlandIds();
135 * Create a device from a set of entities
137 * @param deviceManager
138 * the device manager for this device
140 * the unique identifier for this device object
142 * the initial entities for the device
144 * the entity class associated with the entities
146 public Device(DeviceManagerImpl deviceManager, Long deviceKey,
147 String dhcpClientName, Collection<AttachmentPoint> oldAPs,
148 Collection<AttachmentPoint> attachmentPoints,
149 Collection<Entity> entities, IEntityClass entityClass) {
150 this.deviceManager = deviceManager;
151 this.deviceKey = deviceKey;
152 this.dhcpClientName = dhcpClientName;
153 this.entities = entities.toArray(new Entity[entities.size()]);
155 this.attachmentPoints = null;
156 if (oldAPs != null) {
157 this.oldAPs = new ArrayList<AttachmentPoint>(oldAPs);
159 if (attachmentPoints != null) {
160 this.attachmentPoints = new ArrayList<AttachmentPoint>(
163 this.macAddressString = HexEncode.longToHexString(this.entities[0]
165 this.entityClass = entityClass;
166 Arrays.sort(this.entities);
167 vlanIds = computeVlandIds();
171 * Construct a new device consisting of the entities from the old device
172 * plus an additional entity. The caller needs to ensure that the additional
173 * entity is not already present in the array
176 * the old device object
178 * the entity to add. newEntity must be have the same entity
180 * @param if positive indicates the index in the entities array were the new
181 * entity should be inserted. If negative we will compute the correct
184 public Device(Device device, Entity newEntity, int insertionpoint) {
185 this.deviceManager = device.deviceManager;
186 this.deviceKey = device.deviceKey;
187 this.dhcpClientName = device.dhcpClientName;
189 this.entities = new Entity[device.entities.length + 1];
190 if (insertionpoint < 0) {
191 insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1);
193 if (insertionpoint > 0) {
194 // insertion point is not the beginning:
195 // copy up to insertion point
196 System.arraycopy(device.entities, 0, this.entities, 0,
199 if (insertionpoint < device.entities.length) {
200 // insertion point is not the end
201 // copy from insertion point
202 System.arraycopy(device.entities, insertionpoint, this.entities,
203 insertionpoint + 1, device.entities.length - insertionpoint);
205 this.entities[insertionpoint] = newEntity;
207 * this.entities = Arrays.<Entity>copyOf(device.entities,
208 * device.entities.length + 1); this.entities[this.entities.length - 1]
209 * = newEntity; Arrays.sort(this.entities);
212 if (device.oldAPs != null) {
213 this.oldAPs = new ArrayList<AttachmentPoint>(device.oldAPs);
215 this.attachmentPoints = null;
216 if (device.attachmentPoints != null) {
217 this.attachmentPoints = new ArrayList<AttachmentPoint>(
218 device.attachmentPoints);
221 this.macAddressString = HexEncode.longToHexString(this.entities[0]
224 this.entityClass = device.entityClass;
225 vlanIds = computeVlandIds();
228 private Short[] computeVlandIds() {
229 if (entities.length == 1) {
230 if (entities[0].getVlan() != null) {
231 return new Short[] { entities[0].getVlan() };
233 return new Short[] { Short.valueOf((short) -1) };
237 TreeSet<Short> vals = new TreeSet<Short>();
238 for (Entity e : entities) {
239 if (e.getVlan() == null)
240 vals.add((short) -1);
242 vals.add(e.getVlan());
244 return vals.toArray(new Short[vals.size()]);
248 * Given a list of attachment points (apList), the procedure would return a
249 * map of attachment points for each L2 domain. L2 domain id is the key.
254 private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
258 // ITopologyService topology = deviceManager.topology;
260 // Get the old attachment points and sort them.
261 List<AttachmentPoint> oldAP = new ArrayList<AttachmentPoint>();
263 oldAP.addAll(apList);
265 // Remove invalid attachment points before sorting.
266 List<AttachmentPoint> tempAP = new ArrayList<AttachmentPoint>();
267 for (AttachmentPoint ap : oldAP) {
268 if (deviceManager.isValidAttachmentPoint(ap.getPort())) {
274 Collections.sort(oldAP, deviceManager.apComparator);
276 // Map of attachment point by L2 domain Id.
277 Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
279 for (int i = 0; i < oldAP.size(); ++i) {
280 AttachmentPoint ap = oldAP.get(i);
281 // if this is not a valid attachment point, continue
282 if (!deviceManager.isValidAttachmentPoint(ap.getPort()))
285 // long id = topology.getL2DomainId(ap.getSw());
286 // XXX - Missing functionality
298 * Remove all attachment points that are older than INACTIVITY_INTERVAL from
304 private boolean removeExpiredAttachmentPoints(List<AttachmentPoint> apList) {
306 List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
311 for (AttachmentPoint ap : apList) {
312 if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < System
313 .currentTimeMillis())
316 if (expiredAPs.size() > 0) {
317 apList.removeAll(expiredAPs);
324 * Get a list of duplicate attachment points, given a list of old attachment
325 * points and one attachment point per L2 domain. Given a true attachment
326 * point in the L2 domain, say trueAP, another attachment point in the same
327 * L2 domain, say ap, is duplicate if: 1. ap is inconsistent with trueAP,
328 * and 2. active time of ap is after that of trueAP; and 3. last seen time
329 * of ap is within the last INACTIVITY_INTERVAL
335 List<AttachmentPoint> getDuplicateAttachmentPoints(
336 List<AttachmentPoint> oldAPList, Map<Long, AttachmentPoint> apMap) {
337 // ITopologyService topology = deviceManager.topology;
338 List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
339 long timeThreshold = System.currentTimeMillis()
340 - AttachmentPoint.INACTIVITY_INTERVAL;
342 if (oldAPList == null || apMap == null)
345 for (AttachmentPoint ap : oldAPList) {
346 // XXX - Missing functionality
347 // long id = topology.getL2DomainId(ap.getSw());
349 AttachmentPoint trueAP = apMap.get(id);
353 // XXX - Missing functionality
354 // boolean c = (topology.isConsistent(trueAP.getSw(),
356 // ap.getSw(), ap.getPort()));
358 boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
359 boolean last = ap.getLastSeen() > timeThreshold;
360 if (!c && active && last) {
369 * Update the known attachment points. This method is called whenever
370 * topology changes. The method returns true if there's any change to the
371 * list of attachment points -- which indicates a possible device move.
375 protected boolean updateAttachmentPoint() {
376 boolean moved = false;
377 this.oldAPs = attachmentPoints;
378 if (attachmentPoints == null || attachmentPoints.isEmpty())
381 List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
382 if (attachmentPoints != null)
383 apList.addAll(attachmentPoints);
384 Map<Long, AttachmentPoint> newMap = getAPMap(apList);
385 if (newMap == null || newMap.size() != apList.size()) {
389 // Prepare the new attachment point list.
391 log.info("updateAttachmentPoint: ap {} newmap {} ",
392 attachmentPoints, newMap);
393 List<AttachmentPoint> newAPList = new ArrayList<AttachmentPoint>();
395 newAPList.addAll(newMap.values());
396 this.attachmentPoints = newAPList;
399 // Set the oldAPs to null.
404 * Update the list of attachment points given that a new packet-in was seen
405 * from (sw, port) at time (lastSeen). The return value is true if there was
406 * any change to the list of attachment points for the device -- which
407 * indicates a device move.
414 protected boolean updateAttachmentPoint(NodeConnector port, long lastSeen) {
415 // ITopologyService topology = deviceManager.topology;
416 List<AttachmentPoint> oldAPList;
417 List<AttachmentPoint> apList;
418 boolean oldAPFlag = false;
420 if (!deviceManager.isValidAttachmentPoint(port))
422 AttachmentPoint newAP = new AttachmentPoint(port, lastSeen);
423 // Copy the oldAP and ap list.
424 apList = new ArrayList<AttachmentPoint>();
425 if (attachmentPoints != null)
426 apList.addAll(attachmentPoints);
427 oldAPList = new ArrayList<AttachmentPoint>();
429 oldAPList.addAll(oldAPs);
431 // if the sw, port is in old AP, remove it from there
432 // and update the lastSeen in that object.
433 if (oldAPList.contains(newAP)) {
434 int index = oldAPList.indexOf(newAP);
435 newAP = oldAPList.remove(index);
436 newAP.setLastSeen(lastSeen);
437 this.oldAPs = oldAPList;
441 // newAP now contains the new attachment point.
443 // Get the APMap is null or empty.
444 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
445 if (apMap == null || apMap.isEmpty()) {
447 attachmentPoints = apList;
448 // there are no old attachment points - since the device exists,
450 // may be because the host really moved (so the old AP port went
452 // or it may be because the switch restarted (so old APs were
454 // For now we will treat both cases as host moved.
458 // XXX - Missing functionality
459 // long id = topology.getL2DomainId(sw);
461 AttachmentPoint oldAP = apMap.get(id);
463 if (oldAP == null) // No attachment on this L2 domain.
465 apList = new ArrayList<AttachmentPoint>();
466 apList.addAll(apMap.values());
468 this.attachmentPoints = apList;
469 return true; // new AP found on an L2 island.
472 // There is already a known attachment point on the same L2 island.
473 // we need to compare oldAP and newAP.
474 if (oldAP.equals(newAP)) {
475 // nothing to do here. just the last seen has to be changed.
476 if (newAP.lastSeen > oldAP.lastSeen) {
477 oldAP.setLastSeen(newAP.lastSeen);
479 this.attachmentPoints = new ArrayList<AttachmentPoint>(
481 return false; // nothing to do here.
484 int x = deviceManager.apComparator.compare(oldAP, newAP);
486 // newAP replaces oldAP.
487 apMap.put(id, newAP);
488 this.attachmentPoints = new ArrayList<AttachmentPoint>(
491 oldAPList = new ArrayList<AttachmentPoint>();
493 oldAPList.addAll(oldAPs);
494 oldAPList.add(oldAP);
495 this.oldAPs = oldAPList;
496 // XXX - Missing functionality
497 // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
499 // newAP.getSw(), newAP.getPort()))
500 // return true; // attachment point changed.
502 } else if (oldAPFlag) {
503 // retain oldAP as is. Put the newAP in oldAPs for flagging
504 // possible duplicates.
505 oldAPList = new ArrayList<AttachmentPoint>();
507 oldAPList.addAll(oldAPs);
508 // Add to oldAPList only if it was picked up from the oldAPList
509 oldAPList.add(newAP);
510 this.oldAPs = oldAPList;
511 // XXX - Missing functionality
512 // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
514 // newAP.getSw(), newAP.getPort()))
515 // return true; // attachment point changed.
522 * Delete (sw,port) from the list of list of attachment points and oldAPs.
528 public boolean deleteAttachmentPoint(NodeConnector port) {
529 AttachmentPoint ap = new AttachmentPoint(port, 0);
531 if (this.oldAPs != null) {
532 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
533 apList.addAll(this.oldAPs);
534 int index = apList.indexOf(ap);
536 apList.remove(index);
537 this.oldAPs = apList;
541 if (this.attachmentPoints != null) {
542 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
543 apList.addAll(this.attachmentPoints);
544 int index = apList.indexOf(ap);
546 apList.remove(index);
547 this.attachmentPoints = apList;
559 public SwitchPort[] getOldAP() {
560 List<SwitchPort> sp = new ArrayList<SwitchPort>();
561 SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
563 return returnSwitchPorts;
564 if (oldAPs.isEmpty())
565 return returnSwitchPorts;
568 List<AttachmentPoint> oldAPList;
569 oldAPList = new ArrayList<AttachmentPoint>();
572 oldAPList.addAll(oldAPs);
573 removeExpiredAttachmentPoints(oldAPList);
575 if (oldAPList != null) {
576 for (AttachmentPoint ap : oldAPList) {
577 SwitchPort swport = new SwitchPort(ap.getPort());
581 return sp.toArray(new SwitchPort[sp.size()]);
585 public SwitchPort[] getAttachmentPoints() {
586 return getAttachmentPoints(false);
590 public SwitchPort[] getAttachmentPoints(boolean includeError) {
591 List<SwitchPort> sp = new ArrayList<SwitchPort>();
592 SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
593 if (attachmentPoints == null)
594 return returnSwitchPorts;
595 if (attachmentPoints.isEmpty())
596 return returnSwitchPorts;
599 List<AttachmentPoint> apList = attachmentPoints;
601 if (apList != null) {
602 for (AttachmentPoint ap : apList) {
603 SwitchPort swport = new SwitchPort(ap.getPort());
609 return sp.toArray(new SwitchPort[sp.size()]);
611 List<AttachmentPoint> oldAPList;
612 oldAPList = new ArrayList<AttachmentPoint>();
615 oldAPList.addAll(oldAPs);
617 if (removeExpiredAttachmentPoints(oldAPList))
618 this.oldAPs = oldAPList;
620 List<AttachmentPoint> dupList;
622 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
623 dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
624 if (dupList != null) {
625 for (AttachmentPoint ap : dupList) {
626 SwitchPort swport = new SwitchPort(ap.getPort(),
627 ErrorStatus.DUPLICATE_DEVICE);
631 return sp.toArray(new SwitchPort[sp.size()]);
635 public Long getDeviceKey() {
640 public long getMACAddress() {
641 // we assume only one MAC per device for now.
642 return entities[0].getMacAddress();
646 public String getMACAddressString() {
647 return macAddressString;
651 public Short[] getVlanId() {
652 return Arrays.copyOf(vlanIds, vlanIds.length);
655 static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
658 public Integer[] getIPv4Addresses() {
659 // XXX - TODO we can cache this result. Let's find out if this
660 // is really a performance bottleneck first though.
662 TreeSet<Integer> vals = new TreeSet<Integer>();
663 for (Entity e : entities) {
664 if (e.getIpv4Address() == null)
667 // We have an IP address only if among the devices within the class
668 // we have the most recent entity with that IP.
669 boolean validIP = true;
670 Iterator<Device> devices = deviceManager.queryClassByEntity(
671 entityClass, ipv4Fields, e);
672 while (devices.hasNext()) {
673 Device d = devices.next();
674 if (deviceKey.equals(d.getDeviceKey()))
676 for (Entity se : d.entities) {
677 if (se.getIpv4Address() != null
678 && se.getIpv4Address().equals(e.getIpv4Address())
679 && se.getLastSeenTimestamp() != null
680 && 0 < se.getLastSeenTimestamp().compareTo(
681 e.getLastSeenTimestamp())) {
691 vals.add(e.getIpv4Address());
694 return vals.toArray(new Integer[vals.size()]);
698 public Short[] getSwitchPortVlanIds(SwitchPort swp) {
699 TreeSet<Short> vals = new TreeSet<Short>();
700 for (Entity e : entities) {
701 if (e.getPort().equals(swp.getPort())) {
702 if (e.getVlan() == null)
703 vals.add(VLAN_UNTAGGED);
705 vals.add(e.getVlan());
708 return vals.toArray(new Short[vals.size()]);
712 public Date getLastSeen() {
714 for (int i = 0; i < entities.length; i++) {
716 || entities[i].getLastSeenTimestamp().compareTo(d) > 0)
717 d = entities[i].getLastSeenTimestamp();
727 public IEntityClass getEntityClass() {
731 public Entity[] getEntities() {
735 public String getDHCPClientName() {
736 return dhcpClientName;
744 * Check whether the device contains the specified entity
747 * the entity to search for
748 * @return the index of the entity, or <0 if not found
750 protected int entityIndex(Entity entity) {
751 return Arrays.binarySearch(entities, entity);
759 public int hashCode() {
760 final int prime = 31;
762 result = prime * result + Arrays.hashCode(entities);
767 public boolean equals(Object obj) {
772 if (getClass() != obj.getClass())
774 Device other = (Device) obj;
775 if (!deviceKey.equals(other.deviceKey))
777 if (!Arrays.equals(entities, other.entities))
783 public String toString() {
784 StringBuilder builder = new StringBuilder();
785 builder.append("Device [deviceKey=");
786 builder.append(deviceKey);
787 builder.append(", entityClass=");
788 builder.append(entityClass.getName());
789 builder.append(", MAC=");
790 builder.append(macAddressString);
791 builder.append(", IPs=[");
792 boolean isFirst = true;
793 for (Integer ip : getIPv4Addresses()) {
795 builder.append(", ");
797 // builder.append(IPv4.fromIPv4Address(ip));
800 builder.append("], APs=");
801 builder.append(Arrays.toString(getAttachmentPoints(true)));
803 return builder.toString();