Changes to vRouter to accomodate ofdpa and softrouter pipelines.
Adding arp-spa to flow from vRouter to distinguish between multiple untagged
interfaces with the same macAddress.

Change-Id: Ifd6e00f70c538c780c0f5728d9ba960a4c70b1db
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
index 2294435..b78b0ff 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
@@ -22,6 +22,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.EthType;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
@@ -40,8 +41,10 @@
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.config.RouterConfig;
@@ -110,7 +113,7 @@
                 routingAppId, RoutingService.ROUTER_CONFIG_CLASS);
 
         if (config == null) {
-            log.info("Router config not available");
+            log.warn("Router config not available");
             return;
         }
 
@@ -145,6 +148,23 @@
         PortNumber controlPlanePort = controlPlaneConnectPoint.port();
 
         for (InterfaceIpAddress ip : intf.ipAddresses()) {
+            // create nextObjectives for forwarding to this interface and the
+            // controlPlaneConnectPoint
+            int cpNextId, intfNextId;
+            if (intf.vlan() == VlanId.NONE) {
+                cpNextId = createNextObjective(deviceId, controlPlanePort,
+                               VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                               true);
+                intfNextId = createNextObjective(deviceId, intf.connectPoint().port(),
+                               VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                               true);
+            } else {
+                cpNextId = createNextObjective(deviceId, controlPlanePort,
+                                               intf.vlan(), false);
+                intfNextId = createNextObjective(deviceId, intf.connectPoint().port(),
+                                                 intf.vlan(), false);
+            }
+
             // IPv4 to router
             TrafficSelector toSelector = DefaultTrafficSelector.builder()
                     .matchInPort(intf.connectPoint().port())
@@ -154,12 +174,8 @@
                     .matchIPDst(ip.ipAddress().toIpPrefix())
                     .build();
 
-            TrafficTreatment toTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(controlPlanePort)
-                    .build();
-
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(toSelector, toTreatment, true));
+                    buildForwardingObjective(toSelector, null, cpNextId, true));
 
             // IPv4 from router
             TrafficSelector fromSelector = DefaultTrafficSelector.builder()
@@ -170,12 +186,8 @@
                     .matchIPSrc(ip.ipAddress().toIpPrefix())
                     .build();
 
-            TrafficTreatment intfTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(intf.connectPoint().port())
-                    .build();
-
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(fromSelector, intfTreatment, true));
+                    buildForwardingObjective(fromSelector, null, intfNextId, true));
 
             // ARP to router
             toSelector = DefaultTrafficSelector.builder()
@@ -184,13 +196,12 @@
                     .matchVlanId(intf.vlan())
                     .build();
 
-            toTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(controlPlanePort)
+            TrafficTreatment puntTreatment = DefaultTrafficTreatment.builder()
                     .punt()
                     .build();
 
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(toSelector, toTreatment, true));
+                    buildForwardingObjective(toSelector, puntTreatment, cpNextId, true));
 
             // ARP from router
             fromSelector = DefaultTrafficSelector.builder()
@@ -198,15 +209,11 @@
                     .matchEthSrc(intf.mac())
                     .matchVlanId(intf.vlan())
                     .matchEthType(EthType.EtherType.ARP.ethType().toShort())
-                    .build();
-
-            intfTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(intf.connectPoint().port())
-                    .punt()
+                    .matchArpSpa(ip.ipAddress().getIp4Address())
                     .build();
 
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(fromSelector, intfTreatment, true));
+            buildForwardingObjective(fromSelector, puntTreatment, intfNextId, true));
         }
     }
 
@@ -219,33 +226,85 @@
                 .matchIPProtocol((byte) OSPF_IP_PROTO)
                 .build();
 
