CORD-483 Made virtual network gateway MAC address configurable

- Added 'gatewayMAC' field to network config for cordvtn
- Implemented to send gratuitous ARP when gateway MAC is updated

Change-Id: I4f9050f4be64f04e0568515bbb95474513bbe057
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 c4894bf..19c9e7d 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -36,6 +36,12 @@
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.Port;
 import org.onosproject.net.SparseAnnotations;
+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;
@@ -62,10 +68,10 @@
 import java.util.Map;
 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 java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -83,6 +89,12 @@
     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)
@@ -109,21 +121,31 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OpenstackSwitchingService openstackService;
 
-    private static final int NUM_THREADS = 1;
+    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 LOCATION_IP = "locationIp";
     private static final String OPENSTACK_VM_ID = "openstackVmId";
 
-    private final ExecutorService eventExecutor = Executors
-            .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/cordvtn", "event-handler"));
+    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 gatewayMac = MacAddress.NONE;
 
     /**
      * Creates an cordvtn host location provider.
@@ -134,8 +156,7 @@
 
     @Activate
     protected void activate() {
-        ApplicationId appId = coreService.registerApplication("org.onosproject.cordvtn");
-
+        appId = coreService.registerApplication("org.onosproject.cordvtn");
         ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
                                                  deviceService,
                                                  driverService,
@@ -150,17 +171,24 @@
         hostService.addListener(hostListener);
         hostProvider = hostProviderRegistry.register(this);
 
+        configRegistry.registerConfigFactory(configFactory);
+        configService.addListener(configListener);
+        readConfiguration();
+
         log.info("Started");
     }
 
     @Deactivate
     protected void deactivate() {
+        hostProviderRegistry.unregister(this);
         hostService.removeListener(hostListener);
+
         packetService.removeProcessor(packetProcessor);
 
-        eventExecutor.shutdown();
-        hostProviderRegistry.unregister(this);
+        configRegistry.unregisterConfigFactory(configFactory);
+        configService.removeListener(configListener);
 
+        eventExecutor.shutdown();
         log.info("Stopped");
     }
 
@@ -379,6 +407,10 @@
             // TODO check if the service needs an update on its group buckets after done CORD-433
             ruleInstaller.updateServiceGroup(service);
             arpProxy.addServiceIp(service.serviceIp());
+
+            // sends gratuitous ARP here for the case of adding existing VMs
+            // when ONOS or cordvtn app is restarted
+            arpProxy.sendGratuitousArp(service.serviceIp(), gatewayMac, Sets.newHashSet(host));
         }
 
         ruleInstaller.populateBasicConnectionRules(host, getTunnelIp(host), vNet);
@@ -418,6 +450,47 @@
         }
     }
 
+    /**
+     * Sets service network gateway MAC address and sends out gratuitous ARP to all
+     * VMs to update the gateway MAC address.
+     *
+     * @param mac mac address
+     */
+    private void setServiceGatewayMac(MacAddress mac) {
+        if (mac != null && !mac.equals(gatewayMac)) {
+            gatewayMac = mac;
+            log.debug("Set service gateway MAC address to {}", gatewayMac.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.sendGratuitousArp(
+                        service.serviceIp(),
+                        gatewayMac,
+                        service.hosts().keySet());
+            }
+        });
+    }
+
+    /**
+     * Updates configurations.
+     */
+    private void readConfiguration() {
+        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+        if (config == null) {
+            log.debug("No configuration found");
+            return;
+        }
+
+        setServiceGatewayMac(config.gatewayMac());
+   }
+
     private class InternalHostListener implements HostListener {
 
         @Override
@@ -446,15 +519,31 @@
             }
 
             Ethernet ethPacket = context.inPacket().parsed();
-            if (ethPacket == null) {
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
                 return;
             }
 
-            if (ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+            arpProxy.processArpPacket(context, ethPacket, gatewayMac);
+        }
+    }
+
+    private class InternalConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (!event.configClass().equals(CordVtnConfig.class)) {
                 return;
             }
 
-            arpProxy.processArpPacket(context, ethPacket);
+            switch (event.type()) {
+                case CONFIG_ADDED:
+                case CONFIG_UPDATED:
+                    log.info("Network configuration changed");
+                    eventExecutor.execute(CordVtn.this::readConfiguration);
+                    break;
+                default:
+                    break;
+            }
         }
     }
 }