aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/apps/sdnip/src
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/apps/sdnip/src
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/apps/sdnip/src')
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java980
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java319
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java179
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java31
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java40
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/package-info.java20
-rw-r--r--framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/package-info.java20
-rw-r--r--framework/src/onos/apps/sdnip/src/main/resources/OSGI-INF/blueprint/shell-config.xml23
-rw-r--r--framework/src/onos/apps/sdnip/src/main/resources/config-examples/README5
-rw-r--r--framework/src/onos/apps/sdnip/src/main/resources/config-examples/sdnip.json86
-rw-r--r--framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java662
-rw-r--r--framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java565
-rw-r--r--framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java95
13 files changed, 3025 insertions, 0 deletions
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
new file mode 100644
index 00000000..35ff7054
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
@@ -0,0 +1,980 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.IntentRequestListener;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Synchronizes intents between the in-memory intent store and the
+ * IntentService.
+ */
+public class IntentSynchronizer implements FibListener, IntentRequestListener {
+ private static final int PRIORITY_OFFSET = 100;
+ private static final int PRIORITY_MULTIPLIER = 5;
+ protected static final ImmutableList<Constraint> CONSTRAINTS
+ = ImmutableList.of(new PartialFailureConstraint());
+
+ private static final Logger log =
+ LoggerFactory.getLogger(IntentSynchronizer.class);
+
+ private final ApplicationId appId;
+ private final IntentService intentService;
+ private final HostService hostService;
+ private final InterfaceService interfaceService;
+ private final Map<IntentKey, PointToPointIntent> peerIntents;
+ private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
+
+ //
+ // 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;
+
+ private final RoutingConfigurationService configService;
+
+ /**
+ * Class constructor.
+ *
+ * @param appId the Application ID
+ * @param intentService the intent service
+ * @param hostService the host service
+ * @param configService the SDN-IP configuration service
+ * @param interfaceService the interface service
+ */
+ IntentSynchronizer(ApplicationId appId, IntentService intentService,
+ HostService hostService,
+ RoutingConfigurationService configService,
+ InterfaceService interfaceService) {
+ this.appId = appId;
+ this.intentService = intentService;
+ this.hostService = hostService;
+ this.interfaceService = interfaceService;
+ peerIntents = new ConcurrentHashMap<>();
+ routeIntents = new ConcurrentHashMap<>();
+
+ this.configService = configService;
+
+ bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder()
+ .setNameFormat("sdnip-intents-synchronizer-%d").build());
+ }
+
+ /**
+ * Starts the synchronizer.
+ */
+ public void start() {
+ bgpIntentsSynchronizerExecutor.execute(this::doIntentSynchronizationThread);
+ }
+
+ /**
+ * Stops the synchronizer.
+ */
+ public void stop() {
+ synchronized (this) {
+ // Stop the thread(s)
+ bgpIntentsSynchronizerExecutor.shutdownNow();
+
+ //
+ // Withdraw all SDN-IP intents
+ //
+ if (!isElectedLeader) {
+ return; // Nothing to do: not the leader anymore
+ }
+
+ //
+ // NOTE: We don't withdraw the intents during shutdown, because
+ // it creates flux in the data plane during switchover.
+ //
+
+ /*
+ //
+ // Build a batch operation to withdraw all intents from this
+ // application.
+ //
+ log.debug("SDN-IP Intent Synchronizer shutdown: " +
+ "withdrawing all intents...");
+ IntentOperations.Builder builder = IntentOperations.builder(appId);
+ for (Intent intent : intentService.getIntents()) {
+ // Skip the intents from other applications
+ if (!intent.appId().equals(appId)) {
+ continue;
+ }
+
+ // Skip the intents that are already withdrawn
+ IntentState intentState =
+ intentService.getIntentState(intent.id());
+ if ((intentState == null) ||
+ intentState.equals(IntentState.WITHDRAWING) ||
+ intentState.equals(IntentState.WITHDRAWN)) {
+ continue;
+ }
+
+ log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
+ intent);
+ builder.addWithdrawOperation(intent.id());
+ }
+ IntentOperations intentOperations = builder.build();
+ intentService.execute(intentOperations);
+ leaderChanged(false);
+
+ peerIntents.clear();
+ routeIntents.clear();
+ log.debug("SDN-IP Intent Synchronizer shutdown completed");
+ */
+ }
+ }
+
+ /**
+ * Signals the synchronizer that the SDN-IP leadership has changed.
+ *
+ * @param isLeader true if this instance is now the leader, otherwise false
+ */
+ public void leaderChanged(boolean isLeader) {
+ log.debug("SDN-IP 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 route intents.
+ *
+ * @return the route intents
+ */
+ public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
+ List<MultiPointToSinglePointIntent> result = new LinkedList<>();
+
+ for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+ routeIntents.entrySet()) {
+ result.add(entry.getValue());
+ }
+ return result;
+ }
+
+ /**
+ * 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) {
+ interrupted = true;
+ break;
+ }
+ synchronizeIntents();
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Submits a collection of point-to-point intents.
+ *
+ * @param intents the intents to submit
+ */
+ void submitPeerIntents(Collection<PointToPointIntent> intents) {
+ synchronized (this) {
+ // Store the intents in memory
+ for (PointToPointIntent intent : intents) {
+ peerIntents.put(new IntentKey(intent), intent);
+ }
+
+ // Push the intents
+ if (isElectedLeader && isActivatedLeader) {
+ log.debug("SDN-IP Submitting all Peer Intents...");
+ for (Intent intent : intents) {
+ log.trace("SDN-IP Submitting intents: {}", intent);
+ intentService.submit(intent);
+ }
+ }
+ }
+ }
+
+ /**
+ * Submits a MultiPointToSinglePointIntent for reactive routing.
+ *
+ * @param ipPrefix the IP prefix to match in a MultiPointToSinglePointIntent
+ * @param intent the intent to submit
+ */
+ void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
+ synchronized (this) {
+ // Store the intent in memory
+ routeIntents.put(ipPrefix, intent);
+
+ // Push the intent
+ if (isElectedLeader && isActivatedLeader) {
+ log.trace("SDN-IP submitting reactive routing intent: {}", intent);
+ intentService.submit(intent);
+ }
+ }
+ }
+
+ /**
+ * Generates a route intent for a prefix, the next hop IP address, and
+ * the next hop MAC address.
+ * <p/>
+ * This method will find the egress interface for the intent.
+ * Intent will match dst IP prefix and rewrite dst MAC address at all other
+ * border switches, then forward packets according to dst MAC address.
+ *
+ * @param prefix IP prefix of the route to add
+ * @param nextHopIpAddress IP address of the next hop
+ * @param nextHopMacAddress MAC address of the next hop
+ * @return the generated intent, or null if no intent should be submitted
+ */
+ private MultiPointToSinglePointIntent generateRouteIntent(
+ IpPrefix prefix,
+ IpAddress nextHopIpAddress,
+ MacAddress nextHopMacAddress) {
+
+ // Find the attachment point (egress interface) of the next hop
+ Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress);
+ if (egressInterface == null) {
+ log.warn("No outgoing interface found for {}",
+ nextHopIpAddress);
+ return null;
+ }
+
+ //
+ // Generate the intent itself
+ //
+ Set<ConnectPoint> ingressPorts = new HashSet<>();
+ ConnectPoint egressPort = egressInterface.connectPoint();
+ log.debug("Generating intent for prefix {}, next hop mac {}",
+ prefix, nextHopMacAddress);
+
+ for (Interface intf : interfaceService.getInterfaces()) {
+ // TODO this should be only peering interfaces
+ if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
+ ConnectPoint srcPort = intf.connectPoint();
+ ingressPorts.add(srcPort);
+ }
+ }
+
+ // Match the destination IP prefix at the first hop
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ if (prefix.isIp4()) {
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPDst(prefix);
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ selector.matchIPv6Dst(prefix);
+ }
+
+ // Rewrite the destination MAC address
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(nextHopMacAddress);
+ if (!egressInterface.vlan().equals(VlanId.NONE)) {
+ treatment.setVlanId(egressInterface.vlan());
+ // If we set VLAN ID, we have to make sure a VLAN tag exists.
+ // TODO support no VLAN -> VLAN routing
+ selector.matchVlanId(VlanId.ANY);
+ }
+
+ int priority =
+ prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
+ Key key = Key.of(prefix.toString(), appId);
+ return MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector.build())
+ .treatment(treatment.build())
+ .ingressPoints(ingressPorts)
+ .egressPoint(egressPort)
+ .priority(priority)
+ .constraints(CONSTRAINTS)
+ .build();
+ }
+
+ @Override
+ public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) {
+ checkNotNull(hostIpAddress);
+ Set<ConnectPoint> ingressPoints =
+ configService.getBgpPeerConnectPoints();
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+
+ if (hostIpAddress.isIp4()) {
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ }
+
+ // Match the destination IP prefix at the first hop
+ IpPrefix ipPrefix = hostIpAddress.toIpPrefix();
+ selector.matchIPDst(ipPrefix);
+
+ // Rewrite the destination MAC address
+ MacAddress hostMac = null;
+ ConnectPoint egressPoint = null;
+ for (Host host : hostService.getHostsByIp(hostIpAddress)) {
+ if (host.mac() != null) {
+ hostMac = host.mac();
+ egressPoint = host.location();
+ break;
+ }
+ }
+ if (hostMac == null) {
+ hostService.startMonitoringIp(hostIpAddress);
+ return;
+ }
+
+ TrafficTreatment.Builder treatment =
+ DefaultTrafficTreatment.builder().setEthDst(hostMac);
+ Key key = Key.of(ipPrefix.toString(), appId);
+ int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ + PRIORITY_OFFSET;
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector.build())
+ .treatment(treatment.build())
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .priority(priority)
+ .constraints(CONSTRAINTS)
+ .build();
+
+ log.trace("Generates ConnectivityInternetToHost intent {}", intent);
+ submitReactiveIntent(ipPrefix, intent);
+ }
+
+
+ @Override
+ public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
+ //
+ // NOTE: Semantically, we MUST withdraw existing intents before
+ // submitting new intents.
+ //
+ synchronized (this) {
+ MultiPointToSinglePointIntent intent;
+
+ log.debug("SDN-IP submitting intents = {} withdrawing = {}",
+ updates.size(), withdraws.size());
+
+ //
+ // Prepare the Intent batch operations for the intents to withdraw
+ //
+ for (FibUpdate withdraw : withdraws) {
+ checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
+ "FibUpdate with wrong type in withdraws list");
+
+ IpPrefix prefix = withdraw.entry().prefix();
+ intent = routeIntents.remove(prefix);
+ if (intent == null) {
+ log.trace("SDN-IP No intent in routeIntents to delete " +
+ "for prefix: {}", prefix);
+ continue;
+ }
+ if (isElectedLeader && isActivatedLeader) {
+ log.trace("SDN-IP Withdrawing intent: {}", intent);
+ intentService.withdraw(intent);
+ }
+ }
+
+ //
+ // Prepare the Intent batch operations for the intents to submit
+ //
+ for (FibUpdate update : updates) {
+ checkArgument(update.type() == FibUpdate.Type.UPDATE,
+ "FibUpdate with wrong type in updates list");
+
+ IpPrefix prefix = update.entry().prefix();
+ intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
+ update.entry().nextHopMac());
+
+ if (intent == null) {
+ // This preserves the old semantics - if an intent can't be
+ // generated, we don't do anything with that prefix. But
+ // perhaps we should withdraw the old intent anyway?
+ continue;
+ }
+
+ MultiPointToSinglePointIntent oldIntent =
+ routeIntents.put(prefix, intent);
+ if (isElectedLeader && isActivatedLeader) {
+ if (oldIntent != null) {
+ log.trace("SDN-IP Withdrawing old intent: {}",
+ oldIntent);
+ intentService.withdraw(oldIntent);
+ }
+ log.trace("SDN-IP Submitting intent: {}", intent);
+ intentService.submit(intent);
+ }
+ }
+ }
+ }
+
+ /**
+ * Synchronize the in-memory Intents with the Intents in the Intent
+ * framework.
+ */
+ void synchronizeIntents() {
+ synchronized (this) {
+
+ Map<IntentKey, Intent> localIntents = new HashMap<>();
+ Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
+ Collection<Intent> storeInMemoryIntents = new LinkedList<>();
+ Collection<Intent> addIntents = new LinkedList<>();
+ Collection<Intent> deleteIntents = new LinkedList<>();
+
+ if (!isElectedLeader) {
+ return; // Nothing to do: not the leader anymore
+ }
+ log.debug("SDN-IP synchronizing all intents...");
+
+ // Prepare the local intents
+ for (Intent intent : routeIntents.values()) {
+ localIntents.put(new IntentKey(intent), intent);
+ }
+ for (Intent intent : peerIntents.values()) {
+ localIntents.put(new IntentKey(intent), intent);
+ }
+
+ // Fetch all intents for this application
+ for (Intent intent : intentService.getIntents()) {
+ if (!intent.appId().equals(appId)) {
+ continue;
+ }
+ fetchedIntents.put(new IntentKey(intent), intent);
+ }
+ if (log.isDebugEnabled()) {
+ for (Intent intent: fetchedIntents.values()) {
+ log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
+ intent);
+ }
+ }
+
+ computeIntentsDelta(localIntents, fetchedIntents,
+ storeInMemoryIntents, addIntents,
+ deleteIntents);
+
+ //
+ // 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 the operation
+ // 3. Add intents: check if the leader before the operation
+ //
+ for (Intent intent : storeInMemoryIntents) {
+ // Store the intent in memory based on its type
+ if (intent instanceof MultiPointToSinglePointIntent) {
+ MultiPointToSinglePointIntent mp2pIntent =
+ (MultiPointToSinglePointIntent) intent;
+ // Find the IP prefix
+ Criterion c =
+ mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
+ if (c == null) {
+ // Try IPv6
+ c =
+ mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
+ }
+ if (c != null && c instanceof IPCriterion) {
+ IPCriterion ipCriterion = (IPCriterion) c;
+ IpPrefix ipPrefix = ipCriterion.ip();
+ if (ipPrefix == null) {
+ continue;
+ }
+ log.trace("SDN-IP Intent Synchronizer: updating " +
+ "in-memory Route Intent for prefix {}",
+ ipPrefix);
+ routeIntents.put(ipPrefix, mp2pIntent);
+ } else {
+ log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
+ mp2pIntent.id());
+ }
+ continue;
+ }
+ if (intent instanceof PointToPointIntent) {
+ PointToPointIntent p2pIntent = (PointToPointIntent) intent;
+ log.trace("SDN-IP Intent Synchronizer: updating " +
+ "in-memory Peer Intent {}", p2pIntent);
+ peerIntents.put(new IntentKey(intent), p2pIntent);
+ continue;
+ }
+ }
+
+ // Withdraw Intents
+ for (Intent intent : deleteIntents) {
+ intentService.withdraw(intent);
+ log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
+ intent);
+ }
+ if (!isElectedLeader) {
+ log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
+ "not elected leader anymore");
+ isActivatedLeader = false;
+ return;
+ }
+
+ // Add Intents
+ for (Intent intent : addIntents) {
+ intentService.submit(intent);
+ log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
+ intent);
+ }
+ if (!isElectedLeader) {
+ log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
+ "not elected leader anymore");
+ isActivatedLeader = false;
+ return;
+ }
+
+ if (isElectedLeader) {
+ isActivatedLeader = true; // Allow push of Intents
+ } else {
+ isActivatedLeader = false;
+ }
+ log.debug("SDN-IP intent synchronization completed");
+ }
+ }
+
+ /**
+ * Computes the delta in two sets of Intents: local in-memory Intents,
+ * and intents fetched from the Intent framework.
+ *
+ * @param localIntents the local in-memory Intents
+ * @param fetchedIntents the Intents fetched from the Intent framework
+ * @param storeInMemoryIntents the Intents that should be stored in memory.
+ * Note: This Collection must be allocated by the caller, and it will
+ * be populated by this method.
+ * @param addIntents the Intents that should be added to the Intent
+ * framework. Note: This Collection must be allocated by the caller, and
+ * it will be populated by this method.
+ * @param deleteIntents the Intents that should be deleted from the Intent
+ * framework. Note: This Collection must be allocated by the caller, and
+ * it will be populated by this method.
+ */
+ private void computeIntentsDelta(
+ final Map<IntentKey, Intent> localIntents,
+ final Map<IntentKey, Intent> fetchedIntents,
+ Collection<Intent> storeInMemoryIntents,
+ Collection<Intent> addIntents,
+ Collection<Intent> deleteIntents) {
+
+ //
+ // Compute the deltas between the LOCAL in-memory Intents and the
+ // FETCHED Intents:
+ // - If an Intent is in both the LOCAL and FETCHED sets:
+ // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
+ // the LOCAL Intent should be added/installed; otherwise the
+ // FETCHED intent should be stored in the local memory
+ // (i.e., override the LOCAL Intent) to preserve the original
+ // Intent ID.
+ // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
+ // Intent should be added/installed.
+ // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
+ // Intent should be deleted/withdrawn.
+ //
+ for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
+ IntentKey intentKey = entry.getKey();
+ Intent localIntent = entry.getValue();
+ Intent fetchedIntent = fetchedIntents.get(intentKey);
+
+ if (fetchedIntent == null) {
+ //
+ // No FETCHED Intent found: push the LOCAL Intent.
+ //
+ addIntents.add(localIntent);
+ continue;
+ }
+
+ IntentState state =
+ intentService.getIntentState(fetchedIntent.key());
+ if (state == null ||
+ 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(localIntent);
+ continue;
+ }
+ storeInMemoryIntents.add(fetchedIntent);
+ }
+
+ for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
+ IntentKey intentKey = entry.getKey();
+ Intent fetchedIntent = entry.getValue();
+ Intent localIntent = localIntents.get(intentKey);
+
+ if (localIntent != null) {
+ continue;
+ }
+
+ IntentState state =
+ intentService.getIntentState(fetchedIntent.key());
+ if (state == null ||
+ state == IntentState.WITHDRAWING ||
+ state == IntentState.WITHDRAWN) {
+ // Nothing to do. The intent has been already withdrawn.
+ continue;
+ }
+ //
+ // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
+ //
+ deleteIntents.add(fetchedIntent);
+ }
+ }
+
+ /**
+ * Helper class that can be used to compute the key for an Intent by
+ * by excluding the Intent ID.
+ */
+ static final class IntentKey {
+ private final Intent intent;
+
+ /**
+ * Constructor.
+ *
+ * @param intent the intent to use
+ */
+ IntentKey(Intent intent) {
+ checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
+ (intent instanceof PointToPointIntent),
+ "Intent type not recognized", intent);
+ this.intent = intent;
+ }
+
+ /**
+ * 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
+ */
+ static boolean equalIntents(MultiPointToSinglePointIntent intent1,
+ MultiPointToSinglePointIntent intent2) {
+ return Objects.equals(intent1.appId(), intent2.appId()) &&
+ Objects.equals(intent1.selector(), intent2.selector()) &&
+ Objects.equals(intent1.treatment(), intent2.treatment()) &&
+ Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
+ Objects.equals(intent1.egressPoint(), intent2.egressPoint());
+ }
+
+ /**
+ * Compares two Point-to-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
+ */
+ static boolean equalIntents(PointToPointIntent intent1,
+ PointToPointIntent intent2) {
+ return Objects.equals(intent1.appId(), intent2.appId()) &&
+ Objects.equals(intent1.selector(), intent2.selector()) &&
+ Objects.equals(intent1.treatment(), intent2.treatment()) &&
+ Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
+ Objects.equals(intent1.egressPoint(), intent2.egressPoint());
+ }
+
+ @Override
+ public int hashCode() {
+ if (intent instanceof PointToPointIntent) {
+ PointToPointIntent p2pIntent = (PointToPointIntent) intent;
+ return Objects.hash(p2pIntent.appId(),
+ p2pIntent.resources(),
+ p2pIntent.selector(),
+ p2pIntent.treatment(),
+ p2pIntent.constraints(),
+ p2pIntent.ingressPoint(),
+ p2pIntent.egressPoint());
+ }
+ if (intent instanceof MultiPointToSinglePointIntent) {
+ MultiPointToSinglePointIntent m2pIntent =
+ (MultiPointToSinglePointIntent) intent;
+ return Objects.hash(m2pIntent.appId(),
+ m2pIntent.resources(),
+ m2pIntent.selector(),
+ m2pIntent.treatment(),
+ m2pIntent.constraints(),
+ m2pIntent.ingressPoints(),
+ m2pIntent.egressPoint());
+ }
+ checkArgument(false, "Intent type not recognized", intent);
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (!(obj instanceof IntentKey))) {
+ return false;
+ }
+ IntentKey other = (IntentKey) obj;
+
+ if (this.intent instanceof PointToPointIntent) {
+ if (!(other.intent instanceof PointToPointIntent)) {
+ return false;
+ }
+ return equalIntents((PointToPointIntent) this.intent,
+ (PointToPointIntent) other.intent);
+ }
+ if (this.intent instanceof MultiPointToSinglePointIntent) {
+ if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
+ return false;
+ }
+ return equalIntents(
+ (MultiPointToSinglePointIntent) this.intent,
+ (MultiPointToSinglePointIntent) other.intent);
+ }
+ checkArgument(false, "Intent type not recognized", intent);
+ return false;
+ }
+ }
+
+ @Override
+ public void setUpConnectivityHostToHost(IpAddress dstIpAddress,
+ IpAddress srcIpAddress,
+ MacAddress srcMacAddress,
+ ConnectPoint srcConnectPoint) {
+ checkNotNull(dstIpAddress);
+ checkNotNull(srcIpAddress);
+ checkNotNull(srcMacAddress);
+ checkNotNull(srcConnectPoint);
+
+ IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix();
+ IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
+ ConnectPoint dstConnectPoint = null;
+ MacAddress dstMacAddress = null;
+
+ for (Host host : hostService.getHostsByIp(dstIpAddress)) {
+ if (host.mac() != null) {
+ dstMacAddress = host.mac();
+ dstConnectPoint = host.location();
+ break;
+ }
+ }
+ if (dstMacAddress == null) {
+ hostService.startMonitoringIp(dstIpAddress);
+ return;
+ }
+
+ //
+ // Handle intent from source host to destination host
+ //
+ MultiPointToSinglePointIntent srcToDstIntent =
+ hostToHostIntentGenerator(dstIpAddress, dstConnectPoint,
+ dstMacAddress, srcConnectPoint);
+ submitReactiveIntent(dstIpPrefix, srcToDstIntent);
+
+ //
+ // Handle intent from destination host to source host
+ //
+
+ // Since we proactively handle the intent from destination host to
+ // source host, we should check whether there is an exiting intent
+ // first.
+ if (mp2pIntentExists(srcIpPrefix)) {
+ updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint);
+ return;
+ } else {
+ // There is no existing intent, create a new one.
+ MultiPointToSinglePointIntent dstToSrcIntent =
+ hostToHostIntentGenerator(srcIpAddress, srcConnectPoint,
+ srcMacAddress, dstConnectPoint);
+ submitReactiveIntent(srcIpPrefix, dstToSrcIntent);
+ }
+ }
+
+ /**
+ * Generates MultiPointToSinglePointIntent for both source host and
+ * destination host located in local SDN network.
+ *
+ * @param dstIpAddress the destination IP address
+ * @param dstConnectPoint the destination host connect point
+ * @param dstMacAddress the MAC address of destination host
+ * @param srcConnectPoint the connect point where packet-in from
+ * @return the generated MultiPointToSinglePointIntent
+ */
+ private MultiPointToSinglePointIntent hostToHostIntentGenerator(
+ IpAddress dstIpAddress,
+ ConnectPoint dstConnectPoint,
+ MacAddress dstMacAddress,
+ ConnectPoint srcConnectPoint) {
+ checkNotNull(dstIpAddress);
+ checkNotNull(dstConnectPoint);
+ checkNotNull(dstMacAddress);
+ checkNotNull(srcConnectPoint);
+
+ Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
+ ingressPoints.add(srcConnectPoint);
+ IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ if (dstIpAddress.isIp4()) {
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPDst(dstIpPrefix);
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ selector.matchIPv6Dst(dstIpPrefix);
+ }
+
+ // Rewrite the destination MAC address
+ TrafficTreatment.Builder treatment =
+ DefaultTrafficTreatment.builder().setEthDst(dstMacAddress);
+
+ Key key = Key.of(dstIpPrefix.toString(), appId);
+ int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER
+ + PRIORITY_OFFSET;
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector.build())
+ .treatment(treatment.build())
+ .ingressPoints(ingressPoints)
+ .egressPoint(dstConnectPoint)
+ .priority(priority)
+ .constraints(CONSTRAINTS)
+ .build();
+
+ log.trace("Generates ConnectivityHostToHost = {} ", intent);
+ return intent;
+ }
+
+ @Override
+ public void updateExistingMp2pIntent(IpPrefix ipPrefix,
+ ConnectPoint ingressConnectPoint) {
+ checkNotNull(ipPrefix);
+ checkNotNull(ingressConnectPoint);
+
+ MultiPointToSinglePointIntent existingIntent =
+ getExistingMp2pIntent(ipPrefix);
+ if (existingIntent != null) {
+ Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints();
+ // Add host connect point into ingressPoints of the existing intent
+ if (ingressPoints.add(ingressConnectPoint)) {
+ MultiPointToSinglePointIntent updatedMp2pIntent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(existingIntent.key())
+ .selector(existingIntent.selector())
+ .treatment(existingIntent.treatment())
+ .ingressPoints(ingressPoints)
+ .egressPoint(existingIntent.egressPoint())
+ .priority(existingIntent.priority())
+ .constraints(CONSTRAINTS)
+ .build();
+
+ log.trace("Update an existing MultiPointToSinglePointIntent "
+ + "to new intent = {} ", updatedMp2pIntent);
+ submitReactiveIntent(ipPrefix, updatedMp2pIntent);
+ }
+ // If adding ingressConnectPoint to ingressPoints failed, it
+ // because between the time interval from checking existing intent
+ // to generating new intent, onos updated this intent due to other
+ // packet-in and the new intent also includes the
+ // ingressConnectPoint. This will not affect reactive routing.
+ }
+ }
+
+ @Override
+ public boolean mp2pIntentExists(IpPrefix ipPrefix) {
+ checkNotNull(ipPrefix);
+ return routeIntents.get(ipPrefix) != null;
+ }
+
+ /**
+ * Gets the existing MultiPointToSinglePointIntent from memory for a given
+ * IP prefix.
+ *
+ * @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent
+ * @return the MultiPointToSinglePointIntent if found, otherwise null
+ */
+ private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix
+ ipPrefix) {
+ checkNotNull(ipPrefix);
+ return routeIntents.get(ipPrefix);
+ }
+}
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
new file mode 100644
index 00000000..459db2b7
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.BgpConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Manages the connectivity requirements between peers.
+ */
+public class PeerConnectivityManager {
+ private static final int PRIORITY_OFFSET = 1000;
+
+ private static final Logger log = LoggerFactory.getLogger(
+ PeerConnectivityManager.class);
+
+ private static final short BGP_PORT = 179;
+
+ private final IntentSynchronizer intentSynchronizer;
+ private final NetworkConfigService configService;
+ private final InterfaceService interfaceService;
+
+ private final ApplicationId appId;
+ private final ApplicationId routerAppId;
+
+ /**
+ * Creates a new PeerConnectivityManager.
+ *
+ * @param appId the application ID
+ * @param intentSynchronizer the intent synchronizer
+ * @param configService the SDN-IP config service
+ * @param interfaceService the interface service
+ * @param routerAppId application ID
+ */
+ public PeerConnectivityManager(ApplicationId appId,
+ IntentSynchronizer intentSynchronizer,
+ NetworkConfigService configService,
+ ApplicationId routerAppId,
+ InterfaceService interfaceService) {
+ this.appId = appId;
+ this.intentSynchronizer = intentSynchronizer;
+ this.configService = configService;
+ this.routerAppId = routerAppId;
+ this.interfaceService = interfaceService;
+ }
+
+ /**
+ * Starts the peer connectivity manager.
+ */
+ public void start() {
+ setUpConnectivity();
+ }
+
+ /**
+ * Stops the peer connectivity manager.
+ */
+ public void stop() {
+ }
+
+ /**
+ * Sets up paths to establish connectivity between all internal
+ * BGP speakers and external BGP peers.
+ */
+ private void setUpConnectivity() {
+ List<PointToPointIntent> intents = new ArrayList<>();
+
+ BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
+
+ if (config == null) {
+ log.warn("No BgpConfig found");
+ return;
+ }
+
+ for (BgpConfig.BgpSpeakerConfig bgpSpeaker : config.bgpSpeakers()) {
+ log.debug("Start to set up BGP paths for BGP speaker: {}",
+ bgpSpeaker);
+
+ intents.addAll(buildSpeakerIntents(bgpSpeaker));
+ }
+
+ // Submit all the intents.
+ intentSynchronizer.submitPeerIntents(intents);
+ }
+
+ private Collection<PointToPointIntent> buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker) {
+ List<PointToPointIntent> intents = new ArrayList<>();
+
+ for (IpAddress peerAddress : speaker.peers()) {
+ Interface peeringInterface = interfaceService.getMatchingInterface(peerAddress);
+
+ if (peeringInterface == null) {
+ log.debug("No peering interface found for peer {} on speaker {}",
+ peerAddress, speaker);
+ continue;
+ }
+
+ IpAddress peeringAddress = null;
+ for (InterfaceIpAddress address : peeringInterface.ipAddresses()) {
+ if (address.subnetAddress().contains(peerAddress)) {
+ peeringAddress = address.ipAddress();
+ break;
+ }
+ }
+
+ checkNotNull(peeringAddress);
+
+ intents.addAll(buildIntents(speaker.connectPoint(), peeringAddress,
+ peeringInterface.connectPoint(), peerAddress));
+ }
+
+ return intents;
+ }
+
+ /**
+ * Builds the required intents between the two pairs of connect points and
+ * IP addresses.
+ *
+ * @param portOne the first connect point
+ * @param ipOne the first IP address
+ * @param portTwo the second connect point
+ * @param ipTwo the second IP address
+ * @return the intents to install
+ */
+ private Collection<PointToPointIntent> buildIntents(ConnectPoint portOne,
+ IpAddress ipOne,
+ ConnectPoint portTwo,
+ IpAddress ipTwo) {
+
+ List<PointToPointIntent> intents = new ArrayList<>();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+ TrafficSelector selector;
+
+ byte tcpProtocol;
+ byte icmpProtocol;
+
+ if (ipOne.isIp4()) {
+ tcpProtocol = IPv4.PROTOCOL_TCP;
+ icmpProtocol = IPv4.PROTOCOL_ICMP;
+ } else {
+ tcpProtocol = IPv6.PROTOCOL_TCP;
+ icmpProtocol = IPv6.PROTOCOL_ICMP6;
+ }
+
+ // Path from BGP speaker to BGP peer matching destination TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipOne,
+ ipTwo,
+ null,
+ BGP_PORT);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portOne)
+ .egressPoint(portTwo)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // Path from BGP speaker to BGP peer matching source TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipOne,
+ ipTwo,
+ BGP_PORT,
+ null);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portOne)
+ .egressPoint(portTwo)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // Path from BGP peer to BGP speaker matching destination TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipTwo,
+ ipOne,
+ null,
+ BGP_PORT);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portTwo)
+ .egressPoint(portOne)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // Path from BGP peer to BGP speaker matching source TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipTwo,
+ ipOne,
+ BGP_PORT,
+ null);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portTwo)
+ .egressPoint(portOne)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // ICMP path from BGP speaker to BGP peer
+ selector = buildSelector(icmpProtocol,
+ ipOne,
+ ipTwo,
+ null,
+ null);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portOne)
+ .egressPoint(portTwo)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // ICMP path from BGP peer to BGP speaker
+ selector = buildSelector(icmpProtocol,
+ ipTwo,
+ ipOne,
+ null,
+ null);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portTwo)
+ .egressPoint(portOne)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ return intents;
+ }
+
+ /**
+ * Builds a traffic selector based on the set of input parameters.
+ *
+ * @param ipProto IP protocol
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ * @param srcTcpPort source TCP port, or null if shouldn't be set
+ * @param dstTcpPort destination TCP port, or null if shouldn't be set
+ * @return the new traffic selector
+ */
+ private TrafficSelector buildSelector(byte ipProto, IpAddress srcIp,
+ IpAddress dstIp, Short srcTcpPort,
+ Short dstTcpPort) {
+ TrafficSelector.Builder builder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(ipProto);
+
+ if (dstIp.isIp4()) {
+ builder.matchIPSrc(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET_MASK_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET_MASK_LENGTH));
+ } else {
+ builder.matchIPv6Src(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET6_MASK_LENGTH))
+ .matchIPv6Dst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET6_MASK_LENGTH));
+ }
+
+ if (srcTcpPort != null) {
+ builder.matchTcpSrc(TpPort.tpPort(srcTcpPort));
+ }
+
+ if (dstTcpPort != null) {
+ builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
+ }
+
+ return builder.build();
+ }
+
+}
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
new file mode 100644
index 00000000..3d1fe65c
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Component for the SDN-IP peering application.
+ */
+@Component(immediate = true)
+@Service
+public class SdnIp implements SdnIpService {
+
+ private static final String SDN_IP_APP = "org.onosproject.sdnip";
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentService intentService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RoutingService routingService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RoutingConfigurationService config;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService networkConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ private IntentSynchronizer intentSynchronizer;
+ private PeerConnectivityManager peerConnectivity;
+
+ private LeadershipEventListener leadershipEventListener =
+ new InnerLeadershipEventListener();
+ private ApplicationId appId;
+ private ControllerNode localControllerNode;
+
+ @Activate
+ protected void activate() {
+ log.info("SDN-IP started");
+
+ appId = coreService.registerApplication(SDN_IP_APP);
+
+ localControllerNode = clusterService.getLocalNode();
+
+ intentSynchronizer = new IntentSynchronizer(appId, intentService,
+ hostService,
+ config,
+ interfaceService);
+ intentSynchronizer.start();
+
+ peerConnectivity = new PeerConnectivityManager(appId,
+ intentSynchronizer,
+ networkConfigService,
+ coreService.getAppId(RoutingService.ROUTER_APP_ID),
+ interfaceService);
+ peerConnectivity.start();
+
+ routingService.addFibListener(intentSynchronizer);
+ routingService.addIntentRequestListener(intentSynchronizer);
+ routingService.start();
+
+ leadershipService.addListener(leadershipEventListener);
+ leadershipService.runForLeadership(appId.name());
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ routingService.stop();
+ peerConnectivity.stop();
+ intentSynchronizer.stop();
+
+ leadershipService.withdraw(appId.name());
+ leadershipService.removeListener(leadershipEventListener);
+
+ log.info("SDN-IP Stopped");
+ }
+
+ @Override
+ public void modifyPrimary(boolean isPrimary) {
+ intentSynchronizer.leaderChanged(isPrimary);
+ }
+
+ /**
+ * Converts DPIDs of the form xx:xx:xx:xx:xx:xx:xx to OpenFlow provider
+ * device URIs.
+ *
+ * @param dpid the DPID string to convert
+ * @return the URI string for this device
+ */
+ static String dpidToUri(String dpid) {
+ return "of:" + dpid.replace(":", "");
+ }
+
+ /**
+ * A listener for Leadership Events.
+ */
+ private class InnerLeadershipEventListener
+ implements LeadershipEventListener {
+
+ @Override
+ public void event(LeadershipEvent event) {
+ log.debug("Leadership Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+
+ if (!event.subject().topic().equals(appId.name())) {
+ return; // Not our topic: ignore
+ }
+ if (!Objects.equals(event.subject().leader(), localControllerNode.id())) {
+ return; // The event is not about this instance: ignore
+ }
+
+ switch (event.type()) {
+ case LEADER_ELECTED:
+ log.info("SDN-IP Leader Elected");
+ intentSynchronizer.leaderChanged(true);
+ break;
+ case LEADER_BOOTED:
+ log.info("SDN-IP Leader Lost Election");
+ intentSynchronizer.leaderChanged(false);
+ break;
+ case LEADER_REELECTED:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java
new file mode 100644
index 00000000..d26d3060
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+/**
+ * Service interface exported by SDN-IP.
+ */
+public interface SdnIpService {
+
+ /**
+ * Changes whether this SDN-IP instance is the primary or not based on the
+ * boolean parameter.
+ *
+ * @param isPrimary true if the instance is primary, false if it is not
+ */
+ void modifyPrimary(boolean isPrimary);
+
+}
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java
new file mode 100644
index 00000000..72cc112e
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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.onosproject.sdnip.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.sdnip.SdnIpService;
+
+/**
+ * Command to change whether this SDNIP instance is primary or not.
+ */
+@Command(scope = "onos", name = "sdnip-set-primary",
+ description = "Changes the primary status of this SDN-IP instance")
+public class PrimaryChangeCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "isPrimary",
+ description = "True if this instance should be primary, false if not",
+ required = true, multiValued = false)
+ boolean isPrimary = false;
+
+ @Override
+ protected void execute() {
+ get(SdnIpService.class).modifyPrimary(isPrimary);
+ }
+
+}
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/package-info.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/package-info.java
new file mode 100644
index 00000000..73ea2a4f
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * SDN-IP command-line handlers.
+ */
+package org.onosproject.sdnip.cli;
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/package-info.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/package-info.java
new file mode 100644
index 00000000..1e7d8cb9
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * SDN-IP peering application.
+ */
+package org.onosproject.sdnip;
diff --git a/framework/src/onos/apps/sdnip/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/sdnip/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 00000000..3be1c79a
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ 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.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.sdnip.cli.PrimaryChangeCommand"/>
+ </command>
+ </command-bundle>
+</blueprint>
diff --git a/framework/src/onos/apps/sdnip/src/main/resources/config-examples/README b/framework/src/onos/apps/sdnip/src/main/resources/config-examples/README
new file mode 100644
index 00000000..7642a4dd
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/resources/config-examples/README
@@ -0,0 +1,5 @@
+The SDN-IP configuration files should be copied to directory
+ $ONOS_HOME/tools/package/config
+
+After deployment and starting up the ONOS cluster, ONOS looks for these
+configuration files in /opt/onos/config on each cluster member.
diff --git a/framework/src/onos/apps/sdnip/src/main/resources/config-examples/sdnip.json b/framework/src/onos/apps/sdnip/src/main/resources/config-examples/sdnip.json
new file mode 100644
index 00000000..c51de68a
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/main/resources/config-examples/sdnip.json
@@ -0,0 +1,86 @@
+{
+ "bgpPeers" : [
+ {
+ "attachmentDpid" : "00:00:00:00:00:00:00:a3",
+ "attachmentPort" : "1",
+ "ipAddress" : "192.168.10.1"
+ },
+ {
+ "attachmentDpid" : "00:00:00:00:00:00:00:a5",
+ "attachmentPort" : "1",
+ "ipAddress" : "192.168.20.1"
+ },
+ {
+ "attachmentDpid" : "00:00:00:00:00:00:00:a2",
+ "attachmentPort" : "1",
+ "ipAddress" : "192.168.30.1"
+ },
+ {
+ "attachmentDpid" : "00:00:00:00:00:00:00:a6",
+ "attachmentPort" : "1",
+ "ipAddress" : "192.168.40.1"
+ },
+ {
+ "attachmentDpid" : "00:00:00:00:00:00:00:a4",
+ "attachmentPort" : "4",
+ "ipAddress" : "192.168.60.1"
+ }
+ ],
+ "bgpSpeakers" : [
+ {
+ "name" : "bgpSpeaker1",
+ "attachmentDpid" : "00:00:00:00:00:00:00:a1",
+ "attachmentPort" : "1",
+ "macAddress" : "00:00:00:00:00:01",
+ "interfaceAddresses" : [
+ {
+ "interfaceDpid" : "00:00:00:00:00:00:00:a3",
+ "interfacePort" : "1",
+ "ipAddress" : "192.168.10.101"
+ },
+ {
+ "interfaceDpid" : "00:00:00:00:00:00:00:a5",
+ "interfacePort" : "1",
+ "ipAddress" : "192.168.20.101"
+ },
+ {
+ "interfaceDpid" : "00:00:00:00:00:00:00:a2",
+ "interfacePort" : "1",
+ "ipAddress" : "192.168.30.101"
+ },
+ {
+ "interfaceDpid" : "00:00:00:00:00:00:00:a6",
+ "interfacePort" : "1",
+ "ipAddress" : "192.168.40.101"
+ },
+ {
+ "interfaceDpid" : "00:00:00:00:00:00:00:a4",
+ "interfacePort" : "4",
+ "ipAddress" : "192.168.60.101"
+ }
+
+ ]
+
+ }
+ ],
+ "ip4LocalPrefixes" : [
+ {
+ "ipPrefix" : "100.0.0.0/24",
+ "type" : "PUBLIC",
+ "gatewayIp" : "100.0.0.1"
+ },
+ {
+ "ipPrefix" : "200.0.0.0/8",
+ "type" : "PUBLIC",
+ "gatewayIp" : "200.0.0.3"
+ },
+ {
+ "ipPrefix" : "192.0.0.0/24",
+ "type" : "PRIVATE",
+ "gatewayIp" : "192.0.0.254"
+ }
+ ],
+ "ip6LocalPrefixes" : [
+ ],
+ "virtualGatewayMacAddress" : "00:00:00:00:00:01"
+}
diff --git a/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
new file mode 100644
index 00000000..fc5782e4
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
@@ -0,0 +1,662 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.junit.TestUtils.TestUtilsException;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.config.BgpPeer;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
+
+/**
+ * This class tests the intent synchronization function in the
+ * IntentSynchronizer class.
+ */
+public class IntentSyncTest extends AbstractIntentTest {
+
+ private RoutingConfigurationService routingConfig;
+ private InterfaceService interfaceService;
+ private IntentService intentService;
+ private NetworkConfigService configService;
+
+ private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW4_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000004"),
+ PortNumber.portNumber(1));
+
+ private IntentSynchronizer intentSynchronizer;
+ private final Set<Interface> interfaces = Sets.newHashSet();
+
+ private static final ApplicationId APPID = new ApplicationId() {
+ @Override
+ public short id() {
+ return 1;
+ }
+
+ @Override
+ public String name() {
+ return "SDNIP";
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ routingConfig = createMock(RoutingConfigurationService.class);
+ interfaceService = createMock(InterfaceService.class);
+ configService = createMock(NetworkConfigService.class);
+
+ // These will set expectations on routingConfig
+ setUpInterfaceService();
+ setUpBgpPeers();
+
+ replay(routingConfig);
+ replay(interfaceService);
+
+ intentService = createMock(IntentService.class);
+
+ intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+ null, routingConfig,
+ interfaceService);
+ }
+
+ /**
+ * Sets up BGP peers in external networks.
+ */
+ private void setUpBgpPeers() {
+
+ Map<IpAddress, BgpPeer> peers = new HashMap<>();
+
+ String peerSw1Eth1 = "192.168.10.1";
+ peers.put(IpAddress.valueOf(peerSw1Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
+
+ // Two BGP peers are connected to switch 2 port 1.
+ String peer1Sw2Eth1 = "192.168.20.1";
+ peers.put(IpAddress.valueOf(peer1Sw2Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
+
+ String peer2Sw2Eth1 = "192.168.20.2";
+ peers.put(IpAddress.valueOf(peer2Sw2Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
+
+ String peer1Sw4Eth1 = "192.168.40.1";
+ peers.put(IpAddress.valueOf(peer1Sw4Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
+
+ expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
+ }
+
+ /**
+ * Sets up InterfaceService.
+ */
+ private void setUpInterfaceService() {
+ Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
+ interfaceIpAddresses1.add(new InterfaceIpAddress(
+ IpAddress.valueOf("192.168.10.101"),
+ IpPrefix.valueOf("192.168.10.0/24")));
+ Interface sw1Eth1 = new Interface(SW1_ETH1,
+ interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
+ VlanId.NONE);
+ interfaces.add(sw1Eth1);
+
+ Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
+ interfaceIpAddresses2.add(
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
+ IpPrefix.valueOf("192.168.20.0/24")));
+ Interface sw2Eth1 = new Interface(SW2_ETH1,
+ interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
+ VlanId.NONE);
+ interfaces.add(sw2Eth1);
+
+ Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
+ interfaceIpAddresses3.add(
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
+ IpPrefix.valueOf("192.168.30.0/24")));
+ Interface sw3Eth1 = new Interface(SW3_ETH1,
+ interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
+ VlanId.NONE);
+ interfaces.add(sw3Eth1);
+
+ InterfaceIpAddress interfaceIpAddress4 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
+ IpPrefix.valueOf("192.168.40.0/24"));
+ Interface sw4Eth1 = new Interface(SW4_ETH1,
+ Sets.newHashSet(interfaceIpAddress4),
+ MacAddress.valueOf("00:00:00:00:00:04"),
+ VlanId.vlanId((short) 1));
+
+ expect(interfaceService.getInterfacesByPort(SW4_ETH1)).andReturn(
+ Collections.singleton(sw4Eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.40.1")))
+ .andReturn(sw4Eth1).anyTimes();
+
+ interfaces.add(sw4Eth1);
+
+ expect(interfaceService.getInterfacesByPort(SW1_ETH1)).andReturn(
+ Collections.singleton(sw1Eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.10.1")))
+ .andReturn(sw1Eth1).anyTimes();
+ expect(interfaceService.getInterfacesByPort(SW2_ETH1)).andReturn(
+ Collections.singleton(sw2Eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.20.1")))
+ .andReturn(sw2Eth1).anyTimes();
+ expect(interfaceService.getInterfacesByPort(SW3_ETH1)).andReturn(
+ Collections.singleton(sw3Eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.30.1")))
+ .andReturn(sw3Eth1).anyTimes();
+ expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
+ }
+
+ /**
+ * Tests adding a FIB entry to the IntentSynchronizer.
+ *
+ * We verify that the synchronizer records the correct state and that the
+ * correct intent is submitted to the IntentService.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testFibAdd() throws TestUtilsException {
+ FibEntry fibEntry = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.10.1"),
+ MacAddress.valueOf("00:00:00:00:00:01"));
+
+ // Construct a MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ fibEntry.prefix());
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+ ingressPoints.add(SW2_ETH1);
+ ingressPoints.add(SW3_ETH1);
+ ingressPoints.add(SW4_ETH1);
+
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(selectorBuilder.build())
+ .treatment(treatmentBuilder.build())
+ .ingressPoints(ingressPoints)
+ .egressPoint(SW1_ETH1)
+ .constraints(IntentSynchronizer.CONSTRAINTS)
+ .build();
+
+ // Setup the expected intents
+ intentService.submit(eqExceptId(intent));
+ replay(intentService);
+
+ intentSynchronizer.leaderChanged(true);
+ TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
+ fibEntry);
+ intentSynchronizer.update(Collections.singleton(fibUpdate),
+ Collections.emptyList());
+
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
+ Intent firstIntent =
+ intentSynchronizer.getRouteIntents().iterator().next();
+ IntentKey firstIntentKey = new IntentKey(firstIntent);
+ IntentKey intentKey = new IntentKey(intent);
+ assertTrue(firstIntentKey.equals(intentKey));
+ verify(intentService);
+ }
+
+ /**
+ * Tests adding a FIB entry with to a next hop in a VLAN.
+ *
+ * We verify that the synchronizer records the correct state and that the
+ * correct intent is submitted to the IntentService.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testFibAddWithVlan() throws TestUtilsException {
+ FibEntry fibEntry = new FibEntry(
+ Ip4Prefix.valueOf("3.3.3.0/24"),
+ Ip4Address.valueOf("192.168.40.1"),
+ MacAddress.valueOf("00:00:00:00:00:04"));
+
+ // Construct a MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(fibEntry.prefix())
+ .matchVlanId(VlanId.ANY);
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:04"))
+ .setVlanId(VlanId.vlanId((short) 1));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+ ingressPoints.add(SW1_ETH1);
+ ingressPoints.add(SW2_ETH1);
+ ingressPoints.add(SW3_ETH1);
+
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(selectorBuilder.build())
+ .treatment(treatmentBuilder.build())
+ .ingressPoints(ingressPoints)
+ .egressPoint(SW4_ETH1)
+ .constraints(IntentSynchronizer.CONSTRAINTS)
+ .build();
+
+ // Setup the expected intents
+ intentService.submit(eqExceptId(intent));
+
+ replay(intentService);
+
+ // Run the test
+ intentSynchronizer.leaderChanged(true);
+ TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
+
+ intentSynchronizer.update(Collections.singleton(fibUpdate),
+ Collections.emptyList());
+
+ // Verify
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
+ Intent firstIntent =
+ intentSynchronizer.getRouteIntents().iterator().next();
+ IntentKey firstIntentKey = new IntentKey(firstIntent);
+ IntentKey intentKey = new IntentKey(intent);
+ assertTrue(firstIntentKey.equals(intentKey));
+ verify(intentService);
+ }
+
+ /**
+ * Tests updating a FIB entry.
+ *
+ * We verify that the synchronizer records the correct state and that the
+ * correct intent is submitted to the IntentService.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testFibUpdate() throws TestUtilsException {
+ // Firstly add a route
+ testFibAdd();
+
+ Intent addedIntent =
+ intentSynchronizer.getRouteIntents().iterator().next();
+
+ // Start to construct a new route entry and new intent
+ FibEntry fibEntryUpdate = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.20.1"),
+ MacAddress.valueOf("00:00:00:00:00:02"));
+
+ // Construct a new MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilderNew =
+ DefaultTrafficSelector.builder();
+ selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ fibEntryUpdate.prefix());
+
+ TrafficTreatment.Builder treatmentBuilderNew =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
+
+
+ Set<ConnectPoint> ingressPointsNew = new HashSet<>();
+ ingressPointsNew.add(SW1_ETH1);
+ ingressPointsNew.add(SW3_ETH1);
+ ingressPointsNew.add(SW4_ETH1);
+
+ MultiPointToSinglePointIntent intentNew =
+ MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(selectorBuilderNew.build())
+ .treatment(treatmentBuilderNew.build())
+ .ingressPoints(ingressPointsNew)
+ .egressPoint(SW2_ETH1)
+ .constraints(IntentSynchronizer.CONSTRAINTS)
+ .build();
+
+ // Set up test expectation
+ reset(intentService);
+ // Setup the expected intents
+ intentService.withdraw(eqExceptId(addedIntent));
+ intentService.submit(eqExceptId(intentNew));
+ replay(intentService);
+
+ // Call the update() method in IntentSynchronizer class
+ intentSynchronizer.leaderChanged(true);
+ TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
+ fibEntryUpdate);
+ intentSynchronizer.update(Collections.singletonList(fibUpdate),
+ Collections.emptyList());
+
+ // Verify
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
+ Intent firstIntent =
+ intentSynchronizer.getRouteIntents().iterator().next();
+ IntentKey firstIntentKey = new IntentKey(firstIntent);
+ IntentKey intentNewKey = new IntentKey(intentNew);
+ assertTrue(firstIntentKey.equals(intentNewKey));
+ verify(intentService);
+ }
+
+ /**
+ * Tests deleting a FIB entry.
+ *
+ * We verify that the synchronizer records the correct state and that the
+ * correct intent is withdrawn from the IntentService.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testFibDelete() throws TestUtilsException {
+ // Firstly add a route
+ testFibAdd();
+
+ Intent addedIntent =
+ intentSynchronizer.getRouteIntents().iterator().next();
+
+ // Construct the existing route entry
+ FibEntry fibEntry = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
+
+ // Set up expectation
+ reset(intentService);
+ // Setup the expected intents
+ intentService.withdraw(eqExceptId(addedIntent));
+ replay(intentService);
+
+ // Call the update() method in IntentSynchronizer class
+ intentSynchronizer.leaderChanged(true);
+ TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
+ intentSynchronizer.update(Collections.emptyList(),
+ Collections.singletonList(fibUpdate));
+
+ // Verify
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 0);
+ verify(intentService);
+ }
+
+ /**
+ * This method tests the behavior of intent Synchronizer.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testIntentSync() throws TestUtilsException {
+
+ //
+ // Construct routes and intents.
+ // This test simulates the following cases during the master change
+ // time interval:
+ // 1. RouteEntry1 did not change and the intent also did not change.
+ // 2. RouteEntry2 was deleted, but the intent was not deleted.
+ // 3. RouteEntry3 was newly added, and the intent was also submitted.
+ // 4. RouteEntry4 was updated to RouteEntry4Update, and the intent was
+ // also updated to a new one.
+ // 5. RouteEntry5 did not change, but its intent id changed.
+ // 6. RouteEntry6 was newly added, but the intent was not submitted.
+ //
+ RouteEntry routeEntry1 = new RouteEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.10.1"));
+
+ RouteEntry routeEntry2 = new RouteEntry(
+ Ip4Prefix.valueOf("2.2.2.0/24"),
+ Ip4Address.valueOf("192.168.20.1"));
+
+ RouteEntry routeEntry3 = new RouteEntry(
+ Ip4Prefix.valueOf("3.3.3.0/24"),
+ Ip4Address.valueOf("192.168.30.1"));
+
+ RouteEntry routeEntry4 = new RouteEntry(
+ Ip4Prefix.valueOf("4.4.4.0/24"),
+ Ip4Address.valueOf("192.168.30.1"));
+
+ RouteEntry routeEntry4Update = new RouteEntry(
+ Ip4Prefix.valueOf("4.4.4.0/24"),
+ Ip4Address.valueOf("192.168.20.1"));
+
+ RouteEntry routeEntry5 = new RouteEntry(
+ Ip4Prefix.valueOf("5.5.5.0/24"),
+ Ip4Address.valueOf("192.168.10.1"));
+
+ RouteEntry routeEntry6 = new RouteEntry(
+ Ip4Prefix.valueOf("6.6.6.0/24"),
+ Ip4Address.valueOf("192.168.10.1"));
+
+ RouteEntry routeEntry7 = new RouteEntry(
+ Ip4Prefix.valueOf("7.7.7.0/24"),
+ Ip4Address.valueOf("192.168.10.1"));
+
+ MultiPointToSinglePointIntent intent1 = intentBuilder(
+ routeEntry1.prefix(), "00:00:00:00:00:01", SW1_ETH1);
+ MultiPointToSinglePointIntent intent2 = intentBuilder(
+ routeEntry2.prefix(), "00:00:00:00:00:02", SW2_ETH1);
+ MultiPointToSinglePointIntent intent3 = intentBuilder(
+ routeEntry3.prefix(), "00:00:00:00:00:03", SW3_ETH1);
+ MultiPointToSinglePointIntent intent4 = intentBuilder(
+ routeEntry4.prefix(), "00:00:00:00:00:03", SW3_ETH1);
+ MultiPointToSinglePointIntent intent4Update = intentBuilder(
+ routeEntry4Update.prefix(), "00:00:00:00:00:02", SW2_ETH1);
+ MultiPointToSinglePointIntent intent5 = intentBuilder(
+ routeEntry5.prefix(), "00:00:00:00:00:01", SW1_ETH1);
+ MultiPointToSinglePointIntent intent7 = intentBuilder(
+ routeEntry7.prefix(), "00:00:00:00:00:01", SW1_ETH1);
+
+ // Compose a intent, which is equal to intent5 but the id is different.
+ MultiPointToSinglePointIntent intent5New =
+ staticIntentBuilder(intent5, routeEntry5, "00:00:00:00:00:01");
+ assertThat(IntentSynchronizer.IntentKey.equalIntents(
+ intent5, intent5New),
+ is(true));
+ assertFalse(intent5.equals(intent5New));
+
+ MultiPointToSinglePointIntent intent6 = intentBuilder(
+ routeEntry6.prefix(), "00:00:00:00:00:01", SW1_ETH1);
+
+ // Set up the routeIntents field in IntentSynchronizer class
+ ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent>
+ routeIntents = new ConcurrentHashMap<>();
+ routeIntents.put(routeEntry1.prefix(), intent1);
+ routeIntents.put(routeEntry3.prefix(), intent3);
+ routeIntents.put(routeEntry4Update.prefix(), intent4Update);
+ routeIntents.put(routeEntry5.prefix(), intent5New);
+ routeIntents.put(routeEntry6.prefix(), intent6);
+ routeIntents.put(routeEntry7.prefix(), intent7);
+ TestUtils.setField(intentSynchronizer, "routeIntents", routeIntents);
+
+ // Set up expectation
+ reset(intentService);
+ Set<Intent> intents = new HashSet<>();
+ intents.add(intent1);
+ expect(intentService.getIntentState(intent1.key()))
+ .andReturn(IntentState.INSTALLED).anyTimes();
+ intents.add(intent2);
+ expect(intentService.getIntentState(intent2.key()))
+ .andReturn(IntentState.INSTALLED).anyTimes();
+ intents.add(intent4);
+ expect(intentService.getIntentState(intent4.key()))
+ .andReturn(IntentState.INSTALLED).anyTimes();
+ intents.add(intent5);
+ expect(intentService.getIntentState(intent5.key()))
+ .andReturn(IntentState.INSTALLED).anyTimes();
+ intents.add(intent7);
+ expect(intentService.getIntentState(intent7.key()))
+ .andReturn(IntentState.WITHDRAWING).anyTimes();
+ expect(intentService.getIntents()).andReturn(intents).anyTimes();
+
+ intentService.withdraw(intent2);
+ intentService.withdraw(intent4);
+
+ intentService.submit(intent3);
+ intentService.submit(intent4Update);
+ intentService.submit(intent6);
+ intentService.submit(intent7);
+ replay(intentService);
+
+ // Start the test
+ intentSynchronizer.leaderChanged(true);
+ intentSynchronizer.synchronizeIntents();
+
+ // Verify
+ assertEquals(intentSynchronizer.getRouteIntents().size(), 6);
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent1));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent3));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent4Update));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent5));
+ assertTrue(intentSynchronizer.getRouteIntents().contains(intent6));
+
+ verify(intentService);
+ }
+
+ /**
+ * MultiPointToSinglePointIntent builder.
+ *
+ * @param ipPrefix the ipPrefix to match
+ * @param nextHopMacAddress to which the destination MAC address in packet
+ * should be rewritten
+ * @param egressPoint to which packets should be sent
+ * @return the constructed MultiPointToSinglePointIntent
+ */
+ private MultiPointToSinglePointIntent intentBuilder(IpPrefix ipPrefix,
+ String nextHopMacAddress, ConnectPoint egressPoint) {
+
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ if (ipPrefix.isIp4()) {
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4); // IPv4
+ selectorBuilder.matchIPDst(ipPrefix);
+ } else {
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV6); // IPv6
+ selectorBuilder.matchIPv6Dst(ipPrefix);
+ }
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf(nextHopMacAddress));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+ for (Interface intf : interfaces) {
+ if (!intf.connectPoint().equals(egressPoint)) {
+ ConnectPoint srcPort = intf.connectPoint();
+ ingressPoints.add(srcPort);
+ }
+ }
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(selectorBuilder.build())
+ .treatment(treatmentBuilder.build())
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .constraints(IntentSynchronizer.CONSTRAINTS)
+ .build();
+ return intent;
+ }
+
+ /**
+ * A static MultiPointToSinglePointIntent builder, the returned intent is
+ * equal to the input intent except that the id is different.
+ *
+ * @param intent the intent to be used for building a new intent
+ * @param routeEntry the relative routeEntry of the intent
+ * @return the newly constructed MultiPointToSinglePointIntent
+ * @throws TestUtilsException
+ */
+ private MultiPointToSinglePointIntent staticIntentBuilder(
+ MultiPointToSinglePointIntent intent, RouteEntry routeEntry,
+ String nextHopMacAddress) throws TestUtilsException {
+
+ // Use a different egress ConnectPoint with that in intent
+ // to generate a different id
+ MultiPointToSinglePointIntent intentNew = intentBuilder(
+ routeEntry.prefix(), nextHopMacAddress, SW2_ETH1);
+ TestUtils.setField(intentNew, "egressPoint", intent.egressPoint());
+ TestUtils.setField(intentNew,
+ "ingressPoints", intent.ingressPoints());
+ return intentNew;
+ }
+}
diff --git a/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
new file mode 100644
index 00000000..d89c3c2b
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.junit.TestUtils.TestUtilsException;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.config.BgpConfig;
+import org.onosproject.routing.config.BgpPeer;
+import org.onosproject.routing.config.BgpSpeaker;
+import org.onosproject.routing.config.InterfaceAddress;
+import org.onosproject.routing.config.RoutingConfigurationService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
+
+/**
+ * Unit tests for PeerConnectivityManager.
+ */
+public class PeerConnectivityManagerTest extends AbstractIntentTest {
+
+ private static final ApplicationId APPID = new ApplicationId() {
+ @Override
+ public short id() {
+ return 0;
+ }
+
+ @Override
+ public String name() {
+ return "foo";
+ }
+ };
+
+ private static final ApplicationId CONFIG_APP_ID = APPID;
+
+ private PeerConnectivityManager peerConnectivityManager;
+ private IntentSynchronizer intentSynchronizer;
+ private RoutingConfigurationService routingConfig;
+ private InterfaceService interfaceService;
+ private NetworkConfigService networkConfigService;
+ private IntentService intentService;
+
+ private Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers;
+ private Map<String, Interface> interfaces;
+ private Map<IpAddress, BgpPeer> peers;
+
+ private BgpConfig bgpConfig;
+
+ private Map<String, Interface> configuredInterfaces;
+ private Map<IpAddress, BgpPeer> configuredPeers;
+ private List<PointToPointIntent> intentList;
+
+ private final String dpid1 = "00:00:00:00:00:00:00:01";
+ private final String dpid2 = "00:00:00:00:00:00:00:02";
+
+ private final DeviceId deviceId1 =
+ DeviceId.deviceId(SdnIp.dpidToUri(dpid1));
+ private final DeviceId deviceId2 =
+ DeviceId.deviceId(SdnIp.dpidToUri(dpid2));
+
+ // Interfaces connected to BGP speakers
+ private final ConnectPoint s1Eth100 =
+ new ConnectPoint(deviceId1, PortNumber.portNumber(100));
+ private final ConnectPoint s2Eth100 =
+ new ConnectPoint(deviceId2, PortNumber.portNumber(100));
+
+ // Interfaces connected to BGP peers
+ private final ConnectPoint s1Eth1 =
+ new ConnectPoint(deviceId1, PortNumber.portNumber(1));
+ private final ConnectPoint s2Eth1 =
+ new ConnectPoint(deviceId2, PortNumber.portNumber(1));
+
+ private final TrafficTreatment noTreatment =
+ DefaultTrafficTreatment.emptyTreatment();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ routingConfig = createMock(RoutingConfigurationService.class);
+ interfaceService = createMock(InterfaceService.class);
+ networkConfigService = createMock(NetworkConfigService.class);
+ bgpConfig = createMock(BgpConfig.class);
+
+ // These will set expectations on routingConfig and interfaceService
+ bgpSpeakers = setUpBgpSpeakers();
+ interfaces = Collections.unmodifiableMap(setUpInterfaces());
+ peers = Collections.unmodifiableMap(setUpPeers());
+
+ initPeerConnectivity();
+ intentList = setUpIntentList();
+ }
+
+ /**
+ * Sets up BGP speakers.
+ *
+ * @return configured BGP speakers as a map from speaker name to speaker
+ */
+ private Set<BgpConfig.BgpSpeakerConfig> setUpBgpSpeakers() {
+
+ BgpConfig.BgpSpeakerConfig speaker1 = new BgpConfig.BgpSpeakerConfig(
+ Optional.empty(),
+ s1Eth100, Collections.singleton(IpAddress.valueOf("192.168.10.1")));
+
+ BgpConfig.BgpSpeakerConfig speaker2 = new BgpConfig.BgpSpeakerConfig(
+ Optional.empty(),
+ s1Eth100, Sets.newHashSet(IpAddress.valueOf("192.168.20.1"),
+ IpAddress.valueOf("192.168.30.1")));
+
+ Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers = Sets.newHashSet();
+ bgpSpeakers.add(speaker1);
+ bgpSpeakers.add(speaker2);
+
+ return bgpSpeakers;
+ }
+
+ /**
+ * Sets up logical interfaces, which emulate the configured interfaces
+ * in SDN-IP application.
+ *
+ * @return configured interfaces as a MAP from Interface name to Interface
+ */
+ private Map<String, Interface> setUpInterfaces() {
+
+ configuredInterfaces = new HashMap<>();
+
+ String interfaceSw1Eth1 = "s1-eth1";
+ InterfaceIpAddress ia1 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.10.101"),
+ IpPrefix.valueOf("192.168.10.0/24"));
+ Interface intfsw1eth1 = new Interface(s1Eth1,
+ Collections.singleton(ia1),
+ MacAddress.valueOf("00:00:00:00:00:01"),
+ VlanId.NONE);
+
+ configuredInterfaces.put(interfaceSw1Eth1, intfsw1eth1);
+ String interfaceSw2Eth1 = "s2-eth1";
+ InterfaceIpAddress ia2 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
+ IpPrefix.valueOf("192.168.20.0/24"));
+ Interface intfsw2eth1 = new Interface(s2Eth1,
+ Collections.singleton(ia2),
+ MacAddress.valueOf("00:00:00:00:00:02"),
+ VlanId.NONE);
+ configuredInterfaces.put(interfaceSw2Eth1, intfsw2eth1);
+
+ String interfaceSw2Eth1intf2 = "s2-eth1_2";
+ InterfaceIpAddress ia3 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
+ IpPrefix.valueOf("192.168.30.0/24"));
+ Interface intfsw2eth1intf2 = new Interface(s2Eth1,
+ Collections.singleton(ia3),
+ MacAddress.valueOf("00:00:00:00:00:03"),
+ VlanId.NONE);
+ configuredInterfaces.put(interfaceSw2Eth1intf2, intfsw2eth1intf2);
+
+ expect(interfaceService.getInterfacesByPort(s1Eth1))
+ .andReturn(Collections.singleton(intfsw1eth1)).anyTimes();
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.10.101")))
+ .andReturn(Collections.singleton(intfsw1eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.10.1")))
+ .andReturn(intfsw1eth1).anyTimes();
+ expect(interfaceService.getInterfacesByPort(s2Eth1))
+ .andReturn(Collections.singleton(intfsw2eth1)).anyTimes();
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.20.101")))
+ .andReturn(Collections.singleton(intfsw2eth1)).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.20.1")))
+ .andReturn(intfsw2eth1).anyTimes();
+
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.30.101")))
+ .andReturn(Collections.singleton(intfsw2eth1intf2)).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.30.1")))
+ .andReturn(intfsw2eth1intf2).anyTimes();
+
+ // Non-existent interface used during one of the tests
+ expect(interfaceService.getInterfacesByPort(new ConnectPoint(
+ DeviceId.deviceId(SdnIp.dpidToUri("00:00:00:00:00:00:01:00")),
+ PortNumber.portNumber(1))))
+ .andReturn(null).anyTimes();
+
+ expect(interfaceService.getInterfaces()).andReturn(
+ Sets.newHashSet(configuredInterfaces.values())).anyTimes();
+
+ return configuredInterfaces;
+ }
+
+ /**
+ * Sets up BGP daemon peers.
+ *
+ * @return configured BGP peers as a MAP from peer IP address to BgpPeer
+ */
+ private Map<IpAddress, BgpPeer> setUpPeers() {
+
+ configuredPeers = new HashMap<>();
+
+ String peerSw1Eth1 = "192.168.10.1";
+ configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
+ new BgpPeer(dpid1, 1, peerSw1Eth1));
+
+ // Two BGP peers are connected to switch 2 port 1.
+ String peer1Sw2Eth1 = "192.168.20.1";
+ configuredPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
+ new BgpPeer(dpid2, 1, peer1Sw2Eth1));
+
+ String peer2Sw2Eth1 = "192.168.30.1";
+ configuredPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
+ new BgpPeer(dpid2, 1, peer2Sw2Eth1));
+
+ return configuredPeers;
+ }
+
+ /**
+ * Sets up expected point to point intent list.
+ *
+ * @return point to point intent list
+ */
+ private List<PointToPointIntent> setUpIntentList() {
+
+ intentList = new ArrayList<>();
+
+ setUpBgpIntents();
+ setUpIcmpIntents();
+
+ return intentList;
+
+ }
+
+ /**
+ * Constructs a BGP intent and put it into the intentList.
+ * <p/>
+ * The purpose of this method is too simplify the setUpBgpIntents() method,
+ * and to make the setUpBgpIntents() easy to read.
+ *
+ * @param srcPrefix source IP prefix to match
+ * @param dstPrefix destination IP prefix to match
+ * @param srcTcpPort source TCP port to match
+ * @param dstTcpPort destination TCP port to match
+ * @param srcConnectPoint source connect point for PointToPointIntent
+ * @param dstConnectPoint destination connect point for PointToPointIntent
+ */
+ private void bgpPathintentConstructor(String srcPrefix, String dstPrefix,
+ Short srcTcpPort, Short dstTcpPort,
+ ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+
+ TrafficSelector.Builder builder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_TCP)
+ .matchIPSrc(IpPrefix.valueOf(srcPrefix))
+ .matchIPDst(IpPrefix.valueOf(dstPrefix));
+
+ if (srcTcpPort != null) {
+ builder.matchTcpSrc(TpPort.tpPort(srcTcpPort));
+ }
+ if (dstTcpPort != null) {
+ builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
+ }
+
+ PointToPointIntent intent = PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(builder.build())
+ .treatment(noTreatment)
+ .ingressPoint(srcConnectPoint)
+ .egressPoint(dstConnectPoint)
+ .build();
+
+ intentList.add(intent);
+ }
+
+ /**
+ * Sets up intents for BGP paths.
+ */
+ private void setUpBgpIntents() {
+
+ Short bgpPort = 179;
+
+ // Start to build intents between BGP speaker1 and BGP peer1
+ bgpPathintentConstructor(
+ "192.168.10.101/32", "192.168.10.1/32", null, bgpPort,
+ s1Eth100, s1Eth1);
+ bgpPathintentConstructor(
+ "192.168.10.101/32", "192.168.10.1/32", bgpPort, null,
+ s1Eth100, s1Eth1);
+
+ bgpPathintentConstructor(
+ "192.168.10.1/32", "192.168.10.101/32", null, bgpPort,
+ s1Eth1, s1Eth100);
+ bgpPathintentConstructor(
+ "192.168.10.1/32", "192.168.10.101/32", bgpPort, null,
+ s1Eth1, s1Eth100);
+
+ // Start to build intents between BGP speaker1 and BGP peer2
+ bgpPathintentConstructor(
+ "192.168.20.101/32", "192.168.20.1/32", null, bgpPort,
+ s1Eth100, s2Eth1);
+ bgpPathintentConstructor(
+ "192.168.20.101/32", "192.168.20.1/32", bgpPort, null,
+ s1Eth100, s2Eth1);
+
+ bgpPathintentConstructor(
+ "192.168.20.1/32", "192.168.20.101/32", null, bgpPort,
+ s2Eth1, s1Eth100);
+ bgpPathintentConstructor(
+ "192.168.20.1/32", "192.168.20.101/32", bgpPort, null,
+ s2Eth1, s1Eth100);
+
+ //
+ // Start to build intents between BGP speaker3 and BGP peer1
+ bgpPathintentConstructor(
+ "192.168.30.101/32", "192.168.30.1/32", null, bgpPort,
+ s1Eth100, s2Eth1);
+ bgpPathintentConstructor(
+ "192.168.30.101/32", "192.168.30.1/32", bgpPort, null,
+ s1Eth100, s2Eth1);
+
+ bgpPathintentConstructor(
+ "192.168.30.1/32", "192.168.30.101/32", null, bgpPort,
+ s2Eth1, s1Eth100);
+ bgpPathintentConstructor(
+ "192.168.30.1/32", "192.168.30.101/32", bgpPort, null,
+ s2Eth1, s1Eth100);
+ }
+
+ /**
+ * Constructs a BGP intent and put it into the intentList.
+ * <p/>
+ * The purpose of this method is too simplify the setUpBgpIntents() method,
+ * and to make the setUpBgpIntents() easy to read.
+ *
+ * @param srcPrefix source IP prefix to match
+ * @param dstPrefix destination IP prefix to match
+ * @param srcConnectPoint source connect point for PointToPointIntent
+ * @param dstConnectPoint destination connect point for PointToPointIntent
+ */
+ private void icmpPathintentConstructor(String srcPrefix, String dstPrefix,
+ ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+ .matchIPSrc(IpPrefix.valueOf(srcPrefix))
+ .matchIPDst(IpPrefix.valueOf(dstPrefix))
+ .build();
+
+ PointToPointIntent intent = PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(selector)
+ .treatment(noTreatment)
+ .ingressPoint(srcConnectPoint)
+ .egressPoint(dstConnectPoint)
+ .build();
+
+ intentList.add(intent);
+ }
+
+ /**
+ * Sets up intents for ICMP paths.
+ */
+ private void setUpIcmpIntents() {
+ // Start to build intents between BGP speaker1 and BGP peer1
+ icmpPathintentConstructor(
+ "192.168.10.101/32", "192.168.10.1/32", s1Eth100, s1Eth1);
+ icmpPathintentConstructor(
+ "192.168.10.1/32", "192.168.10.101/32", s1Eth1, s1Eth100);
+
+ // Start to build intents between BGP speaker1 and BGP peer2
+ icmpPathintentConstructor(
+ "192.168.20.101/32", "192.168.20.1/32", s1Eth100, s2Eth1);
+ icmpPathintentConstructor(
+ "192.168.20.1/32", "192.168.20.101/32", s2Eth1, s1Eth100);
+
+ icmpPathintentConstructor(
+ "192.168.30.101/32", "192.168.30.1/32", s1Eth100, s2Eth1);
+ icmpPathintentConstructor(
+ "192.168.30.1/32", "192.168.30.101/32", s2Eth1, s1Eth100);
+ }
+
+ /**
+ * Initializes peer connectivity testing environment.
+ *
+ * @throws TestUtilsException if exceptions when using TestUtils
+ */
+ private void initPeerConnectivity() throws TestUtilsException {
+ expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
+ expect(bgpConfig.bgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
+ replay(bgpConfig);
+ expect(networkConfigService.getConfig(APPID, BgpConfig.class)).andReturn(bgpConfig).anyTimes();
+ replay(networkConfigService);
+ replay(routingConfig);
+ replay(interfaceService);
+
+ intentService = createMock(IntentService.class);
+ replay(intentService);
+
+ intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+ null, routingConfig,
+ interfaceService);
+ intentSynchronizer.leaderChanged(true);
+ TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+
+ peerConnectivityManager =
+ new PeerConnectivityManager(APPID, intentSynchronizer,
+ networkConfigService,
+ CONFIG_APP_ID,
+ interfaceService);
+ }
+
+ /**
+ * Tests whether peer connectivity manager can set up correct BGP and
+ * ICMP intents according to specific configuration.
+ * <p/>
+ * Two tricky cases included in the configuration are: 2 peers on a same
+ * switch port, peer on the same switch with BGPd.
+ */
+ @Test
+ public void testConnectionSetup() {
+
+ reset(intentService);
+
+ // Setup the expected intents
+ for (Intent intent : intentList) {
+ intentService.submit(eqExceptId(intent));
+ }
+ replay(intentService);
+
+ // Running the interface to be tested.
+ peerConnectivityManager.start();
+
+ verify(intentService);
+
+ }
+
+ /**
+ * Tests a corner case, when there are no interfaces in the configuration.
+ */
+ @Test
+ public void testNullInterfaces() {
+ reset(interfaceService);
+
+ expect(interfaceService.getInterfaces()).andReturn(
+ Sets.<Interface>newHashSet()).anyTimes();
+ expect(interfaceService.getInterfacesByPort(s2Eth1))
+ .andReturn(Collections.emptySet()).anyTimes();
+ expect(interfaceService.getInterfacesByPort(s1Eth1))
+ .andReturn(Collections.emptySet()).anyTimes();
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.10.101")))
+ .andReturn(Collections.emptySet()).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.10.1")))
+ .andReturn(null).anyTimes();
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.20.101")))
+ .andReturn(Collections.emptySet()).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.20.1")))
+ .andReturn(null).anyTimes();
+ expect(interfaceService.getInterfacesByIp(IpAddress.valueOf("192.168.30.101")))
+ .andReturn(Collections.emptySet()).anyTimes();
+ expect(interfaceService.getMatchingInterface(IpAddress.valueOf("192.168.30.1")))
+ .andReturn(null).anyTimes();
+
+ replay(interfaceService);
+
+ reset(intentService);
+ replay(intentService);
+ peerConnectivityManager.start();
+ verify(intentService);
+ }
+
+ /**
+ * Tests a corner case, when there is no BGP speakers in the configuration.
+ */
+ @Test
+ public void testNullBgpSpeakers() {
+ reset(routingConfig);
+ reset(bgpConfig);
+
+ expect(bgpConfig.bgpSpeakers()).andReturn(Collections.emptySet()).anyTimes();
+ replay(bgpConfig);
+ expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
+ replay(routingConfig);
+
+ reset(intentService);
+ replay(intentService);
+ peerConnectivityManager.start();
+ verify(intentService);
+ }
+
+ /**
+ * Tests a corner case, when there is no Interface configured for one BGP
+ * peer.
+ */
+ @Test
+ public void testNoPeerInterface() {
+ String peerSw100Eth1 = "192.168.200.1";
+ configuredPeers.put(IpAddress.valueOf(peerSw100Eth1),
+ new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1));
+ testConnectionSetup();
+ }
+
+ /**
+ * Tests a corner case, when there is no Interface configured for one BGP
+ * speaker.
+ */
+ @Ignore
+ @Test
+ public void testNoSpeakerInterface() {
+ BgpSpeaker bgpSpeaker100 = new BgpSpeaker(
+ "bgpSpeaker100",
+ "00:00:00:00:00:00:01:00", 100,
+ "00:00:00:00:01:00");
+ List<InterfaceAddress> interfaceAddresses100 = new LinkedList<>();
+ interfaceAddresses100.add(new InterfaceAddress(dpid1, 1, "192.168.10.201"));
+ interfaceAddresses100.add(new InterfaceAddress(dpid2, 1, "192.168.20.201"));
+ bgpSpeaker100.setInterfaceAddresses(interfaceAddresses100);
+ testConnectionSetup();
+ }
+}
diff --git a/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java
new file mode 100644
index 00000000..69b18aa9
--- /dev/null
+++ b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014-2015 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.onosproject.sdnip;
+
+import org.easymock.IArgumentMatcher;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
+
+import static org.easymock.EasyMock.reportMatcher;
+
+/**
+ * Helper class for testing operations submitted to the IntentService.
+ */
+public final class TestIntentServiceHelper {
+ /**
+ * Default constructor to prevent instantiation.
+ */
+ private TestIntentServiceHelper() {
+ }
+
+ /**
+ * Matcher method to set the expected intent to match against
+ * (ignoring the intent ID for the intent).
+ *
+ * @param intent the expected Intent
+ * @return the submitted Intent
+ */
+ static Intent eqExceptId(Intent intent) {
+ reportMatcher(new IdAgnosticIntentMatcher(intent));
+ return intent;
+ }
+
+ /*
+ * EasyMock matcher that matches {@link Intent} but
+ * ignores the {@link IntentId} when matching.
+ * <p/>
+ * The normal intent equals method tests that the intent IDs are equal,
+ * however in these tests we can't know what the intent IDs will be in
+ * advance, so we can't set up expected intents with the correct IDs. Thus,
+ * the solution is to use an EasyMock matcher that verifies that all the
+ * value properties of the provided intent match the expected values, but
+ * ignores the intent ID when testing equality.
+ *
+ * FIXME this currently does not take key into account
+ */
+ private static final class IdAgnosticIntentMatcher implements
+ IArgumentMatcher {
+
+ private final Intent intent;
+ private String providedString;
+
+ /**
+ * Constructor taking the expected intent to match against.
+ *
+ * @param intent the expected intent
+ */
+ public IdAgnosticIntentMatcher(Intent intent) {
+ this.intent = intent;
+ }
+
+ @Override
+ public void appendTo(StringBuffer strBuffer) {
+ strBuffer.append("IntentMatcher unable to match: "
+ + providedString);
+ }
+
+ @Override
+ public boolean matches(Object object) {
+ if (!(object instanceof Intent)) {
+ return false;
+ }
+
+ Intent providedIntent = (Intent) object;
+ providedString = providedIntent.toString();
+
+ IntentKey thisIntentKey = new IntentKey(intent);
+ IntentKey providedIntentKey = new IntentKey(providedIntent);
+ return thisIntentKey.equals(providedIntentKey);
+ }
+ }
+
+}