add a vBNG application

This is a virtual Broadband Network Gateway (BNG) application.
It mainly has 3 functions:
(1) assigns and replies a public IP address to a REST request
    with a private IP address
(2) maintains the mapping from the private IP address to the
    public IP address
(3) installs point to point intents for the host configured
    with private IP address to access Internet

Change-Id: Ie78614a1703422a57f3c325540b927cc4ae603f4
diff --git a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java
new file mode 100644
index 0000000..a84e3e1
--- /dev/null
+++ b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java
@@ -0,0 +1,305 @@
+/*
+ * 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.virtualbng;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+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.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a virtual Broadband Network Gateway (BNG) application. It mainly
+ * has 3 functions:
+ * (1) assigns and replies a public IP address to a REST request with a private
+ * IP address
+ * (2) maintains the mapping from the private IP address to the public IP address
+ * (3) installs point to point intents for the host configured with private IP
+ * address to access Internet
+ */
+@Component(immediate = true)
+@Service
+public class VbngManager implements VbngService {
+
+    private static final String APP_NAME = "org.onosproject.virtualbng";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VbngConfigurationService vbngConfigurationService;
+
+    private ApplicationId appId;
+    private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost;
+    private Map<IpAddress, PointToPointIntent> p2pIntentsToHost;
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+        p2pIntentsFromHost = new ConcurrentHashMap<>();
+        p2pIntentsToHost = new ConcurrentHashMap<>();
+        log.info("vBNG Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("vBNG Stopped");
+    }
+
+    @Override
+    public IpAddress createVbng(IpAddress privateIpAddress) {
+
+        IpAddress nextHopIpAddress =
+                vbngConfigurationService.getNextHopIpAddress();
+        if (nextHopIpAddress == null) {
+            log.info("Did not find next hop IP address");
+            return null;
+        }
+
+        IpAddress publicIpAddress =
+                vbngConfigurationService.getAvailablePublicIpAddress(
+                                                     privateIpAddress);
+        if (publicIpAddress == null) {
+            log.info("Did not find an available public IP address to use.");
+            return null;
+        }
+        log.info("Private IP to Public IP mapping: {} --> {}",
+                 privateIpAddress, publicIpAddress);
+
+        // Setup paths between the host configured with private IP and
+        // next hop
+        setupForwardingPaths(privateIpAddress, publicIpAddress,
+                             nextHopIpAddress);
+        return publicIpAddress;
+    }
+
+    /**
+     * Sets up forwarding paths in both two directions between host configured
+     * with private IP and next hop.
+     *
+     * @param privateIp the private IP address of a local host
+     * @param publicIp the public IP address assigned for the private IP address
+     * @param nextHopIpAddress the next hop IP address outside local SDN network
+     */
+    private void setupForwardingPaths(IpAddress privateIp, IpAddress publicIp,
+                                      IpAddress nextHopIpAddress) {
+        checkNotNull(privateIp);
+        checkNotNull(publicIp);
+        checkNotNull(nextHopIpAddress);
+
+        // If there are already intents for private IP address in the system,
+        // we will do nothing and directly return.
+        if (p2pIntentsFromHost.containsKey(privateIp)
+                && p2pIntentsToHost.containsKey(privateIp)) {
+            return;
+        }
+
+        Host localHost = null;
+        Host nextHopHost = null;
+        if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) {
+            nextHopHost = hostService.getHostsByIp(nextHopIpAddress)
+                    .iterator().next();
+        } else {
+            // TODO to write a new thread to install intents after ONOS
+            // discovers the next hop host
+            hostService.startMonitoringIp(nextHopIpAddress);
+            if (hostService.getHostsByIp(privateIp).isEmpty()) {
+                hostService.startMonitoringIp(privateIp);
+            }
+            return;
+        }
+
+        if (!hostService.getHostsByIp(privateIp).isEmpty()) {
+            localHost =
+                    hostService.getHostsByIp(privateIp).iterator().next();
+        } else {
+            // TODO to write a new thread to install intents after ONOS
+            // discovers the next hop host
+            hostService.startMonitoringIp(privateIp);
+            return;
+        }
+
+        ConnectPoint nextHopConnectPoint =
+                new ConnectPoint(nextHopHost.location().elementId(),
+                                 nextHopHost.location().port());
+        ConnectPoint localHostConnectPoint =
+                new ConnectPoint(localHost.location().elementId(),
+                                 localHost.location().port());
+
+        // Generate and install intent for traffic from host configured with
+        // private IP
+        if (!p2pIntentsFromHost.containsKey(privateIp)) {
+            PointToPointIntent toNextHopIntent
+                    = srcMatchIntentGenerator(privateIp,
+                                              publicIp,
+                                              nextHopHost.mac(),
+                                              nextHopConnectPoint,
+                                              localHostConnectPoint
+                                              );
+            p2pIntentsFromHost.put(privateIp, toNextHopIntent);
+            intentService.submit(toNextHopIntent);
+        }
+
+        // Generate and install intent for traffic to host configured with
+        // private IP
+        if (!p2pIntentsToHost.containsKey(privateIp)) {
+            PointToPointIntent toLocalHostIntent
+                    = dstMatchIntentGenerator(publicIp,
+                                              privateIp,
+                                              localHost.mac(),
+                                              localHostConnectPoint,
+                                              nextHopConnectPoint);
+            p2pIntentsToHost.put(nextHopIpAddress, toLocalHostIntent);
+            intentService.submit(toLocalHostIntent);
+        }
+
+        return;
+    }
+
+    /**
+     * PointToPointIntent Generator.
+     * <p>
+     * The intent will match the source IP address in packet, rewrite the
+     * source IP address, and rewrite the destination MAC address.
+     * </p>
+     *
+     * @param srcIpAddress the source IP address in packet to match
+     * @param newSrcIpAddress the new source IP address to set
+     * @param dstMacAddress the destination MAC address to set
+     * @param dstConnectPoint the egress point
+     * @param srcConnectPoint the ingress point
+     * @return a PointToPointIntent
+     */
+    private PointToPointIntent srcMatchIntentGenerator(
+                                             IpAddress srcIpAddress,
+                                             IpAddress newSrcIpAddress,
+                                             MacAddress dstMacAddress,
+                                             ConnectPoint dstConnectPoint,
+                                             ConnectPoint srcConnectPoint) {
+        checkNotNull(srcIpAddress);
+        checkNotNull(newSrcIpAddress);
+        checkNotNull(dstMacAddress);
+        checkNotNull(dstConnectPoint);
+        checkNotNull(srcConnectPoint);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress,
+                                             IpPrefix.MAX_INET_MASK_LENGTH));
+
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        treatment.setEthDst(dstMacAddress);
+        treatment.setIpSrc(newSrcIpAddress);
+
+        Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId);
+        PointToPointIntent intent = PointToPointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .egressPoint(dstConnectPoint)
+                .ingressPoint(srcConnectPoint)
+                .build();
+
+        log.info("Generated a PointToPointIntent for traffic from local host "
+                + ": {}", intent);
+        return intent;
+    }
+
+    /**
+     * PointToPointIntent Generator.
+     * <p>
+     * The intent will match the destination IP address in packet, rewrite the
+     * destination IP address, and rewrite the destination MAC address.
+     * </p>
+     *
+     * @param dstIpAddress the destination IP address in packet to match
+     * @param newDstIpAddress the new destination IP address to set
+     * @param dstMacAddress the destination MAC address to set
+     * @param dstConnectPoint the egress point
+     * @param srcConnectPoint the ingress point
+     * @return a PointToPointIntent
+     */
+    private PointToPointIntent dstMatchIntentGenerator(
+                                                IpAddress dstIpAddress,
+                                                IpAddress newDstIpAddress,
+                                                MacAddress dstMacAddress,
+                                                ConnectPoint dstConnectPoint,
+                                                ConnectPoint srcConnectPoint) {
+        checkNotNull(dstIpAddress);
+        checkNotNull(newDstIpAddress);
+        checkNotNull(dstMacAddress);
+        checkNotNull(dstConnectPoint);
+        checkNotNull(srcConnectPoint);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchIPDst(IpPrefix.valueOf(dstIpAddress,
+                                             IpPrefix.MAX_INET_MASK_LENGTH));
+
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        treatment.setEthDst(dstMacAddress);
+        treatment.setIpDst(newDstIpAddress);
+
+        Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId);
+        PointToPointIntent intent = PointToPointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .egressPoint(dstConnectPoint)
+                .ingressPoint(srcConnectPoint)
+                .build();
+        log.info("Generated a PointToPointIntent for traffic to local host "
+                + ": {}", intent);
+
+        return intent;
+    }
+
+
+}