Refactored OpenstackSwitching app

[DONE]
- Restructured to activate or deactivate switching and routing app separately
- Fixed to add or remove host when port is detected or vanished
- Use openstack node service to get integration bridges and data IP

[TODO]
- Remove use of OpenstackPortInfo
- Support installing flow rules for exising VMs
- Call security group update method when port update triggered from OpenStack

Change-Id: Ic0b2ac3f7ab07f0e20c97c6edfdd1928b9767baf
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java
new file mode 100644
index 0000000..c855403
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java
@@ -0,0 +1,221 @@
+/*
+* Copyright 2016-present Open Networking Laboratory
+*
+* 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.onosproject.openstacknetworking.switching;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+/**
+ * Handles ARP packet from VMs.
+ */
+@Component(immediate = true)
+public final class OpenstackArpHandler extends AbstractVmHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String GATEWAY_MAC = "gatewayMac";
+    private static final String DEFAULT_GATEWAY_MAC = "1f:1f:1f:1f:1f:1f";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Property(name = GATEWAY_MAC, value = DEFAULT_GATEWAY_MAC,
+            label = "Fake MAC address for virtual network subnet gateway")
+    private String gatewayMac = DEFAULT_GATEWAY_MAC;
+
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final Set<Ip4Address> gateways = Sets.newConcurrentHashSet();
+
+    @Activate
+    protected void activate() {
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        super.deactivate();
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        String updatedMac;
+
+        updatedMac = Tools.get(properties, GATEWAY_MAC);
+        if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(gatewayMac)) {
+            gatewayMac = updatedMac;
+        }
+
+        log.info("Modified");
+    }
+
+    /**
+     * Processes ARP request packets.
+     * It checks if the target IP is owned by a known host first and then ask to
+     * OpenStack if it's not. This ARP proxy does not support overlapping IP.
+     *
+     * @param context packet context
+     * @param ethPacket ethernet packet
+     */
+    private void processPacketIn(PacketContext context, Ethernet ethPacket) {
+        ARP arpPacket = (ARP) ethPacket.getPayload();
+        if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+            return;
+        }
+
+        Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+        MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
+                getMacFromHostService(targetIp);
+        if (replyMac.equals(MacAddress.NONE)) {
+            replyMac = getMacFromOpenstack(targetIp);
+        }
+
+        if (replyMac == MacAddress.NONE) {
+            log.debug("Failed to find MAC address for {}", targetIp.toString());
+            return;
+        }
+
+        Ethernet ethReply = ARP.buildArpReply(
+                targetIp.getIp4Address(),
+                replyMac,
+                ethPacket);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(context.inPacket().receivedFrom().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                context.inPacket().receivedFrom().deviceId(),
+                treatment,
+                ByteBuffer.wrap(ethReply.serialize())));
+    }
+
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * OpenStack. It does not support overlapping IP.
+     *
+     * @param targetIp target ip address
+     * @return mac address, or null if it fails to fetch the mac
+     */
+    private MacAddress getMacFromOpenstack(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        OpenstackPort openstackPort = openstackService.ports()
+                .stream()
+                .filter(port -> port.fixedIps().containsValue(targetIp.getIp4Address()))
+                .findFirst()
+                .orElse(null);
+
+        if (openstackPort != null) {
+            log.debug("Found MAC from OpenStack for {}", targetIp.toString());
+            return openstackPort.macAddress();
+        } else {
+            return MacAddress.NONE;
+        }
+    }
+
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * host service. It does not support overlapping IP.
+     *
+     * @param targetIp target ip
+     * @return mac address, or null if it fails to find the mac
+     */
+    private MacAddress getMacFromHostService(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        Host host = hostService.getHostsByIp(targetIp)
+                .stream()
+                .findFirst()
+                .orElse(null);
+
+        if (host != null) {
+            log.debug("Found MAC from host service for {}", targetIp.toString());
+            return host.mac();
+        } else {
+            return MacAddress.NONE;
+        }
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        OpenstackNetwork osNet = openstackService.network(host.annotations().value(NETWORK_ID));
+        if (osNet == null) {
+            log.warn("Failed to get OpenStack network for {}", host);
+            return;
+        }
+        osNet.subnets().stream()
+                .forEach(subnet -> gateways.add(Ip4Address.valueOf(subnet.gatewayIp())));
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        // TODO remove subnet gateway from gateways if no hosts exists on that subnet
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+                return;
+            }
+            processPacketIn(context, ethPacket);
+        }
+    }
+}