[ONOS-7444] Optimize SONA gw doesn't use vrouter app and quagga anymore
- Done: Deriving MAC address from external peer router, SNAT, Floating IP-based routing
- Todo: Vlan, GW loadbalancing

Change-Id: I0cc2a61295c28fa6a796046ca142c4ef525b70d3
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
index 9f8cdf02..8a45968 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -48,10 +48,14 @@
 import org.onosproject.openstacknode.api.OpenstackNodeEvent;
 import org.onosproject.openstacknode.api.OpenstackNodeListener;
 import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.ExternalGateway;
 import org.openstack4j.model.network.NetFloatingIP;
 import org.openstack4j.model.network.Network;
 import org.openstack4j.model.network.NetworkType;
 import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,8 +66,10 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_EXTERNAL;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_INTERNAL;
+import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
 import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 
@@ -152,10 +158,61 @@
             throw new IllegalStateException(error);
         }
 
+        setComputeNodeToGateway(instPort, osNet, install);
         setDownstreamRules(floatingIp, osNet, instPort, install);
         setUpstreamRules(floatingIp, osNet, instPort, install);
     }
 
+    private void setComputeNodeToGateway(InstancePort instPort, Network osNet, boolean install) {
+        TrafficTreatment treatment;
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(instPort.ipAddress().toIpPrefix())
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        switch (osNet.getNetworkType()) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(osNet.getProviderSegID()));
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(osNet.getProviderSegID()));
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        osNet.getNetworkType().toString());
+                throw new IllegalStateException(error);
+        }
+
+        OpenstackNode selectedGatewayNode = selectGatewayNode();
+        if (selectedGatewayNode == null) {
+            return;
+        }
+        treatment = DefaultTrafficTreatment.builder()
+                .extension(buildExtension(
+                        deviceService,
+                        instPort.deviceId(),
+                        selectedGatewayNode.dataIp().getIp4Address()),
+                        instPort.deviceId())
+                .setOutput(osNodeService.node(instPort.deviceId()).tunnelPortNum())
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                instPort.deviceId(),
+                sBuilder.build(),
+                treatment,
+                PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE,
+                ROUTING_TABLE,
+                install);
+    }
+
+    private OpenstackNode selectGatewayNode() {
+        //TODO support multiple loadbalancing options.
+        return osNodeService.completeNodes(GATEWAY).stream().findAny().orElse(null);
+    }
+
     private void setDownstreamRules(NetFloatingIP floatingIp, Network osNet,
                                     InstancePort instPort, boolean install) {
         OpenstackNode cNode = osNodeService.node(instPort.deviceId());
@@ -281,11 +338,17 @@
                 throw new IllegalStateException(error);
         }
 
+        MacAddress externalPeerRouterMac = externalPeerRouterMac(osNet);
+        if (externalPeerRouterMac == null) {
+            return;
+        }
+
+
         osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
             TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
                     .setIpSrc(floating.getIp4Address())
-                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
-                    .setEthDst(Constants.DEFAULT_EXTERNAL_ROUTER_MAC);
+                    .setEthSrc(instPort.macAddress())
+                    .setEthDst(externalPeerRouterMac);
 
             if (osNet.getNetworkType().equals(NetworkType.VLAN)) {
                 tBuilder.popVlan();
@@ -295,13 +358,43 @@
                     appId,
                     gNode.intgBridge(),
                     sBuilder.build(),
-                    tBuilder.setOutput(gNode.patchPortNum()).build(),
+                    tBuilder.setOutput(gNode.uplinkPortNum()).build(),
                     PRIORITY_FLOATING_EXTERNAL,
                     GW_COMMON_TABLE,
                     install);
         });
     }
 
+    private MacAddress externalPeerRouterMac(Network network) {
+        if (network == null) {
+            return null;
+        }
+
+        Subnet subnet = osNetworkService.subnets(network.getId()).stream().findAny().orElse(null);
+
+        if (subnet == null) {
+            return null;
+        }
+
+        RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+                .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+                .findAny().orElse(null);
+        if (osRouterIface == null) {
+            return null;
+        }
+
+        Router osRouter = osRouterService.router(osRouterIface.getId());
+        if (osRouter == null) {
+            return null;
+        }
+        if (osRouter.getExternalGatewayInfo() == null) {
+            return null;
+        }
+
+        ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+        return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+    }
     private class InternalFloatingIpListener implements OpenstackRouterListener {
 
         @Override