CORD-1071 Refactor VTN node service

Done
- Separated interface, implementation and store for node management
- Added unit tests for node manager and handler
- Offloaded more of the event handling off of the Atomix event thread

Todo
- Add REST interface for the node service

Change-Id: Ibf90d3a621013497cc891ca3086db6648f5d49df
diff --git a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeCheckCommand.java b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeCheckCommand.java
index 9e988ca..e3bd85d 100644
--- a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeCheckCommand.java
+++ b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeCheckCommand.java
@@ -23,7 +23,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.opencord.cordvtn.api.node.CordVtnNode;
-import org.opencord.cordvtn.impl.CordVtnNodeManager;
+import org.opencord.cordvtn.api.node.CordVtnNodeService;
 import org.onosproject.net.Device;
 import org.onosproject.net.device.DeviceService;
 
@@ -44,18 +44,15 @@
             required = true, multiValued = false)
     private String hostname = null;
 
-    private static final String COMPLETE = "COMPLETE";
-    private static final String INCOMPLETE = "INCOMPLETE";
-    private static final String HINT = "hint: try init again if the state is INCOMPLETE" +
-            " but all settings OK";
+    private static final String HINT = "hint: try init again if the state is not" +
+            " COMPLETE but all settings are OK";
 
     @Override
     protected void execute() {
-        CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+        CordVtnNodeService nodeService = AbstractShellCommand.get(CordVtnNodeService.class);
         DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
 
-        CordVtnNode node = nodeManager.getNodes()
-                .stream()
+        CordVtnNode node = nodeService.nodes().stream()
                 .filter(n -> n.hostname().equals(hostname))
                 .findFirst()
                 .orElse(null);
@@ -64,7 +61,7 @@
             print("Cannot find %s from registered nodes", hostname);
             return;
         }
-        print("Current state: %s (%s)", getState(nodeManager, node), HINT);
+        print("Current state: %s (%s)", node.state().name(), HINT);
         print("%n[Integration Bridge Status]");
         Device device = deviceService.getDevice(node.integrationBridgeId());
         if (device != null) {
@@ -75,7 +72,7 @@
                   deviceService.isAvailable(device.id()),
                   device.annotations());
 
