[ONOS-7624] Implement egress pipeline programming

- Implemented logic to handle double-tagged host in segmentrouting application.
- Added 'DummyVlanId' to segmentrouting application to keep track of dummy vlan for L3L2Unfiltered group chain and egress tables.
- Implemented L2Unfiltered group and Egress pipeline programming support in OFDPA pipeline.
- Added EGRESS flag to the forwardingObjective to program Egress tables.
- Fixed bugs when handling double-tagged ARP request, to get correct vlan id and reply with double-tagged packet.
- Fixed bugs in BasicHostConfig, to set the value of 'outerTpid' to 0x8100 if it is not specified.
- Fixed build(ARP/ICMP/ICMP6)reply to build double-tagged reply if corresponding request is double-tagged.

Change-Id: I1fdc30b55827c3f73fad9e854bcaa5fb23f7bcd0
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 5fdbf69..e14fdc2 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.segmentrouting;
 
+import org.onlab.packet.EthType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
@@ -94,10 +95,17 @@
         Set<IpAddress> ips = host.ipAddresses();
         log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
 
-        processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
-        ips.forEach(ip ->
+        if (isDoubleTaggedHost(host)) {
+            ips.forEach(ip ->
+                processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+                                               host.innerVlan(), hostVlanId, host.tpid(), ip, false)
+            );
+        } else {
+            processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
+            ips.forEach(ip ->
                 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
-        );
+            );
+        }
 
         // Use the pair link temporarily before the second location of a dual-homed host shows up.
         // This do not affect single-homed hosts since the flow will be blocked in
@@ -133,10 +141,17 @@
         log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
 
         locations.forEach(location -> {
-            processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
-            ips.forEach(ip ->
+            if (isDoubleTaggedHost(host)) {
+                ips.forEach(ip ->
+                    processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+                                                   host.innerVlan(), hostVlanId, host.tpid(), ip, true)
+                );
+            } else {
+                processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
+                ips.forEach(ip ->
                     processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
-            );
+                );
+            }
 
             // Also remove redirection flows on the pair device if exists.
             Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
@@ -155,18 +170,20 @@
     }
 
     void processHostMovedEvent(HostEvent event) {
-        MacAddress hostMac = event.subject().mac();
-        VlanId hostVlanId = event.subject().vlan();
         Set<HostLocation> prevLocations = event.prevSubject().locations();
         Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
         Set<HostLocation> newLocations = event.subject().locations();
         Set<IpAddress> newIps = event.subject().ipAddresses();
 
-        processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps);
+        processHostMoved(event.subject(), prevLocations, prevIps, newLocations, newIps);
     }
 
-    private void processHostMoved(MacAddress hostMac, VlanId hostVlanId, Set<HostLocation> prevLocations,
-                                  Set<IpAddress> prevIps, Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+    private void processHostMoved(Host host, Set<HostLocation> prevLocations, Set<IpAddress> prevIps,
+                                  Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+        MacAddress hostMac = host.mac();
+        VlanId hostVlanId = host.vlan();
+        EthType hostTpid = host.tpid();
+        boolean doubleTaggedHost = isDoubleTaggedHost(host);
         log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
         Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
                 .collect(Collectors.toSet());
@@ -174,10 +191,15 @@
         // For each old location
         Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
             // Remove routing rules for old IPs
-            Sets.difference(prevIps, newIps).forEach(ip ->
+            Sets.difference(prevIps, newIps).forEach(ip -> {
+                if (doubleTaggedHost) {
+                    processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+                                                   hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+                } else {
                     processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
-                            ip, true)
-            );
+                                       ip, true);
+                }
+            });
 
             // Redirect the flows to pair link if configured
             // Note: Do not continue removing any rule
@@ -200,10 +222,15 @@
             // Otherwise, do not remove and let the adding part update the old flow
             if (!newDeviceIds.contains(prevLocation.deviceId())) {
                 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
-                Sets.intersection(prevIps, newIps).forEach(ip ->
-                        processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
-                                ip, true)
-                );
+                Sets.intersection(prevIps, newIps).forEach(ip -> {
+                    if (doubleTaggedHost) {
+                        processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+                                                       hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+                    } else {
+                        processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+                                           hostMac, hostVlanId, ip, true);
+                    }
+                });
             }
 
             // Remove bridging rules if new interface vlan is different from old interface vlan
@@ -225,8 +252,13 @@
             Sets.intersection(prevIps, newIps).forEach(ip -> {
                 if (newLocations.stream().noneMatch(newLocation ->
                         srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
-                    processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
-                            ip, true);
+                    if (doubleTaggedHost) {
+                        processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+                                                       hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+                    } else {
+                        processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+                                           hostMac, hostVlanId, ip, true);
+                    }
                 }
             });
         });
@@ -234,23 +266,38 @@
         // For each new location, add all new IPs.
         Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
             processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
