8435a94f5836c28db029001574b37b076f575163
[controller.git] / opendaylight / hosttracker_new / implementation / src / main / java / org / opendaylight / controller / hosttracker / internal / DeviceManagerImpl.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 static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.ADD;
37 import static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.CHANGE;
38 import static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.DELETE;
39
40 import java.util.ArrayList;
41 import java.util.Calendar;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.Comparator;
45 import java.util.Date;
46 import java.util.EnumSet;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Queue;
54 import java.util.Set;
55 import java.util.concurrent.ConcurrentHashMap;
56 import java.util.concurrent.Executors;
57 import java.util.concurrent.ScheduledExecutorService;
58 import java.util.concurrent.TimeUnit;
59
60 import org.opendaylight.controller.hosttracker.Entity;
61 import org.opendaylight.controller.hosttracker.IDevice;
62 import org.opendaylight.controller.hosttracker.IDeviceListener;
63 import org.opendaylight.controller.hosttracker.IDeviceService;
64 import org.opendaylight.controller.hosttracker.IEntityClass;
65 import org.opendaylight.controller.hosttracker.IEntityClassListener;
66 import org.opendaylight.controller.hosttracker.IEntityClassifierService;
67 import org.opendaylight.controller.hosttracker.SwitchPort;
68 import org.opendaylight.controller.sal.core.Edge;
69 import org.opendaylight.controller.sal.core.NodeConnector;
70 import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
71 import org.opendaylight.controller.sal.packet.ARP;
72 import org.opendaylight.controller.sal.packet.Ethernet;
73 import org.opendaylight.controller.sal.packet.IDataPacketService;
74 import org.opendaylight.controller.sal.packet.IListenDataPacket;
75 import org.opendaylight.controller.sal.packet.Packet;
76 import org.opendaylight.controller.sal.packet.PacketResult;
77 import org.opendaylight.controller.sal.packet.RawPacket;
78 import org.opendaylight.controller.sal.topology.TopoEdgeUpdate;
79 import org.opendaylight.controller.sal.utils.ListenerDispatcher;
80 import org.opendaylight.controller.sal.utils.MultiIterator;
81 import org.opendaylight.controller.sal.utils.SingletonTask;
82 import org.opendaylight.controller.switchmanager.ISwitchManager;
83 import org.opendaylight.controller.topologymanager.ITopologyManager;
84 import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
85 import org.slf4j.Logger;
86 import org.slf4j.LoggerFactory;
87
88 /**
89  * DeviceManager creates Devices based upon MAC addresses seen in the network.
90  * It tracks any network addresses mapped to the Device, and its location within
91  * the network.
92  *
93  * @author readams
94  */
95 public class DeviceManagerImpl implements IDeviceService, IEntityClassListener,
96         IListenDataPacket, ITopologyManagerAware {
97     protected static Logger logger = LoggerFactory
98             .getLogger(DeviceManagerImpl.class);
99
100     public static final String MODULE_NAME = "devicemanager";
101
102     // protected ITopologyService topology;
103     // protected IStorageSourceService storageSource;
104     // protected IRestApiService restApi;
105     // protected IThreadPoolService threadPool;
106     // protected IFlowReconcileService flowReconcileMgr;
107     // protected IFlowReconcileEngineService flowReconcileEngine;
108     // protected IDebugCounterService debugCounters;
109     // private ISyncService syncService;
110     // private IStoreClient<String,DeviceSyncRepresentation> storeClient;
111     // private DeviceSyncManager deviceSyncManager;
112
113     private ITopologyManager topology;
114     private ISwitchManager switchManager = null;
115     private IDataPacketService dataPacketService = null;
116
117     public static final String CNT_INCOMING = MODULE_NAME + "-incoming";
118     public static final String CNT_RECONCILE_REQUEST = MODULE_NAME
119             + "-reconcileRequest";
120     public static final String CNT_RECONCILE_NO_SOURCE = MODULE_NAME
121             + "-reconcileNoSourceDevice";
122     public static final String CNT_RECONCILE_NO_DEST = MODULE_NAME
123             + "-reconcileNoDestDevice";
124     public static final String CNT_BROADCAST_SOURCE = MODULE_NAME
125             + "-broadcastSource";
126     public static final String CNT_NO_SOURCE = MODULE_NAME + "-noSourceDevice";
127     public static final String CNT_NO_DEST = MODULE_NAME + "-noDestDevice";
128     public static final String CNT_DHCP_CLIENT_NAME_SNOOPED = MODULE_NAME
129             + "-dhcpClientNameSnooped";
130     public static final String CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED = MODULE_NAME
131             + "-deviceOnInternalPortNotLearned";
132     public static final String CNT_PACKET_NOT_ALLOWED = MODULE_NAME
133             + "-packetNotAllowed";
134     public static final String CNT_NEW_DEVICE = MODULE_NAME + "-newDevice";
135     public static final String CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE = MODULE_NAME
136             + "-packetOnInternalPortForKnownDevice";
137     public static final String CNT_NEW_ENTITY = MODULE_NAME + "-newEntity";
138     public static final String CNT_DEVICE_CHANGED = MODULE_NAME
139             + "-deviceChanged";
140     public static final String CNT_DEVICE_MOVED = MODULE_NAME + "-deviceMoved";
141     public static final String CNT_CLEANUP_ENTITIES_RUNS = MODULE_NAME
142             + "-cleanupEntitiesRuns";
143     public static final String CNT_ENTITY_REMOVED_TIMEOUT = MODULE_NAME
144             + "-entityRemovedTimeout";
145     public static final String CNT_DEVICE_DELETED = MODULE_NAME
146             + "-deviceDeleted";
147     public static final String CNT_DEVICE_RECLASSIFY_DELETE = MODULE_NAME
148             + "-deviceReclassifyDelete";
149     public static final String CNT_DEVICE_STORED = MODULE_NAME
150             + "-deviceStored";
151     public static final String CNT_DEVICE_STORE_THROTTLED = MODULE_NAME
152             + "-deviceStoreThrottled";
153     public static final String CNT_DEVICE_REMOVED_FROM_STORE = MODULE_NAME
154             + "-deviceRemovedFromStore";
155     public static final String CNT_SYNC_EXCEPTION = MODULE_NAME
156             + "-syncException";
157     public static final String CNT_DEVICES_FROM_STORE = MODULE_NAME
158             + "-devicesFromStore";
159     public static final String CNT_CONSOLIDATE_STORE_RUNS = MODULE_NAME
160             + "-consolidateStoreRuns";
161     public static final String CNT_CONSOLIDATE_STORE_DEVICES_REMOVED = MODULE_NAME
162             + "-consolidateStoreDevicesRemoved";
163
164     static final String DEVICE_SYNC_STORE_NAME = DeviceManagerImpl.class
165             .getCanonicalName() + ".stateStore";
166
167     /**
168      * Time interval between writes of entries for the same device to the sync
169      * store.
170      */
171     // static final int DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS =
172     // 5*60*1000; // 5 min
173     // private int syncStoreWriteIntervalMs =
174     // DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS;
175
176     /**
177      * Time after SLAVE->MASTER until we run the consolidate store code.
178      */
179     // static final int DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS =
180     // 15*1000; // 15 sec
181     // private int initialSyncStoreConsolidateMs =
182     // DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS;
183
184     /**
185      * Time interval between consolidate store runs.
186      */
187     // static final int DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS =
188     // 75*60*1000; // 75 min
189     // private final int syncStoreConsolidateIntervalMs =
190     // DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS;
191
192     /**
193      * Time in milliseconds before entities will expire
194      */
195     protected static final int ENTITY_TIMEOUT = 60 * 60 * 1000;
196
197     /**
198      * Time in seconds between cleaning up old entities/devices
199      */
200     protected static final int ENTITY_CLEANUP_INTERVAL = 60 * 60;
201
202     /**
203      * This is the master device map that maps device IDs to {@link Device}
204      * objects.
205      */
206     protected ConcurrentHashMap<Long, Device> deviceMap;
207
208     /**
209      * Counter used to generate device keys
210      */
211     protected long deviceKeyCounter = 0;
212
213     /**
214      * Lock for incrementing the device key counter
215      */
216     protected Object deviceKeyLock = new Object();
217
218     /**
219      * This is the primary entity index that contains all entities
220      */
221     protected DeviceUniqueIndex primaryIndex;
222
223     /**
224      * This stores secondary indices over the fields in the devices
225      */
226     protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
227
228     /**
229      * This map contains state for each of the {@ref IEntityClass} that exist
230      */
231     protected ConcurrentHashMap<String, ClassState> classStateMap;
232
233     /**
234      * This is the list of indices we want on a per-class basis
235      */
236     protected Set<EnumSet<DeviceField>> perClassIndices;
237
238     /**
239      * The entity classifier currently in use
240      */
241     protected IEntityClassifierService entityClassifier;
242
243     /**
244      * Used to cache state about specific entity classes
245      */
246     protected class ClassState {
247
248         /**
249          * The class index
250          */
251         protected DeviceUniqueIndex classIndex;
252
253         /**
254          * This stores secondary indices over the fields in the device for the
255          * class
256          */
257         protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
258
259         /**
260          * Allocate a new {@link ClassState} object for the class
261          *
262          * @param clazz
263          *            the class to use for the state
264          */
265         public ClassState(IEntityClass clazz) {
266             EnumSet<DeviceField> keyFields = clazz.getKeyFields();
267             EnumSet<DeviceField> primaryKeyFields = entityClassifier
268                     .getKeyFields();
269             boolean keyFieldsMatchPrimary = primaryKeyFields.equals(keyFields);
270
271             if (!keyFieldsMatchPrimary)
272                 classIndex = new DeviceUniqueIndex(keyFields);
273
274             secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
275             for (EnumSet<DeviceField> fields : perClassIndices) {
276                 secondaryIndexMap.put(fields, new DeviceMultiIndex(fields));
277             }
278         }
279     }
280
281     /**
282      * Device manager event listeners reclassifyDeviceListeners are notified
283      * first before reconcileDeviceListeners. This is to make sure devices are
284      * correctly reclassified before reconciliation.
285      */
286     protected ListenerDispatcher<String, IDeviceListener> deviceListeners;
287
288     /**
289      * A device update event to be dispatched
290      */
291     protected static class DeviceUpdate {
292         public enum Change {
293             ADD, DELETE, CHANGE;
294         }
295
296         /**
297          * The affected device
298          */
299         protected Device device;
300
301         /**
302          * The change that was made
303          */
304         protected Change change;
305
306         /**
307          * If not added, then this is the list of fields changed
308          */
309         protected EnumSet<DeviceField> fieldsChanged;
310
311         public DeviceUpdate(Device device, Change change,
312                 EnumSet<DeviceField> fieldsChanged) {
313             super();
314             this.device = device;
315             this.change = change;
316             this.fieldsChanged = fieldsChanged;
317         }
318
319         @Override
320         public String toString() {
321             String devIdStr = device.getEntityClass().getName() + "::"
322                     + device.getMACAddressString();
323             return "DeviceUpdate [device=" + devIdStr + ", change=" + change
324                     + ", fieldsChanged=" + fieldsChanged + "]";
325         }
326
327     }
328
329     /**
330      * AttachmentPointComparator
331      *
332      * Compares two attachment points and returns the latest one. It is assumed
333      * that the two attachment points are in the same L2 domain.
334      *
335      * @author srini
336      */
337     protected class AttachmentPointComparator implements
338             Comparator<AttachmentPoint> {
339         public AttachmentPointComparator() {
340             super();
341         }
342
343         @Override
344         public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) {
345             // First compare based on L2 domain ID;
346
347             // XXX - missing functionality -- need topology
348             // long oldDomain = topology.getL2DomainId(oldSw);
349             // boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort);
350             long oldDomain = 0;
351             boolean oldBD = false;
352
353             // XXX - missing functionality -- need topology
354             // long newDomain = topology.getL2DomainId(newSw);
355             // boolean newBD = topology.isBroadcastDomainPort(newSw, newPort);
356             long newDomain = 0;
357             boolean newBD = false;
358
359             if (oldDomain < newDomain)
360                 return -1;
361             else if (oldDomain > newDomain)
362                 return 1;
363
364             // Give preference to OFPP_LOCAL always
365             if (!oldAP.getPort().getType().equals(NodeConnectorIDType.SWSTACK)
366                     && newAP.getPort().getType()
367                             .equals(NodeConnectorIDType.SWSTACK)) {
368                 return -1;
369             } else if (oldAP.getPort().getType()
370                     .equals(NodeConnectorIDType.SWSTACK)
371                     && !newAP.getPort().getType()
372                             .equals(NodeConnectorIDType.SWSTACK)) {
373                 return 1;
374             }
375
376             // We expect that the last seen of the new AP is higher than
377             // old AP, if it is not, just reverse and send the negative
378             // of the result.
379             if (oldAP.getActiveSince() > newAP.getActiveSince())
380                 return -compare(newAP, oldAP);
381
382             long activeOffset = 0;
383             // XXX - missing functionality -- need topology
384             // if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) {
385             if (!newBD && oldBD) {
386                 return -1;
387             }
388             if (newBD && oldBD) {
389                 activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT;
390             } else if (newBD && !oldBD) {
391                 activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT;
392             }
393
394             // } else {
395             // // The attachment point is consistent.
396             // activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT;
397             // }
398
399             if ((newAP.getActiveSince() > oldAP.getLastSeen() + activeOffset)
400                     || (newAP.getLastSeen() > oldAP.getLastSeen()
401                             + AttachmentPoint.INACTIVITY_INTERVAL)) {
402                 return -1;
403             }
404             return 1;
405         }
406     }
407
408     /**
409      * Comparator for sorting by cluster ID
410      */
411     public AttachmentPointComparator apComparator;
412
413     /**
414      * Switch ports where attachment points shouldn't be learned
415      */
416     private Set<SwitchPort> suppressAPs;
417
418     /**
419      * Periodic task to clean up expired entities
420      */
421     public SingletonTask entityCleanupTask;
422
423     // ********************
424     // Dependency injection
425     // ********************
426
427     void setDataPacketService(IDataPacketService s) {
428         this.dataPacketService = s;
429     }
430
431     void unsetDataPacketService(IDataPacketService s) {
432         if (this.dataPacketService == s) {
433             this.dataPacketService = null;
434         }
435     }
436
437     public void setTopologyManager(ITopologyManager s) {
438         this.topology = s;
439     }
440
441     public void unsetTopologyManager(ITopologyManager s) {
442         if (this.topology == s) {
443             logger.debug("Topology Manager Service removed!");
444             this.topology = null;
445         }
446     }
447
448     private volatile boolean stopped = true;
449     private ScheduledExecutorService ses;
450
451     public void stop() {
452         stopped = true;
453         if (ses != null)
454             ses.shutdownNow();
455     }
456
457     public void start() {
458         this.perClassIndices = new HashSet<EnumSet<DeviceField>>();
459
460         // XXX - TODO need to make it possible to register a non-default
461         // classifier
462         entityClassifier = new DefaultEntityClassifier();
463         this.deviceListeners = new ListenerDispatcher<String, IDeviceListener>();
464         this.suppressAPs = Collections
465                 .newSetFromMap(new ConcurrentHashMap<SwitchPort, Boolean>());
466         primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields());
467         secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
468
469         deviceMap = new ConcurrentHashMap<Long, Device>();
470         classStateMap = new ConcurrentHashMap<String, ClassState>();
471         apComparator = new AttachmentPointComparator();
472
473         addIndex(true, EnumSet.of(DeviceField.IPV4));
474
475         // floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
476         // floodlightProvider.addHAListener(this.haListenerDelegate);
477         // if (topology != null)
478         // topology.addListener(this);
479         // flowReconcileMgr.addFlowReconcileListener(this);
480         // entityClassifier.addListener(this);
481
482         stopped = false;
483         // XXX - Should use a common threadpool but this doesn't currently exist
484         ses = Executors.newScheduledThreadPool(1);
485         Runnable ecr = new Runnable() {
486             @Override
487             public void run() {
488                 cleanupEntities();
489                 if (!stopped)
490                     entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
491                             TimeUnit.SECONDS);
492             }
493         };
494         entityCleanupTask = new SingletonTask(ses, ecr);
495         entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, TimeUnit.SECONDS);
496
497         /*
498          * XXX Missing functionality if (restApi != null) {
499          * restApi.addRestletRoutable(new DeviceRoutable()); } else {
500          * logger.debug("Could not instantiate REST API"); }
501          */
502
503         registerDeviceManagerDebugCounters();
504
505         /*
506          * XXX Missing functionality try {
507          * this.syncService.registerStore(DEVICE_SYNC_STORE_NAME, Scope.LOCAL);
508          * this.storeClient = this.syncService
509          * .getStoreClient(DEVICE_SYNC_STORE_NAME, String.class,
510          * DeviceSyncRepresentation.class); } catch (SyncException e) { throw
511          * new FloodlightModuleException("Error while setting up sync service",
512          * e); }
513          *
514          * Runnable consolidateStoreRunner = new Runnable() {
515          *
516          * @Override public void run() { deviceSyncManager.consolidateStore();
517          * storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs,
518          * TimeUnit.MILLISECONDS); debugCounters.flushCounters(); } };
519          * storeConsolidateTask = new SingletonTask(ses,
520          * consolidateStoreRunner); if (isMaster)
521          * storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs,
522          * TimeUnit.MILLISECONDS);
523          */
524     }
525
526     /**
527      * Periodic task to consolidate entries in the store. I.e., delete entries
528      * in the store that are not known to DeviceManager
529      */
530     // XXX - Missing functionality
531     // private SingletonTask storeConsolidateTask;
532
533     // *********************
534     // IDeviceManagerService
535     // *********************
536
537     @Override
538     public IDevice getDevice(Long deviceKey) {
539         return deviceMap.get(deviceKey);
540     }
541
542     @Override
543     public IDevice findDevice(long macAddress, Short vlan, Integer ipv4Address,
544             NodeConnector port) throws IllegalArgumentException {
545         if (vlan != null && vlan.shortValue() <= 0)
546             vlan = null;
547         if (ipv4Address != null && ipv4Address == 0)
548             ipv4Address = null;
549         Entity e = new Entity(macAddress, vlan, ipv4Address, port, null);
550         if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) {
551             throw new IllegalArgumentException("Not all key fields specified."
552                     + " Required fields: " + entityClassifier.getKeyFields());
553         }
554         return findDeviceByEntity(e);
555     }
556
557     @Override
558     public IDevice findClassDevice(IEntityClass entityClass, long macAddress,
559             Short vlan, Integer ipv4Address) throws IllegalArgumentException {
560         if (vlan != null && vlan.shortValue() <= 0)
561             vlan = null;
562         if (ipv4Address != null && ipv4Address == 0)
563             ipv4Address = null;
564         Entity e = new Entity(macAddress, vlan, ipv4Address, null, null);
565         if (entityClass == null
566                 || !allKeyFieldsPresent(e, entityClass.getKeyFields())) {
567             throw new IllegalArgumentException("Not all key fields and/or "
568                     + " no source device specified. Required fields: "
569                     + entityClassifier.getKeyFields());
570         }
571         return findDestByEntity(entityClass, e);
572     }
573
574     @Override
575     public Collection<? extends IDevice> getAllDevices() {
576         return Collections.unmodifiableCollection(deviceMap.values());
577     }
578
579     @Override
580     public void addIndex(boolean perClass, EnumSet<DeviceField> keyFields) {
581         if (perClass) {
582             perClassIndices.add(keyFields);
583         } else {
584             secondaryIndexMap.put(keyFields, new DeviceMultiIndex(keyFields));
585         }
586     }
587
588     @Override
589     public Iterator<? extends IDevice> queryDevices(Long macAddress,
590             Short vlan, Integer ipv4Address, NodeConnector port) {
591         DeviceIndex index = null;
592         if (secondaryIndexMap.size() > 0) {
593             EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
594                     ipv4Address, port);
595             index = secondaryIndexMap.get(keys);
596         }
597
598         Iterator<Device> deviceIterator = null;
599         if (index == null) {
600             // Do a full table scan
601             deviceIterator = deviceMap.values().iterator();
602         } else {
603             // index lookup
604             Entity entity = new Entity((macAddress == null ? 0 : macAddress),
605                     vlan, ipv4Address, port, null);
606             deviceIterator = new DeviceIndexInterator(this,
607                     index.queryByEntity(entity));
608         }
609
610         DeviceIterator di = new DeviceIterator(deviceIterator, null,
611                 macAddress, vlan, ipv4Address, port);
612         return di;
613     }
614
615     @Override
616     public Iterator<? extends IDevice> queryClassDevices(
617             IEntityClass entityClass, Long macAddress, Short vlan,
618             Integer ipv4Address, NodeConnector port) {
619         ArrayList<Iterator<Device>> iterators = new ArrayList<Iterator<Device>>();
620         ClassState classState = getClassState(entityClass);
621
622         DeviceIndex index = null;
623         if (classState.secondaryIndexMap.size() > 0) {
624             EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
625                     ipv4Address, port);
626             index = classState.secondaryIndexMap.get(keys);
627         }
628
629         Iterator<Device> iter;
630         if (index == null) {
631             index = classState.classIndex;
632             if (index == null) {
633                 // scan all devices
634                 return new DeviceIterator(deviceMap.values().iterator(),
635                         new IEntityClass[] { entityClass }, macAddress, vlan,
636                         ipv4Address, port);
637             } else {
638                 // scan the entire class
639                 iter = new DeviceIndexInterator(this, index.getAll());
640             }
641         } else {
642             // index lookup
643             Entity entity = new Entity((macAddress == null ? 0 : macAddress),
644                     vlan, ipv4Address, port, null);
645             iter = new DeviceIndexInterator(this, index.queryByEntity(entity));
646         }
647         iterators.add(iter);
648
649         return new MultiIterator<Device>(iterators.iterator());
650     }
651
652     protected Iterator<Device> getDeviceIteratorForQuery(Long macAddress,
653             Short vlan, Integer ipv4Address, NodeConnector port) {
654         DeviceIndex index = null;
655         if (secondaryIndexMap.size() > 0) {
656             EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
657                     ipv4Address, port);
658             index = secondaryIndexMap.get(keys);
659         }
660
661         Iterator<Device> deviceIterator = null;
662         if (index == null) {
663             // Do a full table scan
664             deviceIterator = deviceMap.values().iterator();
665         } else {
666             // index lookup
667             Entity entity = new Entity((macAddress == null ? 0 : macAddress),
668                     vlan, ipv4Address, port, null);
669             deviceIterator = new DeviceIndexInterator(this,
670                     index.queryByEntity(entity));
671         }
672
673         DeviceIterator di = new DeviceIterator(deviceIterator, null,
674                 macAddress, vlan, ipv4Address, port);
675         return di;
676     }
677
678     @Override
679     public void addListener(IDeviceListener listener) {
680         deviceListeners.addListener("device", listener);
681         logListeners();
682     }
683
684     @Override
685     public void addSuppressAPs(NodeConnector port) {
686         suppressAPs.add(new SwitchPort(port));
687     }
688
689     @Override
690     public void removeSuppressAPs(NodeConnector port) {
691         suppressAPs.remove(new SwitchPort(port));
692     }
693
694     @Override
695     public Set<SwitchPort> getSuppressAPs() {
696         return Collections.unmodifiableSet(suppressAPs);
697     }
698
699     private void logListeners() {
700         List<IDeviceListener> listeners = deviceListeners.getOrderedListeners();
701         if (listeners != null) {
702             StringBuffer sb = new StringBuffer();
703             sb.append("DeviceListeners: ");
704             for (IDeviceListener l : listeners) {
705                 sb.append(l.getName());
706                 sb.append(",");
707             }
708             logger.debug(sb.toString());
709         }
710     }
711
712     // ***************
713     // IFlowReconcileListener
714     // ***************
715     /*
716      * XXX - Missing functionality
717      *
718      * @Override public Command reconcileFlows(ArrayList<OFMatchReconcile>
719      * ofmRcList) { ListIterator<OFMatchReconcile> iter =
720      * ofmRcList.listIterator(); while (iter.hasNext()) { OFMatchReconcile ofm =
721      * iter.next();
722      *
723      * // Remove the STOPPed flow. if (Command.STOP == reconcileFlow(ofm)) {
724      * iter.remove(); } }
725      *
726      * if (ofmRcList.size() > 0) { return Command.CONTINUE; } else { return
727      * Command.STOP; } }
728      *
729      * protected Command reconcileFlow(OFMatchReconcile ofm) {
730      * debugCounters.updateCounter(CNT_RECONCILE_REQUEST); // Extract source
731      * entity information Entity srcEntity =
732      * getEntityFromFlowMod(ofm.ofmWithSwDpid, true); if (srcEntity == null) {
733      * debugCounters.updateCounter(CNT_RECONCILE_NO_SOURCE); return
734      * Command.STOP; }
735      *
736      * // Find the device by source entity Device srcDevice =
737      * findDeviceByEntity(srcEntity); if (srcDevice == null) {
738      * debugCounters.updateCounter(CNT_RECONCILE_NO_SOURCE); return
739      * Command.STOP; } // Store the source device in the context
740      * fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice);
741      *
742      * // Find the device matching the destination from the entity // classes of
743      * the source. Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid,
744      * false); Device dstDevice = null; if (dstEntity != null) { dstDevice =
745      * findDestByEntity(srcDevice.getEntityClass(), dstEntity); if (dstDevice !=
746      * null) fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice); else
747      * debugCounters.updateCounter(CNT_RECONCILE_NO_DEST); } else {
748      * debugCounters.updateCounter(CNT_RECONCILE_NO_DEST); } if
749      * (logger.isTraceEnabled()) {
750      * logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, " +
751      * "dstEntity={}, dstDev={}", new Object[] {ofm.ofmWithSwDpid.getOfMatch(),
752      * srcEntity, srcDevice, dstEntity, dstDevice } ); } return
753      * Command.CONTINUE; }
754      */
755
756     // *****************
757     // IListenDataPacket
758     // *****************
759
760     @Override
761     public PacketResult receiveDataPacket(RawPacket inPkt) {
762         // XXX - Can this really pass in null? Why would you ever want that?
763         if (inPkt == null) {
764             return PacketResult.IGNORED;
765         }
766         try {
767             throw new Exception("Sample");
768         } catch (Exception e) {
769             logger.error("Sample stack trace", e);
770         }
771
772         Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
773         Ethernet eth;
774         if (formattedPak instanceof Ethernet) {
775             eth = (Ethernet) formattedPak;
776         } else {
777             return PacketResult.IGNORED;
778         }
779
780         // Extract source entity information
781         NodeConnector inPort = inPkt.getIncomingNodeConnector();
782         Entity srcEntity = getSourceEntityFromPacket(eth, inPort);
783         if (srcEntity == null) {
784             // debugCounters.updateCounter(CNT_BROADCAST_SOURCE);
785             return PacketResult.CONSUME;
786         }
787
788         // Learn from ARP packet for special VRRP settings.
789         // In VRRP settings, the source MAC address and sender MAC
790         // addresses can be different. In such cases, we need to learn
791         // the IP to MAC mapping of the VRRP IP address. The source
792         // entity will not have that information. Hence, a separate call
793         // to learn devices in such cases.
794         learnDeviceFromArpResponseData(eth, inPort);
795
796         // Learn/lookup device information
797         Device srcDevice = learnDeviceByEntity(srcEntity);
798         if (srcDevice == null) {
799             // debugCounters.updateCounter(CNT_NO_SOURCE);
800             return PacketResult.CONSUME;
801         }
802         logger.trace("Saw packet from device {}", srcDevice);
803
804         // // Store the source device in the context
805         // fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
806         //
807         // // Find the device matching the destination from the entity
808         // // classes of the source.
809         // Entity dstEntity = getDestEntityFromPacket(eth);
810         // Device dstDevice = null;
811         // if (dstEntity != null) {
812         // dstDevice =
813         // findDestByEntity(srcDevice.getEntityClass(), dstEntity);
814         // if (dstDevice != null)
815         // fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
816         // //else
817         // //debugCounters.updateCounter(CNT_NO_DEST);
818         // } else {
819         // //debugCounters.updateCounter(CNT_NO_DEST);
820         // }
821         //
822         // if (logger.isTraceEnabled()) {
823         // logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
824         // " *** srcDev={} *** dstDev={} *** ",
825         // new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
826         // srcDevice, dstDevice });
827         // }
828         //
829         // snoopDHCPClientName(eth, srcDevice);
830
831         return PacketResult.KEEP_PROCESSING;
832     }
833
834     // ****************
835     // Internal methods
836     // ****************
837
838     /**
839      * Snoop and record client-provided host name from DHCP requests
840      *
841      * @param eth
842      * @param srcDevice
843      */
844     // private void snoopDHCPClientName(Ethernet eth, Device srcDevice) {
845     // if (! (eth.getPayload() instanceof IPv4) )
846     // return;
847     // IPv4 ipv4 = (IPv4) eth.getPayload();
848     // if (! (ipv4.getPayload() instanceof UDP) )
849     // return;
850     // UDP udp = (UDP) ipv4.getPayload();
851     // if (!(udp.getPayload() instanceof DHCP))
852     // return;
853     // DHCP dhcp = (DHCP) udp.getPayload();
854     // byte opcode = dhcp.getOpCode();
855     // if (opcode == DHCP.OPCODE_REQUEST) {
856     // DHCPOption dhcpOption = dhcp.getOption(
857     // DHCPOptionCode.OptionCode_Hostname);
858     // if (dhcpOption != null) {
859     // debugCounters.updateCounter(CNT_DHCP_CLIENT_NAME_SNOOPED);
860     // srcDevice.dhcpClientName = new String(dhcpOption.getData());
861     // }
862     // }
863     // }
864
865     /**
866      * Check whether the given attachment point is valid given the current
867      * topology
868      *
869      * @param switchDPID
870      *            the DPID
871      * @param switchPort
872      *            the port
873      * @return true if it's a valid attachment point
874      */
875     public boolean isValidAttachmentPoint(NodeConnector port) {
876         // XXX - missing functionality -- need topology module
877         // if (topology.isAttachmentPointPort(port) == false)
878         // return false;
879         if (topology.isInternal(port))
880             return false;
881         if (!switchManager.isNodeConnectorEnabled(port))
882             return false;
883         if (suppressAPs.contains(new SwitchPort(port)))
884             return false;
885
886         return true;
887     }
888
889     /**
890      * Get sender IP address from packet if the packet is either an ARP packet.
891      *
892      * @param eth
893      * @param dlAddr
894      * @return
895      */
896     private int getSrcNwAddr(Ethernet eth, long dlAddr) {
897         if (eth.getPayload() instanceof ARP) {
898             ARP arp = (ARP) eth.getPayload();
899             if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP)
900                     && (toLong(arp.getSenderHardwareAddress()) == dlAddr)) {
901                 return toIPv4Address(arp.getSenderProtocolAddress());
902             }
903         }
904         return 0;
905     }
906
907     /**
908      * Parse an entity from an {@link Ethernet} packet.
909      *
910      * @param eth
911      *            the packet to parse
912      * @param sw
913      *            the switch on which the packet arrived
914      * @param pi
915      *            the original packetin
916      * @return the entity from the packet
917      */
918     protected Entity getSourceEntityFromPacket(Ethernet eth, NodeConnector port) {
919         byte[] dlAddrArr = eth.getSourceMACAddress();
920         long dlAddr = toLong(dlAddrArr);
921
922         // Ignore broadcast/multicast source
923         if ((dlAddrArr[0] & 0x1) != 0)
924             return null;
925
926         // XXX missing functionality
927         // short vlan = 0;
928         int nwSrc = getSrcNwAddr(eth, dlAddr);
929         return new Entity(dlAddr, null, ((nwSrc != 0) ? nwSrc : null), port,
930                 new Date());
931     }
932
933     /**
934      * Learn device from ARP data in scenarios where the Ethernet source MAC is
935      * different from the sender hardware address in ARP data.
936      */
937     protected void learnDeviceFromArpResponseData(Ethernet eth,
938             NodeConnector port) {
939
940         if (!(eth.getPayload() instanceof ARP))
941             return;
942         ARP arp = (ARP) eth.getPayload();
943
944         byte[] dlAddrArr = eth.getSourceMACAddress();
945         long dlAddr = toLong(dlAddrArr);
946
947         byte[] senderHardwareAddr = arp.getSenderHardwareAddress();
948         long senderAddr = toLong(senderHardwareAddr);
949
950         if (dlAddr == senderAddr)
951             return;
952
953         // Ignore broadcast/multicast source
954         if ((senderHardwareAddr[0] & 0x1) != 0)
955             return;
956
957         // short vlan = eth.getVlanID();
958         int nwSrc = toIPv4Address(arp.getSenderProtocolAddress());
959
960         Entity e = new Entity(senderAddr, null, ((nwSrc != 0) ? nwSrc : null),
961                 port, new Date());
962
963         learnDeviceByEntity(e);
964     }
965
966     /**
967      * Get a (partial) entity for the destination from the packet.
968      *
969      * @param eth
970      * @return
971      */
972     // protected Entity getDestEntityFromPacket(Ethernet eth) {
973     // byte[] dlAddrArr = eth.getDestinationMACAddress();
974     // long dlAddr = Ethernet.toLong(dlAddrArr);
975     // short vlan = eth.getVlanID();
976     // int nwDst = 0;
977     //
978     // // Ignore broadcast/multicast destination
979     // if ((dlAddrArr[0] & 0x1) != 0)
980     // return null;
981     //
982     // if (eth.getPayload() instanceof IPv4) {
983     // IPv4 ipv4 = (IPv4) eth.getPayload();
984     // nwDst = ipv4.getDestinationAddress();
985     // }
986     //
987     // return new Entity(dlAddr,
988     // ((vlan >= 0) ? vlan : null),
989     // ((nwDst != 0) ? nwDst : null),
990     // null,
991     // null,
992     // null);
993     // }
994
995     /**
996      * Parse an entity from an OFMatchWithSwDpid.
997      *
998      * @param ofmWithSwDpid
999      * @return the entity from the packet
1000      */
1001     // private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid,
1002     // boolean isSource) {
1003     // byte[] dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerSource();
1004     // int nwSrc = ofmWithSwDpid.getOfMatch().getNetworkSource();
1005     // if (!isSource) {
1006     // dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerDestination();
1007     // nwSrc = ofmWithSwDpid.getOfMatch().getNetworkDestination();
1008     // }
1009     //
1010     // long dlAddr = Ethernet.toLong(dlAddrArr);
1011     //
1012     // // Ignore broadcast/multicast source
1013     // if ((dlAddrArr[0] & 0x1) != 0)
1014     // return null;
1015     //
1016     // Long swDpid = null;
1017     // Short inPort = null;
1018     //
1019     // if (isSource) {
1020     // swDpid = ofmWithSwDpid.getSwitchDataPathId();
1021     // inPort = ofmWithSwDpid.getOfMatch().getInputPort();
1022     // }
1023     //
1024     // /**for the new flow cache design, the flow mods retrived are not always
1025     // from the source, learn AP should be disabled --meiyang*/
1026     // boolean learnap = false;
1027     // /**
1028     // * if (swDpid == null ||
1029     // inPort == null ||
1030     // !isValidAttachmentPoint(swDpid, inPort)) {
1031     // // If this is an internal port or we otherwise don't want
1032     // // to learn on these ports. In the future, we should
1033     // // handle this case by labeling flows with something that
1034     // // will give us the entity class. For now, we'll do our
1035     // // best assuming attachment point information isn't used
1036     // // as a key field.
1037     // learnap = false;
1038     // }
1039     // */
1040     //
1041     // short vlan = ofmWithSwDpid.getOfMatch().getDataLayerVirtualLan();
1042     // return new Entity(dlAddr,
1043     // ((vlan >= 0) ? vlan : null),
1044     // ((nwSrc != 0) ? nwSrc : null),
1045     // (learnap ? swDpid : null),
1046     // (learnap ? (int)inPort : null),
1047     // new Date());
1048     // }
1049
1050     /**
1051      * Look up a {@link Device} based on the provided {@link Entity}. We first
1052      * check the primary index. If we do not find an entry there we classify the
1053      * device into its IEntityClass and query the classIndex. This implies that
1054      * all key field of the current IEntityClassifier must be present in the
1055      * entity for the lookup to succeed!
1056      *
1057      * @param entity
1058      *            the entity to search for
1059      * @return The {@link Device} object if found
1060      */
1061     protected Device findDeviceByEntity(Entity entity) {
1062         // Look up the fully-qualified entity to see if it already
1063         // exists in the primary entity index.
1064         Long deviceKey = primaryIndex.findByEntity(entity);
1065         IEntityClass entityClass = null;
1066
1067         if (deviceKey == null) {
1068             // If the entity does not exist in the primary entity index,
1069             // use the entity classifier for find the classes for the
1070             // entity. Look up the entity in the returned class'
1071             // class entity index.
1072             entityClass = entityClassifier.classifyEntity(entity);
1073             if (entityClass == null) {
1074                 return null;
1075             }
1076             ClassState classState = getClassState(entityClass);
1077
1078             if (classState.classIndex != null) {
1079                 deviceKey = classState.classIndex.findByEntity(entity);
1080             }
1081         }
1082         if (deviceKey == null)
1083             return null;
1084         return deviceMap.get(deviceKey);
1085     }
1086
1087     /**
1088      * Get a destination device using entity fields that corresponds with the
1089      * given source device. The source device is important since there could be
1090      * ambiguity in the destination device without the attachment point
1091      * information.
1092      *
1093      * @param reference
1094      *            the source device's entity class. The returned destination
1095      *            will be in the same entity class as the source.
1096      * @param dstEntity
1097      *            the entity to look up
1098      * @return an {@link Device} or null if no device is found.
1099      */
1100     protected Device findDestByEntity(IEntityClass reference, Entity dstEntity) {
1101
1102         // Look up the fully-qualified entity to see if it
1103         // exists in the primary entity index
1104         Long deviceKey = primaryIndex.findByEntity(dstEntity);
1105
1106         if (deviceKey == null) {
1107             // This could happen because:
1108             // 1) no destination known, or a broadcast destination
1109             // 2) if we have attachment point key fields since
1110             // attachment point information isn't available for
1111             // destination devices.
1112             // For the second case, we'll need to match up the
1113             // destination device with the class of the source
1114             // device.
1115             ClassState classState = getClassState(reference);
1116             if (classState.classIndex == null) {
1117                 return null;
1118             }
1119             deviceKey = classState.classIndex.findByEntity(dstEntity);
1120         }
1121         if (deviceKey == null)
1122             return null;
1123         return deviceMap.get(deviceKey);
1124     }
1125
1126     /**
1127      * Look up a {@link Device} within a particular entity class based on the
1128      * provided {@link Entity}.
1129      *
1130      * @param clazz
1131      *            the entity class to search for the entity
1132      * @param entity
1133      *            the entity to search for
1134      * @return The {@link Device} object if found private Device
1135      *         findDeviceInClassByEntity(IEntityClass clazz, Entity entity) { //
1136      *         XXX - TODO throw new UnsupportedOperationException(); }
1137      */
1138
1139     /**
1140      * Look up a {@link Device} based on the provided {@link Entity}. Also
1141      * learns based on the new entity, and will update existing devices as
1142      * required.
1143      *
1144      * @param entity
1145      *            the {@link Entity}
1146      * @return The {@link Device} object if found
1147      */
1148     protected Device learnDeviceByEntity(Entity entity) {
1149         logger.info("Primary index {}", primaryIndex);
1150         ArrayList<Long> deleteQueue = null;
1151         LinkedList<DeviceUpdate> deviceUpdates = null;
1152         Device device = null;
1153
1154         // we may need to restart the learning process if we detect
1155         // concurrent modification. Note that we ensure that at least
1156         // one thread should always succeed so we don't get into infinite
1157         // starvation loops
1158         while (true) {
1159             deviceUpdates = null;
1160
1161             // Look up the fully-qualified entity to see if it already
1162             // exists in the primary entity index.
1163             Long deviceKey = primaryIndex.findByEntity(entity);
1164             IEntityClass entityClass = null;
1165
1166             if (deviceKey == null) {
1167                 // If the entity does not exist in the primary entity index,
1168                 // use the entity classifier for find the classes for the
1169                 // entity. Look up the entity in the returned class'
1170                 // class entity index.
1171                 entityClass = entityClassifier.classifyEntity(entity);
1172                 if (entityClass == null) {
1173                     // could not classify entity. No device
1174                     device = null;
1175                     break;
1176                 }
1177                 ClassState classState = getClassState(entityClass);
1178
1179                 if (classState.classIndex != null) {
1180                     deviceKey = classState.classIndex.findByEntity(entity);
1181                 }
1182             }
1183             if (deviceKey != null) {
1184                 // If the primary or secondary index contains the entity
1185                 // use resulting device key to look up the device in the
1186                 // device map, and use the referenced Device below.
1187                 device = deviceMap.get(deviceKey);
1188                 if (device == null) {
1189                     // This can happen due to concurrent modification
1190                     if (logger.isDebugEnabled()) {
1191                         logger.debug("No device for deviceKey {} while "
1192                                 + "while processing entity {}", deviceKey,
1193                                 entity);
1194                     }
1195                     // if so, then try again till we don't even get the device
1196                     // key
1197                     // and so we recreate the device
1198                     continue;
1199                 }
1200             } else {
1201                 // If the secondary index does not contain the entity,
1202                 // create a new Device object containing the entity, and
1203                 // generate a new device ID if the the entity is on an
1204                 // attachment point port. Otherwise ignore.
1205                 if (entity.hasSwitchPort()
1206                         && !isValidAttachmentPoint(entity.getPort())) {
1207                     // debugCounters.updateCounter(CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED);
1208                     if (logger.isDebugEnabled()) {
1209                         logger.debug("Not learning new device on internal"
1210                                 + " link: {}", entity);
1211                     }
1212                     device = null;
1213                     break;
1214                 }
1215                 // Before we create the new device also check if
1216                 // the entity is allowed (e.g., for spoofing protection)
1217                 if (!isEntityAllowed(entity, entityClass)) {
1218                     // debugCounters.updateCounter(CNT_PACKET_NOT_ALLOWED);
1219                     if (logger.isDebugEnabled()) {
1220                         logger.debug("PacketIn is not allowed {} {}",
1221                                 entityClass.getName(), entity);
1222                     }
1223                     device = null;
1224                     break;
1225                 }
1226                 synchronized (deviceKeyLock) {
1227                     deviceKey = Long.valueOf(deviceKeyCounter++);
1228                 }
1229                 device = allocateDevice(deviceKey, entity, entityClass);
1230
1231                 // Add the new device to the primary map with a simple put
1232                 deviceMap.put(deviceKey, device);
1233
1234                 // update indices
1235                 if (!updateIndices(device, deviceKey)) {
1236                     if (deleteQueue == null)
1237                         deleteQueue = new ArrayList<Long>();
1238                     deleteQueue.add(deviceKey);
1239                     continue;
1240                 }
1241
1242                 updateSecondaryIndices(entity, entityClass, deviceKey);
1243
1244                 // We need to count and log here. If we log earlier we could
1245                 // hit a concurrent modification and restart the dev creation
1246                 // and potentially count the device twice.
1247                 // debugCounters.updateCounter(CNT_NEW_DEVICE);
1248                 if (logger.isDebugEnabled()) {
1249                     logger.debug(
1250                             "New device created: {} deviceKey={}, entity={}",
1251                             new Object[] { device, deviceKey, entity });
1252                 }
1253                 // generate new device update
1254                 deviceUpdates = updateUpdates(deviceUpdates, new DeviceUpdate(
1255                         device, ADD, null));
1256
1257                 break;
1258             }
1259             // if it gets here, we have a pre-existing Device for this Entity
1260             if (!isEntityAllowed(entity, device.getEntityClass())) {
1261                 // debugCounters.updateCounter(CNT_PACKET_NOT_ALLOWED);
1262                 if (logger.isDebugEnabled()) {
1263                     logger.info("PacketIn is not allowed {} {}", device
1264                             .getEntityClass().getName(), entity);
1265                 }
1266                 return null;
1267             }
1268             // If this is not an attachment point port we don't learn the new
1269             // entity
1270             // and don't update indexes. But we do allow the device to continue
1271             // up
1272             // the chain.
1273             if (entity.hasSwitchPort()
1274                     && !isValidAttachmentPoint(entity.getPort())) {
1275                 // debugCounters.updateCounter(CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE);
1276                 break;
1277             }
1278             int entityindex = -1;
1279             if ((entityindex = device.entityIndex(entity)) >= 0) {
1280                 // Entity already exists
1281                 // update timestamp on the found entity
1282                 Date lastSeen = entity.getLastSeenTimestamp();
1283                 if (lastSeen == null) {
1284                     lastSeen = new Date();
1285                     entity.setLastSeenTimestamp(lastSeen);
1286                 }
1287                 device.entities[entityindex].setLastSeenTimestamp(lastSeen);
1288                 // we break the loop after checking for changes to the AP
1289             } else {
1290                 // New entity for this device
1291                 // compute the insertion point for the entity.
1292                 // see Arrays.binarySearch()
1293                 entityindex = -(entityindex + 1);
1294                 Device newDevice = allocateDevice(device, entity, entityindex);
1295
1296                 // generate updates
1297                 EnumSet<DeviceField> changedFields = findChangedFields(device,
1298                         entity);
1299
1300                 // update the device map with a replace call
1301                 boolean res = deviceMap.replace(deviceKey, device, newDevice);
1302                 // If replace returns false, restart the process from the
1303                 // beginning (this implies another thread concurrently
1304                 // modified this Device).
1305                 if (!res)
1306                     continue;
1307
1308                 device = newDevice;
1309                 // update indices
1310                 if (!updateIndices(device, deviceKey)) {
1311                     continue;
1312                 }
1313                 updateSecondaryIndices(entity, device.getEntityClass(),
1314                         deviceKey);
1315
1316                 // We need to count here after all the possible "continue"
1317                 // statements in this branch
1318                 // debugCounters.updateCounter(CNT_NEW_ENTITY);
1319                 if (changedFields.size() > 0) {
1320                     // debugCounters.updateCounter(CNT_DEVICE_CHANGED);
1321                     deviceUpdates = updateUpdates(deviceUpdates,
1322                             new DeviceUpdate(newDevice, CHANGE, changedFields));
1323                 }
1324                 // we break the loop after checking for changed AP
1325             }
1326             // Update attachment point (will only be hit if the device
1327             // already existed and no concurrent modification)
1328             if (entity.hasSwitchPort()) {
1329                 boolean moved = device.updateAttachmentPoint(entity.getPort(),
1330                         entity.getLastSeenTimestamp().getTime());
1331                 // TODO: use update mechanism instead of sending the
1332                 // notification directly
1333                 if (moved) {
1334                     // we count device moved events in
1335                     // sendDeviceMovedNotification()
1336                     sendDeviceMovedNotification(device);
1337                     if (logger.isTraceEnabled()) {
1338                         logger.trace("Device moved: attachment points {},"
1339                                 + "entities {}", device.attachmentPoints,
1340                                 device.entities);
1341                     }
1342                 } else {
1343                     if (logger.isTraceEnabled()) {
1344                         logger.trace("Device attachment point updated: "
1345                                 + "attachment points {}," + "entities {}",
1346                                 device.attachmentPoints, device.entities);
1347                     }
1348                 }
1349             }
1350             break;
1351         }
1352
1353         if (deleteQueue != null) {
1354             for (Long l : deleteQueue) {
1355                 Device dev = deviceMap.get(l);
1356                 this.deleteDevice(dev);
1357             }
1358         }
1359
1360         processUpdates(deviceUpdates);
1361         // deviceSyncManager.storeDeviceThrottled(device);
1362
1363         return device;
1364     }
1365
1366     protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) {
1367         return true;
1368     }
1369
1370     protected EnumSet<DeviceField> findChangedFields(Device device,
1371             Entity newEntity) {
1372         EnumSet<DeviceField> changedFields = EnumSet.of(DeviceField.IPV4,
1373                 DeviceField.VLAN, DeviceField.SWITCHPORT);
1374
1375         if (newEntity.getIpv4Address() == null)
1376             changedFields.remove(DeviceField.IPV4);
1377         if (newEntity.getVlan() == null)
1378             changedFields.remove(DeviceField.VLAN);
1379         if (newEntity.getPort() == null)
1380             changedFields.remove(DeviceField.SWITCHPORT);
1381
1382         if (changedFields.size() == 0)
1383             return changedFields;
1384
1385         for (Entity entity : device.getEntities()) {
1386             if (newEntity.getIpv4Address() == null
1387                     || (entity.getIpv4Address() != null && entity
1388                             .getIpv4Address()
1389                             .equals(newEntity.getIpv4Address())))
1390                 changedFields.remove(DeviceField.IPV4);
1391             if (newEntity.getVlan() == null
1392                     || (entity.getVlan() != null && entity.getVlan().equals(
1393                             newEntity.getVlan())))
1394                 changedFields.remove(DeviceField.VLAN);
1395             if (newEntity.getPort() == null
1396                     || (entity.getPort() != null && entity.getPort().equals(
1397                             newEntity.getPort())))
1398                 changedFields.remove(DeviceField.SWITCHPORT);
1399         }
1400
1401         return changedFields;
1402     }
1403
1404     /**
1405      * Send update notifications to listeners
1406      *
1407      * @param updates
1408      *            the updates to process.
1409      */
1410     protected void processUpdates(Queue<DeviceUpdate> updates) {
1411         if (updates == null)
1412             return;
1413         DeviceUpdate update = null;
1414         while (null != (update = updates.poll())) {
1415             if (logger.isTraceEnabled()) {
1416                 logger.trace("Dispatching device update: {}", update);
1417             }
1418             // if (update.change == DeviceUpdate.Change.DELETE)
1419             // deviceSyncManager.removeDevice(update.device);
1420             // else
1421             // deviceSyncManager.storeDevice(update.device);
1422             List<IDeviceListener> listeners = deviceListeners
1423                     .getOrderedListeners();
1424             notifyListeners(listeners, update);
1425         }
1426     }
1427
1428     protected void notifyListeners(List<IDeviceListener> listeners,
1429             DeviceUpdate update) {
1430         if (listeners == null) {
1431             return;
1432         }
1433         for (IDeviceListener listener : listeners) {
1434             switch (update.change) {
1435             case ADD:
1436                 listener.deviceAdded(update.device);
1437                 break;
1438             case DELETE:
1439                 listener.deviceRemoved(update.device);
1440                 break;
1441             case CHANGE:
1442                 for (DeviceField field : update.fieldsChanged) {
1443                     switch (field) {
1444                     case IPV4:
1445                         listener.deviceIPV4AddrChanged(update.device);
1446                         break;
1447                     case SWITCHPORT:
1448                         // listener.deviceMoved(update.device);
1449                         break;
1450                     case VLAN:
1451                         listener.deviceVlanChanged(update.device);
1452                         break;
1453                     default:
1454                         logger.debug("Unknown device field changed {}",
1455                                 update.fieldsChanged.toString());
1456                         break;
1457                     }
1458                 }
1459                 break;
1460             }
1461         }
1462     }
1463
1464     /**
1465      * Check if the entity e has all the keyFields set. Returns false if not
1466      *
1467      * @param e
1468      *            entity to check
1469      * @param keyFields
1470      *            the key fields to check e against
1471      * @return
1472      */
1473     protected boolean allKeyFieldsPresent(Entity e,
1474             EnumSet<DeviceField> keyFields) {
1475         for (DeviceField f : keyFields) {
1476             switch (f) {
1477             case MAC:
1478                 // MAC address is always present
1479                 break;
1480             case IPV4:
1481                 if (e.getIpv4Address() == null)
1482                     return false;
1483                 break;
1484             case SWITCHPORT:
1485                 if (e.getPort() == null)
1486                     return false;
1487                 break;
1488             case VLAN:
1489                 // FIXME: vlan==null is ambiguous: it can mean: not present
1490                 // or untagged
1491                 // if (e.vlan == null) return false;
1492                 break;
1493             default:
1494                 // we should never get here. unless somebody extended
1495                 // DeviceFields
1496                 throw new IllegalStateException();
1497             }
1498         }
1499         return true;
1500     }
1501
1502     private LinkedList<DeviceUpdate> updateUpdates(
1503             LinkedList<DeviceUpdate> list, DeviceUpdate update) {
1504         if (update == null)
1505             return list;
1506         if (list == null)
1507             list = new LinkedList<DeviceUpdate>();
1508         list.add(update);
1509
1510         return list;
1511     }
1512
1513     /**
1514      * Get the secondary index for a class. Will return null if the secondary
1515      * index was created concurrently in another thread.
1516      *
1517      * @param clazz
1518      *            the class for the index
1519      * @return
1520      */
1521     private ClassState getClassState(IEntityClass clazz) {
1522         ClassState classState = classStateMap.get(clazz.getName());
1523         if (classState != null)
1524             return classState;
1525
1526         classState = new ClassState(clazz);
1527         ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState);
1528         if (r != null) {
1529             // concurrent add
1530             return r;
1531         }
1532         return classState;
1533     }
1534
1535     /**
1536      * Update both the primary and class indices for the provided device. If the
1537      * update fails because of an concurrent update, will return false.
1538      *
1539      * @param device
1540      *            the device to update
1541      * @param deviceKey
1542      *            the device key for the device
1543      * @return true if the update succeeded, false otherwise.
1544      */
1545     private boolean updateIndices(Device device, Long deviceKey) {
1546         if (!primaryIndex.updateIndex(device, deviceKey)) {
1547             return false;
1548         }
1549         IEntityClass entityClass = device.getEntityClass();
1550         ClassState classState = getClassState(entityClass);
1551
1552         if (classState.classIndex != null) {
1553             if (!classState.classIndex.updateIndex(device, deviceKey))
1554                 return false;
1555         }
1556         return true;
1557     }
1558
1559     /**
1560      * Update the secondary indices for the given entity and associated entity
1561      * classes
1562      *
1563      * @param entity
1564      *            the entity to update
1565      * @param entityClass
1566      *            the entity class for the entity
1567      * @param deviceKey
1568      *            the device key to set up
1569      */
1570     private void updateSecondaryIndices(Entity entity,
1571             IEntityClass entityClass, Long deviceKey) {
1572         for (DeviceIndex index : secondaryIndexMap.values()) {
1573             index.updateIndex(entity, deviceKey);
1574         }
1575         ClassState state = getClassState(entityClass);
1576         for (DeviceIndex index : state.secondaryIndexMap.values()) {
1577             index.updateIndex(entity, deviceKey);
1578         }
1579     }
1580
1581     /**
1582      * Clean up expired entities/devices
1583      */
1584     protected void cleanupEntities() {
1585         // debugCounters.updateCounter(CNT_CLEANUP_ENTITIES_RUNS);
1586
1587         Calendar c = Calendar.getInstance();
1588         c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT);
1589         Date cutoff = c.getTime();
1590
1591         ArrayList<Entity> toRemove = new ArrayList<Entity>();
1592         ArrayList<Entity> toKeep = new ArrayList<Entity>();
1593
1594         Iterator<Device> diter = deviceMap.values().iterator();
1595         LinkedList<DeviceUpdate> deviceUpdates = new LinkedList<DeviceUpdate>();
1596
1597         while (diter.hasNext()) {
1598             Device d = diter.next();
1599
1600             while (true) {
1601                 deviceUpdates.clear();
1602                 toRemove.clear();
1603                 toKeep.clear();
1604                 for (Entity e : d.getEntities()) {
1605                     if (e.getLastSeenTimestamp() != null
1606                             && 0 > e.getLastSeenTimestamp().compareTo(cutoff)) {
1607                         // individual entity needs to be removed
1608                         toRemove.add(e);
1609                     } else {
1610                         toKeep.add(e);
1611                     }
1612                 }
1613                 if (toRemove.size() == 0) {
1614                     break;
1615                 }
1616
1617                 // debugCounters.updateCounter(CNT_ENTITY_REMOVED_TIMEOUT);
1618                 for (Entity e : toRemove) {
1619                     removeEntity(e, d.getEntityClass(), d.getDeviceKey(),
1620                             toKeep);
1621                 }
1622
1623                 if (toKeep.size() > 0) {
1624                     Device newDevice = allocateDevice(d.getDeviceKey(),
1625                             d.getDHCPClientName(), d.oldAPs,
1626                             d.attachmentPoints, toKeep, d.getEntityClass());
1627
1628                     EnumSet<DeviceField> changedFields = EnumSet
1629                             .noneOf(DeviceField.class);
1630                     for (Entity e : toRemove) {
1631                         changedFields.addAll(findChangedFields(newDevice, e));
1632                     }
1633                     DeviceUpdate update = null;
1634                     if (changedFields.size() > 0) {
1635                         update = new DeviceUpdate(d, CHANGE, changedFields);
1636                     }
1637
1638                     if (!deviceMap.replace(newDevice.getDeviceKey(), d,
1639                             newDevice)) {
1640                         // concurrent modification; try again
1641                         // need to use device that is the map now for the next
1642                         // iteration
1643                         d = deviceMap.get(d.getDeviceKey());
1644                         if (null != d)
1645                             continue;
1646                     }
1647                     if (update != null) {
1648                         // need to count after all possibly continue stmts in
1649                         // this branch
1650                         // debugCounters.updateCounter(CNT_DEVICE_CHANGED);
1651                         deviceUpdates.add(update);
1652                     }
1653                 } else {
1654                     DeviceUpdate update = new DeviceUpdate(d, DELETE, null);
1655                     if (!deviceMap.remove(d.getDeviceKey(), d)) {
1656                         // concurrent modification; try again
1657                         // need to use device that is the map now for the next
1658                         // iteration
1659                         d = deviceMap.get(d.getDeviceKey());
1660                         if (null != d)
1661                             continue;
1662                         // debugCounters.updateCounter(CNT_DEVICE_DELETED);
1663                     }
1664                     deviceUpdates.add(update);
1665                 }
1666                 processUpdates(deviceUpdates);
1667                 break;
1668             }
1669         }
1670     }
1671
1672     protected void removeEntity(Entity removed, IEntityClass entityClass,
1673             Long deviceKey, Collection<Entity> others) {
1674         // Don't count in this method. This method CAN BE called to clean-up
1675         // after concurrent device adds/updates and thus counting here
1676         // is misleading
1677         for (DeviceIndex index : secondaryIndexMap.values()) {
1678             index.removeEntityIfNeeded(removed, deviceKey, others);
1679         }
1680         ClassState classState = getClassState(entityClass);
1681         for (DeviceIndex index : classState.secondaryIndexMap.values()) {
1682             index.removeEntityIfNeeded(removed, deviceKey, others);
1683         }
1684
1685         primaryIndex.removeEntityIfNeeded(removed, deviceKey, others);
1686
1687         if (classState.classIndex != null) {
1688             classState.classIndex.removeEntityIfNeeded(removed, deviceKey,
1689                     others);
1690         }
1691     }
1692
1693     /**
1694      * method to delete a given device, remove all entities first and then
1695      * finally delete the device itself.
1696      *
1697      * @param device
1698      */
1699     protected void deleteDevice(Device device) {
1700         // Don't count in this method. This method CAN BE called to clean-up
1701         // after concurrent device adds/updates and thus counting here
1702         // is misleading
1703         ArrayList<Entity> emptyToKeep = new ArrayList<Entity>();
1704         for (Entity entity : device.getEntities()) {
1705             this.removeEntity(entity, device.getEntityClass(),
1706                     device.getDeviceKey(), emptyToKeep);
1707         }
1708         if (!deviceMap.remove(device.getDeviceKey(), device)) {
1709             if (logger.isDebugEnabled())
1710                 logger.debug("device map does not have this device -"
1711                         + device.toString());
1712         }
1713     }
1714
1715     private EnumSet<DeviceField> getEntityKeys(Long macAddress, Short vlan,
1716             Integer ipv4Address, NodeConnector port) {
1717         // FIXME: vlan==null is a valid search. Need to handle this
1718         // case correctly. Note that the code will still work correctly.
1719         // But we might do a full device search instead of using an index.
1720         EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class);
1721         if (macAddress != null)
1722             keys.add(DeviceField.MAC);
1723         if (vlan != null)
1724             keys.add(DeviceField.VLAN);
1725         if (ipv4Address != null)
1726             keys.add(DeviceField.IPV4);
1727         if (port != null)
1728             keys.add(DeviceField.SWITCHPORT);
1729         return keys;
1730     }
1731
1732     protected Iterator<Device> queryClassByEntity(IEntityClass clazz,
1733             EnumSet<DeviceField> keyFields, Entity entity) {
1734         ClassState classState = getClassState(clazz);
1735         DeviceIndex index = classState.secondaryIndexMap.get(keyFields);
1736         if (index == null)
1737             return Collections.<Device> emptySet().iterator();
1738         return new DeviceIndexInterator(this, index.queryByEntity(entity));
1739     }
1740
1741     protected Device allocateDevice(Long deviceKey, Entity entity,
1742             IEntityClass entityClass) {
1743         return new Device(this, deviceKey, entity, entityClass);
1744     }
1745
1746     // TODO: FIX THIS.
1747     protected Device allocateDevice(Long deviceKey, String dhcpClientName,
1748             List<AttachmentPoint> aps, List<AttachmentPoint> trueAPs,
1749             Collection<Entity> entities, IEntityClass entityClass) {
1750         return new Device(this, deviceKey, dhcpClientName, aps, trueAPs,
1751                 entities, entityClass);
1752     }
1753
1754     protected Device allocateDevice(Device device, Entity entity,
1755             int insertionpoint) {
1756         return new Device(device, entity, insertionpoint);
1757     }
1758
1759     // not used
1760     protected Device allocateDevice(Device device, Set<Entity> entities) {
1761         List<AttachmentPoint> newPossibleAPs = new ArrayList<AttachmentPoint>();
1762         List<AttachmentPoint> newAPs = new ArrayList<AttachmentPoint>();
1763         for (Entity entity : entities) {
1764             if (entity.getPort() != null) {
1765                 AttachmentPoint aP = new AttachmentPoint(entity.getPort(), 0);
1766                 newPossibleAPs.add(aP);
1767             }
1768         }
1769         if (device.attachmentPoints != null) {
1770             for (AttachmentPoint oldAP : device.attachmentPoints) {
1771                 if (newPossibleAPs.contains(oldAP)) {
1772                     newAPs.add(oldAP);
1773                 }
1774             }
1775         }
1776         if (newAPs.isEmpty())
1777             newAPs = null;
1778         Device d = new Device(this, device.getDeviceKey(),
1779                 device.getDHCPClientName(), newAPs, null, entities,
1780                 device.getEntityClass());
1781         d.updateAttachmentPoint();
1782         return d;
1783     }
1784
1785     // *********************
1786     // ITopologyManagerAware
1787     // *********************
1788
1789     @Override
1790     public void edgeUpdate(List<TopoEdgeUpdate> topoedgeupdateList) {
1791         Iterator<Device> diter = deviceMap.values().iterator();
1792
1793         while (diter.hasNext()) {
1794             Device d = diter.next();
1795             if (d.updateAttachmentPoint()) {
1796                 if (logger.isDebugEnabled()) {
1797                     logger.debug("Attachment point changed for device: {}", d);
1798                 }
1799                 sendDeviceMovedNotification(d);
1800             }
1801         }
1802     }
1803
1804     @Override
1805     public void edgeOverUtilized(Edge edge) {
1806         // nothing to do
1807     }
1808
1809     @Override
1810     public void edgeUtilBackToNormal(Edge edge) {
1811         // nothing to do
1812     }
1813
1814     // *********************
1815     // IEntityClassListener
1816     // *********************
1817
1818     @Override
1819     public void entityClassChanged(Set<String> entityClassNames) {
1820         /*
1821          * iterate through the devices, reclassify the devices that belong to
1822          * these entity class names
1823          */
1824         Iterator<Device> diter = deviceMap.values().iterator();
1825         while (diter.hasNext()) {
1826             Device d = diter.next();
1827             if (d.getEntityClass() == null
1828                     || entityClassNames.contains(d.getEntityClass().getName()))
1829                 reclassifyDevice(d);
1830         }
1831     }
1832
1833     // *************
1834     // Local methods
1835     // *************
1836     /**
1837      * Send update notifications to listeners
1838      *
1839      * @param updates
1840      *            the updates to process.
1841      */
1842     protected void sendDeviceMovedNotification(Device d) {
1843         // debugCounters.updateCounter(CNT_DEVICE_MOVED);
1844         // deviceSyncManager.storeDevice(d);
1845         List<IDeviceListener> listeners = deviceListeners.getOrderedListeners();
1846         if (listeners != null) {
1847             for (IDeviceListener listener : listeners) {
1848                 listener.deviceMoved(d);
1849             }
1850         }
1851     }
1852
1853     /**
1854      * this method will reclassify and reconcile a device - possibilities are -
1855      * create new device(s), remove entities from this device. If the device
1856      * entity class did not change then it returns false else true.
1857      *
1858      * @param device
1859      */
1860     protected boolean reclassifyDevice(Device device) {
1861         // first classify all entities of this device
1862         if (device == null) {
1863             logger.debug("In reclassify for null device");
1864             return false;
1865         }
1866         boolean needToReclassify = false;
1867         for (Entity entity : device.entities) {
1868             IEntityClass entityClass = this.entityClassifier
1869                     .classifyEntity(entity);
1870             if (entityClass == null || device.getEntityClass() == null) {
1871                 needToReclassify = true;
1872                 break;
1873             }
1874             if (!entityClass.getName()
1875                     .equals(device.getEntityClass().getName())) {
1876                 needToReclassify = true;
1877                 break;
1878             }
1879         }
1880         if (needToReclassify == false) {
1881             return false;
1882         }
1883
1884         // debugCounters.updateCounter(CNT_DEVICE_RECLASSIFY_DELETE);
1885         LinkedList<DeviceUpdate> deviceUpdates = new LinkedList<DeviceUpdate>();
1886         // delete this device and then re-learn all the entities
1887         this.deleteDevice(device);
1888         deviceUpdates.add(new DeviceUpdate(device, DeviceUpdate.Change.DELETE,
1889                 null));
1890         if (!deviceUpdates.isEmpty())
1891             processUpdates(deviceUpdates);
1892         for (Entity entity : device.entities) {
1893             this.learnDeviceByEntity(entity);
1894         }
1895         return true;
1896     }
1897
1898     /**
1899      * For testing: sets the interval between writes of the same device to the
1900      * device store.
1901      *
1902      * @param intervalMs
1903      */
1904     // void setSyncStoreWriteInterval(int intervalMs) {
1905     // this.syncStoreWriteIntervalMs = intervalMs;
1906     // }
1907
1908     /**
1909      * For testing: sets the time between transition to MASTER and consolidate
1910      * store
1911      *
1912      * @param intervalMs
1913      */
1914     // void setInitialSyncStoreConsolidateMs(int intervalMs) {
1915     // this.initialSyncStoreConsolidateMs = intervalMs;
1916     // }
1917
1918     private long toLong(byte[] address) {
1919         long mac = 0;
1920         for (int i = 0; i < 6; i++) {
1921             long t = (address[i] & 0xffL) << ((5 - i) * 8);
1922             mac |= t;
1923         }
1924         return mac;
1925     }
1926
1927     /**
1928      * Accepts an IPv4 address in a byte array and returns the corresponding
1929      * 32-bit integer value.
1930      *
1931      * @param ipAddress
1932      * @return
1933      */
1934     private static int toIPv4Address(byte[] ipAddress) {
1935         int ip = 0;
1936         for (int i = 0; i < 4; i++) {
1937             int t = (ipAddress[i] & 0xff) << ((3 - i) * 8);
1938             ip |= t;
1939         }
1940         return ip;
1941     }
1942
1943     private void registerDeviceManagerDebugCounters() {
1944         /*
1945          * XXX Missing functionality if (debugCounters == null) {
1946          * logger.error("Debug Counter Service not found."); debugCounters = new
1947          * NullDebugCounter(); return; }
1948          * debugCounters.registerCounter(CNT_INCOMING,
1949          * "All incoming packets seen by this module",
1950          * CounterType.ALWAYS_COUNT);
1951          * debugCounters.registerCounter(CNT_RECONCILE_REQUEST,
1952          * "Number of flows that have been received for reconciliation by " +
1953          * "this module", CounterType.ALWAYS_COUNT);
1954          * debugCounters.registerCounter(CNT_RECONCILE_NO_SOURCE,
1955          * "Number of flow reconcile events that failed because no source " +
1956          * "device could be identified", CounterType.WARN); // is this really a
1957          * warning debugCounters.registerCounter(CNT_RECONCILE_NO_DEST,
1958          * "Number of flow reconcile events that failed because no " +
1959          * "destination device could be identified", CounterType.WARN); // is
1960          * this really a warning
1961          * debugCounters.registerCounter(CNT_BROADCAST_SOURCE,
1962          * "Number of packetIns that were discarded because the source " +
1963          * "MAC was broadcast or multicast", CounterType.WARN);
1964          * debugCounters.registerCounter(CNT_NO_SOURCE,
1965          * "Number of packetIns that were discarded because the " +
1966          * "could not identify a source device. This can happen if a " +
1967          * "packet is not allowed, appears on an illegal port, does not " +
1968          * "have a valid address space, etc.", CounterType.WARN);
1969          * debugCounters.registerCounter(CNT_NO_DEST,
1970          * "Number of packetIns that did not have an associated " +
1971          * "destination device. E.g., because the destination MAC is " +
1972          * "broadcast/multicast or is not yet known to the controller.",
1973          * CounterType.ALWAYS_COUNT);
1974          * debugCounters.registerCounter(CNT_DHCP_CLIENT_NAME_SNOOPED,
1975          * "Number of times a DHCP client name was snooped from a " +
1976          * "packetIn.", CounterType.ALWAYS_COUNT);
1977          * debugCounters.registerCounter(CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED,
1978          * "Number of times packetIn was received on an internal port and" +
1979          * "no source device is known for the source MAC. The packetIn is " +
1980          * "discarded.", CounterType.WARN);
1981          * debugCounters.registerCounter(CNT_PACKET_NOT_ALLOWED,
1982          * "Number of times a packetIn was not allowed due to spoofing " +
1983          * "protection configuration.", CounterType.WARN); // is this really a
1984          * warning? debugCounters.registerCounter(CNT_NEW_DEVICE,
1985          * "Number of times a new device was learned",
1986          * CounterType.ALWAYS_COUNT); debugCounters.registerCounter(
1987          * CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE,
1988          * "Number of times a packetIn was received on an internal port " +
1989          * "for a known device.", CounterType.ALWAYS_COUNT);
1990          * debugCounters.registerCounter(CNT_NEW_ENTITY,
1991          * "Number of times a new entity was learned for an existing device",
1992          * CounterType.ALWAYS_COUNT);
1993          * debugCounters.registerCounter(CNT_DEVICE_CHANGED,
1994          * "Number of times device properties have changed",
1995          * CounterType.ALWAYS_COUNT);
1996          * debugCounters.registerCounter(CNT_DEVICE_MOVED,
1997          * "Number of times devices have moved", CounterType.ALWAYS_COUNT);
1998          * debugCounters.registerCounter(CNT_CLEANUP_ENTITIES_RUNS,
1999          * "Number of times the entity cleanup task has been run",
2000          * CounterType.ALWAYS_COUNT);
2001          * debugCounters.registerCounter(CNT_ENTITY_REMOVED_TIMEOUT,
2002          * "Number of times entities have been removed due to timeout " +
2003          * "(entity has been inactive for " + ENTITY_TIMEOUT/1000 + "s)",
2004          * CounterType.ALWAYS_COUNT);
2005          * debugCounters.registerCounter(CNT_DEVICE_DELETED,
2006          * "Number of devices that have been removed due to inactivity",
2007          * CounterType.ALWAYS_COUNT);
2008          * debugCounters.registerCounter(CNT_DEVICE_RECLASSIFY_DELETE,
2009          * "Number of devices that required reclassification and have been " +
2010          * "temporarily delete for reclassification", CounterType.ALWAYS_COUNT);
2011          * debugCounters.registerCounter(CNT_DEVICE_STORED,
2012          * "Number of device entries written or updated to the sync store",
2013          * CounterType.ALWAYS_COUNT);
2014          * debugCounters.registerCounter(CNT_DEVICE_STORE_THROTTLED,
2015          * "Number of times a device update to the sync store was " +
2016          * "requested but not performed because the same device entities " +
2017          * "have recently been updated already", CounterType.ALWAYS_COUNT);
2018          * debugCounters.registerCounter(CNT_DEVICE_REMOVED_FROM_STORE,
2019          * "Number of devices that were removed from the sync store " +
2020          * "because the local controller removed the device due to " +
2021          * "inactivity", CounterType.ALWAYS_COUNT);
2022          * debugCounters.registerCounter(CNT_SYNC_EXCEPTION,
2023          * "Number of times an operation on the sync store resulted in " +
2024          * "sync exception", CounterType.WARN); // it this an error?
2025          * debugCounters.registerCounter(CNT_DEVICES_FROM_STORE,
2026          * "Number of devices that were read from the sync store after " +
2027          * "the local controller transitioned from SLAVE to MASTER",
2028          * CounterType.ALWAYS_COUNT);
2029          * debugCounters.registerCounter(CNT_CONSOLIDATE_STORE_RUNS,
2030          * "Number of times the task to consolidate entries in the " +
2031          * "store witch live known devices has been run",
2032          * CounterType.ALWAYS_COUNT);
2033          * debugCounters.registerCounter(CNT_CONSOLIDATE_STORE_DEVICES_REMOVED,
2034          * "Number of times a device has been removed from the sync " +
2035          * "store because no corresponding live device is known. " +
2036          * "This indicates a remote controller still writing device " +
2037          * "entries despite the local controller being MASTER or an " +
2038          * "incosistent store update from the local controller.",
2039          * CounterType.WARN);
2040          * debugCounters.registerCounter(CNT_TRANSITION_TO_MASTER,
2041          * "Number of times this controller has transitioned from SLAVE " +
2042          * "to MASTER role. Will be 0 or 1.", CounterType.ALWAYS_COUNT);
2043          */
2044     }
2045
2046     /**
2047      * For testing: consolidate the store NOW
2048      */
2049     // void scheduleConsolidateStoreNow() {
2050     // this.storeConsolidateTask.reschedule(0, TimeUnit.MILLISECONDS);
2051     // }
2052
2053     // private class DeviceSyncManager {
2054     // // maps (opaque) deviceKey to the time in System.nanoTime() when we
2055     // // last wrote the device to the sync store
2056     // private ConcurrentMap<Long, Long> lastWriteTimes =
2057     // new ConcurrentHashMap<Long, Long>();
2058     //
2059     // /**
2060     // * Write the given device to storage if we are MASTER.
2061     // * Use this method if the device has significantly changed (e.g.,
2062     // * new AP, new IP, entities removed).
2063     // * @param d the device to store
2064     // */
2065     // public void storeDevice(Device d) {
2066     // if (!isMaster)
2067     // return;
2068     // if (d == null)
2069     // return;
2070     // long now = System.nanoTime();
2071     // writeUpdatedDeviceToStorage(d);
2072     // lastWriteTimes.put(d.getDeviceKey(), now);
2073     // }
2074     //
2075     // /**
2076     // * Write the given device to storage if we are MASTER and if the
2077     // * last write for the device was more than this.syncStoreIntervalNs
2078     // * time ago.
2079     // * Use this method to updated last active times in the store.
2080     // * @param d the device to store
2081     // */
2082     // public void storeDeviceThrottled(Device d) {
2083     // long intervalNs = syncStoreWriteIntervalMs*1000L*1000L;
2084     // if (!isMaster)
2085     // return;
2086     // if (d == null)
2087     // return;
2088     // long now = System.nanoTime();
2089     // Long last = lastWriteTimes.get(d.getDeviceKey());
2090     // if (last == null ||
2091     // now - last > intervalNs) {
2092     // writeUpdatedDeviceToStorage(d);
2093     // lastWriteTimes.put(d.getDeviceKey(), now);
2094     // } else {
2095     // debugCounters.updateCounter(CNT_DEVICE_STORE_THROTTLED);
2096     // }
2097     // }
2098     //
2099     // /**
2100     // * Remove the given device from the store. If only some entities have
2101     // * been removed the updated device should be written using
2102     // * {@link #storeDevice(Device)}
2103     // * @param d
2104     // */
2105     // public void removeDevice(Device d) {
2106     // if (!isMaster)
2107     // return;
2108     // // FIXME: could we have a problem with concurrent put to the
2109     // // hashMap? I.e., we write a stale entry to the map after the
2110     // // delete and now are left with an entry we'll never clean up
2111     // lastWriteTimes.remove(d.getDeviceKey());
2112     // try {
2113     // // TODO: should probably do versioned delete. OTOH, even
2114     // // if we accidentally delete, we'll write it again after
2115     // // the next entity ....
2116     // debugCounters.updateCounter(CNT_DEVICE_REMOVED_FROM_STORE);
2117     // storeClient.delete(DeviceSyncRepresentation.computeKey(d));
2118     // } catch(ObsoleteVersionException e) {
2119     // // FIXME
2120     // } catch (SyncException e) {
2121     // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
2122     // logger.error("Could not remove device " + d + " from store", e);
2123     // }
2124     // }
2125     //
2126     // /**
2127     // * Remove the given Versioned device from the store. If the device
2128     // * was locally modified ignore the delete request.
2129     // * @param syncedDeviceKey
2130     // */
2131     // private void removeDevice(Versioned<DeviceSyncRepresentation> dev) {
2132     // try {
2133     // debugCounters.updateCounter(CNT_DEVICE_REMOVED_FROM_STORE);
2134     // storeClient.delete(dev.getValue().getKey(),
2135     // dev.getVersion());
2136     // } catch(ObsoleteVersionException e) {
2137     // // Key was locally modified by another thread.
2138     // // Do not delete and ignore.
2139     // } catch(SyncException e) {
2140     // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
2141     // logger.error("Failed to remove device entry for " +
2142     // dev.toString() + " from store.", e);
2143     // }
2144     // }
2145     //
2146     // /**
2147     // * Synchronously transition from SLAVE to MASTER. By iterating through
2148     // * the store and learning all devices from the store
2149     // */
2150     // private void goToMaster() {
2151     // if (logger.isDebugEnabled()) {
2152     // logger.debug("Transitioning to MASTER role");
2153     // }
2154     // debugCounters.updateCounter(CNT_TRANSITION_TO_MASTER);
2155     // IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>>
2156     // iter = null;
2157     // try {
2158     // iter = storeClient.entries();
2159     // } catch (SyncException e) {
2160     // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
2161     // logger.error("Failed to read devices from sync store", e);
2162     // return;
2163     // }
2164     // try {
2165     // while(iter.hasNext()) {
2166     // Versioned<DeviceSyncRepresentation> versionedDevice =
2167     // iter.next().getValue();
2168     // DeviceSyncRepresentation storedDevice =
2169     // versionedDevice.getValue();
2170     // if (storedDevice == null)
2171     // continue;
2172     // debugCounters.updateCounter(CNT_DEVICES_FROM_STORE);
2173     // for(SyncEntity se: storedDevice.getEntities()) {
2174     // learnDeviceByEntity(se.asEntity());
2175     // }
2176     // }
2177     // } finally {
2178     // if (iter != null)
2179     // iter.close();
2180     // }
2181     // storeConsolidateTask.reschedule(initialSyncStoreConsolidateMs,
2182     // TimeUnit.MILLISECONDS);
2183     // }
2184     //
2185     // /**
2186     // * Actually perform the write of the device to the store
2187     // * FIXME: concurrent modification behavior
2188     // * @param device The device to write
2189     // */
2190     // private void writeUpdatedDeviceToStorage(Device device) {
2191     // try {
2192     // debugCounters.updateCounter(CNT_DEVICE_STORED);
2193     // // FIXME: use a versioned put
2194     // DeviceSyncRepresentation storeDevice =
2195     // new DeviceSyncRepresentation(device);
2196     // storeClient.put(storeDevice.getKey(), storeDevice);
2197     // } catch (ObsoleteVersionException e) {
2198     // // FIXME: what's the right behavior here. Can the store client
2199     // // even throw this error?
2200     // } catch (SyncException e) {
2201     // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
2202     // logger.error("Could not write device " + device +
2203     // " to sync store:", e);
2204     // }
2205     // }
2206     //
2207     // /**
2208     // * Iterate through all entries in the sync store. For each device
2209     // * in the store check if any stored entity matches a live device. If
2210     // * no entities match a live device we remove the entry from the store.
2211     // *
2212     // * Note: we do not check if all devices known to device manager are
2213     // * in the store. We rely on regular packetIns for that.
2214     // * Note: it's possible that multiple entries in the store map to the
2215     // * same device. We don't check or handle this case.
2216     // *
2217     // * We need to perform this check after a SLAVE->MASTER transition to
2218     // * get rid of all entries the old master might have written to the
2219     // * store after we took over. We also run it regularly in MASTER
2220     // * state to ensure we don't have stale entries in the store
2221     // */
2222     // private void consolidateStore() {
2223     // if (!isMaster)
2224     // return;
2225     // debugCounters.updateCounter(CNT_CONSOLIDATE_STORE_RUNS);
2226     // if (logger.isDebugEnabled()) {
2227     // logger.debug("Running consolidateStore.");
2228     // }
2229     // IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>>
2230     // iter = null;
2231     // try {
2232     // iter = storeClient.entries();
2233     // } catch (SyncException e) {
2234     // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
2235     // logger.error("Failed to read devices from sync store", e);
2236     // return;
2237     // }
2238     // try {
2239     // while(iter.hasNext()) {
2240     // boolean found = false;
2241     // Versioned<DeviceSyncRepresentation> versionedDevice =
2242     // iter.next().getValue();
2243     // DeviceSyncRepresentation storedDevice =
2244     // versionedDevice.getValue();
2245     // if (storedDevice == null)
2246     // continue;
2247     // for(SyncEntity se: storedDevice.getEntities()) {
2248     // try {
2249     // // Do we have a device for this entity??
2250     // IDevice d = findDevice(se.macAddress, se.vlan,
2251     // se.ipv4Address,
2252     // se.switchDPID,
2253     // se.switchPort);
2254     // if (d != null) {
2255     // found = true;
2256     // break;
2257     // }
2258     // } catch (IllegalArgumentException e) {
2259     // // not all key fields provided. Skip entity
2260     // }
2261     // }
2262     // if (!found) {
2263     // // We currently DO NOT have a live device that
2264     // // matches the current device from the store.
2265     // // Delete device from store.
2266     // if (logger.isDebugEnabled()) {
2267     // logger.debug("Removing device {} from store. No "
2268     // + "corresponding live device",
2269     // storedDevice.getKey());
2270     // }
2271     // debugCounters.updateCounter(CNT_CONSOLIDATE_STORE_DEVICES_REMOVED);
2272     // removeDevice(versionedDevice);
2273     // }
2274     // }
2275     // } finally {
2276     // if (iter != null)
2277     // iter.close();
2278     // }
2279     // }
2280     // }
2281     //
2282     //
2283     // /**
2284     // * For testing. Sets the syncService. Only call after init but before
2285     // * startUp. Used by MockDeviceManager
2286     // * @param syncService
2287     // */
2288     // protected void setSyncServiceIfNotSet(ISyncService syncService) {
2289     // if (this.syncService == null)
2290     // this.syncService = syncService;
2291     // }
2292 }