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