Added FLAT type network

Change-Id: I9b81adac1193cc2f133072683a0c970d2834c157
diff --git a/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java b/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
index 122ccfe..3a00a3d 100644
--- a/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
+++ b/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
@@ -26,13 +26,46 @@
 public interface ServiceNetwork {
 
     enum NetworkType {
+        /**
+         * Isolated tenant network.
+         */
         PRIVATE,
+        /**
+         * Provider network that offers connectivity to external network via L3.
+         * This network relies on the physical network infrastructure or vRouter
+         * for gateway and first hop routing service.
+         */
         PUBLIC,
+        /**
+         * Provider network that offers connectivity to the physical network via L2.
+         * This network runs over the physical data network and allows physical
+         * machines and virtual instances in a same broadcast domain.
+         */
+        FLAT,
+        /**
+         * Virtual instance management network that offers connectivity to head node.
+         * This network runs over the physical management network, and cannot be
+         * part of service chain.
+         */
         MANAGEMENT_HOST,
+        /**
+         * Virtual instance management network that offers limited connectivity
+         * between the virtual instance and the host machine.
+         * This network does not span compute nodes, and cannot be part of
+         * service chain.
+         */
         MANAGEMENT_LOCAL,
+        /**
+         * Special network for R-CORD vSG.
+         * This network type is deprecated in favor of ServicePort VLAN.
+         */
         @Deprecated
         VSG,
-        ACCESS_AGENT
+        /**
+         * Special network for R-CORD access agent.
+         * This network cannot be part of service chain.
+         */
+        ACCESS_AGENT,
     }
 
     enum DependencyType {
diff --git a/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java b/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
index 192a16e..e507de5 100644
--- a/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
+++ b/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
@@ -60,8 +60,10 @@
 import org.opencord.cordvtn.api.core.ServiceNetworkListener;
 import org.opencord.cordvtn.api.core.ServiceNetworkService;
 import org.opencord.cordvtn.api.net.ServiceNetwork;
+import org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType;
 import org.opencord.cordvtn.api.node.CordVtnNode;
 import org.opencord.cordvtn.api.node.CordVtnNodeService;
+import org.opencord.cordvtn.api.node.CordVtnNodeState;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
@@ -259,8 +261,7 @@
                 getMacFromHostService(targetIp);
 
         if (replyMac.equals(MacAddress.NONE)) {
-            log.trace("Failed to find MAC for {}", targetIp);
-            forwardManagementArpRequest(context, ethPacket);
+            forwardArpRequest(context, ethPacket);
             return;
         }
 
@@ -310,33 +311,46 @@
         context.block();
     }
 
-    private void forwardManagementArpRequest(PacketContext context, Ethernet ethPacket) {
+    private void forwardArpRequest(PacketContext context, Ethernet ethPacket) {
         DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
-        PortNumber hostMgmtPort = hostMgmtPort(deviceId);
-        Host host = hostService.getHost(HostId.hostId(ethPacket.getSourceMAC()));
+        PortNumber inputPort = context.inPacket().receivedFrom().port();
 
-        if (host == null ||
-                Instance.of(host).netType() != MANAGEMENT_HOST ||
-                hostMgmtPort == null) {
+        Host host = hostService.getHost(HostId.hostId(ethPacket.getSourceMAC()));
+        NetworkType networkType = Instance.of(host).netType();
+        if (host == null || (networkType != MANAGEMENT_HOST &&
+                networkType != FLAT)) {
+            context.block();
+            log.trace("Failed to handle ARP request");
+            return;
+        }
+
+        PortNumber outputPort = networkType == MANAGEMENT_HOST ?
+                hostMgmtPort(deviceId) : dataPort(deviceId);
+        if (inputPort.equals(outputPort)) {
             context.block();
             return;
         }
 
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(hostMgmtPort)
-                .build();
+        if (outputPort != null) {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(outputPort)
+                    .build();
 
-        packetService.emit(new DefaultOutboundPacket(
-                context.inPacket().receivedFrom().deviceId(),
-                treatment,
-                ByteBuffer.wrap(ethPacket.serialize())));
+            packetService.emit(new DefaultOutboundPacket(
+                    context.inPacket().receivedFrom().deviceId(),
+                    treatment,
+                    ByteBuffer.wrap(ethPacket.serialize())));
+        } else {
+            log.trace("Failed to handle ARP request");
+        }
 
         context.block();
     }
 
     private PortNumber hostMgmtPort(DeviceId deviceId) {
         CordVtnNode node = nodeService.node(deviceId);
-        if (node == null || node.hostManagementInterface() == null) {
+        if (node == null || node.state() != CordVtnNodeState.COMPLETE ||
+                node.hostManagementInterface() == null) {
             return null;
         }
         Optional<Port> port = deviceService.getPorts(deviceId).stream()
@@ -344,7 +358,21 @@
                         .equals(node.hostManagementInterface()) &&
                         p.isEnabled())
                 .findAny();
-        return port.isPresent() ? port.get().number() : null;
+        return port.map(Port::number).orElse(null);
+    }
+
+    private PortNumber dataPort(DeviceId deviceId) {
+        CordVtnNode node = nodeService.node(deviceId);
+        if (node == null || node.state() != CordVtnNodeState.COMPLETE ||
+                node.dataInterface() == null) {
+            return null;
+        }
+        Optional<Port> port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> p.annotations().value(PORT_NAME)
+                        .equals(node.dataInterface()) &&
+                        p.isEnabled())
+                .findAny();
+        return port.map(Port::number).orElse(null);
     }
 
     /**
@@ -465,8 +493,17 @@
             ServiceNetwork snet = event.subject();
             switch (event.type()) {
                 case SERVICE_NETWORK_CREATED:
+                    if (snet.type() == PRIVATE || snet.type() == VSG) {
+                        addGateway(snet.serviceIp(), privateGatewayMac);
+                    }
+                    break;
                 case SERVICE_NETWORK_UPDATED:
-                    addGateway(snet.serviceIp(), privateGatewayMac);
+                    if (snet.type() == PRIVATE || snet.type() == VSG) {
+                        addGateway(snet.serviceIp(), privateGatewayMac);
+                    } else {
+                        // don't service ARP for the other network gateway
+                        removeGateway(snet.serviceIp());
+                    }
                     break;
                 case SERVICE_NETWORK_REMOVED:
                     removeGateway(snet.serviceIp());
@@ -488,9 +525,7 @@
             return;
         }
 
-        config.publicGateways().entrySet().forEach(entry -> {
-            addGateway(entry.getKey(), entry.getValue());
-        });
+        config.publicGateways().forEach(this::addGateway);
         // TODO send gratuitous arp in case the MAC is changed
     }
 
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/FlatInstanceHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/FlatInstanceHandler.java
new file mode 100644
index 0000000..6c2c6f4
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/impl/handler/FlatInstanceHandler.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.opencord.cordvtn.impl.handler;
+
+import com.google.common.collect.ImmutableSet;
+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.onlab.packet.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.opencord.cordvtn.api.core.CordVtnPipeline;
+import org.opencord.cordvtn.api.core.Instance;
+import org.opencord.cordvtn.api.core.InstanceHandler;
+import org.opencord.cordvtn.api.core.ServiceNetworkService;
+import org.opencord.cordvtn.api.net.ServiceNetwork;
+import org.opencord.cordvtn.api.node.CordVtnNodeService;
+
+import static org.opencord.cordvtn.api.core.CordVtnPipeline.PRIORITY_DEFAULT;
+import static org.opencord.cordvtn.api.core.CordVtnPipeline.TABLE_ACCESS;
+import static org.opencord.cordvtn.api.core.CordVtnPipeline.TABLE_DST;
+import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.FLAT;
+
+/**
+ * Provides provider network connectivity to the instance.
+ */
+@Component(immediate = true)
+public class FlatInstanceHandler extends AbstractInstanceHandler implements InstanceHandler {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ServiceNetworkService snetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnPipeline pipeline;
+
+    @Activate
+    protected void activate() {
+        netTypes = ImmutableSet.of(FLAT);
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    public void instanceDetected(Instance instance) {
+        log.info("FLAT network instance is detected {}", instance);
+
+        ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
+        populateDstIpRule(instance, true);
+        populateDstRangeRule(instance.deviceId(),
+                getServiceNetwork(instance).subnet(), true);
+        populateDirectAccessRule(snet.subnet(), true);
+        populateIsolationRule(snet.subnet(), true);
+        populateInPortRule(instance, true);
+    }
+
+    @Override
+    public void instanceRemoved(Instance instance) {
+        log.info("FLAT network instance is removed {}", instance);
+
+        populateDstIpRule(instance, false);
+        if (getInstances(instance.netId()).isEmpty()) {
+            populateDstRangeRule(instance.deviceId(),
+                    getServiceNetwork(instance).subnet(), false);
+            ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
+            populateDirectAccessRule(snet.subnet(), false);
+            populateIsolationRule(snet.subnet(), false);
+        }
+        populateInPortRule(instance, false);
+    }
+
+    private void populateInPortRule(Instance instance, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(instance.ipAddress().toIpPrefix())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(CordVtnPipeline.TABLE_ACCESS)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(CordVtnPipeline.TABLE_IN_SERVICE)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_LOW)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void populateDirectAccessRule(IpPrefix serviceIpRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(serviceIpRange)
+                .matchIPDst(serviceIpRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST)
+                .build();
+
+        nodeService.completeNodes().forEach(node -> {
+            DeviceId deviceId = node.integrationBridgeId();
+            FlowRule flowRuleDirect = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(deviceId)
+                    .forTable(TABLE_ACCESS)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRuleDirect);
+        });
+    }
+
+    private void populateIsolationRule(IpPrefix serviceIpRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(serviceIpRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .drop()
+                .build();
+
+        nodeService.completeNodes().forEach(node -> {
+            FlowRule flowRuleDirect = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(CordVtnPipeline.PRIORITY_LOW)
+                    .forDevice(node.integrationBridgeId())
+                    .forTable(CordVtnPipeline.TABLE_ACCESS)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRuleDirect);
+        });
+    }
+
+    private void populateDstIpRule(Instance instance, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(instance.ipAddress().toIpPrefix())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(instance.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_DST)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void populateDstRangeRule(DeviceId deviceId, IpPrefix dstIpRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(dstIpRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dataPort(deviceId))
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_LOW)
+                .forDevice(deviceId)
+                .forTable(CordVtnPipeline.TABLE_DST)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+}