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