Fix: Add inst port to store if host updated with no inst port found

1. Remove purge state CLI and REST API
2. Remove stateful SNAT rules when removing router and ext gateway

Change-Id: I133e9f280690d2da9f65e39cb83618db41b72502
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
deleted file mode 100644
index ad65e69..0000000
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.onosproject.openstacknetworking.cli;
-
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
-import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
-import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
-
-/**
- * Purges all existing network states.
- */
-@Service
-@Command(scope = "onos", name = "openstack-purge-states",
-        description = "Purges all OpenStack network states")
-public class OpenstackPurgeStateCommand extends AbstractShellCommand {
-
-    @Override
-    protected void doExecute() {
-        get(OpenstackRouterAdminService.class).clear();
-        get(OpenstackNetworkAdminService.class).clear();
-        get(OpenstackSecurityGroupAdminService.class).clear();
-    }
-}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
index 82413d4..ba90b7e 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
@@ -51,10 +51,13 @@
 
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
 import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
 import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
@@ -110,6 +113,8 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected OpenstackRouterService routerService;
 
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
     private final InstancePortStoreDelegate
                             delegate = new InternalInstancePortStoreDelegate();
     private final InternalHostListener
@@ -308,22 +313,31 @@
 
             switch (event.type()) {
                 case HOST_UPDATED:
-                    updateInstancePort(instPort);
+                    eventExecutor.execute(() -> processHostUpdate(instPort));
                     break;
                 case HOST_ADDED:
-                    processHostAddition(instPort);
+                    eventExecutor.execute(() -> processHostAddition(instPort));
                     break;
                 case HOST_REMOVED:
-                    processHostRemoval(instPort);
+                    eventExecutor.execute(() -> processHostRemoval(instPort));
                     break;
                 case HOST_MOVED:
-                    processHostMove(event, instPort);
+                    eventExecutor.execute(() -> processHostMove(event, instPort));
                     break;
                 default:
                     break;
             }
         }
 
+        private void processHostUpdate(InstancePort instPort) {
+            InstancePort existingPort = instancePort(instPort.portId());
+            if (existingPort == null) {
+                createInstancePort(instPort);
+            } else {
+                updateInstancePort(instPort);
+            }
+        }
+
         private void processHostAddition(InstancePort instPort) {
             InstancePort existingPort = instancePort(instPort.portId());
             if (existingPort == null) {
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
index c7c0ef3..55b1a41 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -61,6 +61,8 @@
 import org.onosproject.openstacknetworking.api.OpenstackNetwork.Type;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
 import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
 import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
 import org.onosproject.openstacknetworking.api.OpenstackRouterService;
@@ -109,7 +111,6 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SNAT_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
-import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
 import static org.onosproject.openstacknetworking.api.OpenstackNetwork.Type.FLAT;
 import static org.onosproject.openstacknetworking.api.OpenstackNetwork.Type.VLAN;
 import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.USE_STATEFUL_SNAT;
@@ -144,6 +145,7 @@
     private static final int TP_PORT_MINIMUM_NUM = 1025;
     private static final int TP_PORT_MAXIMUM_NUM = 65535;
     private static final int VM_PREFIX = 32;
+    private static final String DEVICE_OWNER_ROUTER_GW = "network:router_gateway";
 
     private static final String MSG_ENABLED = "Enabled ";
     private static final String MSG_DISABLED = "Disabled ";
@@ -206,6 +208,7 @@
     private final InstancePortListener instancePortListener = new InternalInstancePortListener();
     private final OpenstackRouterListener osRouterListener = new InternalRouterEventListener();
     private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
+    private final OpenstackNetworkListener osNetworkListener = new InternalNetworkEventListener();
 
     private ConsistentMap<Integer, Long> allocatedPortNumMap;
     private DistributedSet<Integer> unUsedPortNumSet;
@@ -236,6 +239,7 @@
         instancePortService.addListener(instancePortListener);
         osRouterService.addListener(osRouterListener);
         osNodeService.addListener(osNodeListener);
+        osNetworkAdminService.addListener(osNetworkListener);
 
         eventExecutor.execute(this::initializeUnusedPortNumSet);
 
@@ -244,6 +248,7 @@
 
     @Deactivate
     protected void deactivate() {
+        osNetworkAdminService.removeListener(osNetworkListener);
         osRouterService.removeListener(osRouterListener);
         osNodeService.removeListener(osNodeListener);
         instancePortService.removeListener(instancePortListener);
@@ -729,8 +734,6 @@
             osNetworkAdminService.deriveExternalPeerRouterMac(exGateway, osRouter, vlanId);
             osRouterService.routerInterfaces(osRouter.getId()).forEach(iface ->
                     setSourceNat(iface, exGateway.isEnableSnat()));
-
-            setStatefulDownstreamRules(osRouter, exGateway.isEnableSnat());
         }
     }
 
@@ -828,39 +831,29 @@
             return;
         }
 
