+
+ @Override
+ public void onSmrInvokedReceived(SmrEvent event) {
+ scheduler.smrReceived(event);
+ }
+
+ /**
+ * Task scheduler is responsible for resending SMR messages to a subscriber (xTR)
+ * {@value ConfigIni#LISP_SMR_RETRY_COUNT} times, or until {@link ISmrNotificationListener#onSmrInvokedReceived}
+ * is triggered.
+ */
+ private class SmrScheduler {
+ final int cpuCores = Runtime.getRuntime().availableProcessors();
+ private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("smr-executor-%d").build();
+ private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(cpuCores * 2, threadFactory);
+ private final Map<Eid, Map<Subscriber, ScheduledFuture<?>>> eidFutureMap = Maps.newConcurrentMap();
+
+ void scheduleSmrs(MapRequestBuilder mrb, Iterator<Subscriber> subscribers) {
+ final Eid srcEid = fixSrcEidMask(mrb.getSourceEid().getEid());
+ cancelExistingFuturesForEid(srcEid);
+
+ final Map<Subscriber, ScheduledFuture<?>> subscriberFutureMap = Maps.newConcurrentMap();
+
+ // Using Iterator ensures that we don't get a ConcurrentModificationException when removing a Subscriber
+ // from a Set.
+ while (subscribers.hasNext()) {
+ Subscriber subscriber = subscribers.next();
+ if (subscriber.timedOut()) {
+ LOG.debug("Lazy removing expired subscriber entry " + subscriber.getString());
+ subscribers.remove();
+ } else {
+ final ScheduledFuture<?> future = executor.scheduleAtFixedRate(new CancellableRunnable(
+ mrb, subscriber), 0L, ConfigIni.getInstance().getSmrTimeout(), TimeUnit.MILLISECONDS);
+ subscriberFutureMap.put(subscriber, future);
+ }
+ }
+
+ if (subscriberFutureMap.isEmpty()) {
+ return;
+ }
+ eidFutureMap.put(srcEid, subscriberFutureMap);
+ }
+
+ void smrReceived(SmrEvent event) {
+ final List<Subscriber> subscriberList = event.getSubscriberList();
+ for (Subscriber subscriber : subscriberList) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("SMR-invoked event, EID {}, subscriber {}",
+ LispAddressStringifier.getString(event.getEid()),
+ subscriber.getString());
+ LOG.trace("eidFutureMap: {}", eidFutureMap);
+ }
+ final Map<Subscriber, ScheduledFuture<?>> subscriberFutureMap = eidFutureMap.get(event.getEid());
+ if (subscriberFutureMap != null) {
+ final ScheduledFuture<?> future = subscriberFutureMap.get(subscriber);
+ if (future != null && !future.isCancelled()) {
+ future.cancel(true);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("SMR-invoked MapRequest received, scheduled task for subscriber {}, EID {} with"
+ + " nonce {} has been cancelled", subscriber.getString(),
+ LispAddressStringifier.getString(event.getEid()), event.getNonce());
+ }
+ subscriberFutureMap.remove(subscriber);
+ } else {
+ if (future == null) {
+ LOG.trace("No outstanding SMR tasks for EID {}, subscriber {}",
+ LispAddressStringifier.getString(event.getEid()), subscriber.getString());
+ } else {
+ LOG.trace("Future {} is cancelled", future);
+ }
+ }
+ if (subscriberFutureMap.isEmpty()) {
+ eidFutureMap.remove(event.getEid());
+ }
+ } else {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("No outstanding SMR tasks for EID {}",
+ LispAddressStringifier.getString(event.getEid()));
+ }
+ }
+ }
+ }
+
+ private void cancelExistingFuturesForEid(Eid eid) {
+ synchronized (eidFutureMap) {
+ if (eidFutureMap.containsKey(eid)) {
+ final Map<Subscriber, ScheduledFuture<?>> subscriberFutureMap = eidFutureMap.get(eid);
+ Iterator<Subscriber> oldSubscribers = subscriberFutureMap.keySet().iterator();
+ while (oldSubscribers.hasNext()) {
+ Subscriber subscriber = oldSubscribers.next();
+ ScheduledFuture<?> subscriberFuture = subscriberFutureMap.get(subscriber);
+ subscriberFuture.cancel(true);
+ }
+ eidFutureMap.remove(eid);
+ }
+ }
+ }
+
+ /*
+ * See https://bugs.opendaylight.org/show_bug.cgi?id=8469#c1 why this is necessary.
+ *
+ * TL;DR The sourceEid field in the MapRequestBuilder object will be serialized to a packet on the wire, and
+ * a Map-Request can't store the prefix length in the source EID.
+ *
+ * Since we store all prefixes as binary internally, we only care about and fix those address types.
+ */
+ private Eid fixSrcEidMask(Eid eid) {
+ Address address = eid.getAddress();
+ if (address instanceof Ipv4PrefixBinary) {
+ return new EidBuilder(eid).setAddress(new Ipv4PrefixBinaryBuilder((Ipv4PrefixBinary) address)
+ .setIpv4MaskLength(MaskUtil.IPV4_MAX_MASK).build()).build();
+ } else if (address instanceof Ipv6PrefixBinary) {
+ return new EidBuilder(eid).setAddress(new Ipv6PrefixBinaryBuilder((Ipv6PrefixBinary) address)
+ .setIpv6MaskLength(MaskUtil.IPV6_MAX_MASK).build()).build();
+ }
+ return eid;
+ }
+
+ private final class CancellableRunnable implements Runnable {
+ private MapRequestBuilder mrb;
+ private Subscriber subscriber;
+ private int executionCount = 1;
+
+ CancellableRunnable(MapRequestBuilder mrb, Subscriber subscriber) {
+ this.mrb = mrb;
+ this.subscriber = subscriber;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void run() {
+ final Eid srcEid = mrb.getSourceEid().getEid();
+
+ try {
+ // The address stored in the SMR's EID record is used as Source EID in the SMR-invoked
+ // Map-Request. To ensure consistent behavior it is set to the value used to originally request
+ // a given mapping.
+ if (executionCount <= ConfigIni.getInstance().getSmrRetryCount()) {
+ synchronized (mrb) {
+ mrb.setEidItem(new ArrayList<EidItem>());
+ mrb.getEidItem().add(new EidItemBuilder().setEid(subscriber.getSrcEid()).build());
+ notifyHandler.handleSMR(mrb.build(), subscriber.getSrcRloc());
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Attempt #{} to send SMR to subscriber {} for EID {}",
+ executionCount,
+ subscriber.getString(),
+ LispAddressStringifier.getString(mrb.getSourceEid().getEid()));
+ }
+ }
+ } else {
+ LOG.trace("Cancelling execution of a SMR Map-Request after {} failed attempts.",
+ executionCount - 1);
+ cancelAndRemove(subscriber, srcEid);
+ return;
+ }
+ } catch (Exception e) {
+ LOG.error("Errors encountered while handling SMR:", e);
+ cancelAndRemove(subscriber, srcEid);
+ return;
+ }
+ executionCount++;
+ }
+
+ private void cancelAndRemove(Subscriber subscriber, Eid eid) {
+ final Map<Subscriber, ScheduledFuture<?>> subscriberFutureMap = eidFutureMap.get(eid);
+ if (subscriberFutureMap == null) {
+ LOG.warn("Couldn't find subscriber {} in SMR scheduler internal list", subscriber);
+ return;
+ }
+
+ if (subscriberFutureMap.containsKey(subscriber)) {
+ ScheduledFuture<?> eidFuture = subscriberFutureMap.get(subscriber);
+ subscriberFutureMap.remove(subscriber);
+ eidFuture.cancel(false);
+ }
+ if (subscriberFutureMap.isEmpty()) {
+ eidFutureMap.remove(eid);
+ }
+ }
+ }
+ }