Support existing VMs running on the newly added node

- Added openstack node state event
- Made openstack switching to listen to the node state events and
  add existing VMs running in the complete state node

Change-Id: I7b7186c3b889376a4bc0385313433604dcd93d70
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
index 563dea7..4508bdd 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
@@ -19,6 +19,7 @@
 import com.google.common.base.Strings;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
+import org.onosproject.openstacknode.OpenstackNodeEvent.NodeState;
 import org.onosproject.openstacknode.OpenstackNodeService.NodeType;
 
 import java.util.Comparator;
@@ -27,6 +28,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.openstacknode.OpenstackNodeEvent.NodeState.INIT;
 
 /**
  * Representation of a compute/gateway node for OpenstackSwitching/Routing service.
@@ -39,7 +41,7 @@
     private final IpAddress dataIp;
     private final DeviceId integrationBridge;
     private final Optional<DeviceId> routerBridge;
-    private final OpenstackNodeState state;
+    private final NodeState state;
 
     public static final Comparator<OpenstackNode> OPENSTACK_NODE_COMPARATOR =
             (node1, node2) -> node1.hostname().compareTo(node2.hostname());
@@ -50,7 +52,7 @@
                           IpAddress dataIp,
                           DeviceId integrationBridge,
                           Optional<DeviceId> routerBridge,
-                          OpenstackNodeState state) {
+                          NodeState state) {
         this.hostname = hostname;
         this.type = type;
         this.managementIp = managementIp;
@@ -67,7 +69,7 @@
      * @param state openstack node init state
      * @return openstack node
      */
