CORD-305 Added basic VTN rules for VMs with openstackswitching

Change-Id: I3eebc3c396b6657457363c183ca8c260b6bb8db4
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
index c3bf77c..6729774 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.cordvtn;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -23,6 +25,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.util.ItemNotFoundException;
+import org.onlab.packet.IpAddress;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.core.ApplicationId;
@@ -31,9 +34,11 @@
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
 import org.onosproject.net.Port;
 import org.onosproject.net.behaviour.BridgeConfig;
 import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.behaviour.ControllerInfo;
 import org.onosproject.net.behaviour.DefaultTunnelDescription;
 import org.onosproject.net.behaviour.TunnelConfig;
@@ -45,9 +50,13 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.openstackswitching.OpenstackNetwork;
+import org.onosproject.openstackswitching.OpenstackPort;
+import org.onosproject.openstackswitching.OpenstackSwitchingService;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbController;
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
@@ -62,8 +71,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.groupedThreads;
@@ -72,8 +83,8 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Provides initial setup or cleanup for provisioning virtual tenant networks
- * on ovsdb, integration bridge and vm when they are added or deleted.
+ * Provisions virtual tenant networks with service chaining capability
+ * in OpenStack environment.
  */
 @Component(immediate = true)
 @Service
@@ -86,7 +97,8 @@
             .register(KryoNamespaces.API)
             .register(CordVtnNode.class)
             .register(NodeState.class);
