Placed API and implementation into separate packages

Change-Id: If8a9223a7a225db1b2aa2d09738857af482736bc
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
new file mode 100644
index 0000000..967ca02
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright 2015-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.cordvtn.impl;
+
+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;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cordvtn.api.CordService;
+import org.onosproject.cordvtn.api.CordServiceId;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcp.DhcpService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provisions virtual tenant networks with service chaining capability
+ * in OpenStack environment.
+ */
+@Component(immediate = true)
+@Service
+public class CordVtn extends AbstractProvider implements CordVtnService, HostProvider {
+
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry configRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostProviderRegistry hostProviderRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DhcpService dhcpService;
+
+    private final ConfigFactory configFactory =
+            new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
+                @Override
+                public CordVtnConfig createConfig() {
+                    return new CordVtnConfig();
+                }
+            };
+
+    private static final String DEFAULT_TUNNEL = "vxlan";
+    private static final String SERVICE_ID = "serviceId";
+    private static final String OPENSTACK_PORT_ID = "openstackPortId";
+    private static final String DATA_PLANE_IP = "dataPlaneIp";
+    private static final String DATA_PLANE_INTF = "dataPlaneIntf";
+    private static final String S_TAG = "stag";
+    private static final String VSG_HOST_ID = "vsgHostId";
+    private static final String CREATED_TIME = "createdTime";
+
+    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
+
+    private final ExecutorService eventExecutor =
+            newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final HostListener hostListener = new InternalHostListener();
+    private final NetworkConfigListener configListener = new InternalConfigListener();
+
+    private ApplicationId appId;
+    private HostProviderService hostProvider;
+    private CordVtnRuleInstaller ruleInstaller;
+    private CordVtnArpProxy arpProxy;
+    private volatile MacAddress privateGatewayMac = MacAddress.NONE;
+
+    /**
+     * Creates an cordvtn host location provider.
+     */
+    public CordVtn() {
+        super(new ProviderId("host", CORDVTN_APP_ID));
+    }
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication("org.onosproject.cordvtn");
+        ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
+                                                 deviceService,
+                                                 driverService,
+                                                 groupService,
+                                                 configRegistry,
+                                                 DEFAULT_TUNNEL);
+
+        arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        arpProxy.requestPacket();
+
+        hostService.addListener(hostListener);
+        hostProvider = hostProviderRegistry.register(this);
+
+        configRegistry.registerConfigFactory(configFactory);
+        configService.addListener(configListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        hostProviderRegistry.unregister(this);
+        hostService.removeListener(hostListener);
+
+        packetService.removeProcessor(packetProcessor);
+
+        configRegistry.unregisterConfigFactory(configFactory);
+        configService.removeListener(configListener);
+
+        eventExecutor.shutdown();
+        log.info("Stopped");
+    }
+
+    @Override
+    public void triggerProbe(Host host) {
+        /*
+         * Note: In CORD deployment, we assume that all hosts are configured.
+         * Therefore no probe is required.
+         */
+    }
+
+    @Override
+    public void createServiceDependency(CordServiceId tServiceId, CordServiceId pServiceId,
+                                        boolean isBidirectional) {
+        CordService tService = getCordService(tServiceId);
+        CordService pService = getCordService(pServiceId);
+
+        if (tService == null || pService == null) {
+            log.error("Failed to create CordService for {}", tServiceId.id());
+            return;
+        }
+
+        log.info("Service dependency from {} to {} created.", tService.id().id(), pService.id().id());
+        ruleInstaller.populateServiceDependencyRules(tService, pService, isBidirectional);
+    }
+
+    @Override
+    public void removeServiceDependency(CordServiceId tServiceId, CordServiceId pServiceId) {
+        CordService tService = getCordService(tServiceId);
+        CordService pService = getCordService(pServiceId);
+
+        if (tService == null || pService == null) {
+            log.error("Failed to create CordService for {}", tServiceId.id());
+            return;
+        }
+
+        log.info("Service dependency from {} to {} removed.", tService.id().id(), pService.id().id());
+        ruleInstaller.removeServiceDependencyRules(tService, pService);
+    }
+
+    @Override
+    public void addServiceVm(CordVtnNode node, ConnectPoint connectPoint) {
+        Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+        OpenstackPort vPort = openstackService.port(port);
+        if (vPort == null) {
+            log.warn("Failed to get OpenstackPort for {}", getPortName(port));
+            return;
+        }
+
+        MacAddress mac = vPort.macAddress();
+        HostId hostId = HostId.hostId(mac);
+
+        Host existingHost = hostService.getHost(hostId);
+        if (existingHost != null) {
+            String serviceId = existingHost.annotations().value(SERVICE_ID);
+            if (serviceId == null || !serviceId.equals(vPort.networkId())) {
+                // this host is not injected by cordvtn or a stale host, remove it
+                hostProvider.hostVanished(existingHost.id());
+            }
+        }
+
+        // Included CREATED_TIME to annotation intentionally to trigger HOST_UPDATED
+        // event so that the flow rule population for this host can happen.
+        // This ensures refreshing data plane by pushing network config always make
+        // the data plane synced.
+        Set<IpAddress> fixedIp = Sets.newHashSet(vPort.fixedIps().values());
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(SERVICE_ID, vPort.networkId())
+                .set(OPENSTACK_PORT_ID, vPort.id())
+                .set(DATA_PLANE_IP, node.dpIp().ip().toString())
+                .set(DATA_PLANE_INTF, node.dpIntf())
+                .set(CREATED_TIME, String.valueOf(System.currentTimeMillis()));
+
+        String serviceVlan = getServiceVlan(vPort);
+        if (serviceVlan != null) {
+            annotations.set(S_TAG, serviceVlan);
+        }
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                mac,
+                VlanId.NONE,
+                new HostLocation(connectPoint, System.currentTimeMillis()),
+                fixedIp,
+                annotations.build());
+
+        hostProvider.hostDetected(hostId, hostDesc, false);
+    }
+
+    @Override
+    public void removeServiceVm(ConnectPoint connectPoint) {
+        hostService.getConnectedHosts(connectPoint)
+                .stream()
+                .forEach(host -> hostProvider.hostVanished(host.id()));
+    }
+
+    @Override
+    public void updateVirtualSubscriberGateways(HostId vSgHostId, String serviceVlan,
+                                                Map<IpAddress, MacAddress> vSgs) {
+        Host vSgHost = hostService.getHost(vSgHostId);
+        if (vSgHost == null || !vSgHost.annotations().value(S_TAG).equals(serviceVlan)) {
+            log.debug("Invalid vSG updates for {}", serviceVlan);
+            return;
+        }
+
+        log.info("Updates vSGs in {} with {}", vSgHost.id(), vSgs.toString());
+        vSgs.entrySet().stream()
+                .filter(entry -> hostService.getHostsByMac(entry.getValue()).isEmpty())
+                .forEach(entry -> addVirtualSubscriberGateway(
+                        vSgHost,
+                        entry.getKey(),
+                        entry.getValue(),
+                        serviceVlan));
+
+        hostService.getConnectedHosts(vSgHost.location()).stream()
+                .filter(host -> !host.mac().equals(vSgHost.mac()))
+                .filter(host -> !vSgs.values().contains(host.mac()))
+                .forEach(host -> {
+                    log.info("Removed vSG {}", host.toString());
+                    hostProvider.hostVanished(host.id());
+                });
+    }
+
+    /**
+     * Adds virtual subscriber gateway to the system.
+     *
+     * @param vSgHost host virtual machine of this vSG
+     * @param vSgIp vSG ip address
+     * @param vSgMac vSG mac address
+     * @param serviceVlan service vlan
+     */
+    private void addVirtualSubscriberGateway(Host vSgHost, IpAddress vSgIp, MacAddress vSgMac,
+                                             String serviceVlan) {
+        log.info("vSG with IP({}) MAC({}) added", vSgIp.toString(), vSgMac.toString());
+
+        HostId hostId = HostId.hostId(vSgMac);
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(S_TAG, serviceVlan)
+                .set(VSG_HOST_ID, vSgHost.id().toString())
+                .set(CREATED_TIME, String.valueOf(System.currentTimeMillis()));
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                vSgMac,
+                VlanId.NONE,
+                vSgHost.location(),
+                Sets.newHashSet(vSgIp),
+                annotations.build());
+
+        hostProvider.hostDetected(hostId, hostDesc, false);
+    }
+
+    /**
+     * Returns public ip addresses of vSGs running inside a give vSG host.
+     *
+     * @param vSgHost vSG host
+     * @return map of ip and mac address, or empty map
+     */
+    private Map<IpAddress, MacAddress> getSubscriberGateways(Host vSgHost) {
+        String vPortId = vSgHost.annotations().value(OPENSTACK_PORT_ID);
+        String serviceVlan = vSgHost.annotations().value(S_TAG);
+
+        OpenstackPort vPort = openstackService.port(vPortId);
+        if (vPort == null) {
+            log.warn("Failed to get OpenStack port {} for VM {}", vPortId, vSgHost.id());
+            return Maps.newHashMap();
+        }
+
+        if (!serviceVlan.equals(getServiceVlan(vPort))) {
+            log.error("Host({}) s-tag does not match with vPort s-tag", vSgHost.id());
+            return Maps.newHashMap();
+        }
+
+        return vPort.allowedAddressPairs();
+    }
+
+    /**
+     * Returns CordService by service ID.
+     *
+     * @param serviceId service id
+     * @return cord service, or null if it fails to get network from OpenStack
+     */
+    private CordService getCordService(CordServiceId serviceId) {
+        OpenstackNetwork vNet = openstackService.network(serviceId.id());
+        if (vNet == null) {
+            log.warn("Couldn't find OpenStack network for service {}", serviceId.id());
+            return null;
+        }
+
+        OpenstackSubnet subnet = vNet.subnets().stream()
+                .findFirst()
+                .orElse(null);
+        if (subnet == null) {
+            log.warn("Couldn't find OpenStack subnet for service {}", serviceId.id());
+            return null;
+        }
+
+        Set<CordServiceId> tServices = Sets.newHashSet();
+        // TODO get tenant services from XOS
+
+        Map<Host, IpAddress> hosts = getHostsWithOpenstackNetwork(vNet)
+                .stream()
+                .collect(Collectors.toMap(host -> host, this::getTunnelIp));
+
+        return new CordService(vNet, subnet, hosts, tServices);
+    }
+
+    /**
+     * Returns CordService by OpenStack network.
+     *
+     * @param vNet OpenStack network
+     * @return cord service
+     */
+    private CordService getCordService(OpenstackNetwork vNet) {
+        checkNotNull(vNet);
+
+        CordServiceId serviceId = CordServiceId.of(vNet.id());
+        OpenstackSubnet subnet = vNet.subnets().stream()
+                .findFirst()
+                .orElse(null);
+        if (subnet == null) {
+            log.warn("Couldn't find OpenStack subnet for service {}", serviceId);
+            return null;
+        }
+
+        Set<CordServiceId> tServices = Sets.newHashSet();
+        // TODO get tenant services from XOS
+
+        Map<Host, IpAddress> hosts = getHostsWithOpenstackNetwork(vNet)
+                .stream()
+                .collect(Collectors.toMap(host -> host, this::getTunnelIp));
+
+        return new CordService(vNet, subnet, hosts, tServices);
+    }
+
+    /**
+     * Returns IP address for tunneling for a given host.
+     *
+     * @param host host
+     * @return ip address, or null
+     */
+    private IpAddress getTunnelIp(Host host) {
+        String ip = host.annotations().value(DATA_PLANE_IP);
+        return ip == null ? null : IpAddress.valueOf(ip);
+    }
+
+    /**
+     * Returns port name.
+     *
+     * @param port port
+     * @return port name
+     */
+    private String getPortName(Port port) {
+        return port.annotations().value("portName");
+    }
+
+    /**
+     * Returns s-tag from a given OpenStack port.
+     *
+     * @param vPort openstack port
+     * @return s-tag string
+     */
+    private String getServiceVlan(OpenstackPort vPort) {
+        checkNotNull(vPort);
+
+        if (vPort.name() != null && vPort.name().startsWith(S_TAG)) {
+            return vPort.name().split("-")[1];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns service ID of this host.
+     *
+     * @param host host
+     * @return service id, or null if not found
+     */
+    private String getServiceId(Host host) {
+        return host.annotations().value(SERVICE_ID);
+    }
+
+    /**
+     * Returns hosts associated with a given OpenStack network.
+     *
+     * @param vNet openstack network
+     * @return set of hosts
+     */
+    private Set<Host> getHostsWithOpenstackNetwork(OpenstackNetwork vNet) {
+        checkNotNull(vNet);
+
+        String vNetId = vNet.id();
+        return StreamSupport.stream(hostService.getHosts().spliterator(), false)
+                .filter(host -> Objects.equals(vNetId, getServiceId(host)))
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Registers static DHCP lease for a given host.
+     *
+     * @param host host
+     * @param service cord service
+     */
+    private void registerDhcpLease(Host host, CordService service) {
+        List<Ip4Address> options = Lists.newArrayList();
+        options.add(Ip4Address.makeMaskPrefix(service.serviceIpRange().prefixLength()));
+        options.add(service.serviceIp().getIp4Address());
+        options.add(service.serviceIp().getIp4Address());
+        options.add(DEFAULT_DNS);
+
+        log.debug("Set static DHCP mapping for {}", host.mac());
+        dhcpService.setStaticMapping(host.mac(),
+                                     host.ipAddresses().stream().findFirst().get().getIp4Address(),
+                                     true,
+                                     options);
+    }
+
+    /**
+     * Handles VM detected situation.
+     *
+     * @param host host
+     */
+    private void serviceVmAdded(Host host) {
+        String serviceVlan = host.annotations().value(S_TAG);
+        if (serviceVlan != null) {
+            virtualSubscriberGatewayAdded(host, serviceVlan);
+        }
+
+        String vNetId = host.annotations().value(SERVICE_ID);
+        if (vNetId == null) {
+            // ignore this host, it is not the service VM, or it's a vSG
+            return;
+        }
+
+        OpenstackNetwork vNet = openstackService.network(vNetId);
+        if (vNet == null) {
+            log.warn("Failed to get OpenStack network {} for VM {}.",
+                     vNetId, host.id());
+            return;
+        }
+
+        log.info("VM is detected, MAC: {} IP: {}",
+                 host.mac(),
+                 host.ipAddresses().stream().findFirst().get());
+
+        CordService service = getCordService(vNet);
+        if (service == null) {
+            return;
+        }
+
+        switch (service.serviceType()) {
+            case MANAGEMENT:
+                ruleInstaller.populateManagementNetworkRules(host, service);
+                break;
+            case PRIVATE:
+                arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
+            case PUBLIC:
+            default:
+                // TODO check if the service needs an update on its group buckets after done CORD-433
+                ruleInstaller.updateServiceGroup(service);
+                // sends gratuitous ARP here for the case of adding existing VMs
+                // when ONOS or cordvtn app is restarted
+                arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(host));
+                break;
+        }
+
+        registerDhcpLease(host, service);
+        ruleInstaller.populateBasicConnectionRules(host, getTunnelIp(host), vNet);
+    }
+
+    /**
+     * Handles VM removed situation.
+     *
+     * @param host host
+     */
+    private void serviceVmRemoved(Host host) {
+        String serviceVlan = host.annotations().value(S_TAG);
+        if (serviceVlan != null) {
+            virtualSubscriberGatewayRemoved(host);
+        }
+
+        String vNetId = host.annotations().value(SERVICE_ID);
+        if (vNetId == null) {
+            // ignore it, it's not the service VM or it's a vSG
+            return;
+        }
+
+        OpenstackNetwork vNet = openstackService.network(vNetId);
+        if (vNet == null) {
+            log.warn("Failed to get OpenStack network {} for VM {}",
+                     vNetId, host.id());
+            return;
+        }
+
+        log.info("VM is vanished, MAC: {} IP: {}",
+                 host.mac(),
+                 host.ipAddresses().stream().findFirst().get());
+
+        ruleInstaller.removeBasicConnectionRules(host);
+        dhcpService.removeStaticMapping(host.mac());
+
+        CordService service = getCordService(vNet);
+        if (service == null) {
+            return;
+        }
+
+        switch (service.serviceType()) {
+            case MANAGEMENT:
+                ruleInstaller.removeManagementNetworkRules(host, service);
+                break;
+            case PRIVATE:
+                if (getHostsWithOpenstackNetwork(vNet).isEmpty()) {
+                    arpProxy.removeGateway(service.serviceIp());
+                }
+            case PUBLIC:
+            default:
+                // TODO check if the service needs an update on its group buckets after done CORD-433
+                ruleInstaller.updateServiceGroup(service);
+                break;
+        }
+    }
+
+
+    /**
+     * Handles virtual subscriber gateway VM or container.
+     *
+     * @param host new host with stag, it can be vsg VM or vsg
+     * @param serviceVlan service vlan
+     */
+    private void virtualSubscriberGatewayAdded(Host host, String serviceVlan) {
+        Map<IpAddress, MacAddress> vSgs;
+        Host vSgHost;
+
+        String vSgHostId = host.annotations().value(VSG_HOST_ID);
+        if (vSgHostId == null) {
+            log.debug("vSG VM detected {}", host.id());
+
+            vSgHost = host;
+            vSgs = getSubscriberGateways(vSgHost);
+            vSgs.entrySet().stream().forEach(entry -> addVirtualSubscriberGateway(
+                    vSgHost,
+                    entry.getKey(),
+                    entry.getValue(),
+                    serviceVlan));
+        } else {
+            vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
+            if (vSgHost == null) {
+                return;
+            }
+
+            log.debug("vSG detected {}", host.id());
+            vSgs = getSubscriberGateways(vSgHost);
+        }
+
+        ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
+    }
+
+    /**
+     * Handles virtual subscriber gateway removed.
+     *
+     * @param vSg vsg host to remove
+     */
+    private void virtualSubscriberGatewayRemoved(Host vSg) {
+        String vSgHostId = vSg.annotations().value(VSG_HOST_ID);
+        if (vSgHostId == null) {
+            return;
+        }
+
+        Host vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
+        if (vSgHost == null) {
+            return;
+        }
+
+        log.info("vSG removed {}", vSg.id());
+        Map<IpAddress, MacAddress> vSgs = getSubscriberGateways(vSgHost);
+        ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
+    }
+
+    /**
+     * Sets service network gateway MAC address and sends out gratuitous ARP to all
+     * VMs to update the gateway MAC address.
+     *
+     * @param newMac mac address to update
+     */
+    private void setPrivateGatewayMac(MacAddress newMac) {
+        if (newMac == null || newMac.equals(privateGatewayMac)) {
+            // no updates, do nothing
+            return;
+        }
+
+        privateGatewayMac = newMac;
+        log.debug("Set service gateway MAC address to {}", privateGatewayMac.toString());
+
+        // TODO get existing service list from XOS and replace the loop below
+        Set<String> vNets = Sets.newHashSet();
+        hostService.getHosts().forEach(host -> vNets.add(host.annotations().value(SERVICE_ID)));
+        vNets.remove(null);
+
+        vNets.stream().forEach(vNet -> {
+            CordService service = getCordService(CordServiceId.of(vNet));
+            if (service != null) {
+                arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
+                arpProxy.sendGratuitousArpForGateway(service.serviceIp(), service.hosts().keySet());
+            }
+        });
+    }
+
+    /**
+     * Sets public gateway MAC address.
+     *
+     * @param publicGateways gateway ip and mac address pairs
+     */
+    private void setPublicGatewayMac(Map<IpAddress, MacAddress> publicGateways) {
+        publicGateways.entrySet()
+                .stream()
+                .forEach(entry -> {
+                    arpProxy.addGateway(entry.getKey(), entry.getValue());
+                    log.info("Added public gateway IP {}, MAC {}",
+                             entry.getKey().toString(), entry.getValue().toString());
+                });
+        // TODO notice gateway MAC change to VMs holds this gateway IP
+    }
+
+    /**
+     * Updates configurations.
+     */
+    private void readConfiguration() {
+        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+        if (config == null) {
+            log.debug("No configuration found");
+            return;
+        }
+
+        setPrivateGatewayMac(config.privateGatewayMac());
+        setPublicGatewayMac(config.publicGateways());
+   }
+
+    private class InternalHostListener implements HostListener {
+
+        @Override
+        public void event(HostEvent event) {
+            Host host = event.subject();
+            if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+                // do not allow to proceed without mastership
+                return;
+            }
+
+            switch (event.type()) {
+                case HOST_UPDATED:
+                case HOST_ADDED:
+                    eventExecutor.submit(() -> serviceVmAdded(host));
+                    break;
+                case HOST_REMOVED:
+                    eventExecutor.submit(() -> serviceVmRemoved(host));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    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;
+            }
+
+            arpProxy.processArpPacket(context, ethPacket);
+        }
+    }
+
+    private class InternalConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (!event.configClass().equals(CordVtnConfig.class)) {
+                return;
+            }
+
+            switch (event.type()) {
+                case CONFIG_ADDED:
+                case CONFIG_UPDATED:
+                    log.info("Network configuration changed");
+                    eventExecutor.execute(CordVtn.this::readConfiguration);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}