-        String netId = osNetworkAdminService.subnet(routerIface.getSubnetId()).getNetworkId();
-
         Map<OpenstackNode, PortRange> gwPortRangeMap = getAssignedPortsForGateway(
                 ImmutableList.copyOf(osNodeService.nodes(GATEWAY)));
 
-        osNodeService.completeNodes(GATEWAY)
-                .forEach(gwNode -> {
-                    instancePortService.instancePorts(netId)
-                            .stream()
-                            .filter(port -> port.state() == ACTIVE)
-                            .forEach(port -> setGatewayToInstanceDownstreamRule(
-                                    gwNode, port, install));
+        osNodeService.completeNodes(GATEWAY).forEach(gwNode -> {
+            if (install) {
+                PortRange gwPortRange = gwPortRangeMap.get(gwNode);
 
-                    if (install) {
-                        PortRange gwPortRange = gwPortRangeMap.get(gwNode);
+                Map<String, PortRange> netPortRangeMap =
+                        getAssignedPortsForNet(getNetIdByRouterId(routerIface.getId()),
+                                gwPortRange.min(), gwPortRange.max());
 
-                        Map<String, PortRange> netPortRangeMap =
-                                getAssignedPortsForNet(getNetIdByRouterId(routerIface.getId()),
-                                        gwPortRange.min(), gwPortRange.max());
+                PortRange netPortRange = netPortRangeMap.get(osNet.getId());
 
-                        PortRange netPortRange = netPortRangeMap.get(osNet.getId());
-
-                        setStatefulSnatUpstreamRule(gwNode, natAddress,
-                                Long.parseLong(osNet.getProviderSegID()),
-                                externalPeerRouter, netPortRange.min(),
-                                netPortRange.max(), install);
-                    } else {
-                        setStatefulSnatUpstreamRule(gwNode, natAddress,
-                                Long.parseLong(osNet.getProviderSegID()),
-                                externalPeerRouter, 0, 0, install);
-                    }
-
-                });
+                setStatefulSnatUpstreamRule(gwNode, natAddress,
+                        Long.parseLong(osNet.getProviderSegID()),
+                        externalPeerRouter, netPortRange.min(),
+                        netPortRange.max(), install);
+            } else {
+                setStatefulSnatUpstreamRule(gwNode, natAddress,
+                        Long.parseLong(osNet.getProviderSegID()),
+                        externalPeerRouter, 0, 0, install);
+            }
+        });
     }
 
     private void setStatefulDownstreamRules(Router osRouter, boolean install) {
@@ -874,11 +867,15 @@
             return;
         }
 
+        setStatefulDownstreamRules(natAddress, install);
+    }
+
+    private void setStatefulDownstreamRules(IpAddress natAddress, boolean install) {
         osNodeService.completeNodes(GATEWAY)
                 .forEach(gwNode -> {
                     setStatefulSnatDownstreamRule(gwNode.intgBridge(),
                             IpPrefix.valueOf(natAddress, VM_PREFIX), install);
-        });
+                });
     }
 
     private List<String> getNetIdByRouterId(String routerId) {
@@ -1234,6 +1231,67 @@
         }
     }
 