-    private static final String DEFAULT_BRIDGE_NAME = "br-int";
+    private static final String DEFAULT_BRIDGE = "br-int";
+    private static final String VPORT_PREFIX = "tap";
     private static final String DEFAULT_TUNNEL = "vxlan";
     private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
         {
@@ -116,11 +128,17 @@
     protected DeviceAdminService adminService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OvsdbController controller;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackSwitchingService openstackService;
+
     private final ExecutorService eventExecutor = Executors
             .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/cordvtn", "event-handler"));
 
@@ -132,6 +150,8 @@
     private final VmHandler vmHandler = new VmHandler();
 
     private ConsistentMap<CordVtnNode, NodeState> nodeStore;
+    private Map<HostId, String> hostNetworkMap = Maps.newHashMap();
+    private CordVtnRuleInstaller ruleInstaller;
 
     private enum NodeState {
 
@@ -185,6 +205,8 @@
                 .withApplicationId(appId)
                 .build();
 
+        ruleInstaller = new CordVtnRuleInstaller(appId, flowObjectiveService,
+                                                 driverService, DEFAULT_TUNNEL);
         deviceService.addListener(deviceListener);
         hostService.addListener(hostListener);
 
@@ -314,11 +336,27 @@
 
     /**
      * Performs tasks after node initialization.
+     * First disconnect unnecessary OVSDB connection and then installs flow rules
+     * for existing VMs if there are any.
      *
      * @param node cordvtn node
      */
     private void postInit(CordVtnNode node) {
         disconnect(node);
+
+        Set<OpenstackNetwork> vNets = Sets.newHashSet();
+        hostService.getConnectedHosts(node.intBrId())
+                .stream()
+                .forEach(host -> {
+                    OpenstackNetwork vNet = getOpenstackNetworkByHost(host);
+                    if (vNet != null) {
+                        log.info("VM {} is detected", host.id());
+
+                        hostNetworkMap.put(host.id(), vNet.id());
+                        vNets.add(vNet);
+                    }
+                });
+        vNets.stream().forEach(this::installFlowRules);
     }
 
     /**
@@ -443,7 +481,7 @@
         }
 
         List<ControllerInfo> controllers = new ArrayList<>();
-        Sets.newHashSet(clusterService.getNodes())
+        Sets.newHashSet(clusterService.getNodes()).stream()
                 .forEach(controller -> {
                     ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
                     controllers.add(ctrlInfo);
@@ -453,7 +491,7 @@
         try {
             DriverHandler handler = driverService.createHandler(node.ovsdbId());
             BridgeConfig bridgeConfig =  handler.behaviour(BridgeConfig.class);
-            bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), dpid, controllers);
+            bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create integration bridge on {}", node.ovsdbId());
         }
@@ -474,13 +512,12 @@
             optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
         }
         TunnelDescription description =
-                new DefaultTunnelDescription(null, null, VXLAN,
-                                             TunnelName.tunnelName(DEFAULT_TUNNEL),
+                new DefaultTunnelDescription(null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
                                              optionBuilder.build());
         try {
             DriverHandler handler = driverService.createHandler(node.ovsdbId());
             TunnelConfig tunnelConfig =  handler.behaviour(TunnelConfig.class);
-            tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), description);
+            tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create tunnel interface on {}", node.ovsdbId());
         }
@@ -516,6 +553,212 @@
         }
     }
 
+    /**
+     * Returns tunnel port of the device.
+     *
+     * @param bridgeId device id
+     * @return port, null if no tunnel port exists on a given device
+     */
+    private Port getTunnelPort(DeviceId bridgeId) {
+        try {
+            return deviceService.getPorts(bridgeId).stream()
+                    .filter(p -> p.annotations().value("portName").contains(DEFAULT_TUNNEL)
+                            && p.isEnabled())
+                    .findFirst().get();
+        } catch (NoSuchElementException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns remote ip address for tunneling.
+     *
+     * @param bridgeId device id
+     * @return ip address, null if no such device exists
+     */
+    private IpAddress getRemoteIp(DeviceId bridgeId) {
+        CordVtnNode node = getNodeByBridgeId(bridgeId);
+        if (node != null) {
+            // TODO get data plane IP for tunneling
+            return node.ovsdbIp();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns destination information of all ports associated with a given
+     * OpenStack network. Output of the destination information is set to local
+     * port or tunnel port according to a given device id.
+     *
+     * @param deviceId device id to install flow rules
+     * @param vNet OpenStack network
+     * @return list of flow information, empty list if no flow information exists
+     */
+    private List<DestinationInfo> getSameNetworkPortsInfo(DeviceId deviceId, OpenstackNetwork vNet) {
+        List<DestinationInfo> dstInfos = Lists.newArrayList();
+        long tunnelId = Long.valueOf(vNet.segmentId());
+
+        for (OpenstackPort vPort : openstackService.ports(vNet.id())) {
+            ConnectPoint cp = getConnectPoint(vPort);
+            if (cp == null) {
+                log.debug("Couldn't find connection point for OpenStack port {}", vPort.id());
+                continue;
+            }
+
+            DestinationInfo.Builder dBuilder = cp.deviceId().equals(deviceId) ?
+                    DestinationInfo.builder(deviceService.getPort(cp.deviceId(), cp.port())) :
+                    DestinationInfo.builder(getTunnelPort(deviceId))
+                            .setRemoteIp(getRemoteIp(cp.deviceId()));
+
+            dBuilder.setMac(vPort.macAddress())
+                    .setTunnelId(tunnelId);
+            dstInfos.add(dBuilder.build());
+        }
+        return dstInfos;
+    }
+
+    /**
+     * Returns local ports associated with a given OpenStack network.
+     *
+     * @param bridgeId device id
+     * @param vNet OpenStack network
+     * @return port list, empty list if no port exists
+     */
+    private List<Port> getLocalSameNetworkPorts(DeviceId bridgeId, OpenstackNetwork vNet) {
+        List<Port> ports = new ArrayList<>();
+        openstackService.ports(vNet.id()).stream().forEach(port -> {
+            ConnectPoint cp = getConnectPoint(port);
+            if (cp != null && cp.deviceId().equals(bridgeId)) {
+                ports.add(deviceService.getPort(cp.deviceId(), cp.port()));
+            }
+        });
+        return ports;
+    }
+
+    /**
+     * Returns OpenStack port associated with a given host.
+     *
+     * @param host host
+     * @return OpenStack port, or null if no port has been found
+     */
+    private OpenstackPort getOpenstackPortByHost(Host host) {
+        Port port = deviceService.getPort(host.location().deviceId(),
+                                          host.location().port());
+        return openstackService.port(port);
+    }
+
+    /**
+     * Returns OpenStack network associated with a given host.
+     *
+     * @param host host
+     * @return OpenStack network, or null if no network has been found
+     */
+    private OpenstackNetwork getOpenstackNetworkByHost(Host host) {
+        OpenstackPort vPort = getOpenstackPortByHost(host);
+        if (vPort != null) {
+            return openstackService.network(vPort.networkId());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns port name with OpenStack port information.
+     *
+     * @param vPort OpenStack port
+     * @return port name
+     */
+    private String getPortName(OpenstackPort vPort) {
+        checkNotNull(vPort);
+        return VPORT_PREFIX + vPort.id().substring(0, 10);
+    }
+
+    /**
+     * Returns connect point of a given OpenStack port.
+     * It assumes there's only one physical port associated with an OpenStack port.
+     *
+     * @param vPort openstack port
+     * @return connect point, null if no such port exists
+     */
+    private ConnectPoint getConnectPoint(OpenstackPort vPort) {
+        try {
+            Host host = hostService.getHostsByMac(vPort.macAddress())
+                    .stream()
+                    .findFirst()
+                    .get();
+            return new ConnectPoint(host.location().deviceId(), host.location().port());
+        } catch (NoSuchElementException e) {
+            log.debug("Not a valid host with {}", vPort.macAddress());
+            return null;
+        }
+    }
+
+    /**
+     * Installs flow rules for a given OpenStack network.
+     *
+     * @param vNet OpenStack network
+     */
+    private void installFlowRules(OpenstackNetwork vNet) {
+        checkNotNull(vNet, "Tenant network should not be null");
+
+        for (Device device : deviceService.getAvailableDevices(SWITCH)) {
+            List<DestinationInfo> dstInfos = getSameNetworkPortsInfo(device.id(), vNet);
+
+            for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) {
+                List<DestinationInfo> localInInfos = dstInfos.stream()
+                        .filter(info -> !info.output().equals(inPort))
+                        .collect(Collectors.toList());
+                ruleInstaller.installFlowRulesLocalIn(device.id(), inPort, localInInfos);
+            }
+
+            Port tunPort = getTunnelPort(device.id());
+            List<DestinationInfo> tunnelInInfos = dstInfos.stream()
+                    .filter(info -> !info.output().equals(tunPort))
+                    .collect(Collectors.toList());
+            ruleInstaller.installFlowRulesTunnelIn(device.id(), tunPort, tunnelInInfos);
+        }
+    }
+
+    /**
+     * Uninstalls flow rules associated with a given host for a given OpenStack network.
+     *
+     * @param vNet OpenStack network
+     * @param host removed host
+     */
+    private void uninstallFlowRules(OpenstackNetwork vNet, Host host) {
+        checkNotNull(vNet, "Tenant network should not be null");
+
+        Port removedPort = deviceService.getPort(host.location().deviceId(),
+                                                 host.location().port());
+
+        for (Device device : deviceService.getAvailableDevices(SWITCH)) {
+            List<DestinationInfo> dstInfos = getSameNetworkPortsInfo(device.id(), vNet);
+
+            for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) {
+                List<DestinationInfo> localInInfos = Lists.newArrayList(
+                        DestinationInfo.builder(getTunnelPort(device.id()))
+                                .setTunnelId(Long.valueOf(vNet.segmentId()))
+                                .setMac(host.mac())
+                                .setRemoteIp(getRemoteIp(host.location().deviceId()))
+                                .build());
+                ruleInstaller.uninstallFlowRules(device.id(), inPort, localInInfos);
+            }
+
+            if (device.id().equals(host.location().deviceId())) {
+                Port tunPort = getTunnelPort(device.id());
+                List<DestinationInfo> tunnelInInfo = Lists.newArrayList(
+                        DestinationInfo.builder(removedPort)
+                                .setTunnelId(Long.valueOf(vNet.segmentId()))
+                                .setMac(host.mac())
+                                .build());
+
+                ruleInstaller.uninstallFlowRules(device.id(), tunPort, tunnelInInfo);
+                ruleInstaller.uninstallFlowRules(device.id(), removedPort, dstInfos);
+            }
+        }
+    }
+
     private class InternalDeviceListener implements DeviceListener {
 
         @Override
@@ -644,12 +887,40 @@
 
         @Override
         public void connected(Host host) {
+            CordVtnNode node = getNodeByBridgeId(host.location().deviceId());
+            if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) {
+                // do nothing for the host on unregistered or unprepared device
+                return;
+            }
+
+            OpenstackNetwork vNet = getOpenstackNetworkByHost(host);
+            if (vNet == null) {
+                return;
+            }
+
             log.info("VM {} is detected", host.id());
+
+            hostNetworkMap.put(host.id(), vNet.id());
+            installFlowRules(vNet);
         }
 
         @Override
         public void disconnected(Host host) {
+            CordVtnNode node = getNodeByBridgeId(host.location().deviceId());
+            if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) {
+                // do nothing for the host on unregistered or unprepared device
+                return;
+            }
+
+            OpenstackNetwork vNet = openstackService.network(hostNetworkMap.get(host.id()));
+            if (vNet == null) {
+                return;
+            }
+
             log.info("VM {} is vanished", host.id());
+
+            uninstallFlowRules(vNet, host);
+            hostNetworkMap.remove(host.id());
         }
     }
 }