-            newIps.forEach(ip ->
+            newIps.forEach(ip -> {
+                if (doubleTaggedHost) {
+                    processDoubleTaggedRoutingRule(newLocation.deviceId(), newLocation.port(),
+                                                   hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
+                } else {
                     processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
-                            ip, false)
-            );
+                                       ip, false);
+                }
+            });
         });
 
         // For each unchanged location, add new IPs and remove old IPs.
         Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
-            Sets.difference(prevIps, newIps).forEach(ip ->
-                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
-                            hostVlanId, ip, true)
-            );
+            Sets.difference(prevIps, newIps).forEach(ip -> {
+                 if (doubleTaggedHost) {
+                     processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+                                                    hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+                 } else {
+                     processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+                                        hostMac, hostVlanId, ip, true);
+                 }
+            });
 
-            Sets.difference(newIps, prevIps).forEach(ip ->
-                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
-                        hostVlanId, ip, false)
-            );
+            Sets.difference(newIps, prevIps).forEach(ip -> {
+                if (doubleTaggedHost) {
+                    processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+                                                   hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
+                } else {
+                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+                                       hostMac, hostVlanId, ip, false);
+                }
+            });
         });
 
         // ensure dual-homed host locations have viable uplinks
@@ -267,18 +314,31 @@
         Host host = event.subject();
         MacAddress hostMac = host.mac();
         VlanId hostVlanId = host.vlan();
+        EthType hostTpid = host.tpid();
         Set<HostLocation> locations = host.locations();
         Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
         Set<IpAddress> newIps = host.ipAddresses();
         log.info("Host {}/{} is updated", hostMac, hostVlanId);
 
         locations.forEach(location -> {
-            Sets.difference(prevIps, newIps).forEach(ip ->
-                    processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
-            );
-            Sets.difference(newIps, prevIps).forEach(ip ->
-                    processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
-            );
+            Sets.difference(prevIps, newIps).forEach(ip -> {
+                if (isDoubleTaggedHost(host)) {
+                    processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+                                                   host.innerVlan(), hostVlanId, hostTpid, ip, true);
+                } else {
+                    processRoutingRule(location.deviceId(), location.port(), hostMac,
+                                       hostVlanId, ip, true);
+                }
+            });
+            Sets.difference(newIps, prevIps).forEach(ip -> {
+                if (isDoubleTaggedHost(host)) {
+                    processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+                                                   host.innerVlan(), hostVlanId, hostTpid, ip, false);
+                } else {
+                    processRoutingRule(location.deviceId(), location.port(), hostMac,
+                                       hostVlanId, ip, false);
+                }
+            });
         });
 
         // Use the pair link temporarily before the second location of a dual-homed host shows up.
@@ -374,8 +434,8 @@
     }
 
     /**
-     * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
-     * output to given port.
+     * Populates or revokes a bridging rule on given deviceId that matches given mac,
+     * given vlan and output to given port.
      *
      * @param deviceId device ID
      * @param port port
@@ -422,7 +482,39 @@
         }
     }
 
-
+    /**
+     * Populate or revoke a routing rule and egress rules on given deviceId that matches given IP,
+     * to set destination mac to given mac, set inner vlan and outer vlan to given vlans,
+     * set outer TPID, and output to given port.
+     *
+     * @param deviceId  device ID
+     * @param port      port
+     * @param mac       mac address
+     * @param innerVlan inner VLAN ID
+     * @param outerVlan outer VLAN ID
+     * @param outerTpid outer TPID
+     * @param ip        IP address
+     * @param revoke    true to revoke the rule; false to populate
+     */
+    private void processDoubleTaggedRoutingRule(DeviceId deviceId, PortNumber port,
+                                                MacAddress mac, VlanId innerVlan,
+                                                VlanId outerVlan, EthType outerTpid,
+                                                IpAddress ip, boolean revoke) {
+        ConnectPoint location = new ConnectPoint(deviceId, port);
+        if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
+            log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
+            return;
+        }
+        log.info("{} routing rule for double-tagged host {} at {}",
+                 revoke ? "Revoking" : "Populating", ip, location);
+        if (revoke) {
+            srManager.defaultRoutingHandler.revokeDoubleTaggedRoute(
+                    deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
+        } else {
+            srManager.defaultRoutingHandler.populateDoubleTaggedRoute(
+                    deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
+        }
+    }
 
     /**
      * Update forwarding objective for unicast bridging and unicast routing.
@@ -510,4 +602,15 @@
                         .forEach(loc -> dualHomedLocations.add(loc.port())));
         return dualHomedLocations;
     }
+
+    /**
+     * Checks if the given host is double-tagged or not.
+     *
+     * @param host host to check
+     * @return true if it is double-tagged, false otherwise
+     */
+    private boolean isDoubleTaggedHost(Host host) {
+        return !host.innerVlan().equals(VlanId.NONE);
+    }
+
 }