-        TrafficTreatment toTreatment = DefaultTrafficTreatment.builder()
-                .setOutput(controlPlaneConnectPoint.port())
-                .build();
-
+        // create nextObjectives for forwarding to the controlPlaneConnectPoint
+        DeviceId deviceId = controlPlaneConnectPoint.deviceId();
+        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+        int cpNextId;
+        if (intf.vlan() == VlanId.NONE) {
+            cpNextId = createNextObjective(deviceId, controlPlanePort,
+                           VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                           true);
+        } else {
+            cpNextId = createNextObjective(deviceId, controlPlanePort,
+                                           intf.vlan(), false);
+        }
+        log.debug("ospf flows intf:{} nextid:{}", intf, cpNextId);
         flowObjectiveService.forward(controlPlaneConnectPoint.deviceId(),
-                buildForwardingObjective(toSelector, toTreatment, ospfEnabled));
+                buildForwardingObjective(toSelector, null, cpNextId, ospfEnabled));
     }
 
     /**
-     * Builds a forwarding objective from the given selector and treatment.
+     * Creates a next objective for forwarding to a port. Handles metadata for
+     * some pipelines that require vlan information for egress port.
+     *
+     * @param deviceId the device on which the next objective is being created
+     * @param portNumber the egress port
+     * @param vlanId vlan information for egress port
+     * @param popVlan if vlan tag should be popped or not
+     * @return nextId of the next objective created
+     */
+    private int createNextObjective(DeviceId deviceId, PortNumber portNumber,
+                                    VlanId vlanId, boolean popVlan) {
+        int nextId = flowObjectiveService.allocateNextId();
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .fromApp(appId);
+
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (popVlan) {
+            ttBuilder.popVlan();
+        }
+        ttBuilder.setOutput(portNumber);
+
+        // setup metadata to pass to nextObjective - indicate the vlan on egress
+        // if needed by the switch pipeline.
+        TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+        metabuilder.matchVlanId(vlanId);
+
+        nextObjBuilder.withMeta(metabuilder.build());
+        nextObjBuilder.addTreatment(ttBuilder.build());
+        log.debug("Submited next objective {} in device {} for port/vlan {}/{}",
+                nextId, deviceId, portNumber, vlanId);
+        flowObjectiveService.next(deviceId, nextObjBuilder.add());
+        return nextId;
+    }
+
+    /**
+     * Builds a forwarding objective from the given selector, treatment and nextId.
      *
      * @param selector selector
-     * @param treatment treatment
+     * @param treatment treatment to apply to packet, can be null
+     * @param nextId next objective to point to for forwarding packet
      * @param add true to create an add objective, false to create a remove
      *            objective
      * @return forwarding objective
      */
     private ForwardingObjective buildForwardingObjective(TrafficSelector selector,
                                                          TrafficTreatment treatment,
+                                                         int nextId,
                                                          boolean add) {
-
-        ForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(appId)
-                .withPriority(PRIORITY)
-                .withFlag(ForwardingObjective.Flag.VERSATILE);
+        DefaultForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder();
+        fobBuilder.withSelector(selector);
+        if (treatment != null) {
+            fobBuilder.withTreatment(treatment);
+        }
+        if (nextId != -1) {
+            fobBuilder.nextStep(nextId);
+        }
+        fobBuilder.fromApp(appId)
+            .withPriority(PRIORITY)
+            .withFlag(ForwardingObjective.Flag.VERSATILE);
 
         return add ? fobBuilder.add() : fobBuilder.remove();
     }
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
index a30c42c..ee9958c 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
@@ -34,6 +34,7 @@
 import org.onosproject.core.CoreService;
 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.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -80,6 +81,8 @@
     private static final int PRIORITY_OFFSET = 100;
     private static final int PRIORITY_MULTIPLIER = 5;
 
+    public static final short ASSIGNED_VLAN = 4094;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
@@ -103,7 +106,8 @@
     // Device id of data-plane switch - should be learned from config
     private DeviceId deviceId;
 
-    private ApplicationId appId;
+    private ConnectPoint controlPlaneConnectPoint;
+
     private ApplicationId routerAppId;
 
     // Reference count for how many times a next hop is used by a route
@@ -121,11 +125,8 @@
 
     @Activate
     protected void activate() {
-        // TODO why are there two of the same app ID?
         routerAppId = coreService.registerApplication(RoutingService.ROUTER_APP_ID);
 
-        appId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
-
         deviceListener = new InternalDeviceListener();
         deviceService.addListener(deviceListener);
 
@@ -156,6 +157,8 @@
             log.info("Router config not available");
             return;
         }
+        controlPlaneConnectPoint = routerConfig.getControlPlaneConnectPoint();
+        log.info("Control Plane Connect Point: {}", controlPlaneConnectPoint);
 
         deviceId = routerConfig.getControlPlaneConnectPoint().deviceId();
 
