blob: feccdc4f87ec0173e9d909edd4e8ebd6a1e9fbb8 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001/**
2 * Copyright 2011,2012 Big Switch Networks, Inc.
3 * Originally created by David Erickson, Stanford University
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may
6 * not use this file except in compliance with the License. You may obtain
7 * a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations
15 * under the License.
16 **/
17
18package net.floodlightcontroller.devicemanager.internal;
19
20import java.util.ArrayList;
21import java.util.Calendar;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Comparator;
25import java.util.Date;
26import java.util.EnumSet;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.LinkedList;
31import java.util.List;
32import java.util.ListIterator;
33import java.util.Map;
34import java.util.Queue;
35import java.util.Set;
36import java.util.concurrent.ConcurrentHashMap;
37import java.util.concurrent.ScheduledExecutorService;
38import java.util.concurrent.TimeUnit;
39
40import net.floodlightcontroller.core.FloodlightContext;
41import net.floodlightcontroller.core.IFloodlightProviderService;
42import net.floodlightcontroller.core.IHAListener;
43import net.floodlightcontroller.core.IInfoProvider;
44import net.floodlightcontroller.core.IOFMessageListener;
45import net.floodlightcontroller.core.IOFSwitch;
46import net.floodlightcontroller.core.IFloodlightProviderService.Role;
47import net.floodlightcontroller.core.module.FloodlightModuleContext;
48import net.floodlightcontroller.core.module.IFloodlightModule;
49import net.floodlightcontroller.core.module.IFloodlightService;
50import net.floodlightcontroller.core.util.SingletonTask;
51import net.floodlightcontroller.devicemanager.IDevice;
52import net.floodlightcontroller.devicemanager.IDeviceService;
53import net.floodlightcontroller.devicemanager.IEntityClass;
54import net.floodlightcontroller.devicemanager.IEntityClassListener;
55import net.floodlightcontroller.devicemanager.IEntityClassifierService;
56import net.floodlightcontroller.devicemanager.IDeviceListener;
57import net.floodlightcontroller.devicemanager.SwitchPort;
58import net.floodlightcontroller.devicemanager.web.DeviceRoutable;
59import net.floodlightcontroller.flowcache.IFlowReconcileListener;
60import net.floodlightcontroller.flowcache.IFlowReconcileService;
61import net.floodlightcontroller.flowcache.OFMatchReconcile;
62import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
63import net.floodlightcontroller.packet.ARP;
64import net.floodlightcontroller.packet.DHCP;
65import net.floodlightcontroller.packet.Ethernet;
66import net.floodlightcontroller.packet.IPv4;
67import net.floodlightcontroller.packet.UDP;
68import net.floodlightcontroller.restserver.IRestApiService;
69import net.floodlightcontroller.storage.IStorageSourceService;
70import net.floodlightcontroller.threadpool.IThreadPoolService;
71import net.floodlightcontroller.topology.ITopologyListener;
72import net.floodlightcontroller.topology.ITopologyService;
73import net.floodlightcontroller.util.MultiIterator;
74import static net.floodlightcontroller.devicemanager.internal.
75DeviceManagerImpl.DeviceUpdate.Change.*;
76
77import org.openflow.protocol.OFMatchWithSwDpid;
78import org.openflow.protocol.OFMessage;
79import org.openflow.protocol.OFPacketIn;
80import org.openflow.protocol.OFType;
81import org.slf4j.Logger;
82import org.slf4j.LoggerFactory;
83
84/**
85 * DeviceManager creates Devices based upon MAC addresses seen in the network.
86 * It tracks any network addresses mapped to the Device, and its location
87 * within the network.
88 * @author readams
89 */
90public class DeviceManagerImpl implements
91IDeviceService, IOFMessageListener, ITopologyListener,
92IFloodlightModule, IEntityClassListener,
93IFlowReconcileListener, IInfoProvider, IHAListener {
94 protected static Logger logger =
95 LoggerFactory.getLogger(DeviceManagerImpl.class);
96
97 protected IFloodlightProviderService floodlightProvider;
98 protected ITopologyService topology;
99 protected IStorageSourceService storageSource;
100 protected IRestApiService restApi;
101 protected IThreadPoolService threadPool;
102 protected IFlowReconcileService flowReconcileMgr;
103
104 /**
105 * Time in milliseconds before entities will expire
106 */
107 protected static final int ENTITY_TIMEOUT = 60*60*1000;
108
109 /**
110 * Time in seconds between cleaning up old entities/devices
111 */
112 protected static final int ENTITY_CLEANUP_INTERVAL = 60*60;
113
114 /**
115 * This is the master device map that maps device IDs to {@link Device}
116 * objects.
117 */
118 protected ConcurrentHashMap<Long, Device> deviceMap;
119
120 /**
121 * Counter used to generate device keys
122 */
123 protected long deviceKeyCounter = 0;
124
125 /**
126 * Lock for incrementing the device key counter
127 */
128 protected Object deviceKeyLock = new Object();
129
130 /**
131 * This is the primary entity index that contains all entities
132 */
133 protected DeviceUniqueIndex primaryIndex;
134
135 /**
136 * This stores secondary indices over the fields in the devices
137 */
138 protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
139
140 /**
141 * This map contains state for each of the {@ref IEntityClass}
142 * that exist
143 */
144 protected ConcurrentHashMap<String, ClassState> classStateMap;
145
146 /**
147 * This is the list of indices we want on a per-class basis
148 */
149 protected Set<EnumSet<DeviceField>> perClassIndices;
150
151 /**
152 * The entity classifier currently in use
153 */
154 protected IEntityClassifierService entityClassifier;
155
156 /**
157 * Used to cache state about specific entity classes
158 */
159 protected class ClassState {
160
161 /**
162 * The class index
163 */
164 protected DeviceUniqueIndex classIndex;
165
166 /**
167 * This stores secondary indices over the fields in the device for the
168 * class
169 */
170 protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
171
172 /**
173 * Allocate a new {@link ClassState} object for the class
174 * @param clazz the class to use for the state
175 */
176 public ClassState(IEntityClass clazz) {
177 EnumSet<DeviceField> keyFields = clazz.getKeyFields();
178 EnumSet<DeviceField> primaryKeyFields =
179 entityClassifier.getKeyFields();
180 boolean keyFieldsMatchPrimary =
181 primaryKeyFields.equals(keyFields);
182
183 if (!keyFieldsMatchPrimary)
184 classIndex = new DeviceUniqueIndex(keyFields);
185
186 secondaryIndexMap =
187 new HashMap<EnumSet<DeviceField>, DeviceIndex>();
188 for (EnumSet<DeviceField> fields : perClassIndices) {
189 secondaryIndexMap.put(fields,
190 new DeviceMultiIndex(fields));
191 }
192 }
193 }
194
195 /**
196 * Device manager event listeners
197 */
198 protected Set<IDeviceListener> deviceListeners;
199
200 /**
201 * A device update event to be dispatched
202 */
203 protected static class DeviceUpdate {
204 public enum Change {
205 ADD, DELETE, CHANGE;
206 }
207
208 /**
209 * The affected device
210 */
211 protected IDevice device;
212
213 /**
214 * The change that was made
215 */
216 protected Change change;
217
218 /**
219 * If not added, then this is the list of fields changed
220 */
221 protected EnumSet<DeviceField> fieldsChanged;
222
223 public DeviceUpdate(IDevice device, Change change,
224 EnumSet<DeviceField> fieldsChanged) {
225 super();
226 this.device = device;
227 this.change = change;
228 this.fieldsChanged = fieldsChanged;
229 }
230
231 @Override
232 public String toString() {
233 String devIdStr = device.getEntityClass().getName() + "::" +
234 device.getMACAddressString();
235 return "DeviceUpdate [device=" + devIdStr + ", change=" + change
236 + ", fieldsChanged=" + fieldsChanged + "]";
237 }
238
239 }
240
241 /**
242 * AttachmentPointComparator
243 *
244 * Compares two attachment points and returns the latest one.
245 * It is assumed that the two attachment points are in the same
246 * L2 domain.
247 *
248 * @author srini
249 */
250 protected class AttachmentPointComparator
251 implements Comparator<AttachmentPoint> {
252 public AttachmentPointComparator() {
253 super();
254 }
255
256 @Override
257 public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) {
258
259 //First compare based on L2 domain ID;
260 long oldSw = oldAP.getSw();
261 short oldPort = oldAP.getPort();
262 long oldDomain = topology.getL2DomainId(oldSw);
263 boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort);
264
265 long newSw = newAP.getSw();
266 short newPort = newAP.getPort();
267 long newDomain = topology.getL2DomainId(newSw);
268 boolean newBD = topology.isBroadcastDomainPort(newSw, newPort);
269
270 if (oldDomain < newDomain) return -1;
271 else if (oldDomain > newDomain) return 1;
272
273 // We expect that the last seen of the new AP is higher than
274 // old AP, if it is not, just reverse and send the negative
275 // of the result.
276 if (oldAP.getActiveSince() > newAP.getActiveSince())
277 return -compare(newAP, oldAP);
278
279 long activeOffset = 0;
280 if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) {
281 if (!newBD && oldBD) {
282 return -1;
283 }
284 if (newBD && oldBD) {
285 activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT;
286 }
287 else if (newBD && !oldBD){
288 activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT;
289 }
290
291 } else {
292 // The attachment point is consistent.
293 activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT;
294 }
295
296
297 if ((newAP.getActiveSince() > oldAP.getLastSeen() + activeOffset) ||
298 (newAP.getLastSeen() > oldAP.getLastSeen() +
299 AttachmentPoint.INACTIVITY_INTERVAL)) {
300 return -1;
301 }
302 return 1;
303 }
304 }
305 /**
306 * Comparator for sorting by cluster ID
307 */
308 public AttachmentPointComparator apComparator;
309
310 /**
311 * Switch ports where attachment points shouldn't be learned
312 */
313 private Set<SwitchPort> suppressAPs;
314
315 /**
316 * Periodic task to clean up expired entities
317 */
318 public SingletonTask entityCleanupTask;
319
320 // *********************
321 // IDeviceManagerService
322 // *********************
323
324 @Override
325 public IDevice getDevice(Long deviceKey) {
326 return deviceMap.get(deviceKey);
327 }
328
329 @Override
330 public IDevice findDevice(long macAddress, Short vlan,
331 Integer ipv4Address, Long switchDPID,
332 Integer switchPort)
333 throws IllegalArgumentException {
334 if (vlan != null && vlan.shortValue() <= 0)
335 vlan = null;
336 if (ipv4Address != null && ipv4Address == 0)
337 ipv4Address = null;
338 Entity e = new Entity(macAddress, vlan, ipv4Address, switchDPID,
339 switchPort, null);
340 if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) {
341 throw new IllegalArgumentException("Not all key fields specified."
342 + " Required fields: " + entityClassifier.getKeyFields());
343 }
344 return findDeviceByEntity(e);
345 }
346
347 @Override
348 public IDevice findDestDevice(IDevice source, long macAddress,
349 Short vlan, Integer ipv4Address)
350 throws IllegalArgumentException {
351 if (vlan != null && vlan.shortValue() <= 0)
352 vlan = null;
353 if (ipv4Address != null && ipv4Address == 0)
354 ipv4Address = null;
355 Entity e = new Entity(macAddress, vlan, ipv4Address,
356 null, null, null);
357 if (source == null ||
358 !allKeyFieldsPresent(e, source.getEntityClass().getKeyFields())) {
359 throw new IllegalArgumentException("Not all key fields and/or "
360 + " no source device specified. Required fields: " +
361 entityClassifier.getKeyFields());
362 }
363 return findDestByEntity(source, e);
364 }
365
366 @Override
367 public Collection<? extends IDevice> getAllDevices() {
368 return Collections.unmodifiableCollection(deviceMap.values());
369 }
370
371 @Override
372 public void addIndex(boolean perClass,
373 EnumSet<DeviceField> keyFields) {
374 if (perClass) {
375 perClassIndices.add(keyFields);
376 } else {
377 secondaryIndexMap.put(keyFields,
378 new DeviceMultiIndex(keyFields));
379 }
380 }
381
382 @Override
383 public Iterator<? extends IDevice> queryDevices(Long macAddress,
384 Short vlan,
385 Integer ipv4Address,
386 Long switchDPID,
387 Integer switchPort) {
388 DeviceIndex index = null;
389 if (secondaryIndexMap.size() > 0) {
390 EnumSet<DeviceField> keys =
391 getEntityKeys(macAddress, vlan, ipv4Address,
392 switchDPID, switchPort);
393 index = secondaryIndexMap.get(keys);
394 }
395
396 Iterator<Device> deviceIterator = null;
397 if (index == null) {
398 // Do a full table scan
399 deviceIterator = deviceMap.values().iterator();
400 } else {
401 // index lookup
402 Entity entity = new Entity((macAddress == null ? 0 : macAddress),
403 vlan,
404 ipv4Address,
405 switchDPID,
406 switchPort,
407 null);
408 deviceIterator =
409 new DeviceIndexInterator(this, index.queryByEntity(entity));
410 }
411
412 DeviceIterator di =
413 new DeviceIterator(deviceIterator,
414 null,
415 macAddress,
416 vlan,
417 ipv4Address,
418 switchDPID,
419 switchPort);
420 return di;
421 }
422
423 @Override
424 public Iterator<? extends IDevice> queryClassDevices(IDevice reference,
425 Long macAddress,
426 Short vlan,
427 Integer ipv4Address,
428 Long switchDPID,
429 Integer switchPort) {
430 IEntityClass entityClass = reference.getEntityClass();
431 ArrayList<Iterator<Device>> iterators =
432 new ArrayList<Iterator<Device>>();
433 ClassState classState = getClassState(entityClass);
434
435 DeviceIndex index = null;
436 if (classState.secondaryIndexMap.size() > 0) {
437 EnumSet<DeviceField> keys =
438 getEntityKeys(macAddress, vlan, ipv4Address,
439 switchDPID, switchPort);
440 index = classState.secondaryIndexMap.get(keys);
441 }
442
443 Iterator<Device> iter;
444 if (index == null) {
445 index = classState.classIndex;
446 if (index == null) {
447 // scan all devices
448 return new DeviceIterator(deviceMap.values().iterator(),
449 new IEntityClass[] { entityClass },
450 macAddress, vlan, ipv4Address,
451 switchDPID, switchPort);
452 } else {
453 // scan the entire class
454 iter = new DeviceIndexInterator(this, index.getAll());
455 }
456 } else {
457 // index lookup
458 Entity entity =
459 new Entity((macAddress == null ? 0 : macAddress),
460 vlan,
461 ipv4Address,
462 switchDPID,
463 switchPort,
464 null);
465 iter = new DeviceIndexInterator(this,
466 index.queryByEntity(entity));
467 }
468 iterators.add(iter);
469
470 return new MultiIterator<Device>(iterators.iterator());
471 }
472
473 protected Iterator<Device> getDeviceIteratorForQuery(Long macAddress,
474 Short vlan,
475 Integer ipv4Address,
476 Long switchDPID,
477 Integer switchPort) {
478 DeviceIndex index = null;
479 if (secondaryIndexMap.size() > 0) {
480 EnumSet<DeviceField> keys =
481 getEntityKeys(macAddress, vlan, ipv4Address,
482 switchDPID, switchPort);
483 index = secondaryIndexMap.get(keys);
484 }
485
486 Iterator<Device> deviceIterator = null;
487 if (index == null) {
488 // Do a full table scan
489 deviceIterator = deviceMap.values().iterator();
490 } else {
491 // index lookup
492 Entity entity = new Entity((macAddress == null ? 0 : macAddress),
493 vlan,
494 ipv4Address,
495 switchDPID,
496 switchPort,
497 null);
498 deviceIterator =
499 new DeviceIndexInterator(this, index.queryByEntity(entity));
500 }
501
502 DeviceIterator di =
503 new DeviceIterator(deviceIterator,
504 null,
505 macAddress,
506 vlan,
507 ipv4Address,
508 switchDPID,
509 switchPort);
510 return di;
511 }
512
513 @Override
514 public void addListener(IDeviceListener listener) {
515 deviceListeners.add(listener);
516 }
517
518 // *************
519 // IInfoProvider
520 // *************
521
522 @Override
523 public Map<String, Object> getInfo(String type) {
524 if (!"summary".equals(type))
525 return null;
526
527 Map<String, Object> info = new HashMap<String, Object>();
528 info.put("# hosts", deviceMap.size());
529 return info;
530 }
531
532 // ******************
533 // IOFMessageListener
534 // ******************
535
536 @Override
537 public String getName() {
538 return "devicemanager";
539 }
540
541 @Override
542 public boolean isCallbackOrderingPrereq(OFType type, String name) {
543 return ((type == OFType.PACKET_IN || type == OFType.FLOW_MOD)
544 && name.equals("topology"));
545 }
546
547 @Override
548 public boolean isCallbackOrderingPostreq(OFType type, String name) {
549 return false;
550 }
551
552 @Override
553 public Command receive(IOFSwitch sw, OFMessage msg,
554 FloodlightContext cntx) {
555 switch (msg.getType()) {
556 case PACKET_IN:
557 return this.processPacketInMessage(sw,
558 (OFPacketIn) msg, cntx);
559 default:
560 break;
561 }
562 return Command.CONTINUE;
563 }
564
565 // ***************
566 // IFlowReconcileListener
567 // ***************
568 @Override
569 public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) {
570 ListIterator<OFMatchReconcile> iter = ofmRcList.listIterator();
571 while (iter.hasNext()) {
572 OFMatchReconcile ofm = iter.next();
573
574 // Remove the STOPPed flow.
575 if (Command.STOP == reconcileFlow(ofm)) {
576 iter.remove();
577 }
578 }
579
580 if (ofmRcList.size() > 0) {
581 return Command.CONTINUE;
582 } else {
583 return Command.STOP;
584 }
585 }
586
587 protected Command reconcileFlow(OFMatchReconcile ofm) {
588 // Extract source entity information
589 Entity srcEntity =
590 getEntityFromFlowMod(ofm.ofmWithSwDpid, true);
591 if (srcEntity == null)
592 return Command.STOP;
593
594 // Find the device by source entity
595 Device srcDevice = findDeviceByEntity(srcEntity);
596 if (srcDevice == null)
597 return Command.STOP;
598
599 // Store the source device in the context
600 fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice);
601
602 // Find the device matching the destination from the entity
603 // classes of the source.
604 Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid, false);
605 Device dstDevice = null;
606 if (dstEntity != null) {
607 dstDevice = findDestByEntity(srcDevice, dstEntity);
608 if (dstDevice != null)
609 fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice);
610 }
611 if (logger.isTraceEnabled()) {
612 logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, "
613 + "dstEntity={}, dstDev={}",
614 new Object[] {ofm.ofmWithSwDpid.getOfMatch(),
615 srcEntity, srcDevice,
616 dstEntity, dstDevice } );
617 }
618 return Command.CONTINUE;
619 }
620
621 // *****************
622 // IFloodlightModule
623 // *****************
624
625 @Override
626 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
627 Collection<Class<? extends IFloodlightService>> l =
628 new ArrayList<Class<? extends IFloodlightService>>();
629 l.add(IDeviceService.class);
630 return l;
631 }
632
633 @Override
634 public Map<Class<? extends IFloodlightService>, IFloodlightService>
635 getServiceImpls() {
636 Map<Class<? extends IFloodlightService>,
637 IFloodlightService> m =
638 new HashMap<Class<? extends IFloodlightService>,
639 IFloodlightService>();
640 // We are the class that implements the service
641 m.put(IDeviceService.class, this);
642 return m;
643 }
644
645 @Override
646 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
647 Collection<Class<? extends IFloodlightService>> l =
648 new ArrayList<Class<? extends IFloodlightService>>();
649 l.add(IFloodlightProviderService.class);
650 l.add(IStorageSourceService.class);
651 l.add(ITopologyService.class);
652 l.add(IRestApiService.class);
653 l.add(IThreadPoolService.class);
654 l.add(IFlowReconcileService.class);
655 l.add(IEntityClassifierService.class);
656 return l;
657 }
658
659 @Override
660 public void init(FloodlightModuleContext fmc) {
661 this.perClassIndices =
662 new HashSet<EnumSet<DeviceField>>();
663 addIndex(true, EnumSet.of(DeviceField.IPV4));
664
665 this.deviceListeners = new HashSet<IDeviceListener>();
666 this.suppressAPs =
667 Collections.synchronizedSet(new HashSet<SwitchPort>());
668
669 this.floodlightProvider =
670 fmc.getServiceImpl(IFloodlightProviderService.class);
671 this.storageSource =
672 fmc.getServiceImpl(IStorageSourceService.class);
673 this.topology =
674 fmc.getServiceImpl(ITopologyService.class);
675 this.restApi = fmc.getServiceImpl(IRestApiService.class);
676 this.threadPool = fmc.getServiceImpl(IThreadPoolService.class);
677 this.flowReconcileMgr = fmc.getServiceImpl(IFlowReconcileService.class);
678 this.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class);
679 }
680
681 @Override
682 public void startUp(FloodlightModuleContext fmc) {
683 primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields());
684 secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
685
686 deviceMap = new ConcurrentHashMap<Long, Device>();
687 classStateMap =
688 new ConcurrentHashMap<String, ClassState>();
689 apComparator = new AttachmentPointComparator();
690
691 floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
692 floodlightProvider.addHAListener(this);
693 if (topology != null)
694 topology.addListener(this);
695 flowReconcileMgr.addFlowReconcileListener(this);
696 entityClassifier.addListener(this);
697
698 Runnable ecr = new Runnable() {
699 @Override
700 public void run() {
701 cleanupEntities();
702 entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
703 TimeUnit.SECONDS);
704 }
705 };
706 ScheduledExecutorService ses = threadPool.getScheduledExecutor();
707 entityCleanupTask = new SingletonTask(ses, ecr);
708 entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
709 TimeUnit.SECONDS);
710
711 if (restApi != null) {
712 restApi.addRestletRoutable(new DeviceRoutable());
713 } else {
714 logger.debug("Could not instantiate REST API");
715 }
716 }
717
718 // ***************
719 // IHAListener
720 // ***************
721
722 @Override
723 public void roleChanged(Role oldRole, Role newRole) {
724 switch(newRole) {
725 case SLAVE:
726 logger.debug("Resetting device state because of role change");
727 startUp(null);
728 break;
729 default:
730 break;
731 }
732 }
733
734 @Override
735 public void controllerNodeIPsChanged(
736 Map<String, String> curControllerNodeIPs,
737 Map<String, String> addedControllerNodeIPs,
738 Map<String, String> removedControllerNodeIPs) {
739 // no-op
740 }
741
742 // ****************
743 // Internal methods
744 // ****************
745
746 protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
747 FloodlightContext cntx) {
748 Ethernet eth =
749 IFloodlightProviderService.bcStore.
750 get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
751
752 // Extract source entity information
753 Entity srcEntity =
754 getSourceEntityFromPacket(eth, sw.getId(), pi.getInPort());
755 if (srcEntity == null)
756 return Command.STOP;
757
758 // Learn/lookup device information
759 Device srcDevice = learnDeviceByEntity(srcEntity);
760 if (srcDevice == null)
761 return Command.STOP;
762
763 // Store the source device in the context
764 fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
765
766 // Find the device matching the destination from the entity
767 // classes of the source.
768 Entity dstEntity = getDestEntityFromPacket(eth);
769 Device dstDevice = null;
770 if (dstEntity != null) {
771 dstDevice =
772 findDestByEntity(srcDevice, dstEntity);
773 if (dstDevice != null)
774 fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
775 }
776
777 if (logger.isTraceEnabled()) {
778 logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
779 " *** srcDev={} *** dstDev={} *** ",
780 new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
781 srcDevice, dstDevice });
782 }
783 return Command.CONTINUE;
784 }
785
786 /**
787 * Check whether the given attachment point is valid given the current
788 * topology
789 * @param switchDPID the DPID
790 * @param switchPort the port
791 * @return true if it's a valid attachment point
792 */
793 public boolean isValidAttachmentPoint(long switchDPID,
794 int switchPort) {
795 if (topology.isAttachmentPointPort(switchDPID,
796 (short)switchPort) == false)
797 return false;
798
799 if (suppressAPs.contains(new SwitchPort(switchDPID, switchPort)))
800 return false;
801
802 return true;
803 }
804
805 /**
806 * Get IP address from packet if the packet is either an ARP
807 * or a DHCP packet
808 * @param eth
809 * @param dlAddr
810 * @return
811 */
812 private int getSrcNwAddr(Ethernet eth, long dlAddr) {
813 if (eth.getPayload() instanceof ARP) {
814 ARP arp = (ARP) eth.getPayload();
815 if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) &&
816 (Ethernet.toLong(arp.getSenderHardwareAddress()) == dlAddr)) {
817 return IPv4.toIPv4Address(arp.getSenderProtocolAddress());
818 }
819 } else if (eth.getPayload() instanceof IPv4) {
820 IPv4 ipv4 = (IPv4) eth.getPayload();
821 if (ipv4.getPayload() instanceof UDP) {
822 UDP udp = (UDP)ipv4.getPayload();
823 if (udp.getPayload() instanceof DHCP) {
824 DHCP dhcp = (DHCP)udp.getPayload();
825 if (dhcp.getOpCode() == DHCP.OPCODE_REPLY) {
826 return ipv4.getSourceAddress();
827 }
828 }
829 }
830 }
831 return 0;
832 }
833
834 /**
835 * Parse an entity from an {@link Ethernet} packet.
836 * @param eth the packet to parse
837 * @param sw the switch on which the packet arrived
838 * @param pi the original packetin
839 * @return the entity from the packet
840 */
841 protected Entity getSourceEntityFromPacket(Ethernet eth,
842 long swdpid,
843 int port) {
844 byte[] dlAddrArr = eth.getSourceMACAddress();
845 long dlAddr = Ethernet.toLong(dlAddrArr);
846
847 // Ignore broadcast/multicast source
848 if ((dlAddrArr[0] & 0x1) != 0)
849 return null;
850
851 short vlan = eth.getVlanID();
852 int nwSrc = getSrcNwAddr(eth, dlAddr);
853 return new Entity(dlAddr,
854 ((vlan >= 0) ? vlan : null),
855 ((nwSrc != 0) ? nwSrc : null),
856 swdpid,
857 port,
858 new Date());
859 }
860
861 /**
862 * Get a (partial) entity for the destination from the packet.
863 * @param eth
864 * @return
865 */
866 protected Entity getDestEntityFromPacket(Ethernet eth) {
867 byte[] dlAddrArr = eth.getDestinationMACAddress();
868 long dlAddr = Ethernet.toLong(dlAddrArr);
869 short vlan = eth.getVlanID();
870 int nwDst = 0;
871
872 // Ignore broadcast/multicast destination
873 if ((dlAddrArr[0] & 0x1) != 0)
874 return null;
875
876 if (eth.getPayload() instanceof IPv4) {
877 IPv4 ipv4 = (IPv4) eth.getPayload();
878 nwDst = ipv4.getDestinationAddress();
879 }
880
881 return new Entity(dlAddr,
882 ((vlan >= 0) ? vlan : null),
883 ((nwDst != 0) ? nwDst : null),
884 null,
885 null,
886 null);
887 }
888
889 /**
890 * Parse an entity from an OFMatchWithSwDpid.
891 * @param ofmWithSwDpid
892 * @return the entity from the packet
893 */
894 private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid,
895 boolean isSource) {
896 byte[] dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerSource();
897 int nwSrc = ofmWithSwDpid.getOfMatch().getNetworkSource();
898 if (!isSource) {
899 dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerDestination();
900 nwSrc = ofmWithSwDpid.getOfMatch().getNetworkDestination();
901 }
902
903 long dlAddr = Ethernet.toLong(dlAddrArr);
904
905 // Ignore broadcast/multicast source
906 if ((dlAddrArr[0] & 0x1) != 0)
907 return null;
908
909 Long swDpid = null;
910 Short inPort = null;
911
912 if (isSource) {
913 swDpid = ofmWithSwDpid.getSwitchDataPathId();
914 inPort = ofmWithSwDpid.getOfMatch().getInputPort();
915 }
916
917 boolean learnap = true;
918 if (swDpid == null ||
919 inPort == null ||
920 !isValidAttachmentPoint(swDpid, inPort)) {
921 // If this is an internal port or we otherwise don't want
922 // to learn on these ports. In the future, we should
923 // handle this case by labeling flows with something that
924 // will give us the entity class. For now, we'll do our
925 // best assuming attachment point information isn't used
926 // as a key field.
927 learnap = false;
928 }
929
930 short vlan = ofmWithSwDpid.getOfMatch().getDataLayerVirtualLan();
931 return new Entity(dlAddr,
932 ((vlan >= 0) ? vlan : null),
933 ((nwSrc != 0) ? nwSrc : null),
934 (learnap ? swDpid : null),
935 (learnap ? (int)inPort : null),
936 new Date());
937 }
938 /**
939 * Look up a {@link Device} based on the provided {@link Entity}. We first
940 * check the primary index. If we do not find an entry there we classify
941 * the device into its IEntityClass and query the classIndex.
942 * This implies that all key field of the current IEntityClassifier must
943 * be present in the entity for the lookup to succeed!
944 * @param entity the entity to search for
945 * @return The {@link Device} object if found
946 */
947 protected Device findDeviceByEntity(Entity entity) {
948 // Look up the fully-qualified entity to see if it already
949 // exists in the primary entity index.
950 Long deviceKey = primaryIndex.findByEntity(entity);
951 IEntityClass entityClass = null;
952
953 if (deviceKey == null) {
954 // If the entity does not exist in the primary entity index,
955 // use the entity classifier for find the classes for the
956 // entity. Look up the entity in the returned class'
957 // class entity index.
958 entityClass = entityClassifier.classifyEntity(entity);
959 if (entityClass == null) {
960 return null;
961 }
962 ClassState classState = getClassState(entityClass);
963
964 if (classState.classIndex != null) {
965 deviceKey =
966 classState.classIndex.findByEntity(entity);
967 }
968 }
969 if (deviceKey == null) return null;
970 return deviceMap.get(deviceKey);
971 }
972
973 /**
974 * Get a destination device using entity fields that corresponds with
975 * the given source device. The source device is important since
976 * there could be ambiguity in the destination device without the
977 * attachment point information.
978 * @param source the source device. The returned destination will be
979 * in the same entity class as the source.
980 * @param dstEntity the entity to look up
981 * @return an {@link Device} or null if no device is found.
982 */
983 protected Device findDestByEntity(IDevice source,
984 Entity dstEntity) {
985
986 // Look up the fully-qualified entity to see if it
987 // exists in the primary entity index
988 Long deviceKey = primaryIndex.findByEntity(dstEntity);
989
990 if (deviceKey == null) {
991 // This could happen because:
992 // 1) no destination known, or a broadcast destination
993 // 2) if we have attachment point key fields since
994 // attachment point information isn't available for
995 // destination devices.
996 // For the second case, we'll need to match up the
997 // destination device with the class of the source
998 // device.
999 ClassState classState = getClassState(source.getEntityClass());
1000 if (classState.classIndex == null) {
1001 return null;
1002 }
1003 deviceKey = classState.classIndex.findByEntity(dstEntity);
1004 }
1005 if (deviceKey == null) return null;
1006 return deviceMap.get(deviceKey);
1007 }
1008
1009
1010 /**
1011 * Look up a {@link Device} within a particular entity class based on
1012 * the provided {@link Entity}.
1013 * @param clazz the entity class to search for the entity
1014 * @param entity the entity to search for
1015 * @return The {@link Device} object if found
1016 private Device findDeviceInClassByEntity(IEntityClass clazz,
1017 Entity entity) {
1018 // XXX - TODO
1019 throw new UnsupportedOperationException();
1020 }
1021 */
1022
1023 /**
1024 * Look up a {@link Device} based on the provided {@link Entity}. Also
1025 * learns based on the new entity, and will update existing devices as
1026 * required.
1027 *
1028 * @param entity the {@link Entity}
1029 * @return The {@link Device} object if found
1030 */
1031 protected Device learnDeviceByEntity(Entity entity) {
1032 ArrayList<Long> deleteQueue = null;
1033 LinkedList<DeviceUpdate> deviceUpdates = null;
1034 Device device = null;
1035
1036 // we may need to restart the learning process if we detect
1037 // concurrent modification. Note that we ensure that at least
1038 // one thread should always succeed so we don't get into infinite
1039 // starvation loops
1040 while (true) {
1041 deviceUpdates = null;
1042
1043 // Look up the fully-qualified entity to see if it already
1044 // exists in the primary entity index.
1045 Long deviceKey = primaryIndex.findByEntity(entity);
1046 IEntityClass entityClass = null;
1047
1048 if (deviceKey == null) {
1049 // If the entity does not exist in the primary entity index,
1050 // use the entity classifier for find the classes for the
1051 // entity. Look up the entity in the returned class'
1052 // class entity index.
1053 entityClass = entityClassifier.classifyEntity(entity);
1054 if (entityClass == null) {
1055 // could not classify entity. No device
1056 return null;
1057 }
1058 ClassState classState = getClassState(entityClass);
1059
1060 if (classState.classIndex != null) {
1061 deviceKey =
1062 classState.classIndex.findByEntity(entity);
1063 }
1064 }
1065 if (deviceKey != null) {
1066 // If the primary or secondary index contains the entity
1067 // use resulting device key to look up the device in the
1068 // device map, and use the referenced Device below.
1069 device = deviceMap.get(deviceKey);
1070 if (device == null)
1071 throw new IllegalStateException("Corrupted device index");
1072 } else {
1073 // If the secondary index does not contain the entity,
1074 // create a new Device object containing the entity, and
1075 // generate a new device ID. However, we first check if
1076 // the entity is allowed (e.g., for spoofing protection)
1077 if (!isEntityAllowed(entity, entityClass)) {
1078 logger.info("PacketIn is not allowed {} {}",
1079 entityClass.getName(), entity);
1080 return null;
1081 }
1082 synchronized (deviceKeyLock) {
1083 deviceKey = Long.valueOf(deviceKeyCounter++);
1084 }
1085 device = allocateDevice(deviceKey, entity, entityClass);
1086 if (logger.isDebugEnabled()) {
1087 logger.debug("New device created: {} deviceKey={}, entity={}",
1088 new Object[]{device, deviceKey, entity});
1089 }
1090
1091 // Add the new device to the primary map with a simple put
1092 deviceMap.put(deviceKey, device);
1093
1094 // update indices
1095 if (!updateIndices(device, deviceKey)) {
1096 if (deleteQueue == null)
1097 deleteQueue = new ArrayList<Long>();
1098 deleteQueue.add(deviceKey);
1099 continue;
1100 }
1101
1102 updateSecondaryIndices(entity, entityClass, deviceKey);
1103
1104 // generate new device update
1105 deviceUpdates =
1106 updateUpdates(deviceUpdates,
1107 new DeviceUpdate(device, ADD, null));
1108
1109 break;
1110 }
1111
1112 if (!isEntityAllowed(entity, device.getEntityClass())) {
1113 logger.info("PacketIn is not allowed {} {}",
1114 device.getEntityClass().getName(), entity);
1115 return null;
1116 }
1117 int entityindex = -1;
1118 if ((entityindex = device.entityIndex(entity)) >= 0) {
1119 // update timestamp on the found entity
1120 Date lastSeen = entity.getLastSeenTimestamp();
1121 if (lastSeen == null) lastSeen = new Date();
1122 device.entities[entityindex].setLastSeenTimestamp(lastSeen);
1123 if (device.entities[entityindex].getSwitchDPID() != null &&
1124 device.entities[entityindex].getSwitchPort() != null) {
1125 long sw = device.entities[entityindex].getSwitchDPID();
1126 short port = device.entities[entityindex].getSwitchPort().shortValue();
1127
1128 boolean moved =
1129 device.updateAttachmentPoint(sw,
1130 port,
1131 lastSeen.getTime());
1132
1133 if (moved) {
1134 sendDeviceMovedNotification(device);
1135 if (logger.isTraceEnabled()) {
1136 logger.trace("Device moved: attachment points {}," +
1137 "entities {}", device.attachmentPoints,
1138 device.entities);
1139 }
1140 } else {
1141 if (logger.isTraceEnabled()) {
1142 logger.trace("Device attachment point NOT updated: " +
1143 "attachment points {}," +
1144 "entities {}", device.attachmentPoints,
1145 device.entities);
1146 }
1147 }
1148 }
1149 break;
1150 } else {
1151 boolean moved = false;
1152 Device newDevice = allocateDevice(device, entity);
1153 if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) {
1154 moved = newDevice.updateAttachmentPoint(entity.getSwitchDPID(),
1155 entity.getSwitchPort().shortValue(),
1156 entity.getLastSeenTimestamp().getTime());
1157 }
1158
1159 // generate updates
1160 EnumSet<DeviceField> changedFields =
1161 findChangedFields(device, entity);
1162 if (changedFields.size() > 0)
1163 deviceUpdates =
1164 updateUpdates(deviceUpdates,
1165 new DeviceUpdate(newDevice, CHANGE,
1166 changedFields));
1167
1168 // update the device map with a replace call
1169 boolean res = deviceMap.replace(deviceKey, device, newDevice);
1170 // If replace returns false, restart the process from the
1171 // beginning (this implies another thread concurrently
1172 // modified this Device).
1173 if (!res)
1174 continue;
1175
1176 device = newDevice;
1177
1178 // update indices
1179 if (!updateIndices(device, deviceKey)) {
1180 continue;
1181 }
1182 updateSecondaryIndices(entity,
1183 device.getEntityClass(),
1184 deviceKey);
1185
1186 if (moved) {
1187 sendDeviceMovedNotification(device);
1188 if (logger.isDebugEnabled()) {
1189 logger.debug("Device moved: attachment points {}," +
1190 "entities {}", device.attachmentPoints,
1191 device.entities);
1192 }
1193 } else {
1194 if (logger.isDebugEnabled()) {
1195 logger.debug("Device attachment point updated: " +
1196 "attachment points {}," +
1197 "entities {}", device.attachmentPoints,
1198 device.entities);
1199 }
1200 }
1201 break;
1202 }
1203 }
1204
1205 if (deleteQueue != null) {
1206 for (Long l : deleteQueue) {
1207 Device dev = deviceMap.get(l);
1208 this.deleteDevice(dev);
1209
1210
1211 // generate new device update
1212 deviceUpdates =
1213 updateUpdates(deviceUpdates,
1214 new DeviceUpdate(dev, DELETE, null));
1215 }
1216 }
1217
1218 processUpdates(deviceUpdates);
1219
1220 return device;
1221 }
1222
1223 protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) {
1224 return true;
1225 }
1226
1227 protected EnumSet<DeviceField> findChangedFields(Device device,
1228 Entity newEntity) {
1229 EnumSet<DeviceField> changedFields =
1230 EnumSet.of(DeviceField.IPV4,
1231 DeviceField.VLAN,
1232 DeviceField.SWITCH);
1233
1234 if (newEntity.getIpv4Address() == null)
1235 changedFields.remove(DeviceField.IPV4);
1236 if (newEntity.getVlan() == null)
1237 changedFields.remove(DeviceField.VLAN);
1238 if (newEntity.getSwitchDPID() == null ||
1239 newEntity.getSwitchPort() == null)
1240 changedFields.remove(DeviceField.SWITCH);
1241
1242 if (changedFields.size() == 0) return changedFields;
1243
1244 for (Entity entity : device.getEntities()) {
1245 if (newEntity.getIpv4Address() == null ||
1246 (entity.getIpv4Address() != null &&
1247 entity.getIpv4Address().equals(newEntity.getIpv4Address())))
1248 changedFields.remove(DeviceField.IPV4);
1249 if (newEntity.getVlan() == null ||
1250 (entity.getVlan() != null &&
1251 entity.getVlan().equals(newEntity.getVlan())))
1252 changedFields.remove(DeviceField.VLAN);
1253 if (newEntity.getSwitchDPID() == null ||
1254 newEntity.getSwitchPort() == null ||
1255 (entity.getSwitchDPID() != null &&
1256 entity.getSwitchPort() != null &&
1257 entity.getSwitchDPID().equals(newEntity.getSwitchDPID()) &&
1258 entity.getSwitchPort().equals(newEntity.getSwitchPort())))
1259 changedFields.remove(DeviceField.SWITCH);
1260 }
1261
1262 return changedFields;
1263 }
1264
1265 /**
1266 * Send update notifications to listeners
1267 * @param updates the updates to process.
1268 */
1269 protected void processUpdates(Queue<DeviceUpdate> updates) {
1270 if (updates == null) return;
1271 DeviceUpdate update = null;
1272 while (null != (update = updates.poll())) {
1273 if (logger.isTraceEnabled()) {
1274 logger.trace("Dispatching device update: {}", update);
1275 }
1276 for (IDeviceListener listener : deviceListeners) {
1277 switch (update.change) {
1278 case ADD:
1279 listener.deviceAdded(update.device);
1280 break;
1281 case DELETE:
1282 listener.deviceRemoved(update.device);
1283 break;
1284 case CHANGE:
1285 for (DeviceField field : update.fieldsChanged) {
1286 switch (field) {
1287 case IPV4:
1288 listener.deviceIPV4AddrChanged(update.device);
1289 break;
1290 case SWITCH:
1291 case PORT:
1292 //listener.deviceMoved(update.device);
1293 break;
1294 case VLAN:
1295 listener.deviceVlanChanged(update.device);
1296 break;
1297 default:
1298 logger.debug("Unknown device field changed {}",
1299 update.fieldsChanged.toString());
1300 break;
1301 }
1302 }
1303 break;
1304 }
1305 }
1306 }
1307 }
1308
1309 /**
1310 * Check if the entity e has all the keyFields set. Returns false if not
1311 * @param e entity to check
1312 * @param keyFields the key fields to check e against
1313 * @return
1314 */
1315 protected boolean allKeyFieldsPresent(Entity e, EnumSet<DeviceField> keyFields) {
1316 for (DeviceField f : keyFields) {
1317 switch (f) {
1318 case MAC:
1319 // MAC address is always present
1320 break;
1321 case IPV4:
1322 if (e.ipv4Address == null) return false;
1323 break;
1324 case SWITCH:
1325 if (e.switchDPID == null) return false;
1326 break;
1327 case PORT:
1328 if (e.switchPort == null) return false;
1329 break;
1330 case VLAN:
1331 // FIXME: vlan==null is ambiguous: it can mean: not present
1332 // or untagged
1333 //if (e.vlan == null) return false;
1334 break;
1335 default:
1336 // we should never get here. unless somebody extended
1337 // DeviceFields
1338 throw new IllegalStateException();
1339 }
1340 }
1341 return true;
1342 }
1343
1344 private LinkedList<DeviceUpdate>
1345 updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) {
1346 if (update == null) return list;
1347 if (list == null)
1348 list = new LinkedList<DeviceUpdate>();
1349 list.add(update);
1350
1351 return list;
1352 }
1353
1354 /**
1355 * Get the secondary index for a class. Will return null if the
1356 * secondary index was created concurrently in another thread.
1357 * @param clazz the class for the index
1358 * @return
1359 */
1360 private ClassState getClassState(IEntityClass clazz) {
1361 ClassState classState = classStateMap.get(clazz.getName());
1362 if (classState != null) return classState;
1363
1364 classState = new ClassState(clazz);
1365 ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState);
1366 if (r != null) {
1367 // concurrent add
1368 return r;
1369 }
1370 return classState;
1371 }
1372
1373 /**
1374 * Update both the primary and class indices for the provided device.
1375 * If the update fails because of an concurrent update, will return false.
1376 * @param device the device to update
1377 * @param deviceKey the device key for the device
1378 * @return true if the update succeeded, false otherwise.
1379 */
1380 private boolean updateIndices(Device device, Long deviceKey) {
1381 if (!primaryIndex.updateIndex(device, deviceKey)) {
1382 return false;
1383 }
1384 IEntityClass entityClass = device.getEntityClass();
1385 ClassState classState = getClassState(entityClass);
1386
1387 if (classState.classIndex != null) {
1388 if (!classState.classIndex.updateIndex(device,
1389 deviceKey))
1390 return false;
1391 }
1392 return true;
1393 }
1394
1395 /**
1396 * Update the secondary indices for the given entity and associated
1397 * entity classes
1398 * @param entity the entity to update
1399 * @param entityClass the entity class for the entity
1400 * @param deviceKey the device key to set up
1401 */
1402 private void updateSecondaryIndices(Entity entity,
1403 IEntityClass entityClass,
1404 Long deviceKey) {
1405 for (DeviceIndex index : secondaryIndexMap.values()) {
1406 index.updateIndex(entity, deviceKey);
1407 }
1408 ClassState state = getClassState(entityClass);
1409 for (DeviceIndex index : state.secondaryIndexMap.values()) {
1410 index.updateIndex(entity, deviceKey);
1411 }
1412 }
1413
1414 // *********************
1415 // IEntityClassListener
1416 // *********************
1417 @Override
1418 public void entityClassChanged (Set<String> entityClassNames) {
1419 /* iterate through the devices, reclassify the devices that belong
1420 * to these entity class names
1421 */
1422 Iterator<Device> diter = deviceMap.values().iterator();
1423 while (diter.hasNext()) {
1424 Device d = diter.next();
1425 if (d.getEntityClass() == null ||
1426 entityClassNames.contains(d.getEntityClass().getName()))
1427 reclassifyDevice(d);
1428 }
1429 }
1430
1431 /**
1432 * Clean up expired entities/devices
1433 */
1434 protected void cleanupEntities () {
1435
1436 Calendar c = Calendar.getInstance();
1437 c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT);
1438 Date cutoff = c.getTime();
1439
1440 ArrayList<Entity> toRemove = new ArrayList<Entity>();
1441 ArrayList<Entity> toKeep = new ArrayList<Entity>();
1442
1443 Iterator<Device> diter = deviceMap.values().iterator();
1444 LinkedList<DeviceUpdate> deviceUpdates =
1445 new LinkedList<DeviceUpdate>();
1446
1447 while (diter.hasNext()) {
1448 Device d = diter.next();
1449
1450 while (true) {
1451 deviceUpdates.clear();
1452 toRemove.clear();
1453 toKeep.clear();
1454 for (Entity e : d.getEntities()) {
1455 if (e.getLastSeenTimestamp() != null &&
1456 0 > e.getLastSeenTimestamp().compareTo(cutoff)) {
1457 // individual entity needs to be removed
1458 toRemove.add(e);
1459 } else {
1460 toKeep.add(e);
1461 }
1462 }
1463 if (toRemove.size() == 0) {
1464 break;
1465 }
1466
1467 for (Entity e : toRemove) {
1468 removeEntity(e, d.getEntityClass(), d.deviceKey, toKeep);
1469 }
1470
1471 if (toKeep.size() > 0) {
1472 Device newDevice = allocateDevice(d.getDeviceKey(),
1473 d.oldAPs,
1474 d.attachmentPoints,
1475 toKeep,
1476 d.entityClass);
1477
1478 EnumSet<DeviceField> changedFields =
1479 EnumSet.noneOf(DeviceField.class);
1480 for (Entity e : toRemove) {
1481 changedFields.addAll(findChangedFields(newDevice, e));
1482 }
1483 if (changedFields.size() > 0)
1484 deviceUpdates.add(new DeviceUpdate(d, CHANGE,
1485 changedFields));
1486
1487 if (!deviceMap.replace(newDevice.getDeviceKey(),
1488 d,
1489 newDevice)) {
1490 // concurrent modification; try again
1491 // need to use device that is the map now for the next
1492 // iteration
1493 d = deviceMap.get(d.getDeviceKey());
1494 if (null != d)
1495 continue;
1496 }
1497 } else {
1498 deviceUpdates.add(new DeviceUpdate(d, DELETE, null));
1499 if (!deviceMap.remove(d.getDeviceKey(), d))
1500 // concurrent modification; try again
1501 // need to use device that is the map now for the next
1502 // iteration
1503 d = deviceMap.get(d.getDeviceKey());
1504 if (null != d)
1505 continue;
1506 }
1507 processUpdates(deviceUpdates);
1508 break;
1509 }
1510 }
1511 }
1512
1513 protected void removeEntity(Entity removed,
1514 IEntityClass entityClass,
1515 Long deviceKey,
1516 Collection<Entity> others) {
1517 for (DeviceIndex index : secondaryIndexMap.values()) {
1518 index.removeEntityIfNeeded(removed, deviceKey, others);
1519 }
1520 ClassState classState = getClassState(entityClass);
1521 for (DeviceIndex index : classState.secondaryIndexMap.values()) {
1522 index.removeEntityIfNeeded(removed, deviceKey, others);
1523 }
1524
1525 primaryIndex.removeEntityIfNeeded(removed, deviceKey, others);
1526
1527 if (classState.classIndex != null) {
1528 classState.classIndex.removeEntityIfNeeded(removed,
1529 deviceKey,
1530 others);
1531 }
1532 }
1533
1534 /**
1535 * method to delete a given device, remove all entities first and then
1536 * finally delete the device itself.
1537 * @param device
1538 */
1539 protected void deleteDevice(Device device) {
1540 ArrayList<Entity> emptyToKeep = new ArrayList<Entity>();
1541 for (Entity entity : device.getEntities()) {
1542 this.removeEntity(entity, device.getEntityClass(),
1543 device.getDeviceKey(), emptyToKeep);
1544 }
1545 if (!deviceMap.remove(device.getDeviceKey(), device)) {
1546 if (logger.isDebugEnabled())
1547 logger.debug("device map does not have this device -" +
1548 device.toString());
1549 }
1550 }
1551
1552 private EnumSet<DeviceField> getEntityKeys(Long macAddress,
1553 Short vlan,
1554 Integer ipv4Address,
1555 Long switchDPID,
1556 Integer switchPort) {
1557 // FIXME: vlan==null is a valid search. Need to handle this
1558 // case correctly. Note that the code will still work correctly.
1559 // But we might do a full device search instead of using an index.
1560 EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class);
1561 if (macAddress != null) keys.add(DeviceField.MAC);
1562 if (vlan != null) keys.add(DeviceField.VLAN);
1563 if (ipv4Address != null) keys.add(DeviceField.IPV4);
1564 if (switchDPID != null) keys.add(DeviceField.SWITCH);
1565 if (switchPort != null) keys.add(DeviceField.PORT);
1566 return keys;
1567 }
1568
1569
1570 protected Iterator<Device> queryClassByEntity(IEntityClass clazz,
1571 EnumSet<DeviceField> keyFields,
1572 Entity entity) {
1573 ClassState classState = getClassState(clazz);
1574 DeviceIndex index = classState.secondaryIndexMap.get(keyFields);
1575 if (index == null) return Collections.<Device>emptySet().iterator();
1576 return new DeviceIndexInterator(this, index.queryByEntity(entity));
1577 }
1578
1579 protected Device allocateDevice(Long deviceKey,
1580 Entity entity,
1581 IEntityClass entityClass) {
1582 return new Device(this, deviceKey, entity, entityClass);
1583 }
1584
1585 // TODO: FIX THIS.
1586 protected Device allocateDevice(Long deviceKey,
1587 List<AttachmentPoint> aps,
1588 List<AttachmentPoint> trueAPs,
1589 Collection<Entity> entities,
1590 IEntityClass entityClass) {
1591 return new Device(this, deviceKey, aps, trueAPs, entities, entityClass);
1592 }
1593
1594 protected Device allocateDevice(Device device,
1595 Entity entity) {
1596 return new Device(device, entity);
1597 }
1598
1599 protected Device allocateDevice(Device device, Set <Entity> entities) {
1600 List <AttachmentPoint> newPossibleAPs =
1601 new ArrayList<AttachmentPoint>();
1602 List <AttachmentPoint> newAPs =
1603 new ArrayList<AttachmentPoint>();
1604 for (Entity entity : entities) {
1605 if (entity.switchDPID != null && entity.switchPort != null) {
1606 AttachmentPoint aP =
1607 new AttachmentPoint(entity.switchDPID.longValue(),
1608 entity.switchPort.shortValue(), 0);
1609 newPossibleAPs.add(aP);
1610 }
1611 }
1612 if (device.attachmentPoints != null) {
1613 for (AttachmentPoint oldAP : device.attachmentPoints) {
1614 if (newPossibleAPs.contains(oldAP)) {
1615 newAPs.add(oldAP);
1616 }
1617 }
1618 }
1619 if (newAPs.isEmpty())
1620 newAPs = null;
1621 Device d = new Device(this, device.getDeviceKey(),newAPs, null,
1622 entities, device.getEntityClass());
1623 d.updateAttachmentPoint();
1624 return d;
1625 }
1626
1627 @Override
1628 public void addSuppressAPs(long swId, short port) {
1629 suppressAPs.add(new SwitchPort(swId, port));
1630 }
1631
1632 @Override
1633 public void removeSuppressAPs(long swId, short port) {
1634 suppressAPs.remove(new SwitchPort(swId, port));
1635 }
1636
1637 /**
1638 * Topology listener method.
1639 */
1640 @Override
1641 public void topologyChanged() {
1642 Iterator<Device> diter = deviceMap.values().iterator();
1643 List<LDUpdate> updateList = topology.getLastLinkUpdates();
1644 if (updateList != null) {
1645 if (logger.isTraceEnabled()) {
1646 for(LDUpdate update: updateList) {
1647 logger.trace("Topo update: {}", update);
1648 }
1649 }
1650 }
1651
1652 while (diter.hasNext()) {
1653 Device d = diter.next();
1654 if (d.updateAttachmentPoint()) {
1655 if (logger.isDebugEnabled()) {
1656 logger.debug("Attachment point changed for device: {}", d);
1657 }
1658 sendDeviceMovedNotification(d);
1659 }
1660 }
1661 }
1662
1663 /**
1664 * Send update notifications to listeners
1665 * @param updates the updates to process.
1666 */
1667 protected void sendDeviceMovedNotification(Device d) {
1668 for (IDeviceListener listener : deviceListeners) {
1669 listener.deviceMoved(d);
1670 }
1671 }
1672
1673 /**
1674 * this method will reclassify and reconcile a device - possibilities
1675 * are - create new device(s), remove entities from this device. If the
1676 * device entity class did not change then it returns false else true.
1677 * @param device
1678 */
1679 protected boolean reclassifyDevice(Device device)
1680 {
1681 // first classify all entities of this device
1682 if (device == null) {
1683 logger.debug("In reclassify for null device");
1684 return false;
1685 }
1686 boolean needToReclassify = false;
1687 for (Entity entity : device.entities) {
1688 IEntityClass entityClass =
1689 this.entityClassifier.classifyEntity(entity);
1690 if (entityClass == null || device.getEntityClass() == null) {
1691 needToReclassify = true;
1692 break;
1693 }
1694 if (!entityClass.getName().
1695 equals(device.getEntityClass().getName())) {
1696 needToReclassify = true;
1697 break;
1698 }
1699 }
1700 if (needToReclassify == false) {
1701 return false;
1702 }
1703
1704 LinkedList<DeviceUpdate> deviceUpdates =
1705 new LinkedList<DeviceUpdate>();
1706 // delete this device and then re-learn all the entities
1707 this.deleteDevice(device);
1708 deviceUpdates.add(new DeviceUpdate(device,
1709 DeviceUpdate.Change.DELETE, null));
1710 if (!deviceUpdates.isEmpty())
1711 processUpdates(deviceUpdates);
1712 for (Entity entity: device.entities ) {
1713 this.learnDeviceByEntity(entity);
1714 }
1715 return true;
1716 }
1717}