[ONOS-4482] Implement dynamic add or remove a gateway node

 - Implements command line interface for dynamic management
 - Implements dynamic gateway node management
 - Add CLIs

Change-Id: I27bb945968a262d2813d317fc79e75ee768b2825
diff --git a/apps/scalablegateway/BUCK b/apps/scalablegateway/BUCK
index d4fdb0d..1c8fbbf 100644
--- a/apps/scalablegateway/BUCK
+++ b/apps/scalablegateway/BUCK
@@ -1,5 +1,8 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
+    '//lib:org.apache.karaf.shell.console',
+    '//cli:onos-cli',
+    '//core/store/serializers:onos-core-serializers',
 ]
 
 osgi_jar_with_tests (
diff --git a/apps/scalablegateway/pom.xml b/apps/scalablegateway/pom.xml
index a79429e..0cfc633 100644
--- a/apps/scalablegateway/pom.xml
+++ b/apps/scalablegateway/pom.xml
@@ -53,5 +53,15 @@
             <artifactId>onos-core-serializers</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <version>3.0.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>1.7.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 </project>
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayAddCommand.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayAddCommand.java
new file mode 100644
index 0000000..911062c
--- /dev/null
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayAddCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.scalablegateway.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Adds gateway node information for scalablegateway node managements.
+ */
+
+@Command(scope = "onos", name = "gateway-add",
+        description = "Adds gateway node information for scalablegateway node managements")
+public class ScalableGatewayAddCommand extends AbstractShellCommand {
+
+    private static final String SUCCESS = "Process of adding gateway node is succeed";
+    private static final String FAIL = "Process of adding gateway node is failed";
+
+    @Argument(index = 0, name = "DeviceId", description = "GatewayNode device id",
+            required = true, multiValued = false)
+    String deviceId = null;
+
+    @Argument(index = 1, name = "dataPlaneIp",
+            description = "GatewayNode datePlane interface ip address",
+            required = true, multiValued = false)
+    String ipAddress = null;
+
+    @Argument(index = 2, name = "extInterfaceNames",
+            description = "GatewayNode Interface name to outgoing external network",
+            required = true, multiValued = true)
+    String interfaceName = null;
+
+    @Override
+    protected void execute() {
+        ScalableGatewayService service = get(ScalableGatewayService.class);
+
+        GatewayNode gatewayNode = GatewayNode.builder()
+                .gatewayDeviceId(DeviceId.deviceId(deviceId))
+                .dataIpAddress(Ip4Address.valueOf(ipAddress))
+                .gatewayExternalInterfaceNames(splitNameList(interfaceName))
+                .build();
+        if (service.addGatewayNode(gatewayNode)) {
+            print(SUCCESS);
+        } else {
+            print(FAIL);
+        }
+    }
+
+    private List<String> splitNameList(String interfaceName) {
+        List<String> list = Lists.newArrayList();
+        return Collections.addAll(list, interfaceName.split(",")) ? list : null;
+    }
+}
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayDeleteCommand.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayDeleteCommand.java
new file mode 100644
index 0000000..8274b7b
--- /dev/null
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayDeleteCommand.java
@@ -0,0 +1,59 @@
+/*
+ * 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.scalablegateway.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+
+/**
+ * Deletes gateway node information for scalablegateway node managements.
+ */
+
+@Command(scope = "onos", name = "gateway-delete",
+        description = "Deletes gateway node information for scalablegateway node managements")
+public class ScalableGatewayDeleteCommand extends AbstractShellCommand {
+
+    private static final String SUCCESS = "Process of deleting gateway node is succeed.";
+    private static final String FAIL = "Process of deleting gateway node is failed.";
+    private static final String UNKNOWN = "Unknown device id is given.";
+
+    @Argument(index = 0, name = "DeviceId", description = "GatewayNode device id",
+            required = true, multiValued = false)
+    String deviceId = null;
+
+    @Override
+    protected void execute() {
+        ScalableGatewayService service = get(ScalableGatewayService.class);
+
+        GatewayNode gatewayNode = service.getGatewayNode(DeviceId.deviceId(deviceId));
+        if (gatewayNode == null) {
+            print(UNKNOWN);
+            return;
+        }
+
+        if (service.deleteGatewayNode(gatewayNode)) {
+            print(SUCCESS);
+        } else {
+            print(FAIL);
+        }
+    }
+
+}
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayListCommand.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayListCommand.java
new file mode 100644
index 0000000..d48dd43
--- /dev/null
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/ScalableGatewayListCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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.scalablegateway.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+
+/**
+ * Lists all gateway node information of scalablegateway.
+ */
+
+@Command(scope = "onos", name = "gateways",
+        description = "Lists gateway node information")
+public class ScalableGatewayListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "GatewayNode Id[%s]: DataPlane Ip[%s], External Interface names[%s]";
+    @Override
+    protected void execute() {
+        ScalableGatewayService service = get(ScalableGatewayService.class);
+        service.getGatewayNodes().forEach(node -> print(FORMAT,
+                node.getGatewayDeviceId().toString(),
+                node.getDataIpAddress().toString(),
+                node.getGatewayExternalInterfaceNames().toString()));
+    }
+}
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/package-info.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/package-info.java
new file mode 100644
index 0000000..7521cf9
--- /dev/null
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Command line interface for Scaleable Gateway management.
+ */
+package org.onosproject.scalablegateway.cli;
\ No newline at end of file
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/impl/ScalableGatewayManager.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/impl/ScalableGatewayManager.java
index 44db634..8b75335 100644
--- a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/impl/ScalableGatewayManager.java
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/impl/ScalableGatewayManager.java
@@ -17,7 +17,6 @@
 package org.onosproject.scalablegateway.impl;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -25,6 +24,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 
