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