Moved the BGP Route Intent synchronization mechanism from the Router class
to the new class IntentSynchronizer.
Also, minor cleanup in some of the method names and access scope.
Change-Id: I38257cd1d9516ef3b3dd50f23c28015054c73d70
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/IntentSynchronizer.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/IntentSynchronizer.java
new file mode 100644
index 0000000..bf7f141
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/IntentSynchronizer.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.onos.sdnip;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
+import org.onlab.onos.net.flow.criteria.Criterion;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
+import org.onlab.packet.Ip4Prefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+public class IntentSynchronizer {
+ private static final Logger log =
+ LoggerFactory.getLogger(IntentSynchronizer.class);
+
+ private final ApplicationId appId;
+ private final IntentService intentService;
+ private final Map<Ip4Prefix, MultiPointToSinglePointIntent> pushedRouteIntents;
+
+ //
+ // State to deal with SDN-IP Leader election and pushing Intents
+ //
+ private final ExecutorService bgpIntentsSynchronizerExecutor;
+ private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
+ private volatile boolean isElectedLeader = false;
+ private volatile boolean isActivatedLeader = false;
+
+ /**
+ * Class constructor.
+ *
+ * @param appId the Application ID
+ * @param intentService the intent service
+ */
+ IntentSynchronizer(ApplicationId appId, IntentService intentService) {
+ this.appId = appId;
+ this.intentService = intentService;
+ pushedRouteIntents = new ConcurrentHashMap<>();
+
+ bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder()
+ .setNameFormat("bgp-intents-synchronizer-%d").build());
+ }
+
+ /**
+ * Starts the synchronizer.
+ */
+ public void start() {
+ bgpIntentsSynchronizerExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ doIntentSynchronizationThread();
+ }
+ });
+ }
+
+ /**
+ * Stops the synchronizer.
+ */
+ public void stop() {
+ // Stop the thread(s)
+ bgpIntentsSynchronizerExecutor.shutdownNow();
+
+ //
+ // Withdraw all SDN-IP intents
+ //
+ if (!isElectedLeader) {
+ return; // Nothing to do: not the leader anymore
+ }
+ log.debug("Withdrawing all SDN-IP Route Intents...");
+ for (Intent intent : intentService.getIntents()) {
+ if (!(intent instanceof MultiPointToSinglePointIntent)
+ || !intent.appId().equals(appId)) {
+ continue;
+ }
+ intentService.withdraw(intent);
+ }
+
+ pushedRouteIntents.clear();
+ }
+
+ //@Override TODO hook this up to something
+ public void leaderChanged(boolean isLeader) {
+ log.debug("Leader changed: {}", isLeader);
+
+ if (!isLeader) {
+ this.isElectedLeader = false;
+ this.isActivatedLeader = false;
+ return; // Nothing to do
+ }
+ this.isActivatedLeader = false;
+ this.isElectedLeader = true;
+
+ //
+ // Tell the Intents Synchronizer thread to start the synchronization
+ //
+ intentsSynchronizerSemaphore.release();
+ }
+
+ /**
+ * Gets the pushed route intents.
+ *
+ * @return the pushed route intents
+ */
+ public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
+ List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
+
+ for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
+ pushedRouteIntents.entrySet()) {
+ pushedIntents.add(entry.getValue());
+ }
+ return pushedIntents;
+ }
+
+ /**
+ * Thread for Intent Synchronization.
+ */
+ private void doIntentSynchronizationThread() {
+ boolean interrupted = false;
+ try {
+ while (!interrupted) {
+ try {
+ intentsSynchronizerSemaphore.acquire();
+ //
+ // Drain all permits, because a single synchronization is
+ // sufficient.
+ //
+ intentsSynchronizerSemaphore.drainPermits();
+ } catch (InterruptedException e) {
+ log.debug("Interrupted while waiting to become " +
+ "Intent Synchronization leader");
+ interrupted = true;
+ break;
+ }
+ syncIntents();
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Submits a multi-point-to-single-point intent.
+ *
+ * @param prefix the IPv4 matching prefix for the intent to submit
+ * @param intent the intent to submit
+ */
+ void submitRouteIntent(Ip4Prefix prefix,
+ MultiPointToSinglePointIntent intent) {
+ synchronized (this) {
+ if (isElectedLeader && isActivatedLeader) {
+ log.debug("Intent installation: adding Intent for prefix: {}",
+ prefix);
+ intentService.submit(intent);
+ }
+
+ // Maintain the Intent
+ pushedRouteIntents.put(prefix, intent);
+ }
+ }
+
+ /**
+ * Withdraws a multi-point-to-single-point intent.
+ *
+ * @param prefix the IPv4 matching prefix for the intent to withdraw.
+ */
+ void withdrawRouteIntent(Ip4Prefix prefix) {
+ synchronized (this) {
+ MultiPointToSinglePointIntent intent =
+ pushedRouteIntents.remove(prefix);
+
+ if (intent == null) {
+ log.debug("There is no intent in pushedRouteIntents to delete " +
+ "for prefix: {}", prefix);
+ return;
+ }
+
+ if (isElectedLeader && isActivatedLeader) {
+ log.debug("Intent installation: deleting Intent for prefix: {}",
+ prefix);
+ intentService.withdraw(intent);
+ }
+ }
+ }
+
+ /**
+ * Performs Intents Synchronization between the internally stored Route
+ * Intents and the installed Route Intents.
+ */
+ private void syncIntents() {
+ synchronized (this) {
+ if (!isElectedLeader) {
+ return; // Nothing to do: not the leader anymore
+ }
+ log.debug("Syncing SDN-IP Route Intents...");
+
+ Map<Ip4Prefix, MultiPointToSinglePointIntent> fetchedIntents =
+ new HashMap<>();
+
+ //
+ // Fetch all intents, and classify the Multi-Point-to-Point Intents
+ // based on the matching prefix.
+ //
+ for (Intent intent : intentService.getIntents()) {
+
+ if (!(intent instanceof MultiPointToSinglePointIntent)
+ || !intent.appId().equals(appId)) {
+ continue;
+ }
+ MultiPointToSinglePointIntent mp2pIntent =
+ (MultiPointToSinglePointIntent) intent;
+
+ Criterion c =
+ mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
+ if (c != null && c instanceof IPCriterion) {
+ IPCriterion ipCriterion = (IPCriterion) c;
+ Ip4Prefix ip4Prefix = ipCriterion.ip().getIp4Prefix();
+ if (ip4Prefix == null) {
+ // TODO: For now we support only IPv4
+ continue;
+ }
+ fetchedIntents.put(ip4Prefix, mp2pIntent);
+ } else {
+ log.warn("No IPV4_DST criterion found for intent {}",
+ mp2pIntent.id());
+ }
+
+ }
+
+ //
+ // Compare for each prefix the local IN-MEMORY Intents with the
+ // FETCHED Intents:
+ // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
+ // the FETCHED Intent in the local memory (i.e., override the
+ // IN-MEMORY Intent) to preserve the original Intent ID
+ // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
+ // delete the FETCHED Intent, and push/install the IN-MEMORY
+ // Intent.
+ // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
+ // Intent for same prefix, then push/install the IN-MEMORY
+ // Intent.
+ // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
+ // Intent for same prefix, then delete/withdraw the FETCHED
+ // Intent.
+ //
+ Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
+ storeInMemoryIntents = new LinkedList<>();
+ Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
+ addIntents = new LinkedList<>();
+ Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
+ deleteIntents = new LinkedList<>();
+ for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
+ pushedRouteIntents.entrySet()) {
+ Ip4Prefix prefix = entry.getKey();
+ MultiPointToSinglePointIntent inMemoryIntent =
+ entry.getValue();
+ MultiPointToSinglePointIntent fetchedIntent =
+ fetchedIntents.get(prefix);
+
+ if (fetchedIntent == null) {
+ //
+ // No FETCHED Intent for same prefix: push the IN-MEMORY
+ // Intent.
+ //
+ addIntents.add(Pair.of(prefix, inMemoryIntent));
+ continue;
+ }
+
+ IntentState state = intentService.getIntentState(fetchedIntent.id());
+ if (state == IntentState.WITHDRAWING ||
+ state == IntentState.WITHDRAWN) {
+ // The intent has been withdrawn but according to our route
+ // table it should be installed. We'll reinstall it.
+ addIntents.add(Pair.of(prefix, inMemoryIntent));
+ }
+
+ //
+ // If IN-MEMORY Intent is same as the FETCHED Intent,
+ // store the FETCHED Intent in the local memory.
+ //
+ if (compareMultiPointToSinglePointIntents(inMemoryIntent,
+ fetchedIntent)) {
+ storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
+ } else {
+ //
+ // The IN-MEMORY Intent is not same as the FETCHED Intent,
+ // hence delete the FETCHED Intent, and install the
+ // IN-MEMORY Intent.
+ //
+ deleteIntents.add(Pair.of(prefix, fetchedIntent));
+ addIntents.add(Pair.of(prefix, inMemoryIntent));
+ }
+ fetchedIntents.remove(prefix);
+ }
+
+ //
+ // Any remaining FETCHED Intents have to be deleted/withdrawn
+ //
+ for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
+ fetchedIntents.entrySet()) {
+ Ip4Prefix prefix = entry.getKey();
+ MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
+ deleteIntents.add(Pair.of(prefix, fetchedIntent));
+ }
+
+ //
+ // Perform the actions:
+ // 1. Store in memory fetched intents that are same. Can be done
+ // even if we are not the leader anymore
+ // 2. Delete intents: check if the leader before each operation
+ // 3. Add intents: check if the leader before each operation
+ //
+ for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
+ storeInMemoryIntents) {
+ Ip4Prefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ log.debug("Intent synchronization: updating in-memory " +
+ "Intent for prefix: {}", prefix);
+ pushedRouteIntents.put(prefix, intent);
+ }
+ //
+ isActivatedLeader = true; // Allow push of Intents
+ for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
+ deleteIntents) {
+ Ip4Prefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ return;
+ }
+ log.debug("Intent synchronization: deleting Intent for " +
+ "prefix: {}", prefix);
+ intentService.withdraw(intent);
+ }
+ //
+ for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
+ addIntents) {
+ Ip4Prefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ return;
+ }
+ log.debug("Intent synchronization: adding Intent for " +
+ "prefix: {}", prefix);
+ intentService.submit(intent);
+ }
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ }
+ log.debug("Syncing SDN-IP routes completed.");
+ }
+ }
+
+ /**
+ * Compares two Multi-point to Single Point Intents whether they represent
+ * same logical intention.
+ *
+ * @param intent1 the first Intent to compare
+ * @param intent2 the second Intent to compare
+ * @return true if both Intents represent same logical intention, otherwise
+ * false
+ */
+ private boolean compareMultiPointToSinglePointIntents(
+ MultiPointToSinglePointIntent intent1,
+ MultiPointToSinglePointIntent intent2) {
+
+ return Objects.equal(intent1.appId(), intent2.appId()) &&
+ Objects.equal(intent1.selector(), intent2.selector()) &&
+ Objects.equal(intent1.treatment(), intent2.treatment()) &&
+ Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
+ Objects.equal(intent1.egressPoint(), intent2.egressPoint());
+ }
+}