-    public static OpenstackNode getUpdatedNode(OpenstackNode node, OpenstackNodeState state) {
+    public static OpenstackNode getUpdatedNode(OpenstackNode node, NodeState state) {
         return new OpenstackNode(node.hostname,
                 node.type,
                 node.managementIp,
@@ -137,7 +139,7 @@
      *
      * @return init state
      */
-    public OpenstackNodeState state() {
+    public NodeState state() {
         return state;
     }
 
@@ -212,7 +214,7 @@
         private IpAddress dataIp;
         private DeviceId integrationBridge;
         private Optional<DeviceId> routerBridge = Optional.empty();
-        private OpenstackNodeState state = OpenstackNodeState.noState();
+        private NodeState state = INIT;
 
         private Builder() {
         }
@@ -305,7 +307,7 @@
          * @param state node init state
          * @return openstack node builder
          */
-        public Builder state(OpenstackNodeState state) {
+        public Builder state(NodeState state) {
             this.state = state;
             return this;
         }
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeEvent.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeEvent.java
new file mode 100644
index 0000000..1224573
--- /dev/null
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeEvent.java
@@ -0,0 +1,86 @@
+/*
+ * 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.openstacknode;
+
+import org.joda.time.LocalDateTime;
+import org.onosproject.event.AbstractEvent;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Describes OpenStack node init state event.
+ */
+public class OpenstackNodeEvent extends AbstractEvent<OpenstackNodeEvent.NodeState, Object> {
+
+    public enum NodeState {
+        /**
+         * Indicates the node is newly added.
+         */
+        INIT {
+            @Override
+            public void process(OpenstackNodeService nodeService, OpenstackNode node) {
+                nodeService.processInitState(node);
+            }
+        },
+        /**
+         * Indicates bridge devices are added according to the node state.
+         */
+        DEVICE_CREATED {
+            @Override
+            public void process(OpenstackNodeService nodeService, OpenstackNode node) {
+                nodeService.processDeviceCreatedState(node);
+            }
+        },
+        /**
+         * Indicates all node initialization is done.
+         */
+        COMPLETE {
+            @Override
+            public void process(OpenstackNodeService nodeService, OpenstackNode node) {
+                nodeService.processCompleteState(node);
+            }
+        },
+        /**
+         * Indicates node initialization is not done but unable to proceed to
+         * the next step for some reason.
+         */
+        INCOMPLETE {
+            @Override
+            public void process(OpenstackNodeService nodeService, OpenstackNode node) {
+                nodeService.processIncompleteState(node);
+            }
+        };
+
+        public abstract void process(OpenstackNodeService nodeService, OpenstackNode node);
+    }
+
+    public OpenstackNodeEvent(NodeState state, Object subject) {
+        super(state, subject);
+    }
+
+    public OpenstackNode node() {
+        return (OpenstackNode) subject();
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("time", new LocalDateTime(time()))
+                .add("state", type())
+                .add("node", subject())
+                .toString();
+    }
+}
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeListener.java
similarity index 72%
rename from apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java
rename to apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeListener.java
index e8fdba9..df8cf0d 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeListener.java
@@ -15,16 +15,10 @@
  */
 package org.onosproject.openstacknode;
 
+import org.onosproject.event.EventListener;
+
 /**
- * Entity that defines possible init state of the OpenStack node.
+ * Listener for OpenStack node events.
  */
-public interface OpenstackNodeState {
-    /**
-     * Returns null for no state.
-     *
-     * @return null
-     */
-    static OpenstackNodeState noState() {
-        return null;
-    }
-}
\ No newline at end of file
+public interface OpenstackNodeListener extends EventListener<OpenstackNodeEvent> {
+}
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
index 039c3ed..56c422c 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
@@ -34,6 +34,7 @@
 import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
@@ -57,6 +58,7 @@
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.openstacknode.OpenstackNodeEvent.NodeState;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbController;
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
@@ -76,6 +78,7 @@
 import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
 import static org.onosproject.openstacknode.Constants.*;
+import static org.onosproject.openstacknode.OpenstackNodeEvent.NodeState.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 import java.util.Dictionary;
@@ -92,8 +95,9 @@
  */
 @Component(immediate = true)
 @Service
-public final class OpenstackNodeManager implements OpenstackNodeService {
-    protected final Logger log = getLogger(getClass());
+public final class OpenstackNodeManager extends ListenerRegistry<OpenstackNodeEvent, OpenstackNodeListener>
+        implements OpenstackNodeService {
+    private final Logger log = getLogger(getClass());
 
     private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
             .register(KryoNamespaces.API)
@@ -160,59 +164,6 @@
     private ApplicationId appId;
     private NodeId localNodeId;
 
-    private enum NodeState implements OpenstackNodeState {
-
-        INIT {
-            @Override
-            public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
-                // make sure there is OVSDB connection
-                if (!nodeManager.isOvsdbConnected(node)) {
-                    nodeManager.connectOvsdb(node);
-                    return;
-                }
-                nodeManager.createBridge(node,
-                        INTEGRATION_BRIDGE,
-                        node.intBridge().toString().substring(DPID_BEGIN));
-
-                // creates additional router bridge if the node type is GATEWAY
-                if (node.type().equals(NodeType.GATEWAY)) {
-                    nodeManager.createBridge(node,
-                            ROUTER_BRIDGE,
-                            node.routerBridge().get().toString().substring(DPID_BEGIN));
-                }
-            }
-        },
-        BRIDGE_CREATED {
-            @Override
-            public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
-                // make sure there is OVSDB connection
-                if (!nodeManager.isOvsdbConnected(node)) {
-                    nodeManager.connectOvsdb(node);
-                    return;
-                }
-                nodeManager.createTunnelInterface(node);
-                // creates additional patch ports connecting integration bridge and
-                // router bridge if the node type is GATEWAY
-                if (node.type().equals(NodeType.GATEWAY)) {
-                    nodeManager.createPatchInterface(node);
-                }
-            }
-        },
-        COMPLETE {
-            @Override
-            public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
-                nodeManager.postInit(node);
-            }
-        },
-        INCOMPLETE {
-            @Override
-            public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
-            }
-        };
-
-        public abstract void process(OpenstackNodeManager nodeManager, OpenstackNode node);
-    }
-
     @Activate
     protected void activate() {
         appId = coreService.getAppId(APP_ID);
@@ -275,6 +226,57 @@
             controller.getOvsdbClient(ovsdb).disconnect();
         }
         nodeStore.remove(node.hostname());
+        process(new OpenstackNodeEvent(INCOMPLETE, node));
+    }
+
+    @Override
+    public void processInitState(OpenstackNode node) {
+        // make sure there is OVSDB connection
+        if (!isOvsdbConnected(node)) {
+            connectOvsdb(node);
+            return;
+        }
+
+        process(new OpenstackNodeEvent(INIT, node));
+        createBridge(node, INTEGRATION_BRIDGE,
+                     node.intBridge().toString().substring(DPID_BEGIN));
+
+        // creates additional router bridge if the node type is GATEWAY
+        if (node.type().equals(NodeType.GATEWAY)) {
+            createBridge(node, ROUTER_BRIDGE,
+                         node.routerBridge().get().toString().substring(DPID_BEGIN));
+        }
+    }
+
+    @Override
+    public void processDeviceCreatedState(OpenstackNode node) {
+        // make sure there is OVSDB connection
+        if (!isOvsdbConnected(node)) {
+            connectOvsdb(node);
+            return;
+        }
+        process(new OpenstackNodeEvent(DEVICE_CREATED, node));
+        createTunnelInterface(node);
+        // creates additional patch ports connecting integration bridge and
+        // router bridge if the node type is GATEWAY
+        if (node.type().equals(NodeType.GATEWAY)) {
+            createPatchInterface(node);
+        }
+    }
+
+    @Override
+    public void processCompleteState(OpenstackNode node) {
+        if (isOvsdbConnected(node)) {
+            OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
+            controller.getOvsdbClient(ovsdb).disconnect();
+        }
+        process(new OpenstackNodeEvent(COMPLETE, node));
+        log.info("Finished init {}", node.hostname());
+    }
+
+    @Override
+    public void processIncompleteState(OpenstackNode node) {
+        process(new OpenstackNodeEvent(INCOMPLETE, node));
     }
 
     @Override
@@ -285,22 +287,11 @@
     @Override
     public Set<OpenstackNode> completeNodes() {
         return nodeStore.values().stream().map(Versioned::value)
-                .filter(node -> node.state().equals(NodeState.COMPLETE))
+                .filter(node -> node.state().equals(COMPLETE))
                 .collect(Collectors.toSet());
     }
 
     @Override
-    public boolean isComplete(String hostname) {
-        Versioned<OpenstackNode> versionedNode = nodeStore.get(hostname);
-        if (versionedNode == null) {
-            log.warn("Node {} does not exist", hostname);
-            return false;
-        }
-        OpenstackNodeState state = versionedNode.value().state();
-        return state != null && state.equals(NodeState.COMPLETE);
-    }
-
-    @Override
     public Optional<IpAddress> dataIp(DeviceId deviceId) {
         OpenstackNode node = nodeByDeviceId(deviceId);
         if (node == null) {
@@ -337,21 +328,11 @@
     }
 
     private void initNode(OpenstackNode node) {
-        NodeState state = (NodeState) node.state();
+        NodeState state = node.state();
         state.process(this, node);
         log.debug("Processing node: {} state: {}", node.hostname(), state);
     }
 
-    private void postInit(OpenstackNode node) {
-        if (isOvsdbConnected(node)) {
-            OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
-            controller.getOvsdbClient(ovsdb).disconnect();
-        }
-
-        // TODO add gateway node to scalable gateway pool
-        log.info("Finished init {}", node.hostname());
-    }
-
     private void setNodeState(OpenstackNode node, NodeState newState) {
         log.debug("Changed {} state: {}", node.hostname(), newState);
         nodeStore.put(node.hostname(), OpenstackNode.getUpdatedNode(node, newState));
@@ -359,23 +340,23 @@
 
     private NodeState nodeState(OpenstackNode node) {
         if (!deviceService.isAvailable(node.intBridge())) {
-            return NodeState.INIT;
+            return INIT;
         }
         if (node.type().equals(NodeType.GATEWAY) &&
                 !deviceService.isAvailable(node.routerBridge().get())) {
-            return NodeState.INIT;
+            return INIT;
         }
 
         if (!isIfaceCreated(node.intBridge(), DEFAULT_TUNNEL)) {
-            return NodeState.BRIDGE_CREATED;
+            return DEVICE_CREATED;
         }
         if (node.type().equals(NodeType.GATEWAY) && (
                 !isIfaceCreated(node.routerBridge().get(), PATCH_ROUT_BRIDGE) ||
                 !isIfaceCreated(node.intBridge(), PATCH_INTG_BRIDGE))) {
-            return NodeState.BRIDGE_CREATED;
+            return DEVICE_CREATED;
         }
 
-        return NodeState.COMPLETE;
+        return COMPLETE;
     }
 
     private boolean isIfaceCreated(DeviceId deviceId, String ifaceName) {
@@ -683,4 +664,3 @@
         }
     }
 }
-
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java
index 1987150..f7c16ba 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java
@@ -16,6 +16,7 @@
 package org.onosproject.openstacknode;
 
 import org.onlab.packet.IpAddress;
+import org.onosproject.event.ListenerService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
@@ -26,7 +27,8 @@
 /**
  * Handles the bootstrap request for compute/gateway node.
  */
-public interface OpenstackNodeService {
+public interface OpenstackNodeService
+        extends ListenerService<OpenstackNodeEvent, OpenstackNodeListener> {
 
     enum NodeType {
         /**
@@ -44,6 +46,34 @@
     void addOrUpdateNode(OpenstackNode node);
 
     /**
+     * Bootstraps node with INIT state.
+     *
+     * @param node openstack node
+     */
+    void processInitState(OpenstackNode node);
+
+    /**
+     * Bootstraps node with DEVICE_CREATED state.
+     *
+     * @param node openstack node
+     */
+    void processDeviceCreatedState(OpenstackNode node);
+
+    /**
+     * Bootstraps node with COMPLETE state.
+     *
+     * @param node openstack node
+     */
+    void processCompleteState(OpenstackNode node);
+
+    /**
+     * Bootstraps node with INCOMPLETE state.
+     *
+     * @param node openstack node
+     */
+    void processIncompleteState(OpenstackNode node);
+
+    /**
      * Deletes a node from the service.
      *
      * @param node openstack node
@@ -65,14 +95,6 @@
     Set<OpenstackNode> completeNodes();
 
     /**
-     * Returns node initialization state is complete or not.
-     *
-     * @param hostname hostname of the node
-     * @return true if initial node setup is completed, otherwise false
-     */
-    boolean isComplete(String hostname);
-
-    /**
      * Returns data network IP address of a given integration bridge device.
      *
      * @param intBridgeId integration bridge device id
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
index bfb1070..2649b05 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
@@ -34,9 +34,6 @@
         description = "Lists all nodes registered in OpenStack node service")
 public class OpenstackNodeListCommand extends AbstractShellCommand {
 
-    private static final String COMPLETE = "COMPLETE";
-    private static final String INCOMPLETE = "INCOMPLETE";
-
     @Override
     protected void execute() {
         OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
@@ -44,7 +41,7 @@
         Collections.sort(nodes, OpenstackNode.OPENSTACK_NODE_COMPARATOR);
 
         if (outputJson()) {
-            print("%s", json(nodeService, nodes));
+            print("%s", json(nodes));
         } else {
             for (OpenstackNode node : nodes) {
                 print("hostname=%s, type=%s, managementIp=%s, dataIp=%s, intBridge=%s, routerBridge=%s init=%s",
@@ -54,13 +51,13 @@
                         node.dataIp(),
                         node.intBridge(),
                         node.routerBridge(),
-                        getState(nodeService, node));
+                        node.state());
             }
             print("Total %s nodes", nodeService.nodes().size());
         }
     }
 
-    private JsonNode json(OpenstackNodeService nodeService, List<OpenstackNode> nodes) {
+    private JsonNode json(List<OpenstackNode> nodes) {
         ObjectMapper mapper = new ObjectMapper();
         ArrayNode result = mapper.createArrayNode();
         for (OpenstackNode node : nodes) {
@@ -71,12 +68,8 @@
                     .put("dataIp", node.dataIp().toString())
                     .put("intBridge", node.intBridge().toString())
                     .put("routerBridge", node.routerBridge().toString())
-                    .put("state", getState(nodeService, node)));
+                    .put("state", node.state().name()));
         }
         return result;
     }
-
-    private String getState(OpenstackNodeService nodeService, OpenstackNode node) {
-        return nodeService.isComplete(node.hostname()) ? COMPLETE : INCOMPLETE;
-    }
 }
\ No newline at end of file