diff options
Diffstat (limited to 'framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java')
-rw-r--r-- | framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java | 980 |
1 files changed, 980 insertions, 0 deletions
diff --git a/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java new file mode 100644 index 00000000..35ff7054 --- /dev/null +++ b/framework/src/onos/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java @@ -0,0 +1,980 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.sdnip; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.core.ApplicationId; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Host; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.intent.constraint.PartialFailureConstraint; +import org.onosproject.routing.FibListener; +import org.onosproject.routing.FibUpdate; +import org.onosproject.routing.IntentRequestListener; +import org.onosproject.routing.config.RoutingConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Synchronizes intents between the in-memory intent store and the + * IntentService. + */ +public class IntentSynchronizer implements FibListener, IntentRequestListener { + private static final int PRIORITY_OFFSET = 100; + private static final int PRIORITY_MULTIPLIER = 5; + protected static final ImmutableList<Constraint> CONSTRAINTS + = ImmutableList.of(new PartialFailureConstraint()); + + private static final Logger log = + LoggerFactory.getLogger(IntentSynchronizer.class); + + private final ApplicationId appId; + private final IntentService intentService; + private final HostService hostService; + private final InterfaceService interfaceService; + private final Map<IntentKey, PointToPointIntent> peerIntents; + private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents; + + // + // State to deal with SDN-IP Leader election and pushing Intents + // + private final ExecutorService bgpIntentsSynchronizerExecutor; + private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0); + private volatile boolean isElectedLeader = false; + private volatile boolean isActivatedLeader = false; + + private final RoutingConfigurationService configService; + + /** + * Class constructor. + * + * @param appId the Application ID + * @param intentService the intent service + * @param hostService the host service + * @param configService the SDN-IP configuration service + * @param interfaceService the interface service + */ + IntentSynchronizer(ApplicationId appId, IntentService intentService, + HostService hostService, + RoutingConfigurationService configService, + InterfaceService interfaceService) { + this.appId = appId; + this.intentService = intentService; + this.hostService = hostService; + this.interfaceService = interfaceService; + peerIntents = new ConcurrentHashMap<>(); + routeIntents = new ConcurrentHashMap<>(); + + this.configService = configService; + + bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setNameFormat("sdnip-intents-synchronizer-%d").build()); + } + + /** + * Starts the synchronizer. + */ + public void start() { + bgpIntentsSynchronizerExecutor.execute(this::doIntentSynchronizationThread); + } + + /** + * Stops the synchronizer. + */ + public void stop() { + synchronized (this) { + // Stop the thread(s) + bgpIntentsSynchronizerExecutor.shutdownNow(); + + // + // Withdraw all SDN-IP intents + // + if (!isElectedLeader) { + return; // Nothing to do: not the leader anymore + } + + // + // NOTE: We don't withdraw the intents during shutdown, because + // it creates flux in the data plane during switchover. + // + + /* + // + // Build a batch operation to withdraw all intents from this + // application. + // + log.debug("SDN-IP Intent Synchronizer shutdown: " + + "withdrawing all intents..."); + IntentOperations.Builder builder = IntentOperations.builder(appId); + for (Intent intent : intentService.getIntents()) { + // Skip the intents from other applications + if (!intent.appId().equals(appId)) { + continue; + } + + // Skip the intents that are already withdrawn + IntentState intentState = + intentService.getIntentState(intent.id()); + if ((intentState == null) || + intentState.equals(IntentState.WITHDRAWING) || + intentState.equals(IntentState.WITHDRAWN)) { + continue; + } + + log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}", + intent); + builder.addWithdrawOperation(intent.id()); + } + IntentOperations intentOperations = builder.build(); + intentService.execute(intentOperations); + leaderChanged(false); + + peerIntents.clear(); + routeIntents.clear(); + log.debug("SDN-IP Intent Synchronizer shutdown completed"); + */ + } + } + + /** + * Signals the synchronizer that the SDN-IP leadership has changed. + * + * @param isLeader true if this instance is now the leader, otherwise false + */ + public void leaderChanged(boolean isLeader) { + log.debug("SDN-IP Leader changed: {}", isLeader); + + if (!isLeader) { + this.isElectedLeader = false; + this.isActivatedLeader = false; + return; // Nothing to do + } + this.isActivatedLeader = false; + this.isElectedLeader = true; + + // + // Tell the Intents Synchronizer thread to start the synchronization + // + intentsSynchronizerSemaphore.release(); + } + + /** + * Gets the route intents. + * + * @return the route intents + */ + public Collection<MultiPointToSinglePointIntent> getRouteIntents() { + List<MultiPointToSinglePointIntent> result = new LinkedList<>(); + + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : + routeIntents.entrySet()) { + result.add(entry.getValue()); + } + return result; + } + + /** + * Thread for Intent Synchronization. + */ + private void doIntentSynchronizationThread() { + boolean interrupted = false; + try { + while (!interrupted) { + try { + intentsSynchronizerSemaphore.acquire(); + // + // Drain all permits, because a single synchronization is + // sufficient. + // + intentsSynchronizerSemaphore.drainPermits(); + } catch (InterruptedException e) { + interrupted = true; + break; + } + synchronizeIntents(); + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Submits a collection of point-to-point intents. + * + * @param intents the intents to submit + */ + void submitPeerIntents(Collection<PointToPointIntent> intents) { + synchronized (this) { + // Store the intents in memory + for (PointToPointIntent intent : intents) { + peerIntents.put(new IntentKey(intent), intent); + } + + // Push the intents + if (isElectedLeader && isActivatedLeader) { + log.debug("SDN-IP Submitting all Peer Intents..."); + for (Intent intent : intents) { + log.trace("SDN-IP Submitting intents: {}", intent); + intentService.submit(intent); + } + } + } + } + + /** + * Submits a MultiPointToSinglePointIntent for reactive routing. + * + * @param ipPrefix the IP prefix to match in a MultiPointToSinglePointIntent + * @param intent the intent to submit + */ + void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) { + synchronized (this) { + // Store the intent in memory + routeIntents.put(ipPrefix, intent); + + // Push the intent + if (isElectedLeader && isActivatedLeader) { + log.trace("SDN-IP submitting reactive routing intent: {}", intent); + intentService.submit(intent); + } + } + } + + /** + * Generates a route intent for a prefix, the next hop IP address, and + * the next hop MAC address. + * <p/> + * This method will find the egress interface for the intent. + * Intent will match dst IP prefix and rewrite dst MAC address at all other + * border switches, then forward packets according to dst MAC address. + * + * @param prefix IP prefix of the route to add + * @param nextHopIpAddress IP address of the next hop + * @param nextHopMacAddress MAC address of the next hop + * @return the generated intent, or null if no intent should be submitted + */ + private MultiPointToSinglePointIntent generateRouteIntent( + IpPrefix prefix, + IpAddress nextHopIpAddress, + MacAddress nextHopMacAddress) { + + // Find the attachment point (egress interface) of the next hop + Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress); + if (egressInterface == null) { + log.warn("No outgoing interface found for {}", + nextHopIpAddress); + return null; + } + + // + // Generate the intent itself + // + Set<ConnectPoint> ingressPorts = new HashSet<>(); + ConnectPoint egressPort = egressInterface.connectPoint(); + log.debug("Generating intent for prefix {}, next hop mac {}", + prefix, nextHopMacAddress); + + for (Interface intf : interfaceService.getInterfaces()) { + // TODO this should be only peering interfaces + if (!intf.connectPoint().equals(egressInterface.connectPoint())) { + ConnectPoint srcPort = intf.connectPoint(); + ingressPorts.add(srcPort); + } + } + + // Match the destination IP prefix at the first hop + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + if (prefix.isIp4()) { + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(prefix); + } else { + selector.matchEthType(Ethernet.TYPE_IPV6); + selector.matchIPv6Dst(prefix); + } + + // Rewrite the destination MAC address + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder() + .setEthDst(nextHopMacAddress); + if (!egressInterface.vlan().equals(VlanId.NONE)) { + treatment.setVlanId(egressInterface.vlan()); + // If we set VLAN ID, we have to make sure a VLAN tag exists. + // TODO support no VLAN -> VLAN routing + selector.matchVlanId(VlanId.ANY); + } + + int priority = + prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET; + Key key = Key.of(prefix.toString(), appId); + return MultiPointToSinglePointIntent.builder() + .appId(appId) + .key(key) + .selector(selector.build()) + .treatment(treatment.build()) + .ingressPoints(ingressPorts) + .egressPoint(egressPort) + .priority(priority) + .constraints(CONSTRAINTS) + .build(); + } + + @Override + public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) { + checkNotNull(hostIpAddress); + Set<ConnectPoint> ingressPoints = + configService.getBgpPeerConnectPoints(); + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + + if (hostIpAddress.isIp4()) { + selector.matchEthType(Ethernet.TYPE_IPV4); + } else { + selector.matchEthType(Ethernet.TYPE_IPV6); + } + + // Match the destination IP prefix at the first hop + IpPrefix ipPrefix = hostIpAddress.toIpPrefix(); + selector.matchIPDst(ipPrefix); + + // Rewrite the destination MAC address + MacAddress hostMac = null; + ConnectPoint egressPoint = null; + for (Host host : hostService.getHostsByIp(hostIpAddress)) { + if (host.mac() != null) { + hostMac = host.mac(); + egressPoint = host.location(); + break; + } + } + if (hostMac == null) { + hostService.startMonitoringIp(hostIpAddress); + return; + } + + TrafficTreatment.Builder treatment = + DefaultTrafficTreatment.builder().setEthDst(hostMac); + Key key = Key.of(ipPrefix.toString(), appId); + int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER + + PRIORITY_OFFSET; + MultiPointToSinglePointIntent intent = + MultiPointToSinglePointIntent.builder() + .appId(appId) + .key(key) + .selector(selector.build()) + .treatment(treatment.build()) + .ingressPoints(ingressPoints) + .egressPoint(egressPoint) + .priority(priority) + .constraints(CONSTRAINTS) + .build(); + + log.trace("Generates ConnectivityInternetToHost intent {}", intent); + submitReactiveIntent(ipPrefix, intent); + } + + + @Override + public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) { + // + // NOTE: Semantically, we MUST withdraw existing intents before + // submitting new intents. + // + synchronized (this) { + MultiPointToSinglePointIntent intent; + + log.debug("SDN-IP submitting intents = {} withdrawing = {}", + updates.size(), withdraws.size()); + + // + // Prepare the Intent batch operations for the intents to withdraw + // + for (FibUpdate withdraw : withdraws) { + checkArgument(withdraw.type() == FibUpdate.Type.DELETE, + "FibUpdate with wrong type in withdraws list"); + + IpPrefix prefix = withdraw.entry().prefix(); + intent = routeIntents.remove(prefix); + if (intent == null) { + log.trace("SDN-IP No intent in routeIntents to delete " + + "for prefix: {}", prefix); + continue; + } + if (isElectedLeader && isActivatedLeader) { + log.trace("SDN-IP Withdrawing intent: {}", intent); + intentService.withdraw(intent); + } + } + + // + // Prepare the Intent batch operations for the intents to submit + // + for (FibUpdate update : updates) { + checkArgument(update.type() == FibUpdate.Type.UPDATE, + "FibUpdate with wrong type in updates list"); + + IpPrefix prefix = update.entry().prefix(); + intent = generateRouteIntent(prefix, update.entry().nextHopIp(), + update.entry().nextHopMac()); + + if (intent == null) { + // This preserves the old semantics - if an intent can't be + // generated, we don't do anything with that prefix. But + // perhaps we should withdraw the old intent anyway? + continue; + } + + MultiPointToSinglePointIntent oldIntent = + routeIntents.put(prefix, intent); + if (isElectedLeader && isActivatedLeader) { + if (oldIntent != null) { + log.trace("SDN-IP Withdrawing old intent: {}", + oldIntent); + intentService.withdraw(oldIntent); + } + log.trace("SDN-IP Submitting intent: {}", intent); + intentService.submit(intent); + } + } + } + } + + /** + * Synchronize the in-memory Intents with the Intents in the Intent + * framework. + */ + void synchronizeIntents() { + synchronized (this) { + + Map<IntentKey, Intent> localIntents = new HashMap<>(); + Map<IntentKey, Intent> fetchedIntents = new HashMap<>(); + Collection<Intent> storeInMemoryIntents = new LinkedList<>(); + Collection<Intent> addIntents = new LinkedList<>(); + Collection<Intent> deleteIntents = new LinkedList<>(); + + if (!isElectedLeader) { + return; // Nothing to do: not the leader anymore + } + log.debug("SDN-IP synchronizing all intents..."); + + // Prepare the local intents + for (Intent intent : routeIntents.values()) { + localIntents.put(new IntentKey(intent), intent); + } + for (Intent intent : peerIntents.values()) { + localIntents.put(new IntentKey(intent), intent); + } + + // Fetch all intents for this application + for (Intent intent : intentService.getIntents()) { + if (!intent.appId().equals(appId)) { + continue; + } + fetchedIntents.put(new IntentKey(intent), intent); + } + if (log.isDebugEnabled()) { + for (Intent intent: fetchedIntents.values()) { + log.trace("SDN-IP Intent Synchronizer: fetched intent: {}", + intent); + } + } + + computeIntentsDelta(localIntents, fetchedIntents, + storeInMemoryIntents, addIntents, + deleteIntents); + + // + // Perform the actions: + // 1. Store in memory fetched intents that are same. Can be done + // even if we are not the leader anymore + // 2. Delete intents: check if the leader before the operation + // 3. Add intents: check if the leader before the operation + // + for (Intent intent : storeInMemoryIntents) { + // Store the intent in memory based on its type + if (intent instanceof MultiPointToSinglePointIntent) { + MultiPointToSinglePointIntent mp2pIntent = + (MultiPointToSinglePointIntent) intent; + // Find the IP prefix + Criterion c = + mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST); + if (c == null) { + // Try IPv6 + c = + mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST); + } + if (c != null && c instanceof IPCriterion) { + IPCriterion ipCriterion = (IPCriterion) c; + IpPrefix ipPrefix = ipCriterion.ip(); + if (ipPrefix == null) { + continue; + } + log.trace("SDN-IP Intent Synchronizer: updating " + + "in-memory Route Intent for prefix {}", + ipPrefix); + routeIntents.put(ipPrefix, mp2pIntent); + } else { + log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}", + mp2pIntent.id()); + } + continue; + } + if (intent instanceof PointToPointIntent) { + PointToPointIntent p2pIntent = (PointToPointIntent) intent; + log.trace("SDN-IP Intent Synchronizer: updating " + + "in-memory Peer Intent {}", p2pIntent); + peerIntents.put(new IntentKey(intent), p2pIntent); + continue; + } + } + + // Withdraw Intents + for (Intent intent : deleteIntents) { + intentService.withdraw(intent); + log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}", + intent); + } + if (!isElectedLeader) { + log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " + + "not elected leader anymore"); + isActivatedLeader = false; + return; + } + + // Add Intents + for (Intent intent : addIntents) { + intentService.submit(intent); + log.trace("SDN-IP Intent Synchronizer: submitting intent: {}", + intent); + } + if (!isElectedLeader) { + log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " + + "not elected leader anymore"); + isActivatedLeader = false; + return; + } + + if (isElectedLeader) { + isActivatedLeader = true; // Allow push of Intents + } else { + isActivatedLeader = false; + } + log.debug("SDN-IP intent synchronization completed"); + } + } + + /** + * Computes the delta in two sets of Intents: local in-memory Intents, + * and intents fetched from the Intent framework. + * + * @param localIntents the local in-memory Intents + * @param fetchedIntents the Intents fetched from the Intent framework + * @param storeInMemoryIntents the Intents that should be stored in memory. + * Note: This Collection must be allocated by the caller, and it will + * be populated by this method. + * @param addIntents the Intents that should be added to the Intent + * framework. Note: This Collection must be allocated by the caller, and + * it will be populated by this method. + * @param deleteIntents the Intents that should be deleted from the Intent + * framework. Note: This Collection must be allocated by the caller, and + * it will be populated by this method. + */ + private void computeIntentsDelta( + final Map<IntentKey, Intent> localIntents, + final Map<IntentKey, Intent> fetchedIntents, + Collection<Intent> storeInMemoryIntents, + Collection<Intent> addIntents, + Collection<Intent> deleteIntents) { + + // + // Compute the deltas between the LOCAL in-memory Intents and the + // FETCHED Intents: + // - If an Intent is in both the LOCAL and FETCHED sets: + // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then + // the LOCAL Intent should be added/installed; otherwise the + // FETCHED intent should be stored in the local memory + // (i.e., override the LOCAL Intent) to preserve the original + // Intent ID. + // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL + // Intent should be added/installed. + // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED + // Intent should be deleted/withdrawn. + // + for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) { + IntentKey intentKey = entry.getKey(); + Intent localIntent = entry.getValue(); + Intent fetchedIntent = fetchedIntents.get(intentKey); + + if (fetchedIntent == null) { + // + // No FETCHED Intent found: push the LOCAL Intent. + // + addIntents.add(localIntent); + continue; + } + + IntentState state = + intentService.getIntentState(fetchedIntent.key()); + if (state == null || + state == IntentState.WITHDRAWING || + state == IntentState.WITHDRAWN) { + // The intent has been withdrawn but according to our route + // table it should be installed. We'll reinstall it. + addIntents.add(localIntent); + continue; + } + storeInMemoryIntents.add(fetchedIntent); + } + + for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) { + IntentKey intentKey = entry.getKey(); + Intent fetchedIntent = entry.getValue(); + Intent localIntent = localIntents.get(intentKey); + + if (localIntent != null) { + continue; + } + + IntentState state = + intentService.getIntentState(fetchedIntent.key()); + if (state == null || + state == IntentState.WITHDRAWING || + state == IntentState.WITHDRAWN) { + // Nothing to do. The intent has been already withdrawn. + continue; + } + // + // No LOCAL Intent found: delete/withdraw the FETCHED Intent. + // + deleteIntents.add(fetchedIntent); + } + } + + /** + * Helper class that can be used to compute the key for an Intent by + * by excluding the Intent ID. + */ + static final class IntentKey { + private final Intent intent; + + /** + * Constructor. + * + * @param intent the intent to use + */ + IntentKey(Intent intent) { + checkArgument((intent instanceof MultiPointToSinglePointIntent) || + (intent instanceof PointToPointIntent), + "Intent type not recognized", intent); + this.intent = intent; + } + + /** + * Compares two Multi-Point to Single-Point Intents whether they + * represent same logical intention. + * + * @param intent1 the first Intent to compare + * @param intent2 the second Intent to compare + * @return true if both Intents represent same logical intention, + * otherwise false + */ + static boolean equalIntents(MultiPointToSinglePointIntent intent1, + MultiPointToSinglePointIntent intent2) { + return Objects.equals(intent1.appId(), intent2.appId()) && + Objects.equals(intent1.selector(), intent2.selector()) && + Objects.equals(intent1.treatment(), intent2.treatment()) && + Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) && + Objects.equals(intent1.egressPoint(), intent2.egressPoint()); + } + + /** + * Compares two Point-to-Point Intents whether they represent + * same logical intention. + * + * @param intent1 the first Intent to compare + * @param intent2 the second Intent to compare + * @return true if both Intents represent same logical intention, + * otherwise false + */ + static boolean equalIntents(PointToPointIntent intent1, + PointToPointIntent intent2) { + return Objects.equals(intent1.appId(), intent2.appId()) && + Objects.equals(intent1.selector(), intent2.selector()) && + Objects.equals(intent1.treatment(), intent2.treatment()) && + Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) && + Objects.equals(intent1.egressPoint(), intent2.egressPoint()); + } + + @Override + public int hashCode() { + if (intent instanceof PointToPointIntent) { + PointToPointIntent p2pIntent = (PointToPointIntent) intent; + return Objects.hash(p2pIntent.appId(), + p2pIntent.resources(), + p2pIntent.selector(), + p2pIntent.treatment(), + p2pIntent.constraints(), + p2pIntent.ingressPoint(), + p2pIntent.egressPoint()); + } + if (intent instanceof MultiPointToSinglePointIntent) { + MultiPointToSinglePointIntent m2pIntent = + (MultiPointToSinglePointIntent) intent; + return Objects.hash(m2pIntent.appId(), + m2pIntent.resources(), + m2pIntent.selector(), + m2pIntent.treatment(), + m2pIntent.constraints(), + m2pIntent.ingressPoints(), + m2pIntent.egressPoint()); + } + checkArgument(false, "Intent type not recognized", intent); + return 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (!(obj instanceof IntentKey))) { + return false; + } + IntentKey other = (IntentKey) obj; + + if (this.intent instanceof PointToPointIntent) { + if (!(other.intent instanceof PointToPointIntent)) { + return false; + } + return equalIntents((PointToPointIntent) this.intent, + (PointToPointIntent) other.intent); + } + if (this.intent instanceof MultiPointToSinglePointIntent) { + if (!(other.intent instanceof MultiPointToSinglePointIntent)) { + return false; + } + return equalIntents( + (MultiPointToSinglePointIntent) this.intent, + (MultiPointToSinglePointIntent) other.intent); + } + checkArgument(false, "Intent type not recognized", intent); + return false; + } + } + + @Override + public void setUpConnectivityHostToHost(IpAddress dstIpAddress, + IpAddress srcIpAddress, + MacAddress srcMacAddress, + ConnectPoint srcConnectPoint) { + checkNotNull(dstIpAddress); + checkNotNull(srcIpAddress); + checkNotNull(srcMacAddress); + checkNotNull(srcConnectPoint); + + IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix(); + IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix(); + ConnectPoint dstConnectPoint = null; + MacAddress dstMacAddress = null; + + for (Host host : hostService.getHostsByIp(dstIpAddress)) { + if (host.mac() != null) { + dstMacAddress = host.mac(); + dstConnectPoint = host.location(); + break; + } + } + if (dstMacAddress == null) { + hostService.startMonitoringIp(dstIpAddress); + return; + } + + // + // Handle intent from source host to destination host + // + MultiPointToSinglePointIntent srcToDstIntent = + hostToHostIntentGenerator(dstIpAddress, dstConnectPoint, + dstMacAddress, srcConnectPoint); + submitReactiveIntent(dstIpPrefix, srcToDstIntent); + + // + // Handle intent from destination host to source host + // + + // Since we proactively handle the intent from destination host to + // source host, we should check whether there is an exiting intent + // first. + if (mp2pIntentExists(srcIpPrefix)) { + updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint); + return; + } else { + // There is no existing intent, create a new one. + MultiPointToSinglePointIntent dstToSrcIntent = + hostToHostIntentGenerator(srcIpAddress, srcConnectPoint, + srcMacAddress, dstConnectPoint); + submitReactiveIntent(srcIpPrefix, dstToSrcIntent); + } + } + + /** + * Generates MultiPointToSinglePointIntent for both source host and + * destination host located in local SDN network. + * + * @param dstIpAddress the destination IP address + * @param dstConnectPoint the destination host connect point + * @param dstMacAddress the MAC address of destination host + * @param srcConnectPoint the connect point where packet-in from + * @return the generated MultiPointToSinglePointIntent + */ + private MultiPointToSinglePointIntent hostToHostIntentGenerator( + IpAddress dstIpAddress, + ConnectPoint dstConnectPoint, + MacAddress dstMacAddress, + ConnectPoint srcConnectPoint) { + checkNotNull(dstIpAddress); + checkNotNull(dstConnectPoint); + checkNotNull(dstMacAddress); + checkNotNull(srcConnectPoint); + + Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>(); + ingressPoints.add(srcConnectPoint); + IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix(); + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + if (dstIpAddress.isIp4()) { + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(dstIpPrefix); + } else { + selector.matchEthType(Ethernet.TYPE_IPV6); + selector.matchIPv6Dst(dstIpPrefix); + } + + // Rewrite the destination MAC address + TrafficTreatment.Builder treatment = + DefaultTrafficTreatment.builder().setEthDst(dstMacAddress); + + Key key = Key.of(dstIpPrefix.toString(), appId); + int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER + + PRIORITY_OFFSET; + MultiPointToSinglePointIntent intent = + MultiPointToSinglePointIntent.builder() + .appId(appId) + .key(key) + .selector(selector.build()) + .treatment(treatment.build()) + .ingressPoints(ingressPoints) + .egressPoint(dstConnectPoint) + .priority(priority) + .constraints(CONSTRAINTS) + .build(); + + log.trace("Generates ConnectivityHostToHost = {} ", intent); + return intent; + } + + @Override + public void updateExistingMp2pIntent(IpPrefix ipPrefix, + ConnectPoint ingressConnectPoint) { + checkNotNull(ipPrefix); + checkNotNull(ingressConnectPoint); + + MultiPointToSinglePointIntent existingIntent = + getExistingMp2pIntent(ipPrefix); + if (existingIntent != null) { + Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints(); + // Add host connect point into ingressPoints of the existing intent + if (ingressPoints.add(ingressConnectPoint)) { + MultiPointToSinglePointIntent updatedMp2pIntent = + MultiPointToSinglePointIntent.builder() + .appId(appId) + .key(existingIntent.key()) + .selector(existingIntent.selector()) + .treatment(existingIntent.treatment()) + .ingressPoints(ingressPoints) + .egressPoint(existingIntent.egressPoint()) + .priority(existingIntent.priority()) + .constraints(CONSTRAINTS) + .build(); + + log.trace("Update an existing MultiPointToSinglePointIntent " + + "to new intent = {} ", updatedMp2pIntent); + submitReactiveIntent(ipPrefix, updatedMp2pIntent); + } + // If adding ingressConnectPoint to ingressPoints failed, it + // because between the time interval from checking existing intent + // to generating new intent, onos updated this intent due to other + // packet-in and the new intent also includes the + // ingressConnectPoint. This will not affect reactive routing. + } + } + + @Override + public boolean mp2pIntentExists(IpPrefix ipPrefix) { + checkNotNull(ipPrefix); + return routeIntents.get(ipPrefix) != null; + } + + /** + * Gets the existing MultiPointToSinglePointIntent from memory for a given + * IP prefix. + * + * @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent + * @return the MultiPointToSinglePointIntent if found, otherwise null + */ + private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix + ipPrefix) { + checkNotNull(ipPrefix); + return routeIntents.get(ipPrefix); + } +} |