a97cb273837257a23a34460696de49cf29942241
[controller.git] / opendaylight / hosttracker_new / implementation / src / main / java / org / opendaylight / controller / hosttracker / internal / Device.java
1 /*
2  * Copyright (c) 2011,2012 Big Switch Networks, Inc.
3  *
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
7  *
8  *      http://www.eclipse.org/legal/epl-v10.html
9  *
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.
15  *
16  * This file incorporates work covered by the following copyright and
17  * permission notice:
18  *
19  *    Originally created by David Erickson, Stanford University
20  *
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
24  *
25  *         http://www.apache.org/licenses/LICENSE-2.0
26  *
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.
32  */
33
34 package org.opendaylight.controller.hosttracker.internal;
35
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;
46 import java.util.Map;
47 import java.util.TreeSet;
48
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;
60
61 /**
62  * Concrete implementation of {@link IDevice}
63  *
64  * @author readams
65  */
66 public class Device implements IDevice {
67     protected static Logger log = LoggerFactory.getLogger(Device.class);
68     public static final short VLAN_UNTAGGED = (short) 0xffff;
69
70     private final Long deviceKey;
71     protected final DeviceManagerImpl deviceManager;
72
73     protected final Entity[] entities;
74     private final IEntityClass entityClass;
75
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;
80
81     /**
82      * These are the old attachment points for the device that were valid no
83      * more than INACTIVITY_TIME ago.
84      */
85     protected volatile List<AttachmentPoint> oldAPs;
86     /**
87      * The current attachment points for the device.
88      */
89     protected volatile List<AttachmentPoint> attachmentPoints;
90
91     // ************
92     // Constructors
93     // ************
94
95     /**
96      * Create a device from an entities
97      *
98      * @param deviceManager
99      *            the device manager for this device
100      * @param deviceKey
101      *            the unique identifier for this device object
102      * @param entity
103      *            the initial entity for the device
104      * @param entityClass
105      *            the entity classes associated with the entity
106      */
107     public Device(DeviceManagerImpl deviceManager, Long deviceKey,
108             Entity entity, IEntityClass entityClass) {
109         this.deviceManager = deviceManager;
110         this.deviceKey = deviceKey;
111         this.entities = new Entity[] { entity };
112         this.macAddressString = HexEncode.longToHexString(entity
113                 .getMacAddress());
114         this.entityClass = entityClass;
115         Arrays.sort(this.entities);
116
117         this.dhcpClientName = null;
118         this.oldAPs = null;
119         this.attachmentPoints = null;
120
121         if (entity.getPort() != null) {
122             NodeConnector port = entity.getPort();
123
124             if (deviceManager.isValidAttachmentPoint(port)) {
125                 AttachmentPoint ap;
126                 ap = new AttachmentPoint(port, entity.getLastSeenTimestamp()
127                         .getTime());
128
129                 this.attachmentPoints = new ArrayList<AttachmentPoint>();
130                 this.attachmentPoints.add(ap);
131             }
132         }
133         vlanIds = computeVlandIds();
134     }
135
136     /**
137      * Create a device from a set of entities
138      *
139      * @param deviceManager
140      *            the device manager for this device
141      * @param deviceKey
142      *            the unique identifier for this device object
143      * @param entities
144      *            the initial entities for the device
145      * @param entityClass
146      *            the entity class associated with the entities
147      */
148     public Device(DeviceManagerImpl deviceManager, Long deviceKey,
149             String dhcpClientName, Collection<AttachmentPoint> oldAPs,
150             Collection<AttachmentPoint> attachmentPoints,
151             Collection<Entity> entities, IEntityClass entityClass) {
152         this.deviceManager = deviceManager;
153         this.deviceKey = deviceKey;
154         this.dhcpClientName = dhcpClientName;
155         this.entities = entities.toArray(new Entity[entities.size()]);
156         this.oldAPs = null;
157         this.attachmentPoints = null;
158         if (oldAPs != null) {
159             this.oldAPs = new ArrayList<AttachmentPoint>(oldAPs);
160         }
161         if (attachmentPoints != null) {
162             this.attachmentPoints = new ArrayList<AttachmentPoint>(
163                     attachmentPoints);
164         }
165         this.macAddressString = HexEncode.longToHexString(this.entities[0]
166                 .getMacAddress());
167         this.entityClass = entityClass;
168         Arrays.sort(this.entities);
169         vlanIds = computeVlandIds();
170     }
171
172     /**
173      * Construct a new device consisting of the entities from the old device
174      * plus an additional entity. The caller needs to ensure that the additional
175      * entity is not already present in the array
176      *
177      * @param device
178      *            the old device object
179      * @param newEntity
180      *            the entity to add. newEntity must be have the same entity
181      *            class as device
182      * @param if positive indicates the index in the entities array were the new
183      *        entity should be inserted. If negative we will compute the correct
184      *        insertion point
185      */
186     public Device(Device device, Entity newEntity, int insertionpoint) {
187         this.deviceManager = device.deviceManager;
188         this.deviceKey = device.deviceKey;
189         this.dhcpClientName = device.dhcpClientName;
190
191         this.entities = new Entity[device.entities.length + 1];
192         if (insertionpoint < 0) {
193             insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1);
194         }
195         if (insertionpoint > 0) {
196             // insertion point is not the beginning:
197             // copy up to insertion point
198             System.arraycopy(device.entities, 0, this.entities, 0,
199                     insertionpoint);
200         }
201         if (insertionpoint < device.entities.length) {
202             // insertion point is not the end
203             // copy from insertion point
204             System.arraycopy(device.entities, insertionpoint, this.entities,
205                     insertionpoint + 1, device.entities.length - insertionpoint);
206         }
207         this.entities[insertionpoint] = newEntity;
208         /*
209          * this.entities = Arrays.<Entity>copyOf(device.entities,
210          * device.entities.length + 1); this.entities[this.entities.length - 1]
211          * = newEntity; Arrays.sort(this.entities);
212          */
213         this.oldAPs = null;
214         if (device.oldAPs != null) {
215             this.oldAPs = new ArrayList<AttachmentPoint>(device.oldAPs);
216         }
217         this.attachmentPoints = null;
218         if (device.attachmentPoints != null) {
219             this.attachmentPoints = new ArrayList<AttachmentPoint>(
220                     device.attachmentPoints);
221         }
222
223         this.macAddressString = HexEncode.longToHexString(this.entities[0]
224                 .getMacAddress());
225
226         this.entityClass = device.entityClass;
227         vlanIds = computeVlandIds();
228     }
229
230     private Short[] computeVlandIds() {
231         if (entities.length == 1) {
232             if (entities[0].getVlan() != null) {
233                 return new Short[] { entities[0].getVlan() };
234             } else {
235                 return new Short[] { Short.valueOf((short) -1) };
236             }
237         }
238
239         TreeSet<Short> vals = new TreeSet<Short>();
240         for (Entity e : entities) {
241             if (e.getVlan() == null)
242                 vals.add((short) -1);
243             else
244                 vals.add(e.getVlan());
245         }
246         return vals.toArray(new Short[vals.size()]);
247     }
248
249     /**
250      * Given a list of attachment points (apList), the procedure would return a
251      * map of attachment points for each L2 domain. L2 domain id is the key.
252      *
253      * @param apList
254      * @return
255      */
256     private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
257
258         if (apList == null)
259             return null;
260         // ITopologyService topology = deviceManager.topology;
261
262         // Get the old attachment points and sort them.
263         List<AttachmentPoint> oldAP = new ArrayList<AttachmentPoint>();
264         if (apList != null)
265             oldAP.addAll(apList);
266
267         // Remove invalid attachment points before sorting.
268         List<AttachmentPoint> tempAP = new ArrayList<AttachmentPoint>();
269         for (AttachmentPoint ap : oldAP) {
270             if (deviceManager.isValidAttachmentPoint(ap.getPort())) {
271                 tempAP.add(ap);
272             }
273         }
274         oldAP = tempAP;
275
276         Collections.sort(oldAP, deviceManager.apComparator);
277
278         // Map of attachment point by L2 domain Id.
279         Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
280
281         for (int i = 0; i < oldAP.size(); ++i) {
282             AttachmentPoint ap = oldAP.get(i);
283             // if this is not a valid attachment point, continue
284             if (!deviceManager.isValidAttachmentPoint(ap.getPort()))
285                 continue;
286
287             // long id = topology.getL2DomainId(ap.getSw());
288             // XXX - Missing functionality
289             long id = 0;
290
291             apMap.put(id, ap);
292         }
293
294         if (apMap.isEmpty())
295             return null;
296         return apMap;
297     }
298
299     /**
300      * Remove all attachment points that are older than INACTIVITY_INTERVAL from
301      * the list.
302      *
303      * @param apList
304      * @return
305      */
306     private boolean removeExpiredAttachmentPoints(List<AttachmentPoint> apList) {
307
308         List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
309
310         if (apList == null)
311             return false;
312
313         for (AttachmentPoint ap : apList) {
314             if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < System
315                     .currentTimeMillis())
316                 expiredAPs.add(ap);
317         }
318         if (expiredAPs.size() > 0) {
319             apList.removeAll(expiredAPs);
320             return true;
321         } else
322             return false;
323     }
324
325     /**
326      * Get a list of duplicate attachment points, given a list of old attachment
327      * points and one attachment point per L2 domain. Given a true attachment
328      * point in the L2 domain, say trueAP, another attachment point in the same
329      * L2 domain, say ap, is duplicate if: 1. ap is inconsistent with trueAP,
330      * and 2. active time of ap is after that of trueAP; and 3. last seen time
331      * of ap is within the last INACTIVITY_INTERVAL
332      *
333      * @param oldAPList
334      * @param apMap
335      * @return
336      */
337     List<AttachmentPoint> getDuplicateAttachmentPoints(
338             List<AttachmentPoint> oldAPList, Map<Long, AttachmentPoint> apMap) {
339         // ITopologyService topology = deviceManager.topology;
340         List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
341         long timeThreshold = System.currentTimeMillis()
342                 - AttachmentPoint.INACTIVITY_INTERVAL;
343
344         if (oldAPList == null || apMap == null)
345             return dupAPs;
346
347         for (AttachmentPoint ap : oldAPList) {
348             // XXX - Missing functionality
349             // long id = topology.getL2DomainId(ap.getSw());
350             long id = 0;
351             AttachmentPoint trueAP = apMap.get(id);
352
353             if (trueAP == null)
354                 continue;
355             // XXX - Missing functionality
356             // boolean c = (topology.isConsistent(trueAP.getSw(),
357             // trueAP.getPort(),
358             // ap.getSw(), ap.getPort()));
359             boolean c = true;
360             boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
361             boolean last = ap.getLastSeen() > timeThreshold;
362             if (!c && active && last) {
363                 dupAPs.add(ap);
364             }
365         }
366
367         return dupAPs;
368     }
369
370     /**
371      * Update the known attachment points. This method is called whenever
372      * topology changes. The method returns true if there's any change to the
373      * list of attachment points -- which indicates a possible device move.
374      *
375      * @return
376      */
377     protected boolean updateAttachmentPoint() {
378         boolean moved = false;
379         this.oldAPs = attachmentPoints;
380         if (attachmentPoints == null || attachmentPoints.isEmpty())
381             return false;
382
383         List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
384         if (attachmentPoints != null)
385             apList.addAll(attachmentPoints);
386         Map<Long, AttachmentPoint> newMap = getAPMap(apList);
387         if (newMap == null || newMap.size() != apList.size()) {
388             moved = true;
389         }
390
391         // Prepare the new attachment point list.
392         if (moved) {
393             log.info("updateAttachmentPoint: ap {}  newmap {} ",
394                     attachmentPoints, newMap);
395             List<AttachmentPoint> newAPList = new ArrayList<AttachmentPoint>();
396             if (newMap != null)
397                 newAPList.addAll(newMap.values());
398             this.attachmentPoints = newAPList;
399         }
400
401         // Set the oldAPs to null.
402         return moved;
403     }
404
405     /**
406      * Update the list of attachment points given that a new packet-in was seen
407      * from (sw, port) at time (lastSeen). The return value is true if there was
408      * any change to the list of attachment points for the device -- which
409      * indicates a device move.
410      *
411      * @param sw
412      * @param port
413      * @param lastSeen
414      * @return
415      */
416     protected boolean updateAttachmentPoint(NodeConnector port, long lastSeen) {
417         // ITopologyService topology = deviceManager.topology;
418         List<AttachmentPoint> oldAPList;
419         List<AttachmentPoint> apList;
420         boolean oldAPFlag = false;
421
422         if (!deviceManager.isValidAttachmentPoint(port))
423             return false;
424         AttachmentPoint newAP = new AttachmentPoint(port, lastSeen);
425         // Copy the oldAP and ap list.
426         apList = new ArrayList<AttachmentPoint>();
427         if (attachmentPoints != null)
428             apList.addAll(attachmentPoints);
429         oldAPList = new ArrayList<AttachmentPoint>();
430         if (oldAPs != null)
431             oldAPList.addAll(oldAPs);
432
433         // if the sw, port is in old AP, remove it from there
434         // and update the lastSeen in that object.
435         if (oldAPList.contains(newAP)) {
436             int index = oldAPList.indexOf(newAP);
437             newAP = oldAPList.remove(index);
438             newAP.setLastSeen(lastSeen);
439             this.oldAPs = oldAPList;
440             oldAPFlag = true;
441         }
442
443         // newAP now contains the new attachment point.
444
445         // Get the APMap is null or empty.
446         Map<Long, AttachmentPoint> apMap = getAPMap(apList);
447         if (apMap == null || apMap.isEmpty()) {
448             apList.add(newAP);
449             attachmentPoints = apList;
450             // there are no old attachment points - since the device exists,
451             // this
452             // may be because the host really moved (so the old AP port went
453             // down);
454             // or it may be because the switch restarted (so old APs were
455             // nullified).
456             // For now we will treat both cases as host moved.
457             return true;
458         }
459
460         // XXX - Missing functionality
461         // long id = topology.getL2DomainId(sw);
462         long id = 0;
463         AttachmentPoint oldAP = apMap.get(id);
464
465         if (oldAP == null) // No attachment on this L2 domain.
466         {
467             apList = new ArrayList<AttachmentPoint>();
468             apList.addAll(apMap.values());
469             apList.add(newAP);
470             this.attachmentPoints = apList;
471             return true; // new AP found on an L2 island.
472         }
473
474         // There is already a known attachment point on the same L2 island.
475         // we need to compare oldAP and newAP.
476         if (oldAP.equals(newAP)) {
477             // nothing to do here. just the last seen has to be changed.
478             if (newAP.lastSeen > oldAP.lastSeen) {
479                 oldAP.setLastSeen(newAP.lastSeen);
480             }
481             this.attachmentPoints = new ArrayList<AttachmentPoint>(
482                     apMap.values());
483             return false; // nothing to do here.
484         }
485
486         int x = deviceManager.apComparator.compare(oldAP, newAP);
487         if (x < 0) {
488             // newAP replaces oldAP.
489             apMap.put(id, newAP);
490             this.attachmentPoints = new ArrayList<AttachmentPoint>(
491                     apMap.values());
492
493             oldAPList = new ArrayList<AttachmentPoint>();
494             if (oldAPs != null)
495                 oldAPList.addAll(oldAPs);
496             oldAPList.add(oldAP);
497             this.oldAPs = oldAPList;
498             // XXX - Missing functionality
499             // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
500             // oldAP.getPort(),
501             // newAP.getSw(), newAP.getPort()))
502             // return true; // attachment point changed.
503             return true;
504         } else if (oldAPFlag) {
505             // retain oldAP as is. Put the newAP in oldAPs for flagging
506             // possible duplicates.
507             oldAPList = new ArrayList<AttachmentPoint>();
508             if (oldAPs != null)
509                 oldAPList.addAll(oldAPs);
510             // Add to oldAPList only if it was picked up from the oldAPList
511             oldAPList.add(newAP);
512             this.oldAPs = oldAPList;
513             // XXX - Missing functionality
514             // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
515             // oldAP.getPort(),
516             // newAP.getSw(), newAP.getPort()))
517             // return true; // attachment point changed.
518             return true;
519         }
520         return false;
521     }
522
523     /**
524      * Delete (sw,port) from the list of list of attachment points and oldAPs.
525      *
526      * @param sw
527      * @param port
528      * @return
529      */
530     public boolean deleteAttachmentPoint(NodeConnector port) {
531         AttachmentPoint ap = new AttachmentPoint(port, 0);
532
533         if (this.oldAPs != null) {
534             ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
535             apList.addAll(this.oldAPs);
536             int index = apList.indexOf(ap);
537             if (index > 0) {
538                 apList.remove(index);
539                 this.oldAPs = apList;
540             }
541         }
542
543         if (this.attachmentPoints != null) {
544             ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
545             apList.addAll(this.attachmentPoints);
546             int index = apList.indexOf(ap);
547             if (index > 0) {
548                 apList.remove(index);
549                 this.attachmentPoints = apList;
550                 return true;
551             }
552         }
553         return false;
554     }
555
556     // *******
557     // IDevice
558     // *******
559
560     @Override
561     public SwitchPort[] getOldAP() {
562         List<SwitchPort> sp = new ArrayList<SwitchPort>();
563         SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
564         if (oldAPs == null)
565             return returnSwitchPorts;
566         if (oldAPs.isEmpty())
567             return returnSwitchPorts;
568
569         // copy ap list.
570         List<AttachmentPoint> oldAPList;
571         oldAPList = new ArrayList<AttachmentPoint>();
572
573         if (oldAPs != null)
574             oldAPList.addAll(oldAPs);
575         removeExpiredAttachmentPoints(oldAPList);
576
577         if (oldAPList != null) {
578             for (AttachmentPoint ap : oldAPList) {
579                 SwitchPort swport = new SwitchPort(ap.getPort());
580                 sp.add(swport);
581             }
582         }
583         return sp.toArray(new SwitchPort[sp.size()]);
584     }
585
586     @Override
587     public SwitchPort[] getAttachmentPoints() {
588         return getAttachmentPoints(false);
589     }
590
591     @Override
592     public SwitchPort[] getAttachmentPoints(boolean includeError) {
593         List<SwitchPort> sp = new ArrayList<SwitchPort>();
594         SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
595         if (attachmentPoints == null)
596             return returnSwitchPorts;
597         if (attachmentPoints.isEmpty())
598             return returnSwitchPorts;
599
600         // copy ap list.
601         List<AttachmentPoint> apList = attachmentPoints;
602
603         if (apList != null) {
604             for (AttachmentPoint ap : apList) {
605                 SwitchPort swport = new SwitchPort(ap.getPort());
606                 sp.add(swport);
607             }
608         }
609
610         if (!includeError)
611             return sp.toArray(new SwitchPort[sp.size()]);
612
613         List<AttachmentPoint> oldAPList;
614         oldAPList = new ArrayList<AttachmentPoint>();
615
616         if (oldAPs != null)
617             oldAPList.addAll(oldAPs);
618
619         if (removeExpiredAttachmentPoints(oldAPList))
620             this.oldAPs = oldAPList;
621
622         List<AttachmentPoint> dupList;
623         // get AP map.
624         Map<Long, AttachmentPoint> apMap = getAPMap(apList);
625         dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
626         if (dupList != null) {
627             for (AttachmentPoint ap : dupList) {
628                 SwitchPort swport = new SwitchPort(ap.getPort(),
629                         ErrorStatus.DUPLICATE_DEVICE);
630                 sp.add(swport);
631             }
632         }
633         return sp.toArray(new SwitchPort[sp.size()]);
634     }
635
636     @Override
637     public Long getDeviceKey() {
638         return deviceKey;
639     }
640
641     @Override
642     public long getMACAddress() {
643         // we assume only one MAC per device for now.
644         return entities[0].getMacAddress();
645     }
646
647     @Override
648     public String getMACAddressString() {
649         return macAddressString;
650     }
651
652     @Override
653     public Short[] getVlanId() {
654         return Arrays.copyOf(vlanIds, vlanIds.length);
655     }
656
657     static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
658
659     @Override
660     public Integer[] getIPv4Addresses() {
661         // XXX - TODO we can cache this result. Let's find out if this
662         // is really a performance bottleneck first though.
663
664         TreeSet<Integer> vals = new TreeSet<Integer>();
665         for (Entity e : entities) {
666             if (e.getIpv4Address() == null)
667                 continue;
668
669             // We have an IP address only if among the devices within the class
670             // we have the most recent entity with that IP.
671             boolean validIP = true;
672             Iterator<Device> devices = deviceManager.queryClassByEntity(
673                     entityClass, ipv4Fields, e);
674             while (devices.hasNext()) {
675                 Device d = devices.next();
676                 if (deviceKey.equals(d.getDeviceKey()))
677                     continue;
678                 for (Entity se : d.entities) {
679                     if (se.getIpv4Address() != null
680                             && se.getIpv4Address().equals(e.getIpv4Address())
681                             && se.getLastSeenTimestamp() != null
682                             && 0 < se.getLastSeenTimestamp().compareTo(
683                                     e.getLastSeenTimestamp())) {
684                         validIP = false;
685                         break;
686                     }
687                 }
688                 if (!validIP)
689                     break;
690             }
691
692             if (validIP)
693                 vals.add(e.getIpv4Address());
694         }
695
696         return vals.toArray(new Integer[vals.size()]);
697     }
698
699     @Override
700     public Short[] getSwitchPortVlanIds(SwitchPort swp) {
701         TreeSet<Short> vals = new TreeSet<Short>();
702         for (Entity e : entities) {
703             if (e.getPort().equals(swp.getPort())) {
704                 if (e.getVlan() == null)
705                     vals.add(VLAN_UNTAGGED);
706                 else
707                     vals.add(e.getVlan());
708             }
709         }
710         return vals.toArray(new Short[vals.size()]);
711     }
712
713     @Override
714     public Date getLastSeen() {
715         Date d = null;
716         for (int i = 0; i < entities.length; i++) {
717             if (d == null
718                     || entities[i].getLastSeenTimestamp().compareTo(d) > 0)
719                 d = entities[i].getLastSeenTimestamp();
720         }
721         return d;
722     }
723
724     // ***************
725     // Getters/Setters
726     // ***************
727
728     @Override
729     public IEntityClass getEntityClass() {
730         return entityClass;
731     }
732
733     public Entity[] getEntities() {
734         return entities;
735     }
736
737     public String getDHCPClientName() {
738         return dhcpClientName;
739     }
740
741     // ***************
742     // Utility Methods
743     // ***************
744
745     /**
746      * Check whether the device contains the specified entity
747      *
748      * @param entity
749      *            the entity to search for
750      * @return the index of the entity, or <0 if not found
751      */
752     protected int entityIndex(Entity entity) {
753         return Arrays.binarySearch(entities, entity);
754     }
755
756     // ******
757     // Object
758     // ******
759
760     @Override
761     public int hashCode() {
762         final int prime = 31;
763         int result = 1;
764         result = prime * result + Arrays.hashCode(entities);
765         return result;
766     }
767
768     @Override
769     public boolean equals(Object obj) {
770         if (this == obj)
771             return true;
772         if (obj == null)
773             return false;
774         if (getClass() != obj.getClass())
775             return false;
776         Device other = (Device) obj;
777         if (!deviceKey.equals(other.deviceKey))
778             return false;
779         if (!Arrays.equals(entities, other.entities))
780             return false;
781         return true;
782     }
783
784     public HostNodeConnector toHostNodeConnector() {
785         Integer[] ipv4s = this.getIPv4Addresses();
786         try {
787             Entity e = this.entities[this.entities.length-1];
788             NodeConnector n = null;
789             if(e!=null)
790                  n = e.getPort();
791             InetAddress ip = InetAddress.getByName(ipv4s[ipv4s.length - 1]
792                     .toString());
793             byte[] macAddr = macLongToByte(this.getMACAddress());
794             HostNodeConnector nc = new HostNodeConnector(macAddr, ip, n,
795                     (short) 0);
796             return nc;
797         } catch (Exception e) {
798             return null;
799         }
800     }
801
802     private byte[] macLongToByte(long mac) {
803         byte[] macAddr = new byte[6];
804         for (int i = 0; i < 6; i++) {
805             macAddr[5 - i] = (byte) (mac >> (8 * i));
806         }
807         return macAddr;
808     }
809
810     @Override
811     public String toString() {
812         StringBuilder builder = new StringBuilder();
813         builder.append("Device [deviceKey=");
814         builder.append(deviceKey);
815         builder.append(", entityClass=");
816         builder.append(entityClass.getName());
817         builder.append(", MAC=");
818         builder.append(macAddressString);
819         builder.append(", IPs=[");
820         boolean isFirst = true;
821         for (Integer ip : getIPv4Addresses()) {
822             if (!isFirst)
823                 builder.append(", ");
824             isFirst = false;
825             // builder.append(IPv4.fromIPv4Address(ip));
826             builder.append(ip);
827         }
828         builder.append("], APs=");
829         builder.append(Arrays.toString(getAttachmentPoints(true)));
830         builder.append("]");
831         return builder.toString();
832     }
833 }