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