@@ -38,6 +38,8 @@
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.group.Group;
@@ -48,8 +50,12 @@
 import org.onosproject.scalablegateway.api.ScalableGatewayService;
 
 import java.util.List;
-import java.util.Map;
 
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,6 +78,7 @@
     private static final String FAIL_ADD_GATEWAY = "Adding process is failed as existing deivce id";
     private static final String FAIL_REMOVE_GATEWAY = "Removing process is failed as unknown deivce id";
     private static final String PORT_NAME = "portName";
+    private static final String GATEWAYNODE_MAP_NAME = "gatewaynode-map";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -91,10 +98,14 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected GroupService groupService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
     private GatewayNodeConfig config;
     private SelectGroupHandler selectGroupHandler;
 
     private final NetworkConfigListener configListener = new InternalConfigListener();
+    private InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
 
     private final ConfigFactory configFactory =
             new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, GatewayNodeConfig.class, APP_NAME) {
@@ -103,17 +114,28 @@
                     return new GatewayNodeConfig();
                 }
             };
-    private Map<DeviceId, GatewayNode> gatewayNodeMap = Maps.newHashMap(); // Map<GatewayNode`s Id, GatewayNode object>
+    private ConsistentMap<DeviceId, GatewayNode> gatewayNodeMap; // Map<GatewayNode`s Id, GatewayNode object>
+    private static final KryoNamespace.Builder GATEWAYNODE_SERIALIZER = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(DeviceId.class)
+            .register(GatewayNode.class);
 
     @Activate
     protected void activate() {
         appId = coreService.registerApplication(APP_ID);
         configRegistry.registerConfigFactory(configFactory);
         configService.addListener(configListener);
+        deviceService.addListener(internalDeviceListener);
 
         selectGroupHandler = new SelectGroupHandler(groupService, deviceService, driverService, appId);
         readConfiguration();
 
+        gatewayNodeMap = storageService.<DeviceId, GatewayNode>consistentMapBuilder()
+                .withSerializer(Serializer.using(GATEWAYNODE_SERIALIZER.build()))
+                .withName(GATEWAYNODE_MAP_NAME)
+                .withApplicationId(appId)
+                .build();
+
         log.info("started");
     }
 
