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