Yuta HIGUCHI

GossipLinkStore AE bugfix + cleanup

Change-Id: If4cbaa65f980f10713488e6bf1be5d01c3131780
1 package org.onlab.onos.store.link.impl; 1 package org.onlab.onos.store.link.impl;
2 2
3 import com.google.common.base.Function; 3 import com.google.common.base.Function;
4 -import com.google.common.base.Predicate;
5 import com.google.common.collect.FluentIterable; 4 import com.google.common.collect.FluentIterable;
6 import com.google.common.collect.HashMultimap; 5 import com.google.common.collect.HashMultimap;
7 import com.google.common.collect.ImmutableList; 6 import com.google.common.collect.ImmutableList;
...@@ -27,7 +26,6 @@ import org.onlab.onos.net.Link; ...@@ -27,7 +26,6 @@ import org.onlab.onos.net.Link;
27 import org.onlab.onos.net.SparseAnnotations; 26 import org.onlab.onos.net.SparseAnnotations;
28 import org.onlab.onos.net.Link.Type; 27 import org.onlab.onos.net.Link.Type;
29 import org.onlab.onos.net.LinkKey; 28 import org.onlab.onos.net.LinkKey;
30 -import org.onlab.onos.net.Provided;
31 import org.onlab.onos.net.device.DeviceClockService; 29 import org.onlab.onos.net.device.DeviceClockService;
32 import org.onlab.onos.net.link.DefaultLinkDescription; 30 import org.onlab.onos.net.link.DefaultLinkDescription;
33 import org.onlab.onos.net.link.LinkDescription; 31 import org.onlab.onos.net.link.LinkDescription;
...@@ -70,7 +68,9 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*; ...@@ -70,7 +68,9 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*;
70 import static org.onlab.util.Tools.namedThreads; 68 import static org.onlab.util.Tools.namedThreads;
71 import static org.slf4j.LoggerFactory.getLogger; 69 import static org.slf4j.LoggerFactory.getLogger;
72 import static com.google.common.collect.Multimaps.synchronizedSetMultimap; 70 import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
71 +import static com.google.common.base.Preconditions.checkNotNull;
73 import static com.google.common.base.Predicates.notNull; 72 import static com.google.common.base.Predicates.notNull;
73 +import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
74 74
75 /** 75 /**
76 * Manages inventory of infrastructure links in distributed data store 76 * Manages inventory of infrastructure links in distributed data store
...@@ -239,9 +239,9 @@ public class GossipLinkStore ...@@ -239,9 +239,9 @@ public class GossipLinkStore
239 LinkKey key = linkKey(linkDescription.src(), linkDescription.dst()); 239 LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
240 final LinkEvent event; 240 final LinkEvent event;
241 final Timestamped<LinkDescription> mergedDesc; 241 final Timestamped<LinkDescription> mergedDesc;
242 - synchronized (getLinkDescriptions(key)) { 242 + synchronized (getOrCreateLinkDescriptions(key)) {
243 event = createOrUpdateLinkInternal(providerId, deltaDesc); 243 event = createOrUpdateLinkInternal(providerId, deltaDesc);
244 - mergedDesc = getLinkDescriptions(key).get(providerId); 244 + mergedDesc = getOrCreateLinkDescriptions(key).get(providerId);
245 } 245 }
246 246
247 if (event != null) { 247 if (event != null) {
...@@ -265,7 +265,7 @@ public class GossipLinkStore ...@@ -265,7 +265,7 @@ public class GossipLinkStore
265 265
266 LinkKey key = linkKey(linkDescription.value().src(), 266 LinkKey key = linkKey(linkDescription.value().src(),
267 linkDescription.value().dst()); 267 linkDescription.value().dst());
268 - Map<ProviderId, Timestamped<LinkDescription>> descs = getLinkDescriptions(key); 268 + Map<ProviderId, Timestamped<LinkDescription>> descs = getOrCreateLinkDescriptions(key);
269 269
270 synchronized (descs) { 270 synchronized (descs) {
271 // if the link was previously removed, we should proceed if and 271 // if the link was previously removed, we should proceed if and
...@@ -296,7 +296,7 @@ public class GossipLinkStore ...@@ -296,7 +296,7 @@ public class GossipLinkStore
296 ProviderId providerId, 296 ProviderId providerId,
297 Timestamped<LinkDescription> linkDescription) { 297 Timestamped<LinkDescription> linkDescription) {
298 298
299 - // merge existing attributes and merge 299 + // merge existing annotations
300 Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId); 300 Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId);
301 if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) { 301 if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) {
302 return null; 302 return null;
...@@ -377,14 +377,54 @@ public class GossipLinkStore ...@@ -377,14 +377,54 @@ public class GossipLinkStore
377 return event; 377 return event;
378 } 378 }
379 379
380 + private static Timestamped<LinkDescription> getPrimaryDescription(
381 + Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
382 +
383 + synchronized (linkDescriptions) {
384 + for (Entry<ProviderId, Timestamped<LinkDescription>>
385 + e : linkDescriptions.entrySet()) {
386 +
387 + if (!e.getKey().isAncillary()) {
388 + return e.getValue();
389 + }
390 + }
391 + }
392 + return null;
393 + }
394 +
395 +
396 + // TODO: consider slicing out as Timestamp utils
397 + /**
398 + * Checks is timestamp is more recent than timestamped object.
399 + *
400 + * @param timestamp to check if this is more recent then other
401 + * @param timestamped object to be tested against
402 + * @return true if {@code timestamp} is more recent than {@code timestamped}
403 + * or {@code timestamped is null}
404 + */
405 + private static boolean isMoreRecent(Timestamp timestamp, Timestamped<?> timestamped) {
406 + checkNotNull(timestamp);
407 + if (timestamped == null) {
408 + return true;
409 + }
410 + return timestamp.compareTo(timestamped.timestamp()) > 0;
411 + }
412 +
380 private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) { 413 private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
381 - Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions = 414 + Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions
382 - getLinkDescriptions(key); 415 + = getOrCreateLinkDescriptions(key);
416 +
383 synchronized (linkDescriptions) { 417 synchronized (linkDescriptions) {
418 + if (linkDescriptions.isEmpty()) {
419 + // never seen such link before. keeping timestamp for record
420 + removedLinks.put(key, timestamp);
421 + return null;
422 + }
384 // accept removal request if given timestamp is newer than 423 // accept removal request if given timestamp is newer than
385 // the latest Timestamp from Primary provider 424 // the latest Timestamp from Primary provider
386 - ProviderId primaryProviderId = pickPrimaryProviderId(linkDescriptions); 425 + Timestamped<LinkDescription> prim = getPrimaryDescription(linkDescriptions);
387 - if (linkDescriptions.get(primaryProviderId).isNewer(timestamp)) { 426 + if (!isMoreRecent(timestamp, prim)) {
427 + // outdated remove request, ignore
388 return null; 428 return null;
389 } 429 }
390 removedLinks.put(key, timestamp); 430 removedLinks.put(key, timestamp);
...@@ -406,12 +446,13 @@ public class GossipLinkStore ...@@ -406,12 +446,13 @@ public class GossipLinkStore
406 /** 446 /**
407 * @return primary ProviderID, or randomly chosen one if none exists 447 * @return primary ProviderID, or randomly chosen one if none exists
408 */ 448 */
409 - private ProviderId pickPrimaryProviderId( 449 + private static ProviderId pickBaseProviderId(
410 Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) { 450 Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
411 451
412 ProviderId fallBackPrimary = null; 452 ProviderId fallBackPrimary = null;
413 for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) { 453 for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) {
414 if (!e.getKey().isAncillary()) { 454 if (!e.getKey().isAncillary()) {
455 + // found primary
415 return e.getKey(); 456 return e.getKey();
416 } else if (fallBackPrimary == null) { 457 } else if (fallBackPrimary == null) {
417 // pick randomly as a fallback in case there is no primary 458 // pick randomly as a fallback in case there is no primary
...@@ -421,9 +462,10 @@ public class GossipLinkStore ...@@ -421,9 +462,10 @@ public class GossipLinkStore
421 return fallBackPrimary; 462 return fallBackPrimary;
422 } 463 }
423 464
465 + // Guarded by linkDescs value (=locking each Link)
424 private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) { 466 private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) {
425 - ProviderId primaryProviderId = pickPrimaryProviderId(descs); 467 + ProviderId baseProviderId = pickBaseProviderId(descs);
426 - Timestamped<LinkDescription> base = descs.get(primaryProviderId); 468 + Timestamped<LinkDescription> base = descs.get(baseProviderId);
427 469
428 ConnectPoint src = base.value().src(); 470 ConnectPoint src = base.value().src();
429 ConnectPoint dst = base.value().dst(); 471 ConnectPoint dst = base.value().dst();
...@@ -432,7 +474,7 @@ public class GossipLinkStore ...@@ -432,7 +474,7 @@ public class GossipLinkStore
432 annotations = merge(annotations, base.value().annotations()); 474 annotations = merge(annotations, base.value().annotations());
433 475
434 for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) { 476 for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) {
435 - if (primaryProviderId.equals(e.getKey())) { 477 + if (baseProviderId.equals(e.getKey())) {
436 continue; 478 continue;
437 } 479 }
438 480
...@@ -445,10 +487,10 @@ public class GossipLinkStore ...@@ -445,10 +487,10 @@ public class GossipLinkStore
445 annotations = merge(annotations, e.getValue().value().annotations()); 487 annotations = merge(annotations, e.getValue().value().annotations());
446 } 488 }
447 489
448 - return new DefaultLink(primaryProviderId , src, dst, type, annotations); 490 + return new DefaultLink(baseProviderId, src, dst, type, annotations);
449 } 491 }
450 492
451 - private Map<ProviderId, Timestamped<LinkDescription>> getLinkDescriptions(LinkKey key) { 493 + private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
452 Map<ProviderId, Timestamped<LinkDescription>> r; 494 Map<ProviderId, Timestamped<LinkDescription>> r;
453 r = linkDescs.get(key); 495 r = linkDescs.get(key);
454 if (r != null) { 496 if (r != null) {
...@@ -464,11 +506,11 @@ public class GossipLinkStore ...@@ -464,11 +506,11 @@ public class GossipLinkStore
464 } 506 }
465 } 507 }
466 508
467 - private Timestamped<LinkDescription> getLinkDescription(LinkKey key, ProviderId providerId) {
468 - return getLinkDescriptions(key).get(providerId);
469 - }
470 -
471 private final Function<LinkKey, Link> lookupLink = new LookupLink(); 509 private final Function<LinkKey, Link> lookupLink = new LookupLink();
510 + /**
511 + * Returns a Function to lookup Link instance using LinkKey from cache.
512 + * @return
513 + */
472 private Function<LinkKey, Link> lookupLink() { 514 private Function<LinkKey, Link> lookupLink() {
473 return lookupLink; 515 return lookupLink;
474 } 516 }
...@@ -480,26 +522,12 @@ public class GossipLinkStore ...@@ -480,26 +522,12 @@ public class GossipLinkStore
480 } 522 }
481 } 523 }
482 524
483 - private static final class IsPrimary implements Predicate<Provided> {
484 -
485 - private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
486 - public static final Predicate<Provided> isPrimary() {
487 - return IS_PRIMARY;
488 - }
489 -
490 - @Override
491 - public boolean apply(Provided input) {
492 - return !input.providerId().isAncillary();
493 - }
494 - }
495 -
496 private void notifyDelegateIfNotNull(LinkEvent event) { 525 private void notifyDelegateIfNotNull(LinkEvent event) {
497 if (event != null) { 526 if (event != null) {
498 notifyDelegate(event); 527 notifyDelegate(event);
499 } 528 }
500 } 529 }
501 530
502 - // TODO: should we be throwing exception?
503 private void broadcastMessage(MessageSubject subject, Object event) throws IOException { 531 private void broadcastMessage(MessageSubject subject, Object event) throws IOException {
504 ClusterMessage message = new ClusterMessage( 532 ClusterMessage message = new ClusterMessage(
505 clusterService.getLocalNode().id(), 533 clusterService.getLocalNode().id(),
...@@ -508,17 +536,12 @@ public class GossipLinkStore ...@@ -508,17 +536,12 @@ public class GossipLinkStore
508 clusterCommunicator.broadcast(message); 536 clusterCommunicator.broadcast(message);
509 } 537 }
510 538
511 - // TODO: should we be throwing exception? 539 + private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
512 - private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) { 540 + ClusterMessage message = new ClusterMessage(
513 - try { 541 + clusterService.getLocalNode().id(),
514 - ClusterMessage message = new ClusterMessage( 542 + subject,
515 - clusterService.getLocalNode().id(), 543 + SERIALIZER.encode(event));
516 - subject, 544 + clusterCommunicator.unicast(message, recipient);
517 - SERIALIZER.encode(event));
518 - clusterCommunicator.unicast(message, recipient);
519 - } catch (IOException e) {
520 - log.error("Failed to send a {} message to {}", subject.value(), recipient);
521 - }
522 } 545 }
523 546
524 private void notifyPeers(InternalLinkEvent event) throws IOException { 547 private void notifyPeers(InternalLinkEvent event) throws IOException {
...@@ -529,12 +552,22 @@ public class GossipLinkStore ...@@ -529,12 +552,22 @@ public class GossipLinkStore
529 broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event); 552 broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
530 } 553 }
531 554
555 + // notify peer, silently ignoring error
532 private void notifyPeer(NodeId peer, InternalLinkEvent event) { 556 private void notifyPeer(NodeId peer, InternalLinkEvent event) {
533 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event); 557 + try {
558 + unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
559 + } catch (IOException e) {
560 + log.debug("Failed to notify peer {} with message {}", peer, event);
561 + }
534 } 562 }
535 563
564 + // notify peer, silently ignoring error
536 private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) { 565 private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) {
537 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event); 566 + try {
567 + unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
568 + } catch (IOException e) {
569 + log.debug("Failed to notify peer {} with message {}", peer, event);
570 + }
538 } 571 }
539 572
540 private final class SendAdvertisementTask implements Runnable { 573 private final class SendAdvertisementTask implements Runnable {
...@@ -573,9 +606,9 @@ public class GossipLinkStore ...@@ -573,9 +606,9 @@ public class GossipLinkStore
573 } 606 }
574 607
575 try { 608 try {
576 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT, ad); 609 + unicastMessage(peer, LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
577 - } catch (Exception e) { 610 + } catch (IOException e) {
578 - log.error("Failed to send anti-entropy advertisement", e); 611 + log.debug("Failed to send anti-entropy advertisement to {}", peer);
579 return; 612 return;
580 } 613 }
581 } catch (Exception e) { 614 } catch (Exception e) {
...@@ -608,42 +641,75 @@ public class GossipLinkStore ...@@ -608,42 +641,75 @@ public class GossipLinkStore
608 return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones); 641 return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones);
609 } 642 }
610 643
611 - private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement advertisement) { 644 + private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement ad) {
612 -
613 - NodeId peer = advertisement.sender();
614 645
615 - Map<LinkFragmentId, Timestamp> linkTimestamps = advertisement.linkTimestamps(); 646 + final NodeId sender = ad.sender();
616 - Map<LinkKey, Timestamp> linkTombstones = advertisement.linkTombstones(); 647 + boolean localOutdated = false;
617 - for (Map.Entry<LinkFragmentId, Timestamp> entry : linkTimestamps.entrySet()) {
618 - LinkFragmentId linkFragmentId = entry.getKey();
619 - Timestamp peerTimestamp = entry.getValue();
620 648
621 - LinkKey key = linkFragmentId.linkKey(); 649 + for (Entry<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>>
622 - ProviderId providerId = linkFragmentId.providerId(); 650 + l : linkDescs.entrySet()) {
623 - 651 +
624 - Timestamped<LinkDescription> linkDescription = getLinkDescription(key, providerId); 652 + final LinkKey key = l.getKey();
625 - if (linkDescription.isNewer(peerTimestamp)) { 653 + final Map<ProviderId, Timestamped<LinkDescription>> link = l.getValue();
626 - // I have more recent link description. update peer. 654 + synchronized (link) {
627 - notifyPeer(peer, new InternalLinkEvent(providerId, linkDescription)); 655 + Timestamp localLatest = removedLinks.get(key);
628 - } 656 +
629 - // else TODO: Peer has more recent link description. request it. 657 + for (Entry<ProviderId, Timestamped<LinkDescription>> p : link.entrySet()) {
630 - 658 + final ProviderId providerId = p.getKey();
631 - Timestamp linkRemovedTimestamp = removedLinks.get(key); 659 + final Timestamped<LinkDescription> pDesc = p.getValue();
632 - if (linkRemovedTimestamp != null && linkRemovedTimestamp.compareTo(peerTimestamp) > 0) { 660 +
633 - // peer has a zombie link. update peer. 661 + final LinkFragmentId fragId = new LinkFragmentId(key, providerId);
634 - notifyPeer(peer, new InternalLinkRemovedEvent(key, linkRemovedTimestamp)); 662 + // remote
663 + Timestamp remoteTimestamp = ad.linkTimestamps().get(fragId);
664 + if (remoteTimestamp == null) {
665 + remoteTimestamp = ad.linkTombstones().get(key);
666 + }
667 + if (remoteTimestamp == null ||
668 + pDesc.isNewer(remoteTimestamp)) {
669 + // I have more recent link description. update peer.
670 + notifyPeer(sender, new InternalLinkEvent(providerId, pDesc));
671 + } else {
672 + final Timestamp remoteLive = ad.linkTimestamps().get(fragId);
673 + if (remoteLive != null &&
674 + remoteLive.compareTo(pDesc.timestamp()) > 0) {
675 + // I have something outdated
676 + localOutdated = true;
677 + }
678 + }
679 +
680 + // search local latest along the way
681 + if (localLatest == null ||
682 + pDesc.isNewer(localLatest)) {
683 + localLatest = pDesc.timestamp();
684 + }
685 + }
686 + // Tests if remote remove is more recent then local latest.
687 + final Timestamp remoteRemove = ad.linkTombstones().get(key);
688 + if (remoteRemove != null) {
689 + if (localLatest != null &&
690 + localLatest.compareTo(remoteRemove) < 0) {
691 + // remote remove is more recent
692 + notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
693 + }
694 + }
635 } 695 }
636 } 696 }
637 697
638 - for (Map.Entry<LinkKey, Timestamp> entry : linkTombstones.entrySet()) { 698 + // populate remove info if not known locally
639 - LinkKey key = entry.getKey(); 699 + for (Entry<LinkKey, Timestamp> remoteRm : ad.linkTombstones().entrySet()) {
640 - Timestamp peerTimestamp = entry.getValue(); 700 + final LinkKey key = remoteRm.getKey();
701 + final Timestamp remoteRemove = remoteRm.getValue();
702 + // relying on removeLinkInternal to ignore stale info
703 + notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
704 + }
641 705
642 - ProviderId primaryProviderId = pickPrimaryProviderId(getLinkDescriptions(key)); 706 + if (localOutdated) {
643 - if (primaryProviderId != null) { 707 + // send back advertisement to speed up convergence
644 - if (!getLinkDescription(key, primaryProviderId).isNewer(peerTimestamp)) { 708 + try {
645 - notifyDelegateIfNotNull(removeLinkInternal(key, peerTimestamp)); 709 + unicastMessage(sender, LINK_ANTI_ENTROPY_ADVERTISEMENT,
646 - } 710 + createAdvertisement());
711 + } catch (IOException e) {
712 + log.debug("Failed to send back active advertisement");
647 } 713 }
648 } 714 }
649 } 715 }
......