@@ -121,6 +143,7 @@
     protected void deactivate() {
         gatewayNodeMap.clear();
 
+        deviceService.removeListener(internalDeviceListener);
         configService.removeListener(configListener);
 
         log.info("stopped");
@@ -128,12 +151,12 @@
 
     @Override
     public GatewayNode getGatewayNode(DeviceId deviceId) {
-        return checkNotNull(gatewayNodeMap.get(deviceId), GATEWAYNODE_CAN_NOT_BE_NULL);
+        return checkNotNull(gatewayNodeMap.get(deviceId).value(), GATEWAYNODE_CAN_NOT_BE_NULL);
     }
 
     @Override
     public List<PortNumber> getGatewayExternalPorts(DeviceId deviceId) {
-        GatewayNode gatewayNode = checkNotNull(gatewayNodeMap.get(deviceId), GATEWAYNODE_CAN_NOT_BE_NULL);
+        GatewayNode gatewayNode = checkNotNull(gatewayNodeMap.get(deviceId).value(), GATEWAYNODE_CAN_NOT_BE_NULL);
         List<PortNumber> portNumbers = Lists.newArrayList();
         gatewayNode.getGatewayExternalInterfaceNames()
                 .stream()
@@ -164,6 +187,7 @@
         List<GatewayNode> gatewayNodeList = Lists.newArrayList();
         gatewayNodeMap.values()
                 .stream()
+                .map(Versioned::value)
                 .forEach(gatewayNode -> gatewayNodeList.add(gatewayNode));
         return gatewayNodeList;
 
@@ -174,6 +198,7 @@
         List<DeviceId> deviceIdList = Lists.newArrayList();
         gatewayNodeMap.values()
                 .stream()
+                .map(Versioned::value)
                 .forEach(gatewayNode -> deviceIdList.add(gatewayNode.getGatewayDeviceId()));
         return deviceIdList;
 
@@ -182,12 +207,24 @@
     @Override
     public boolean addGatewayNode(GatewayNode gatewayNode) {
         gatewayNodeMap.putIfAbsent(gatewayNode.getGatewayDeviceId(), gatewayNode);
+        updateGatewayLoadBalance(gatewayNode, true);
         return true;
     }
 
     @Override
     public boolean deleteGatewayNode(GatewayNode gatewayNode) {
-        return gatewayNodeMap.remove(gatewayNode.getGatewayDeviceId(), gatewayNode);
+        boolean result = gatewayNodeMap.remove(gatewayNode.getGatewayDeviceId(), gatewayNode);
+        if (result) {
+            updateGatewayLoadBalance(gatewayNode, false);
+        }
+        return result;
+    }
+
+    private void updateGatewayLoadBalance(GatewayNode gatewayNode, boolean nodeInsertion) {
+        deviceService.getAvailableDevices().forEach(device ->
+                groupService.getGroups(device.id(), appId).forEach(group ->
+                        selectGroupHandler.updateBucketToSelectGroupInVxlan(device.id(), group.appCookie(),
+                                Lists.newArrayList(gatewayNode), nodeInsertion)));
     }
 
     private class InternalConfigListener implements NetworkConfigListener {
@@ -211,6 +248,17 @@
         }
     }
 
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent deviceEvent) {
+            if (deviceEvent.type() == DeviceEvent.Type.DEVICE_SUSPENDED ||
+                    deviceEvent.type() == DeviceEvent.Type.DEVICE_REMOVED) {
+                deleteGatewayNode(getGatewayNode(deviceEvent.subject().id()));
+            }
+        }
+    }
+
     private void readConfiguration() {
         config = configService.getConfig(appId, GatewayNodeConfig.class);
         if (config == null) {
diff --git a/apps/scalablegateway/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/scalablegateway/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..776cccc
--- /dev/null
+++ b/apps/scalablegateway/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.scalablegateway.cli.ScalableGatewayListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.scalablegateway.cli.ScalableGatewayAddCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.scalablegateway.cli.ScalableGatewayDeleteCommand"/>
+        </command>
+    </command-bundle>
+</blueprint>