-            node.systemIfaces().stream().forEach(iface -> print(
+            node.systemInterfaces().forEach(iface -> print(
                     getPortState(deviceService, node.integrationBridgeId(), iface)));
         } else {
             print("%s %s=%s is not available",
@@ -89,7 +86,8 @@
         if (session != null) {
             Set<IpAddress> ips = getCurrentIps(session, INTEGRATION_BRIDGE);
             boolean isUp = isInterfaceUp(session, INTEGRATION_BRIDGE);
-            boolean isIp = ips.contains(node.dataIp().ip()) && ips.contains(node.localMgmtIp().ip());
+            boolean isIp = ips.contains(node.dataIp().ip()) &&
+                    ips.contains(node.localManagementIp().ip());
 
             print("%s %s up=%s Ips=%s",
                   isUp && isIp ? MSG_OK : MSG_NO,
@@ -97,9 +95,9 @@
                   isUp ? Boolean.TRUE : Boolean.FALSE,
                   getCurrentIps(session, INTEGRATION_BRIDGE));
 
-            print(getSystemIfaceState(session, node.dataIface()));
-            if (node.hostMgmtIface().isPresent()) {
-                print(getSystemIfaceState(session, node.hostMgmtIface().get()));
+            print(getSystemIfaceState(session, node.dataInterface()));
+            if (node.hostManagementInterface() != null) {
+                print(getSystemIfaceState(session, node.hostManagementInterface()));
             }
 
             disconnect(session);
@@ -108,7 +106,8 @@
         }
     }
 
-    private String getPortState(DeviceService deviceService, DeviceId deviceId, String portName) {
+    private String getPortState(DeviceService deviceService, DeviceId deviceId,
+                                String portName) {
         Port port = deviceService.getPorts(deviceId).stream()
                 .filter(p -> p.annotations().value(PORT_NAME).equals(portName) &&
                         p.isEnabled())
@@ -116,11 +115,11 @@
 
         if (port != null) {
             return String.format("%s %s portNum=%s enabled=%s %s",
-                                 port.isEnabled() ? MSG_OK : MSG_NO,
-                                 portName,
-                                 port.number(),
-                                 port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
-                                 port.annotations());
+                    port.isEnabled() ? MSG_OK : MSG_NO,
+                    portName,
+                    port.number(),
+                    port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
+                    port.annotations());
         } else {
             return String.format("%s %s does not exist", MSG_NO, portName);
         }
@@ -135,8 +134,4 @@
               isUp ? Boolean.TRUE : Boolean.FALSE,
               isIp ? Boolean.TRUE : Boolean.FALSE);
     }
-
-    private String getState(CordVtnNodeManager nodeManager, CordVtnNode node) {
-        return nodeManager.isNodeInitComplete(node) ? COMPLETE : INCOMPLETE;
-    }
 }
diff --git a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeDeleteCommand.java b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeDeleteCommand.java
index d098efe..eea9f44 100644
--- a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeDeleteCommand.java
+++ b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeDeleteCommand.java
@@ -19,11 +19,9 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.opencord.cordvtn.impl.CordVtnNodeManager;
+import org.opencord.cordvtn.api.node.CordVtnNodeAdminService;
 import org.opencord.cordvtn.api.node.CordVtnNode;
 
-import java.util.NoSuchElementException;
-
 /**
  * Deletes nodes from the service.
  */
@@ -37,21 +35,14 @@
 
     @Override
     protected void execute() {
-        CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+        CordVtnNodeAdminService nodeAdminService =
+                AbstractShellCommand.get(CordVtnNodeAdminService.class);
 
         for (String hostname : hostnames) {
-            CordVtnNode node;
-            try {
-                node = nodeManager.getNodes()
-                        .stream()
-                        .filter(n -> n.hostname().equals(hostname))
-                        .findFirst().get();
-            } catch (NoSuchElementException e) {
+            CordVtnNode node = nodeAdminService.removeNode(hostname);
+            if (node == null) {
                 print("Unable to find %s", hostname);
-                continue;
             }
-
-            nodeManager.deleteNode(node);
         }
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeInitCommand.java b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeInitCommand.java
index c7da215..1a2650e 100644
--- a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeInitCommand.java
+++ b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeInitCommand.java
@@ -19,10 +19,9 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.opencord.cordvtn.impl.CordVtnNodeManager;
+import org.opencord.cordvtn.api.node.CordVtnNodeAdminService;
 import org.opencord.cordvtn.api.node.CordVtnNode;
-
-import java.util.NoSuchElementException;
+import org.opencord.cordvtn.api.node.CordVtnNodeService;
 
 /**
  * Initializes nodes for CordVtn service.
@@ -37,21 +36,17 @@
 
     @Override
     protected void execute() {
-        CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+        CordVtnNodeService nodeService = AbstractShellCommand.get(CordVtnNodeService.class);
+        CordVtnNodeAdminService nodeAdminService =
+                AbstractShellCommand.get(CordVtnNodeAdminService.class);
 
         for (String hostname : hostnames) {
-            CordVtnNode node;
-            try {
-                node = nodeManager.getNodes()
-                        .stream()
-                        .filter(n -> n.hostname().equals(hostname))
-                        .findFirst().get();
-            } catch (NoSuchElementException e) {
+            CordVtnNode node = nodeService.node(hostname);
+            if (node == null) {
                 print("Unable to find %s", hostname);
-                continue;
+            } else {
+                nodeAdminService.updateNode(node);
             }
-
-            nodeManager.addOrUpdateNode(node);
         }
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeListCommand.java b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeListCommand.java
index 7086a09..f1db3fb 100644
--- a/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeListCommand.java
+++ b/src/main/java/org/opencord/cordvtn/cli/CordVtnNodeListCommand.java
@@ -18,9 +18,10 @@
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.opencord.cordvtn.impl.CordVtnNodeManager;
+import org.opencord.cordvtn.api.node.CordVtnNodeService;
 import org.opencord.cordvtn.api.node.CordVtnNode;
 
 import java.util.Comparator;
@@ -35,19 +36,17 @@
         description = "Lists all nodes registered in CORD VTN service")
 public class CordVtnNodeListCommand extends AbstractShellCommand {
 
-    private static final String COMPLETE = "COMPLETE";
-    private static final String INCOMPLETE = "INCOMPLETE";
     private static final String FORMAT = "%-30s%-20s%-20s%-15s%-24s%s";
 
     @Override
     protected void execute() {
-        CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
-        List<CordVtnNode> nodes = nodeManager.getNodes();
+        CordVtnNodeService nodeService = AbstractShellCommand.get(CordVtnNodeService.class);
+        List<CordVtnNode> nodes = Lists.newArrayList(nodeService.nodes());
         nodes.sort(Comparator.comparing(CordVtnNode::hostname));
 
         if (outputJson()) {
             try {
-                print("%s", mapper().writeValueAsString(json(nodeManager, nodes)));
+                print("%s", mapper().writeValueAsString(json(nodes)));
             } catch (JsonProcessingException e) {
                 print("Failed to list networks in JSON format");
             }
@@ -57,31 +56,27 @@
 
             for (CordVtnNode node : nodes) {
                 print(FORMAT, node.hostname(),
-                      node.hostMgmtIp().cidr(),
+                      node.hostManagementIp().cidr(),
                       node.dataIp().cidr(),
-                      node.dataIface(),
+                      node.dataInterface(),
                       node.integrationBridgeId().toString(),
-                      getState(nodeManager, node));
+                      node.state().name());
             }
-            print("Total %s nodes", nodeManager.getNodeCount());
+            print("Total %s nodes", nodes.size());
         }
     }
 
-    private JsonNode json(CordVtnNodeManager nodeManager, List<CordVtnNode> nodes) {
+    private JsonNode json(List<CordVtnNode> nodes) {
         ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
         for (CordVtnNode node : nodes) {
             result.add(mapper().createObjectNode()
                                .put("hostname", node.hostname())
-                               .put("managementIp", node.hostMgmtIp().cidr())
+                               .put("managementIp", node.hostManagementIp().cidr())
                                .put("dataIp", node.dataIp().cidr())
-                               .put("dataInterface", node.dataIface())
+                               .put("dataInterface", node.dataInterface())
                                .put("bridgeId", node.integrationBridgeId().toString())
-                               .put("state", getState(nodeManager, node)));
+                               .put("state", node.state().name()));
         }
         return result;
     }
-
-    private String getState(CordVtnNodeManager nodeManager, CordVtnNode node) {
-        return nodeManager.isNodeInitComplete(node) ? COMPLETE : INCOMPLETE;
-    }
 }
diff --git a/src/main/java/org/opencord/cordvtn/cli/CordVtnPurgeRulesCommand.java b/src/main/java/org/opencord/cordvtn/cli/CordVtnPurgeRulesCommand.java
index 06f5aee..e5a4ec5 100644
--- a/src/main/java/org/opencord/cordvtn/cli/CordVtnPurgeRulesCommand.java
+++ b/src/main/java/org/opencord/cordvtn/cli/CordVtnPurgeRulesCommand.java
@@ -17,7 +17,7 @@
 
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.opencord.cordvtn.impl.CordVtnPipeline;
+import org.opencord.cordvtn.api.core.CordVtnPipeline;
 
 /**
  * Purges all flow rules installed by CORD VTN service.
@@ -29,6 +29,6 @@
     @Override
     protected void execute() {
         CordVtnPipeline pipeline = AbstractShellCommand.get(CordVtnPipeline.class);
-        pipeline.flushRules();
+        pipeline.cleanupPipeline();
     }
 }