From 6a07d2d622eaa06953f3353e39c080984076e8de Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Fri, 9 Oct 2015 18:32:44 -0700 Subject: Updated master to commit id 6ee8aa3e67ce89908a8c93aa9445c6f71a18f986 Change-Id: I94b055ee2f298daf71e2ec794fd0f2495bd8081f --- .../org/onosproject/sdnip/IntentSynchronizer.java | 901 +++------------------ .../java/org/onosproject/sdnip/IntentUtils.java | 81 ++ .../onosproject/sdnip/PeerConnectivityManager.java | 74 +- .../src/main/java/org/onosproject/sdnip/SdnIp.java | 18 +- .../main/java/org/onosproject/sdnip/SdnIpFib.java | 216 +++++ .../sdnip/cli/PrimaryChangeCommand.java | 2 +- .../java/org/onosproject/sdnip/IntentSyncTest.java | 460 +++-------- .../sdnip/PeerConnectivityManagerTest.java | 81 +- .../java/org/onosproject/sdnip/SdnIpFibTest.java | 417 ++++++++++ .../onosproject/sdnip/TestIntentServiceHelper.java | 7 +- 10 files changed, 1028 insertions(+), 1229 deletions(-) create mode 100644 framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java create mode 100644 framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java create mode 100644 framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java (limited to 'framework/src/onos/apps/sdnip') 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 index d8d8f45d..eaabed33 100644 --- 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 @@ -15,118 +15,79 @@ */ 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.onosproject.routing.IntentSynchronizationService; 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; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.groupedThreads; /** * 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 CONSTRAINTS - = ImmutableList.of(new PartialFailureConstraint()); +public class IntentSynchronizer implements IntentSynchronizationService { 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 peerIntents; - private final Map routeIntents; + + private final Map intents; // // 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 + */ + IntentSynchronizer(ApplicationId appId, IntentService intentService) { + this(appId, intentService, + newSingleThreadExecutor(groupedThreads("onos/sdnip", "sync"))); + } /** * 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 + * @param executorService executor service for synchronization thread */ IntentSynchronizer(ApplicationId appId, IntentService intentService, - HostService hostService, - RoutingConfigurationService configService, - InterfaceService interfaceService) { + ExecutorService executorService) { this.appId = appId; this.intentService = intentService; - this.hostService = hostService; - this.interfaceService = interfaceService; - peerIntents = new ConcurrentHashMap<>(); - routeIntents = new ConcurrentHashMap<>(); - this.configService = configService; + intents = new ConcurrentHashMap<>(); - bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder() - .setNameFormat("sdnip-intents-synchronizer-%d").build()); + bgpIntentsSynchronizerExecutor = executorService; } /** * Starts the synchronizer. */ public void start() { - bgpIntentsSynchronizerExecutor.execute(this::doIntentSynchronizationThread); + } /** @@ -187,794 +148,118 @@ public class IntentSynchronizer implements FibListener, IntentRequestListener { } } - /** - * 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 getRouteIntents() { - List result = new LinkedList<>(); - - for (Map.Entry 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 intents) { + @Override + public void submit(Intent intent) { synchronized (this) { - // Store the intents in memory - for (PointToPointIntent intent : intents) { - peerIntents.put(new IntentKey(intent), intent); - } - - // Push the intents + intents.put(intent.key(), intent); 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); - } + log.trace("SDN-IP Submitting intent: {}", 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) { + @Override + public void withdraw(Intent intent) { synchronized (this) { - // Store the intent in memory - routeIntents.put(ipPrefix, intent); - - // Push the intent + intents.remove(intent.key(), intent); if (isElectedLeader && isActivatedLeader) { - log.trace("SDN-IP submitting reactive routing intent: {}", intent); - intentService.submit(intent); + log.trace("SDN-IP Withdrawing intent: {}", intent); + intentService.withdraw(intent); } } } /** - * Generates a route intent for a prefix, the next hop IP address, and - * the next hop MAC address. - *

- * 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. + * Signals the synchronizer that the SDN-IP leadership has changed. * - * @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 + * @param isLeader true if this instance is now the leader, otherwise false */ - 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 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 ingressPoints = - configService.getBgpPeerConnectPoints(); - - TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); - - if (hostIpAddress.isIp4()) { - selector.matchEthType(Ethernet.TYPE_IPV4); - } else { - selector.matchEthType(Ethernet.TYPE_IPV6); - } + public void leaderChanged(boolean isLeader) { + log.debug("SDN-IP Leader changed: {}", isLeader); - // 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; + if (!isLeader) { + this.isElectedLeader = false; + this.isActivatedLeader = false; + return; // Nothing to do } + this.isActivatedLeader = false; + this.isElectedLeader = true; - 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 updates, Collection 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); - } - } - } + // Run the synchronization method off-thread + bgpIntentsSynchronizerExecutor.execute(this::synchronizeIntents); } - /** - * Synchronize the in-memory Intents with the Intents in the Intent - * framework. - */ - void synchronizeIntents() { - synchronized (this) { - - Map localIntents = new HashMap<>(); - Map fetchedIntents = new HashMap<>(); - Collection storeInMemoryIntents = new LinkedList<>(); - Collection addIntents = new LinkedList<>(); - Collection 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); + private void synchronizeIntents() { + Map serviceIntents = new HashMap<>(); + intentService.getIntents().forEach(i -> { + if (i.appId().equals(appId)) { + serviceIntents.put(i.key(), i); } + }); - // 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); + List intentsToAdd = new LinkedList<>(); + List intentsToRemove = new LinkedList<>(); - // - // 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 + for (Intent localIntent : intents.values()) { + Intent serviceIntent = serviceIntents.remove(localIntent.key()); + if (serviceIntent == null) { + intentsToAdd.add(localIntent); } 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 localIntents, - final Map fetchedIntents, - Collection storeInMemoryIntents, - Collection addIntents, - Collection 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 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; + IntentState state = intentService.getIntentState(serviceIntent.key()); + if (!IntentUtils.equals(serviceIntent, localIntent) || state == null || + state == IntentState.WITHDRAW_REQ || + state == IntentState.WITHDRAWING || + state == IntentState.WITHDRAWN) { + intentsToAdd.add(localIntent); + } } - storeInMemoryIntents.add(fetchedIntent); } - for (Map.Entry entry : fetchedIntents.entrySet()) { - IntentKey intentKey = entry.getKey(); - Intent fetchedIntent = entry.getValue(); - Intent localIntent = localIntents.get(intentKey); - - if (localIntent != null) { - continue; + for (Intent serviceIntent : serviceIntents.values()) { + IntentState state = intentService.getIntentState(serviceIntent.key()); + if (state != null && state != IntentState.WITHDRAW_REQ + && state != IntentState.WITHDRAWING + && state != IntentState.WITHDRAWN) { + intentsToRemove.add(serviceIntent); } - - 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()); - } + log.debug("SDN-IP Intent Synchronizer: submitting {}, withdrawing {}", + intentsToAdd.size(), intentsToRemove.size()); - /** - * 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()); + // Withdraw Intents + for (Intent intent : intentsToRemove) { + intentService.withdraw(intent); + log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}", + intent); } - - @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; + if (!isElectedLeader) { + log.debug("SDN-IP Intent Synchronizer: cannot withdraw intents: " + + "not elected leader anymore"); + isActivatedLeader = false; + return; } - } - @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; - } + // Add Intents + for (Intent intent : intentsToAdd) { + intentService.submit(intent); + log.trace("SDN-IP Intent Synchronizer: submitting intent: {}", + intent); } - if (dstMacAddress == null) { - hostService.startMonitoringIp(dstIpAddress); + if (!isElectedLeader) { + log.debug("SDN-IP Intent Synchronizer: cannot submit intents: " + + "not elected leader anymore"); + isActivatedLeader = false; 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; + if (isElectedLeader) { + isActivatedLeader = true; // Allow push of Intents } else { - // There is no existing intent, create a new one. - MultiPointToSinglePointIntent dstToSrcIntent = - hostToHostIntentGenerator(srcIpAddress, srcConnectPoint, - srcMacAddress, dstConnectPoint); - submitReactiveIntent(srcIpPrefix, dstToSrcIntent); + isActivatedLeader = false; } + log.debug("SDN-IP intent synchronization completed"); } - /** - * 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 ingressPoints = new HashSet<>(); - 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 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/IntentUtils.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java new file mode 100644 index 00000000..8e2a3df3 --- /dev/null +++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 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.onosproject.net.intent.Intent; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Utilities for dealing with intents. + */ +public final class IntentUtils { + + private static final Logger log = LoggerFactory.getLogger(IntentUtils.class); + + private IntentUtils() { + + } + + /** + * Checks if two intents represent the same value. + * + *

({@link Intent#equals(Object)} only checks ID equality)

+ * + *

Both intents must be of the same type.

+ * + * @param one first intent + * @param two second intent + * @return true if the two intents represent the same value, otherwise false + */ + public static boolean equals(Intent one, Intent two) { + checkArgument(one.getClass() == two.getClass(), + "Intents are not the same type"); + + if (!(Objects.equals(one.appId(), two.appId()) && + Objects.equals(one.key(), two.key()))) { + return false; + } + + if (one instanceof MultiPointToSinglePointIntent) { + MultiPointToSinglePointIntent intent1 = (MultiPointToSinglePointIntent) one; + MultiPointToSinglePointIntent intent2 = (MultiPointToSinglePointIntent) two; + + return Objects.equals(intent1.selector(), intent2.selector()) && + Objects.equals(intent1.treatment(), intent2.treatment()) && + Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) && + Objects.equals(intent1.egressPoint(), intent2.egressPoint()); + } else if (one instanceof PointToPointIntent) { + PointToPointIntent intent1 = (PointToPointIntent) one; + PointToPointIntent intent2 = (PointToPointIntent) two; + + return Objects.equals(intent1.selector(), intent2.selector()) && + Objects.equals(intent1.treatment(), intent2.treatment()) && + Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) && + Objects.equals(intent1.egressPoint(), intent2.egressPoint()); + } else { + log.error("Unimplemented intent type"); + return false; + } + } +} 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 index 459db2b7..b2ce0f8a 100644 --- 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 @@ -15,6 +15,8 @@ */ package org.onosproject.sdnip; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import org.onlab.packet.Ethernet; import org.onlab.packet.IPv4; import org.onlab.packet.IPv6; @@ -22,16 +24,18 @@ 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.config.NetworkConfigService; 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.Key; import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.routing.IntentSynchronizationService; import org.onosproject.routing.RoutingService; import org.onosproject.routing.config.BgpConfig; import org.slf4j.Logger; @@ -49,18 +53,26 @@ import static com.google.common.base.Preconditions.checkNotNull; public class PeerConnectivityManager { private static final int PRIORITY_OFFSET = 1000; + private static final String SUFFIX_DST = "dst"; + private static final String SUFFIX_SRC = "src"; + private static final String SUFFIX_ICMP = "icmp"; + private static final Logger log = LoggerFactory.getLogger( PeerConnectivityManager.class); private static final short BGP_PORT = 179; - private final IntentSynchronizer intentSynchronizer; + private final IntentSynchronizationService intentSynchronizer; private final NetworkConfigService configService; private final InterfaceService interfaceService; private final ApplicationId appId; private final ApplicationId routerAppId; + // Just putting something random here for now. Figure out exactly what + // indexes we need when we start making use of them. + private final Multimap peerIntents; + /** * Creates a new PeerConnectivityManager. * @@ -71,7 +83,7 @@ public class PeerConnectivityManager { * @param routerAppId application ID */ public PeerConnectivityManager(ApplicationId appId, - IntentSynchronizer intentSynchronizer, + IntentSynchronizationService intentSynchronizer, NetworkConfigService configService, ApplicationId routerAppId, InterfaceService interfaceService) { @@ -80,6 +92,8 @@ public class PeerConnectivityManager { this.configService = configService; this.routerAppId = routerAppId; this.interfaceService = interfaceService; + + peerIntents = HashMultimap.create(); } /** @@ -100,8 +114,6 @@ public class PeerConnectivityManager { * BGP speakers and external BGP peers. */ private void setUpConnectivity() { - List intents = new ArrayList<>(); - BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS); if (config == null) { @@ -113,11 +125,12 @@ public class PeerConnectivityManager { log.debug("Start to set up BGP paths for BGP speaker: {}", bgpSpeaker); - intents.addAll(buildSpeakerIntents(bgpSpeaker)); - } + buildSpeakerIntents(bgpSpeaker).forEach(i -> { + peerIntents.put(bgpSpeaker, i); + intentSynchronizer.submit(i); + }); - // Submit all the intents. - intentSynchronizer.submitPeerIntents(intents); + } } private Collection buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker) { @@ -167,8 +180,8 @@ public class PeerConnectivityManager { List intents = new ArrayList<>(); TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); - TrafficSelector selector; + Key key; byte tcpProtocol; byte icmpProtocol; @@ -188,8 +201,11 @@ public class PeerConnectivityManager { null, BGP_PORT); + key = buildKey(ipOne, ipTwo, SUFFIX_DST); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portOne) @@ -204,8 +220,11 @@ public class PeerConnectivityManager { BGP_PORT, null); + key = buildKey(ipOne, ipTwo, SUFFIX_SRC); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portOne) @@ -220,8 +239,11 @@ public class PeerConnectivityManager { null, BGP_PORT); + key = buildKey(ipTwo, ipOne, SUFFIX_DST); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portTwo) @@ -236,8 +258,11 @@ public class PeerConnectivityManager { BGP_PORT, null); + key = buildKey(ipTwo, ipOne, SUFFIX_SRC); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portTwo) @@ -252,8 +277,11 @@ public class PeerConnectivityManager { null, null); + key = buildKey(ipOne, ipTwo, SUFFIX_ICMP); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portOne) @@ -268,8 +296,11 @@ public class PeerConnectivityManager { null, null); + key = buildKey(ipTwo, ipOne, SUFFIX_ICMP); + intents.add(PointToPointIntent.builder() .appId(appId) + .key(key) .selector(selector) .treatment(treatment) .ingressPoint(portTwo) @@ -316,4 +347,27 @@ public class PeerConnectivityManager { return builder.build(); } + /** + * Builds an intent Key for a point-to-point intent based off the source + * and destination IP address, as well as a suffix String to distinguish + * between different types of intents between the same source and + * destination. + * + * @param srcIp source IP address + * @param dstIp destination IP address + * @param suffix suffix string + * @return + */ + private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) { + String keyString = new StringBuilder() + .append(srcIp.toString()) + .append("-") + .append(dstIp.toString()) + .append("-") + .append(suffix) + .toString(); + + return Key.of(keyString, appId); + } + } 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 index 3d1fe65c..1b3eda9d 100644 --- 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 @@ -32,7 +32,9 @@ 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.IntentSynchronizationService; import org.onosproject.routing.RoutingService; +import org.onosproject.routing.SdnIpService; import org.onosproject.routing.config.RoutingConfigurationService; import org.slf4j.Logger; @@ -79,6 +81,7 @@ public class SdnIp implements SdnIpService { private IntentSynchronizer intentSynchronizer; private PeerConnectivityManager peerConnectivity; + private SdnIpFib fib; private LeadershipEventListener leadershipEventListener = new InnerLeadershipEventListener(); @@ -93,10 +96,7 @@ public class SdnIp implements SdnIpService { localControllerNode = clusterService.getLocalNode(); - intentSynchronizer = new IntentSynchronizer(appId, intentService, - hostService, - config, - interfaceService); + intentSynchronizer = new IntentSynchronizer(appId, intentService); intentSynchronizer.start(); peerConnectivity = new PeerConnectivityManager(appId, @@ -106,8 +106,9 @@ public class SdnIp implements SdnIpService { interfaceService); peerConnectivity.start(); - routingService.addFibListener(intentSynchronizer); - routingService.addIntentRequestListener(intentSynchronizer); + fib = new SdnIpFib(appId, interfaceService, intentSynchronizer); + + routingService.addFibListener(fib); routingService.start(); leadershipService.addListener(leadershipEventListener); @@ -131,6 +132,11 @@ public class SdnIp implements SdnIpService { intentSynchronizer.leaderChanged(isPrimary); } + @Override + public IntentSynchronizationService getIntentSynchronizationService() { + return intentSynchronizer; + } + /** * Converts DPIDs of the form xx:xx:xx:xx:xx:xx:xx to OpenFlow provider * device URIs. diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java new file mode 100644 index 00000000..c0001bdc --- /dev/null +++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java @@ -0,0 +1,216 @@ +/* + * Copyright 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 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.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.net.intent.constraint.PartialFailureConstraint; +import org.onosproject.routing.FibListener; +import org.onosproject.routing.FibUpdate; +import org.onosproject.routing.IntentSynchronizationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * FIB component of SDN-IP. + */ +public class SdnIpFib implements FibListener { + private Logger log = LoggerFactory.getLogger(getClass()); + + private static final int PRIORITY_OFFSET = 100; + private static final int PRIORITY_MULTIPLIER = 5; + protected static final ImmutableList CONSTRAINTS + = ImmutableList.of(new PartialFailureConstraint()); + + private final Map routeIntents; + + private final ApplicationId appId; + private final InterfaceService interfaceService; + private final IntentSynchronizationService intentSynchronizer; + + /** + * Class constructor. + * + * @param appId application ID to use when generating intents + * @param interfaceService interface service + * @param intentSynchronizer intent synchronizer + */ + public SdnIpFib(ApplicationId appId, InterfaceService interfaceService, + IntentSynchronizationService intentSynchronizer) { + routeIntents = new ConcurrentHashMap<>(); + + this.appId = appId; + this.interfaceService = interfaceService; + this.intentSynchronizer = intentSynchronizer; + } + + + @Override + public void update(Collection updates, Collection withdraws) { + int submitCount = 0, withdrawCount = 0; + // + // NOTE: Semantically, we MUST withdraw existing intents before + // submitting new intents. + // + synchronized (this) { + MultiPointToSinglePointIntent intent; + + // + // 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; + } + intentSynchronizer.withdraw(intent); + withdrawCount++; + } + + // + // 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; + } + + routeIntents.put(prefix, intent); + intentSynchronizer.submit(intent); + submitCount++; + } + + log.debug("SDN-IP submitted {}/{}, withdrew = {}/{}", submitCount, + updates.size(), withdrawCount, withdraws.size()); + } + } + + /** + * Generates a route intent for a prefix, the next hop IP address, and + * the next hop MAC address. + *

+ * 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 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(); + } + +} 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 index 72cc112e..7a17cfe0 100644 --- 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 @@ -18,7 +18,7 @@ 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; +import org.onosproject.routing.SdnIpService; /** * Command to change whether this SDNIP instance is primary or not. 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 index fc5782e4..6dc3ce10 100644 --- 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 @@ -16,6 +16,7 @@ package org.onosproject.sdnip; import com.google.common.collect.Sets; +import com.google.common.util.concurrent.MoreExecutors; import org.junit.Before; import org.junit.Test; import org.onlab.junit.TestUtils; @@ -27,10 +28,9 @@ import org.onlab.packet.IpAddress; import org.onlab.packet.IpPrefix; import org.onlab.packet.MacAddress; import org.onlab.packet.VlanId; +import org.onosproject.TestApplicationId; 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; @@ -43,20 +43,13 @@ 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.Key; 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; @@ -64,11 +57,8 @@ 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 @@ -76,10 +66,7 @@ import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId; */ 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"), @@ -100,65 +87,18 @@ public class IntentSyncTest extends AbstractIntentTest { private IntentSynchronizer intentSynchronizer; private final Set interfaces = Sets.newHashSet(); - private static final ApplicationId APPID = new ApplicationId() { - @Override - public short id() { - return 1; - } - - @Override - public String name() { - return "SDNIP"; - } - }; + private static final ApplicationId APPID = TestApplicationId.create("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 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(); + MoreExecutors.newDirectExecutorService()); } /** @@ -200,267 +140,13 @@ public class IntentSyncTest extends AbstractIntentTest { 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 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 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 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. + * Tests the synchronization behavior of intent synchronizer. We set up + * a discrepancy between the intent service state and the intent + * synchronizer's state and ensure that this is reconciled correctly. * * @throws TestUtilsException */ @@ -529,27 +215,13 @@ public class IntentSyncTest extends AbstractIntentTest { // 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)); + assertThat(IntentUtils.equals(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 - 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 intents = new HashSet<>(); intents.add(intent1); expect(intentService.getIntentState(intent1.key())) @@ -568,9 +240,9 @@ public class IntentSyncTest extends AbstractIntentTest { .andReturn(IntentState.WITHDRAWING).anyTimes(); expect(intentService.getIntents()).andReturn(intents).anyTimes(); + // These are the operations that should be done to the intentService + // during synchronization intentService.withdraw(intent2); - intentService.withdraw(intent4); - intentService.submit(intent3); intentService.submit(intent4Update); intentService.submit(intent6); @@ -578,16 +250,101 @@ public class IntentSyncTest extends AbstractIntentTest { replay(intentService); // Start the test + + // Simulate some input from the clients. The intent synchronizer has not + // gained the global leadership yet, but it will remember this input for + // when it does. + intentSynchronizer.submit(intent1); + intentSynchronizer.submit(intent2); + intentSynchronizer.withdraw(intent2); + intentSynchronizer.submit(intent3); + intentSynchronizer.submit(intent4); + intentSynchronizer.submit(intent4Update); + intentSynchronizer.submit(intent5); + intentSynchronizer.submit(intent6); + intentSynchronizer.submit(intent7); + + // Give the leadership to the intent synchronizer. It will now attempt + // to synchronize the intents in the store with the intents it has + // recorded based on the earlier user input. + intentSynchronizer.leaderChanged(true); + + verify(intentService); + } + + /** + * Tests the behavior of the submit API, both when the synchronizer has + * leadership and when it does not. + */ + @Test + public void testSubmit() { + IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24"); + Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1); + + // Set up expectations + intentService.submit(intent); + expect(intentService.getIntents()).andReturn(Collections.emptyList()) + .anyTimes(); + replay(intentService); + + // Give the intent synchronizer leadership so it will submit intents + // to the intent service + intentSynchronizer.leaderChanged(true); + + // Test the submit + intentSynchronizer.submit(intent); + + verify(intentService); + + // Now we'll remove leadership from the intent synchronizer and verify + // that it does not submit any intents to the intent service when we + // call the submit API + reset(intentService); + replay(intentService); + + intentSynchronizer.leaderChanged(false); + + intentSynchronizer.submit(intent); + + verify(intentService); + } + + /** + * Tests the behavior of the withdraw API, both when the synchronizer has + * leadership and when it does not. + */ + @Test + public void testWithdraw() { + IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24"); + Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1); + + // Submit an intent first so we can withdraw it later + intentService.submit(intent); + intentService.withdraw(intent); + expect(intentService.getIntents()).andReturn(Collections.emptyList()) + .anyTimes(); + replay(intentService); + + // Give the intent synchronizer leadership so it will submit intents + // to the intent service 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)); + // Test the submit then withdraw + intentSynchronizer.submit(intent); + intentSynchronizer.withdraw(intent); + + verify(intentService); + + // Now we'll remove leadership from the intent synchronizer and verify + // that it does not withdraw any intents to the intent service when we + // call the withdraw API + reset(intentService); + replay(intentService); + + intentSynchronizer.leaderChanged(false); + + intentSynchronizer.submit(intent); + intentSynchronizer.withdraw(intent); verify(intentService); } @@ -607,10 +364,10 @@ public class IntentSyncTest extends AbstractIntentTest { TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); if (ipPrefix.isIp4()) { - selectorBuilder.matchEthType(Ethernet.TYPE_IPV4); // IPv4 + selectorBuilder.matchEthType(Ethernet.TYPE_IPV4); selectorBuilder.matchIPDst(ipPrefix); } else { - selectorBuilder.matchEthType(Ethernet.TYPE_IPV6); // IPv6 + selectorBuilder.matchEthType(Ethernet.TYPE_IPV6); selectorBuilder.matchIPv6Dst(ipPrefix); } @@ -628,11 +385,12 @@ public class IntentSyncTest extends AbstractIntentTest { MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder() .appId(APPID) + .key(Key.of(ipPrefix.toString(), APPID)) .selector(selectorBuilder.build()) .treatment(treatmentBuilder.build()) .ingressPoints(ingressPoints) .egressPoint(egressPoint) - .constraints(IntentSynchronizer.CONSTRAINTS) + .constraints(SdnIpFib.CONSTRAINTS) .build(); return intent; } @@ -646,7 +404,7 @@ public class IntentSyncTest extends AbstractIntentTest { * @return the newly constructed MultiPointToSinglePointIntent * @throws TestUtilsException */ - private MultiPointToSinglePointIntent staticIntentBuilder( + private MultiPointToSinglePointIntent staticIntentBuilder( MultiPointToSinglePointIntent intent, RouteEntry routeEntry, String nextHopMacAddress) throws TestUtilsException { 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 index d89c3c2b..c4b2daad 100644 --- 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 @@ -19,7 +19,6 @@ 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; @@ -28,13 +27,14 @@ import org.onlab.packet.IpPrefix; import org.onlab.packet.MacAddress; import org.onlab.packet.TpPort; import org.onlab.packet.VlanId; +import org.onosproject.TestApplicationId; 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.config.NetworkConfigService; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; @@ -42,8 +42,9 @@ 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.Key; import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.routing.IntentSynchronizationService; import org.onosproject.routing.config.BgpConfig; import org.onosproject.routing.config.BgpPeer; import org.onosproject.routing.config.BgpSpeaker; @@ -71,26 +72,15 @@ import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId; */ 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 APPID = TestApplicationId.create("foo"); private static final ApplicationId CONFIG_APP_ID = APPID; private PeerConnectivityManager peerConnectivityManager; - private IntentSynchronizer intentSynchronizer; + private IntentSynchronizationService intentSynchronizer; private RoutingConfigurationService routingConfig; private InterfaceService interfaceService; private NetworkConfigService networkConfigService; - private IntentService intentService; private Set bgpSpeakers; private Map interfaces; @@ -98,8 +88,6 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { private BgpConfig bgpConfig; - private Map configuredInterfaces; - private Map configuredPeers; private List intentList; private final String dpid1 = "00:00:00:00:00:00:00:01"; @@ -136,7 +124,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { // These will set expectations on routingConfig and interfaceService bgpSpeakers = setUpBgpSpeakers(); interfaces = Collections.unmodifiableMap(setUpInterfaces()); - peers = Collections.unmodifiableMap(setUpPeers()); + peers = setUpPeers(); initPeerConnectivity(); intentList = setUpIntentList(); @@ -169,11 +157,11 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { * Sets up logical interfaces, which emulate the configured interfaces * in SDN-IP application. * - * @return configured interfaces as a MAP from Interface name to Interface + * @return configured interfaces as a map from interface name to Interface */ private Map setUpInterfaces() { - configuredInterfaces = new HashMap<>(); + Map configuredInterfaces = new HashMap<>(); String interfaceSw1Eth1 = "s1-eth1"; InterfaceIpAddress ia1 = @@ -242,7 +230,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { */ private Map setUpPeers() { - configuredPeers = new HashMap<>(); + Map configuredPeers = new HashMap<>(); String peerSw1Eth1 = "192.168.10.1"; configuredPeers.put(IpAddress.valueOf(peerSw1Eth1), @@ -266,14 +254,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { * @return point to point intent list */ private List setUpIntentList() { - intentList = new ArrayList<>(); setUpBgpIntents(); setUpIcmpIntents(); return intentList; - } /** @@ -306,8 +292,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { builder.matchTcpDst(TpPort.tpPort(dstTcpPort)); } + Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0] + + "-" + ((srcTcpPort == null) ? "dst" : "src"), APPID); + PointToPointIntent intent = PointToPointIntent.builder() .appId(APPID) + .key(key) .selector(builder.build()) .treatment(noTreatment) .ingressPoint(srcConnectPoint) @@ -392,8 +382,12 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { .matchIPDst(IpPrefix.valueOf(dstPrefix)) .build(); + Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0] + + "-" + "icmp", APPID); + PointToPointIntent intent = PointToPointIntent.builder() .appId(APPID) + .key(key) .selector(selector) .treatment(noTreatment) .ingressPoint(srcConnectPoint) @@ -434,19 +428,14 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes(); expect(bgpConfig.bgpSpeakers()).andReturn(bgpSpeakers).anyTimes(); replay(bgpConfig); - expect(networkConfigService.getConfig(APPID, BgpConfig.class)).andReturn(bgpConfig).anyTimes(); + 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); + intentSynchronizer = createMock(IntentSynchronizationService.class); + replay(intentSynchronizer); peerConnectivityManager = new PeerConnectivityManager(APPID, intentSynchronizer, @@ -464,20 +453,18 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { */ @Test public void testConnectionSetup() { - - reset(intentService); + reset(intentSynchronizer); // Setup the expected intents for (Intent intent : intentList) { - intentService.submit(eqExceptId(intent)); + intentSynchronizer.submit(eqExceptId(intent)); } - replay(intentService); + replay(intentSynchronizer); // Running the interface to be tested. peerConnectivityManager.start(); - verify(intentService); - + verify(intentSynchronizer); } /** @@ -488,7 +475,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { reset(interfaceService); expect(interfaceService.getInterfaces()).andReturn( - Sets.newHashSet()).anyTimes(); + Sets.newHashSet()).anyTimes(); expect(interfaceService.getInterfacesByPort(s2Eth1)) .andReturn(Collections.emptySet()).anyTimes(); expect(interfaceService.getInterfacesByPort(s1Eth1)) @@ -508,10 +495,10 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { replay(interfaceService); - reset(intentService); - replay(intentService); + reset(intentSynchronizer); + replay(intentSynchronizer); peerConnectivityManager.start(); - verify(intentService); + verify(intentSynchronizer); } /** @@ -527,10 +514,10 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes(); replay(routingConfig); - reset(intentService); - replay(intentService); + reset(intentSynchronizer); + replay(intentSynchronizer); peerConnectivityManager.start(); - verify(intentService); + verify(intentSynchronizer); } /** @@ -540,7 +527,7 @@ public class PeerConnectivityManagerTest extends AbstractIntentTest { @Test public void testNoPeerInterface() { String peerSw100Eth1 = "192.168.200.1"; - configuredPeers.put(IpAddress.valueOf(peerSw100Eth1), + peers.put(IpAddress.valueOf(peerSw100Eth1), new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1)); testConnectionSetup(); } diff --git a/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java new file mode 100644 index 00000000..5466d520 --- /dev/null +++ b/framework/src/onos/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java @@ -0,0 +1,417 @@ +/* + * Copyright 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.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.TestApplicationId; +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.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.Key; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.routing.FibEntry; +import org.onosproject.routing.FibUpdate; +import org.onosproject.routing.IntentSynchronizationService; +import org.onosproject.routing.config.BgpPeer; +import org.onosproject.routing.config.RoutingConfigurationService; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +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 SdnIpFib. + */ +public class SdnIpFibTest extends AbstractIntentTest { + + private RoutingConfigurationService routingConfig; + private InterfaceService interfaceService; + + 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 SdnIpFib sdnipFib; + private IntentSynchronizationService intentSynchronizer; + private final Set interfaces = Sets.newHashSet(); + + private static final ApplicationId APPID = TestApplicationId.create("SDNIP"); + + @Before + public void setUp() throws Exception { + super.setUp(); + + routingConfig = createMock(RoutingConfigurationService.class); + interfaceService = createMock(InterfaceService.class); + + // These will set expectations on routingConfig and interfaceService + setUpInterfaceService(); + setUpBgpPeers(); + + replay(routingConfig); + replay(interfaceService); + + intentSynchronizer = createMock(IntentSynchronizationService.class); + + sdnipFib = new SdnIpFib(APPID, interfaceService, intentSynchronizer); + } + + /** + * Sets up BGP peers in external networks. + */ + private void setUpBgpPeers() { + + Map 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 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 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 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. + */ + @Test + public void testFibAdd() { + IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24"); + FibEntry fibEntry = new FibEntry(prefix, + 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 ingressPoints = new HashSet<>(); + ingressPoints.add(SW2_ETH1); + ingressPoints.add(SW3_ETH1); + ingressPoints.add(SW4_ETH1); + + MultiPointToSinglePointIntent intent = + MultiPointToSinglePointIntent.builder() + .appId(APPID) + .key(Key.of(prefix.toString(), APPID)) + .selector(selectorBuilder.build()) + .treatment(treatmentBuilder.build()) + .ingressPoints(ingressPoints) + .egressPoint(SW1_ETH1) + .constraints(SdnIpFib.CONSTRAINTS) + .build(); + + // Setup the expected intents + intentSynchronizer.submit(eqExceptId(intent)); + replay(intentSynchronizer); + + // Send in the UPDATE FibUpdate + FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry); + sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList()); + + verify(intentSynchronizer); + } + + /** + * 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. + */ + @Test + public void testFibAddWithVlan() { + IpPrefix prefix = Ip4Prefix.valueOf("3.3.3.0/24"); + FibEntry fibEntry = new FibEntry(prefix, + 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 ingressPoints = new HashSet<>(); + ingressPoints.add(SW1_ETH1); + ingressPoints.add(SW2_ETH1); + ingressPoints.add(SW3_ETH1); + + MultiPointToSinglePointIntent intent = + MultiPointToSinglePointIntent.builder() + .appId(APPID) + .key(Key.of(prefix.toString(), APPID)) + .selector(selectorBuilder.build()) + .treatment(treatmentBuilder.build()) + .ingressPoints(ingressPoints) + .egressPoint(SW4_ETH1) + .constraints(SdnIpFib.CONSTRAINTS) + .build(); + + // Setup the expected intents + intentSynchronizer.submit(eqExceptId(intent)); + + replay(intentSynchronizer); + + // Send in the UPDATE FibUpdate + FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry); + sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList()); + + verify(intentSynchronizer); + } + + /** + * Tests updating a FIB entry. + * + * We verify that the synchronizer records the correct state and that the + * correct intent is submitted to the IntentService. + */ + @Test + public void testFibUpdate() { + // Firstly add a route + testFibAdd(); + + IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24"); + + // Start to construct a new route entry and new intent + FibEntry fibEntryUpdate = new FibEntry(prefix, + 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 ingressPointsNew = new HashSet<>(); + ingressPointsNew.add(SW1_ETH1); + ingressPointsNew.add(SW3_ETH1); + ingressPointsNew.add(SW4_ETH1); + + MultiPointToSinglePointIntent intentNew = + MultiPointToSinglePointIntent.builder() + .appId(APPID) + .key(Key.of(prefix.toString(), APPID)) + .selector(selectorBuilderNew.build()) + .treatment(treatmentBuilderNew.build()) + .ingressPoints(ingressPointsNew) + .egressPoint(SW2_ETH1) + .constraints(SdnIpFib.CONSTRAINTS) + .build(); + + // Set up test expectation + reset(intentSynchronizer); + + // Setup the expected intents + intentSynchronizer.submit(eqExceptId(intentNew)); + replay(intentSynchronizer); + + // Send in the UPDATE FibUpdate + FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, + fibEntryUpdate); + sdnipFib.update(Collections.singletonList(fibUpdate), + Collections.emptyList()); + + verify(intentSynchronizer); + } + + /** + * Tests deleting a FIB entry. + * + * We verify that the synchronizer records the correct state and that the + * correct intent is withdrawn from the IntentService. + */ + @Test + public void testFibDelete() { + // Firstly add a route + testFibAdd(); + + IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24"); + + // Construct the existing route entry + FibEntry fibEntry = new FibEntry(prefix, null, null); + + // Construct the existing MultiPointToSinglePoint 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 ingressPoints = new HashSet<>(); + ingressPoints.add(SW2_ETH1); + ingressPoints.add(SW3_ETH1); + ingressPoints.add(SW4_ETH1); + + MultiPointToSinglePointIntent addedIntent = + MultiPointToSinglePointIntent.builder() + .appId(APPID) + .key(Key.of(prefix.toString(), APPID)) + .selector(selectorBuilder.build()) + .treatment(treatmentBuilder.build()) + .ingressPoints(ingressPoints) + .egressPoint(SW1_ETH1) + .constraints(SdnIpFib.CONSTRAINTS) + .build(); + + // Set up expectation + reset(intentSynchronizer); + // Setup the expected intents + intentSynchronizer.withdraw(eqExceptId(addedIntent)); + replay(intentSynchronizer); + + // Send in the DELETE FibUpdate + FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry); + sdnipFib.update(Collections.emptyList(), Collections.singletonList(fibUpdate)); + + verify(intentSynchronizer); + } +} 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 index 69b18aa9..7f825e81 100644 --- 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 @@ -17,7 +17,6 @@ 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; @@ -53,8 +52,6 @@ public final class TestIntentServiceHelper { * 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 { @@ -86,9 +83,7 @@ public final class TestIntentServiceHelper { Intent providedIntent = (Intent) object; providedString = providedIntent.toString(); - IntentKey thisIntentKey = new IntentKey(intent); - IntentKey providedIntentKey = new IntentKey(providedIntent); - return thisIntentKey.equals(providedIntentKey); + return IntentUtils.equals(intent, providedIntent); } } -- cgit 1.2.3-korg