blob: 05ebcf3c9fc77a9f40f8982fcb3b18bad315e6a8 [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.Arrays;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Date;
25import java.util.EnumSet;
26import java.util.HashMap;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import java.util.TreeSet;
31
32import org.codehaus.jackson.map.annotate.JsonSerialize;
33import org.openflow.util.HexString;
34import org.slf4j.Logger;
35import org.slf4j.LoggerFactory;
36
37import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
38import net.floodlightcontroller.devicemanager.web.DeviceSerializer;
39import net.floodlightcontroller.devicemanager.IDevice;
40import net.floodlightcontroller.devicemanager.IEntityClass;
41import net.floodlightcontroller.devicemanager.SwitchPort;
42import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus;
43import net.floodlightcontroller.packet.Ethernet;
44import net.floodlightcontroller.packet.IPv4;
45import net.floodlightcontroller.topology.ITopologyService;
46
47/**
48 * Concrete implementation of {@link IDevice}
49 * @author readams
50 */
51@JsonSerialize(using=DeviceSerializer.class)
52public class Device implements IDevice {
Yuta HIGUCHI6ac8d182013-10-22 15:24:56 -070053 protected final static Logger log =
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -080054 LoggerFactory.getLogger(Device.class);
55
56 protected Long deviceKey;
57 protected DeviceManagerImpl deviceManager;
58
59 protected Entity[] entities;
60 protected IEntityClass entityClass;
61
62 protected String macAddressString;
63
64 /**
65 * These are the old attachment points for the device that were
66 * valid no more than INACTIVITY_TIME ago.
67 */
68 protected List<AttachmentPoint> oldAPs;
69 /**
70 * The current attachment points for the device.
71 */
72 protected List<AttachmentPoint> attachmentPoints;
73 // ************
74 // Constructors
75 // ************
76
77 /**
78 * Create a device from an entities
79 * @param deviceManager the device manager for this device
80 * @param deviceKey the unique identifier for this device object
81 * @param entity the initial entity for the device
82 * @param entityClass the entity classes associated with the entity
83 */
84 public Device(DeviceManagerImpl deviceManager,
85 Long deviceKey,
86 Entity entity,
87 IEntityClass entityClass) {
88 this.deviceManager = deviceManager;
89 this.deviceKey = deviceKey;
90 this.entities = new Entity[] {entity};
91 this.macAddressString =
92 HexString.toHexString(entity.getMacAddress(), 6);
93 this.entityClass = entityClass;
94 Arrays.sort(this.entities);
95
96 this.oldAPs = null;
97 this.attachmentPoints = null;
98
99 if (entity.getSwitchDPID() != null &&
100 entity.getSwitchPort() != null){
101 long sw = entity.getSwitchDPID();
102 short port = entity.getSwitchPort().shortValue();
103
104 if (deviceManager.isValidAttachmentPoint(sw, port)) {
105 AttachmentPoint ap;
106 ap = new AttachmentPoint(sw, port,
107entity.getLastSeenTimestamp().getTime());
108
109 this.attachmentPoints = new ArrayList<AttachmentPoint>();
110 this.attachmentPoints.add(ap);
111 }
112 }
113 }
114
115 /**
116 * Create a device from a set of entities
117 * @param deviceManager the device manager for this device
118 * @param deviceKey the unique identifier for this device object
119 * @param entities the initial entities for the device
120 * @param entityClass the entity class associated with the entities
121 */
122 public Device(DeviceManagerImpl deviceManager,
123 Long deviceKey,
124 Collection<AttachmentPoint> oldAPs,
125 Collection<AttachmentPoint> attachmentPoints,
126 Collection<Entity> entities,
127 IEntityClass entityClass) {
128 this.deviceManager = deviceManager;
129 this.deviceKey = deviceKey;
130 this.entities = entities.toArray(new Entity[entities.size()]);
131 this.oldAPs = null;
132 this.attachmentPoints = null;
133 if (oldAPs != null) {
134 this.oldAPs =
135 new ArrayList<AttachmentPoint>(oldAPs);
136 }
137 if (attachmentPoints != null) {
138 this.attachmentPoints =
139 new ArrayList<AttachmentPoint>(attachmentPoints);
140 }
141 this.macAddressString =
142 HexString.toHexString(this.entities[0].getMacAddress(), 6);
143 this.entityClass = entityClass;
144 Arrays.sort(this.entities);
145 }
146
147 /**
148 * Construct a new device consisting of the entities from the old device
149 * plus an additional entity
150 * @param device the old device object
151 * @param newEntity the entity to add. newEntity must be have the same
152 * entity class as device
153 */
154 public Device(Device device,
155 Entity newEntity) {
156 this.deviceManager = device.deviceManager;
157 this.deviceKey = device.deviceKey;
158 this.entities = Arrays.<Entity>copyOf(device.entities,
159 device.entities.length + 1);
160 this.entities[this.entities.length - 1] = newEntity;
161 Arrays.sort(this.entities);
162 this.oldAPs = null;
163 if (device.oldAPs != null) {
164 this.oldAPs =
165 new ArrayList<AttachmentPoint>(device.oldAPs);
166 }
167 this.attachmentPoints = null;
168 if (device.attachmentPoints != null) {
169 this.attachmentPoints =
170 new ArrayList<AttachmentPoint>(device.attachmentPoints);
171 }
172
173 this.macAddressString =
174 HexString.toHexString(this.entities[0].getMacAddress(), 6);
175
176 this.entityClass = device.entityClass;
177 }
178
179 /**
180 * Given a list of attachment points (apList), the procedure would return
181 * a map of attachment points for each L2 domain. L2 domain id is the key.
182 * @param apList
183 * @return
184 */
185 private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
186
187 if (apList == null) return null;
188 ITopologyService topology = deviceManager.topology;
189
190 // Get the old attachment points and sort them.
191 List<AttachmentPoint>oldAP = new ArrayList<AttachmentPoint>();
192 if (apList != null) oldAP.addAll(apList);
193
194 // Remove invalid attachment points before sorting.
195 List<AttachmentPoint>tempAP =
196 new ArrayList<AttachmentPoint>();
197 for(AttachmentPoint ap: oldAP) {
198 if (deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())){
199 tempAP.add(ap);
200 }
201 }
202 oldAP = tempAP;
203
204 Collections.sort(oldAP, deviceManager.apComparator);
205
206 // Map of attachment point by L2 domain Id.
207 Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
208
209 for(int i=0; i<oldAP.size(); ++i) {
210 AttachmentPoint ap = oldAP.get(i);
211 // if this is not a valid attachment point, continue
212 if (!deviceManager.isValidAttachmentPoint(ap.getSw(),
213 ap.getPort()))
214 continue;
215
216 long id = topology.getL2DomainId(ap.getSw());
217 apMap.put(id, ap);
218 }
219
220 if (apMap.isEmpty()) return null;
221 return apMap;
222 }
223
224 /**
225 * Remove all attachment points that are older than INACTIVITY_INTERVAL
226 * from the list.
227 * @param apList
228 * @return
229 */
230 private boolean removeExpiredAttachmentPoints(List<AttachmentPoint>apList) {
231
232 List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
233
234 if (apList == null) return false;
235
236 for(AttachmentPoint ap: apList) {
237 if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL <
238 System.currentTimeMillis())
239 expiredAPs.add(ap);
240 }
241 if (expiredAPs.size() > 0) {
242 apList.removeAll(expiredAPs);
243 return true;
244 } else return false;
245 }
246
247 /**
248 * Get a list of duplicate attachment points, given a list of old attachment
249 * points and one attachment point per L2 domain. Given a true attachment
250 * point in the L2 domain, say trueAP, another attachment point in the
251 * same L2 domain, say ap, is duplicate if:
252 * 1. ap is inconsistent with trueAP, and
253 * 2. active time of ap is after that of trueAP; and
254 * 3. last seen time of ap is within the last INACTIVITY_INTERVAL
255 * @param oldAPList
256 * @param apMap
257 * @return
258 */
259 List<AttachmentPoint> getDuplicateAttachmentPoints(List<AttachmentPoint>oldAPList,
260 Map<Long, AttachmentPoint>apMap) {
261 ITopologyService topology = deviceManager.topology;
262 List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
263 long timeThreshold = System.currentTimeMillis() -
264 AttachmentPoint.INACTIVITY_INTERVAL;
265
266 if (oldAPList == null || apMap == null)
267 return dupAPs;
268
269 for(AttachmentPoint ap: oldAPList) {
270 long id = topology.getL2DomainId(ap.getSw());
271 AttachmentPoint trueAP = apMap.get(id);
272
273 if (trueAP == null) continue;
274 boolean c = (topology.isConsistent(trueAP.getSw(), trueAP.getPort(),
275 ap.getSw(), ap.getPort()));
276 boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
277 boolean last = ap.getLastSeen() > timeThreshold;
278 if (!c && active && last) {
279 dupAPs.add(ap);
280 }
281 }
282
283 return dupAPs;
284 }
285
286 /**
287 * Update the known attachment points. This method is called whenever
288 * topology changes. The method returns true if there's any change to
289 * the list of attachment points -- which indicates a possible device
290 * move.
291 * @return
292 */
293 protected boolean updateAttachmentPoint() {
294 boolean moved = false;
295
296 if (attachmentPoints == null || attachmentPoints.isEmpty())
297 return false;
298
299 List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
300 if (attachmentPoints != null) apList.addAll(attachmentPoints);
301 Map<Long, AttachmentPoint> newMap = getAPMap(apList);
302 if (newMap == null || newMap.size() != apList.size()) {
303 moved = true;
304 }
305
306 // Prepare the new attachment point list.
307 if (moved) {
308 List<AttachmentPoint> newAPList =
309 new ArrayList<AttachmentPoint>();
310 if (newMap != null) newAPList.addAll(newMap.values());
311 this.attachmentPoints = newAPList;
312 }
313
314 // Set the oldAPs to null.
315 this.oldAPs = null;
316 return moved;
317 }
318
319 /**
320 * Update the list of attachment points given that a new packet-in
321 * was seen from (sw, port) at time (lastSeen). The return value is true
322 * if there was any change to the list of attachment points for the device
323 * -- which indicates a device move.
324 * @param sw
325 * @param port
326 * @param lastSeen
327 * @return
328 */
329 protected boolean updateAttachmentPoint(long sw, short port, long lastSeen){
330 ITopologyService topology = deviceManager.topology;
331 List<AttachmentPoint> oldAPList;
332 List<AttachmentPoint> apList;
333 boolean oldAPFlag = false;
334
335 if (!deviceManager.isValidAttachmentPoint(sw, port)) return false;
336 AttachmentPoint newAP = new AttachmentPoint(sw, port, lastSeen);
337
338 //Copy the oldAP and ap list.
339 apList = new ArrayList<AttachmentPoint>();
340 if (attachmentPoints != null) apList.addAll(attachmentPoints);
341 oldAPList = new ArrayList<AttachmentPoint>();
342 if (oldAPs != null) oldAPList.addAll(oldAPs);
343
344 // if the sw, port is in old AP, remove it from there
345 // and update the lastSeen in that object.
346 if (oldAPList.contains(newAP)) {
347 int index = oldAPList.indexOf(newAP);
348 newAP = oldAPList.remove(index);
349 newAP.setLastSeen(lastSeen);
350 this.oldAPs = oldAPList;
351 oldAPFlag = true;
352 }
353
354 // newAP now contains the new attachment point.
355
356 // Get the APMap is null or empty.
357 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
358 if (apMap == null || apMap.isEmpty()) {
359 apList.add(newAP);
360 attachmentPoints = apList;
361 return true;
362 }
363
364 long id = topology.getL2DomainId(sw);
365 AttachmentPoint oldAP = apMap.get(id);
366
367 if (oldAP == null) // No attachment on this L2 domain.
368 {
369 apList = new ArrayList<AttachmentPoint>();
370 apList.addAll(apMap.values());
371 apList.add(newAP);
372 this.attachmentPoints = apList;
373 return true; // new AP found on an L2 island.
374 }
375
376 // There is already a known attachment point on the same L2 island.
377 // we need to compare oldAP and newAP.
378 if (oldAP.equals(newAP)) {
379 // nothing to do here. just the last seen has to be changed.
380 if (newAP.lastSeen > oldAP.lastSeen) {
381 oldAP.setLastSeen(newAP.lastSeen);
382 }
383 this.attachmentPoints =
384 new ArrayList<AttachmentPoint>(apMap.values());
385 return false; // nothing to do here.
386 }
387
388 int x = deviceManager.apComparator.compare(oldAP, newAP);
389 if (x < 0) {
390 // newAP replaces oldAP.
391 apMap.put(id, newAP);
392 this.attachmentPoints =
393 new ArrayList<AttachmentPoint>(apMap.values());
394
395 oldAPList = new ArrayList<AttachmentPoint>();
396 if (oldAPs != null) oldAPList.addAll(oldAPs);
397 oldAPList.add(oldAP);
398 this.oldAPs = oldAPList;
399 if (!topology.isInSameBroadcastDomain(oldAP.getSw(), oldAP.getPort(),
400 newAP.getSw(), newAP.getPort()))
401 return true; // attachment point changed.
402 } else if (oldAPFlag) {
403 // retain oldAP as is. Put the newAP in oldAPs for flagging
404 // possible duplicates.
405 oldAPList = new ArrayList<AttachmentPoint>();
406 if (oldAPs != null) oldAPList.addAll(oldAPs);
407 // Add ot oldAPList only if it was picked up from the oldAPList
408 oldAPList.add(newAP);
409 this.oldAPs = oldAPList;
410 }
411 return false;
412 }
413
414 /**
415 * Delete (sw,port) from the list of list of attachment points
416 * and oldAPs.
417 * @param sw
418 * @param port
419 * @return
420 */
421 public boolean deleteAttachmentPoint(long sw, short port) {
422 AttachmentPoint ap = new AttachmentPoint(sw, port, 0);
423
424 if (this.oldAPs != null) {
425 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
426 apList.addAll(this.oldAPs);
427 int index = apList.indexOf(ap);
428 if (index > 0) {
429 apList.remove(index);
430 this.oldAPs = apList;
431 }
432 }
433
434 if (this.attachmentPoints != null) {
435 ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
436 apList.addAll(this.attachmentPoints);
437 int index = apList.indexOf(ap);
438 if (index > 0) {
439 apList.remove(index);
440 this.attachmentPoints = apList;
441 return true;
442 }
443 }
444 return false;
445 }
446
447 public boolean deleteAttachmentPoint(long sw) {
448 boolean deletedFlag;
449 ArrayList<AttachmentPoint> apList;
450 ArrayList<AttachmentPoint> modifiedList;
451
452 // Delete the APs on switch sw in oldAPs.
453 deletedFlag = false;
454 apList = new ArrayList<AttachmentPoint>();
455 if (this.oldAPs != null)
456 apList.addAll(this.oldAPs);
457 modifiedList = new ArrayList<AttachmentPoint>();
458
459 for(AttachmentPoint ap: apList) {
460 if (ap.getSw() == sw) {
461 deletedFlag = true;
462 } else {
463 modifiedList.add(ap);
464 }
465 }
466
467 if (deletedFlag) {
468 this.oldAPs = modifiedList;
469 }
470
471 // Delete the APs on switch sw in attachmentPoints.
472 deletedFlag = false;
473 apList = new ArrayList<AttachmentPoint>();
474 if (this.attachmentPoints != null)
475 apList.addAll(this.attachmentPoints);
476 modifiedList = new ArrayList<AttachmentPoint>();
477
478 for(AttachmentPoint ap: apList) {
479 if (ap.getSw() == sw) {
480 deletedFlag = true;
481 } else {
482 modifiedList.add(ap);
483 }
484 }
485
486 if (deletedFlag) {
487 this.attachmentPoints = modifiedList;
488 return true;
489 }
490
491 return false;
492 }
493
494
495 @Override
496 public SwitchPort[] getAttachmentPoints() {
497 return getAttachmentPoints(false);
498 }
499
500 @Override
501 public SwitchPort[] getAttachmentPoints(boolean includeError) {
502 List<SwitchPort> sp = new ArrayList<SwitchPort>();
503 SwitchPort [] returnSwitchPorts = new SwitchPort[] {};
504 if (attachmentPoints == null) return returnSwitchPorts;
505 if (attachmentPoints.isEmpty()) return returnSwitchPorts;
506
507
508 // copy ap list.
509 List<AttachmentPoint> apList;
510 apList = new ArrayList<AttachmentPoint>();
511 if (attachmentPoints != null) apList.addAll(attachmentPoints);
512 // get AP map.
513 Map<Long, AttachmentPoint> apMap = getAPMap(apList);
514
515 if (apMap != null) {
516 for(AttachmentPoint ap: apMap.values()) {
517 SwitchPort swport = new SwitchPort(ap.getSw(),
518 ap.getPort());
519 sp.add(swport);
520 }
521 }
522
523 if (!includeError)
524 return sp.toArray(new SwitchPort[sp.size()]);
525
526 List<AttachmentPoint> oldAPList;
527 oldAPList = new ArrayList<AttachmentPoint>();
528
529 if (oldAPs != null) oldAPList.addAll(oldAPs);
530
531 if (removeExpiredAttachmentPoints(oldAPList))
532 this.oldAPs = oldAPList;
533
534 List<AttachmentPoint> dupList;
535 dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
536 if (dupList != null) {
537 for(AttachmentPoint ap: dupList) {
538 SwitchPort swport = new SwitchPort(ap.getSw(),
539 ap.getPort(),
540 ErrorStatus.DUPLICATE_DEVICE);
541 sp.add(swport);
542 }
543 }
544 return sp.toArray(new SwitchPort[sp.size()]);
545 }
546
547 // *******
548 // IDevice
549 // *******
550
551 @Override
552 public Long getDeviceKey() {
553 return deviceKey;
554 }
555
556 @Override
557 public long getMACAddress() {
558 // we assume only one MAC per device for now.
559 return entities[0].getMacAddress();
560 }
561
562 @Override
563 public String getMACAddressString() {
564 return macAddressString;
565 }
566
567 @Override
568 public Short[] getVlanId() {
569 if (entities.length == 1) {
570 if (entities[0].getVlan() != null) {
571 return new Short[]{ entities[0].getVlan() };
572 } else {
573 return new Short[] { Short.valueOf((short)-1) };
574 }
575 }
576
577 TreeSet<Short> vals = new TreeSet<Short>();
578 for (Entity e : entities) {
579 if (e.getVlan() == null)
580 vals.add((short)-1);
581 else
582 vals.add(e.getVlan());
583 }
584 return vals.toArray(new Short[vals.size()]);
585 }
586
587 static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
588
589 @Override
590 public Integer[] getIPv4Addresses() {
591 // XXX - TODO we can cache this result. Let's find out if this
592 // is really a performance bottleneck first though.
593
594 TreeSet<Integer> vals = new TreeSet<Integer>();
595 for (Entity e : entities) {
596 if (e.getIpv4Address() == null) continue;
597
598 // We have an IP address only if among the devices within the class
599 // we have the most recent entity with that IP.
600 boolean validIP = true;
601 Iterator<Device> devices =
602 deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
603 while (devices.hasNext()) {
604 Device d = devices.next();
605 if (deviceKey.equals(d.getDeviceKey()))
606 continue;
607 for (Entity se : d.entities) {
608 if (se.getIpv4Address() != null &&
609 se.getIpv4Address().equals(e.getIpv4Address()) &&
610 se.getLastSeenTimestamp() != null &&
611 0 < se.getLastSeenTimestamp().
612 compareTo(e.getLastSeenTimestamp())) {
613 validIP = false;
614 break;
615 }
616 }
617 if (!validIP)
618 break;
619 }
620
621 if (validIP)
622 vals.add(e.getIpv4Address());
623 }
624
625 return vals.toArray(new Integer[vals.size()]);
626 }
627
628 @Override
629 public Short[] getSwitchPortVlanIds(SwitchPort swp) {
630 TreeSet<Short> vals = new TreeSet<Short>();
631 for (Entity e : entities) {
632 if (e.switchDPID == swp.getSwitchDPID()
633 && e.switchPort == swp.getPort()) {
634 if (e.getVlan() == null)
635 vals.add(Ethernet.VLAN_UNTAGGED);
636 else
637 vals.add(e.getVlan());
638 }
639 }
640 return vals.toArray(new Short[vals.size()]);
641 }
642
643 @Override
644 public Date getLastSeen() {
645 Date d = null;
646 for (int i = 0; i < entities.length; i++) {
647 if (d == null ||
648 entities[i].getLastSeenTimestamp().compareTo(d) > 0)
649 d = entities[i].getLastSeenTimestamp();
650 }
651 return d;
652 }
653
654 // ***************
655 // Getters/Setters
656 // ***************
657
658 @Override
659 public IEntityClass getEntityClass() {
660 return entityClass;
661 }
662
663 public Entity[] getEntities() {
664 return entities;
665 }
666
667 // ***************
668 // Utility Methods
669 // ***************
670
671 /**
672 * Check whether the device contains the specified entity
673 * @param entity the entity to search for
674 * @return the index of the entity, or <0 if not found
675 */
676 protected int entityIndex(Entity entity) {
677 return Arrays.binarySearch(entities, entity);
678 }
679
680 // ******
681 // Object
682 // ******
683
684 @Override
685 public int hashCode() {
686 final int prime = 31;
687 int result = 1;
688 result = prime * result + Arrays.hashCode(entities);
689 return result;
690 }
691
692 @Override
693 public boolean equals(Object obj) {
694 if (this == obj) return true;
695 if (obj == null) return false;
696 if (getClass() != obj.getClass()) return false;
697 Device other = (Device) obj;
698 if (!deviceKey.equals(other.deviceKey)) return false;
699 if (!Arrays.equals(entities, other.entities)) return false;
700 return true;
701 }
702
703 @Override
704 public String toString() {
705 StringBuilder builder = new StringBuilder();
706 builder.append("Device [deviceKey=");
707 builder.append(deviceKey);
708 builder.append(", entityClass=");
709 builder.append(entityClass.getName());
710 builder.append(", MAC=");
711 builder.append(macAddressString);
712 builder.append(", IPs=[");
713 boolean isFirst = true;
714 for (Integer ip: getIPv4Addresses()) {
715 if (!isFirst)
716 builder.append(", ");
717 isFirst = false;
718 builder.append(IPv4.fromIPv4Address(ip));
719 }
720 builder.append("], APs=");
721 builder.append(Arrays.toString(getAttachmentPoints(true)));
722 builder.append("]");
723 return builder.toString();
724 }
725}