@@ -231,7 +234,7 @@
         int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
 
         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder()
-                .fromApp(appId)
+                .fromApp(routerAppId)
                 .makePermanent()
                 .withSelector(selector)
                 .withPriority(priority)
@@ -266,23 +269,30 @@
                     .setEthSrc(egressIntf.mac())
                     .setEthDst(nextHop.mac());
 
+            TrafficSelector.Builder metabuilder = null;
             if (!egressIntf.vlan().equals(VlanId.NONE)) {
                 treatment.pushVlan()
                         .setVlanId(egressIntf.vlan())
                         .setVlanPcp((byte) 0);
+            } else {
+                // untagged outgoing port may require internal vlan in some pipelines
+                metabuilder = DefaultTrafficSelector.builder();
+                metabuilder.matchVlanId(VlanId.vlanId(ASSIGNED_VLAN));
             }
 
             treatment.setOutput(egressIntf.connectPoint().port());
 
             int nextId = flowObjectiveService.allocateNextId();
-
-            NextObjective nextObjective = DefaultNextObjective.builder()
+            NextObjective.Builder nextBuilder = DefaultNextObjective.builder()
                     .withId(nextId)
                     .addTreatment(treatment.build())
                     .withType(NextObjective.Type.SIMPLE)
-                    .fromApp(appId)
-                    .add(); // TODO add callbacks
+                    .fromApp(routerAppId);
+            if (metabuilder != null) {
+                nextBuilder.withMeta(metabuilder.build());
+            }
 
+            NextObjective nextObjective = nextBuilder.add(); // TODO add callbacks
             flowObjectiveService.next(deviceId, nextObjective);
 
             nextHops.put(nextHop.ip(), nextId);
@@ -329,33 +339,48 @@
             }
 
             FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
+            // first add filter for the interface
             fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
-                    .addCondition(Criteria.matchEthDst(intf.mac()))
-                    .addCondition(Criteria.matchVlanId(intf.vlan()));
-
+                .addCondition(Criteria.matchEthDst(intf.mac()))
+                .addCondition(Criteria.matchVlanId(intf.vlan()));
             fob.withPriority(PRIORITY_OFFSET);
+            if (intf.vlan() == VlanId.NONE) {
+                TrafficTreatment tt = DefaultTrafficTreatment.builder()
+                        .pushVlan().setVlanId(VlanId.vlanId(ASSIGNED_VLAN)).build();
+                fob.withMeta(tt);
+            }
 
-            fob.permit().fromApp(appId);
-            flowObjectiveService.filter(
-                    deviceId,
-                    fob.add(new ObjectiveContext() {
-                        @Override
-                        public void onSuccess(Objective objective) {
-                            log.info("Successfully installed interface based "
-                                    + "filtering objectives for intf {}", intf);
-                        }
-
-                        @Override
-                        public void onError(Objective objective,
-                                            ObjectiveError error) {
-                            log.error("Failed to install interface filters for intf {}: {}",
-                                    intf, error);
-                            // TODO something more than just logging
-                        }
-                    }));
+            fob.permit().fromApp(routerAppId);
+            sendFilteringObjective(install, fob, intf);
+            if (controlPlaneConnectPoint != null) {
+                // then add the same mac/vlan filters for control-plane connect point
+                fob.withKey(Criteria.matchInPort(controlPlaneConnectPoint.port()));
+                sendFilteringObjective(install, fob, intf);
+            }
         }
     }
 
+    private void sendFilteringObjective(boolean install, FilteringObjective.Builder fob,
+                                        Interface intf) {
+        flowObjectiveService.filter(
+            deviceId,
+            fob.add(new ObjectiveContext() {
+                @Override
+                public void onSuccess(Objective objective) {
+                    log.info("Successfully installed interface based "
+                            + "filtering objectives for intf {}", intf);
+                }
+
+                @Override
+                public void onError(Objective objective,
+                                    ObjectiveError error) {
+                    log.error("Failed to install interface filters for intf {}: {}",
+                              intf, error);
+                    // TODO something more than just logging
+                }
+            }));
+    }
+
     private class InternalFibListener implements FibListener {
 
         @Override