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