Generalize IntentSynchronizer and separate reactive routing code

 * IntentSynchronizer can now handle any intent rather than having use
   case specific APIs
 * IntentSynchronizer does not generate or store intents anymore, it only
   perform synchronization
 * SdnIpFib generates and manages the procative route-based intents
 * ReactiveRoutingFib generates and manages the reactive intents
 * Unit tests have been tightned up to only test single components, rather
   than multiple components together
 * PeerConnectivityManager uses meaningful keys when creating intents

Change-Id: I4bb036ec8d056f43ece46f7dfc71d5e5a136b77d
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java
new file mode 100644
index 0000000..8e86056
--- /dev/null
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java
@@ -0,0 +1,395 @@
+/*
+ * 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.reactive.routing;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+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.host.HostService;
+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.IntentRequestListener;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * FIB component for reactive routing intents.
+ */
+public class ReactiveRoutingFib implements 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 final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final ApplicationId appId;
+    private final HostService hostService;
+    private final RoutingConfigurationService configService;
+    private final InterfaceService interfaceService;
+    private final IntentSynchronizationService intentSynchronizer;
+
+    private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
+
+    /**
+     * Class constructor.
+     *
+     * @param appId application ID to use to generate intents
+     * @param hostService host service
+     * @param configService routing configuration service
+     * @param interfaceService interface service
+     * @param intentSynchronizer intent synchronization service
+     */
+    public ReactiveRoutingFib(ApplicationId appId, HostService hostService,
+                              RoutingConfigurationService configService,
+                              InterfaceService interfaceService,
+                              IntentSynchronizationService intentSynchronizer) {
+        this.appId = appId;
+        this.hostService = hostService;
+        this.configService = configService;
+        this.interfaceService = interfaceService;
+        this.intentSynchronizer = intentSynchronizer;
+
+        routeIntents = Maps.newConcurrentMap();
+    }
+
+    @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 setUpConnectivityHostToInternet(IpAddress hostIp, IpPrefix prefix,
+                                                IpAddress nextHopIpAddress) {
+        // 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;
+        }
+
+        Set<Host> hosts = hostService.getHostsByIp(nextHopIpAddress);
+        if (hosts.isEmpty()) {
+            log.warn("No host found for next hop IP address");
+            return;
+        }
+        MacAddress nextHopMacAddress = null;
+        for (Host host : hosts) {
+            nextHopMacAddress = host.mac();
+            break;
+        }
+
+        hosts = hostService.getHostsByIp(hostIp);
+        if (hosts.isEmpty()) {
+            log.warn("No host found for host IP address");
+            return;
+        }
+        Host host = hosts.stream().findFirst().get();
+        ConnectPoint ingressPoint = host.location();
+
+        // Generate the intent itself
+        ConnectPoint egressPort = egressInterface.connectPoint();
+        log.debug("Generating intent for prefix {}, next hop mac {}",
+                prefix, nextHopMacAddress);
+
+        // 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() + "-reactive", appId);
+        MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .ingressPoints(Collections.singleton(ingressPoint))
+                .egressPoint(egressPort)
+                .priority(priority)
+                .constraints(CONSTRAINTS)
+                .build();
+
+        submitReactiveIntent(prefix, intent);
+    }
+
+    @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<>();
+        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.
+        }
+    }
+
+    /**
+     * Submits a reactive intent to the intent synchronizer.
+     *
+     * @param ipPrefix IP prefix of the intent
+     * @param intent intent to submit
+     */
+    void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
+        routeIntents.put(ipPrefix, intent);
+
+        intentSynchronizer.submit(intent);
+    }
+
+    /**
+     * 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);
+    }
+
+    @Override
+    public boolean mp2pIntentExists(IpPrefix ipPrefix) {
+        checkNotNull(ipPrefix);
+        return routeIntents.get(ipPrefix) != null;
+    }
+}