+    private class InternalNetworkEventListener implements OpenstackNetworkListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNetworkEvent event) {
+            Port osPort = event.port();
+            if (osPort == null || osPort.getFixedIps() == null) {
+                return false;
+            }
+
+            return DEVICE_OWNER_ROUTER_GW.equals(osPort.getDeviceOwner()) &&
+                    getStatefulSnatFlag();
+        }
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(OpenstackNetworkEvent event) {
+            IpAddress ipAddress = externalIp(event.port());
+            switch (event.type()) {
+                case OPENSTACK_PORT_CREATED:
+                case OPENSTACK_PORT_UPDATED:
+                    eventExecutor.execute(() -> processPortCreation(ipAddress));
+                    break;
+                case OPENSTACK_PORT_REMOVED:
+                    eventExecutor.execute(() -> processPortRemoval(ipAddress));
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processPortCreation(IpAddress ipAddress) {
+            if (!isRelevantHelper() || ipAddress == null) {
+                return;
+            }
+
+            setStatefulDownstreamRules(ipAddress, true);
+        }
+
+        private void processPortRemoval(IpAddress ipAddress) {
+            if (!isRelevantHelper() || ipAddress == null) {
+                return;
+            }
+
+            setStatefulDownstreamRules(ipAddress, false);
+        }
+
+        private IpAddress externalIp(Port port) {
+            IP ip = port.getFixedIps().stream().findAny().orElse(null);
+
+            if (ip != null && ip.getIpAddress() != null) {
+                return IpAddress.valueOf(ip.getIpAddress());
+            }
+
+            return null;
+        }
+    }
+
     private class InternalRouterEventListener implements OpenstackRouterListener {
 
         private boolean isRelevantHelper() {
@@ -1255,12 +1313,6 @@
                 case OPENSTACK_ROUTER_INTERFACE_REMOVED:
                     eventExecutor.execute(() -> processRouterIntfRemoval(event));
                     break;
-                case OPENSTACK_ROUTER_GATEWAY_ADDED:
-                    eventExecutor.execute(() -> processRouterGatewayAddition(event));
-                    break;
-                case OPENSTACK_ROUTER_GATEWAY_REMOVED:
-                    eventExecutor.execute(() -> processRouterGatewayRemoval(event));
-                    break;
                 default:
                     break;
             }
@@ -1313,28 +1365,6 @@
 
             routerIfaceRemoved(event.subject(), event.routerIface());
         }
-
-        private void processRouterGatewayAddition(OpenstackRouterEvent event) {
-            if (!isRelevantHelper()) {
-                return;
-            }
-
-            log.debug("Router external gateway {} added",
-                    event.externalGateway().getNetworkId());
-
-            setStatefulDownstreamRules(event.subject(), true);
-        }
-
-        private void processRouterGatewayRemoval(OpenstackRouterEvent event) {
-            if (!isRelevantHelper()) {
-                return;
-            }
-
-            log.debug("Router external gateway {} removed",
-                    event.externalGateway().getNetworkId());
-
-            setStatefulDownstreamRules(event.subject(), false);
-        }
     }
 
     private class InternalPacketProcessor implements PacketProcessor {
@@ -1442,6 +1472,8 @@
                 osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
                     routerIfaceAdded(osRouter, iface);
                 });
+
+                setStatefulDownstreamRules(osRouter, true);
             });
             log.info("Reconfigure routers for {}", osNode.hostname());
         }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResource.java
index 115f66c..008c5e5 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResource.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResource.java
@@ -202,23 +202,6 @@
     }
 
     /**
-     * Purges the network states.
-     *
-     * @return 200 OK with purge result, 404 not found
-     */
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @Path("purge/states")
-    public Response purgeStates() {
-
-        osRouterAdminService.clear();
-        osNetAdminService.clear();
-        osSgAdminService.clear();
-
-        return ok(mapper().createObjectNode()).build();
-    }
-
-    /**
      * Purges the flow rules installed by openstacknetworking.
      *
      * @return 200 OK with purge result, 404 not found
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResourceTest.java
index 2b1aa0f..6980b0a 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResourceTest.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackManagementWebResourceTest.java
@@ -81,30 +81,6 @@
     }
 
     /**
-     * Tests the purge states method.
-     */
-    @Test
-    public void testPurgeStates() {
-        mockOpenstackRouterAdminService.clear();
-        replay(mockOpenstackRouterAdminService);
-
-        mockOpenstackNetworkAdminService.clear();
-        replay(mockOpenstackNetworkAdminService);
-
-        mockOpenstackSecurityGroupAdminService.clear();
-        replay(mockOpenstackSecurityGroupAdminService);
-
-        final WebTarget wt = target();
-
-        Response response = wt.path(PATH + "/purge/states")
-                                .request()
-                                .get();
-        final int status = response.getStatus();
-
-        assertThat(status, is(200));
-    }
-
-    /**
      * Tests the get all floating IPs method.
      */
     @Test