[ONOS-4747] NETCONF function for FUJITSU OLT #2

 - Enhanced device-setcontrollers command to apply additional key-value pair.
    e.g. onos> device-setcontrollers netconf:10.10.1.11:830 tcp:10.10.1.11:6630,ofconfig-id=1

Change-Id: I2cb5941dbd9829ade6fa89d5546bbc6aab44f83f
diff --git a/cli/src/main/java/org/onosproject/cli/net/DeviceSetControllersCommand.java b/cli/src/main/java/org/onosproject/cli/net/DeviceSetControllersCommand.java
index 556dcc9..5d0707d 100644
--- a/cli/src/main/java/org/onosproject/cli/net/DeviceSetControllersCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/DeviceSetControllersCommand.java
@@ -18,7 +18,10 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
 import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.ControllerConfig;
 import org.onosproject.net.behaviour.ControllerInfo;
@@ -52,7 +55,7 @@
     protected void execute() {
 
         Arrays.asList(controllersListStrings).forEach(
-                cInfoString -> newControllers.add(new ControllerInfo(cInfoString)));
+                cInfoString -> newControllers.add(parseCInfoString(cInfoString)));
         DriverService service = get(DriverService.class);
         deviceId = DeviceId.deviceId(uri);
         DriverHandler h = service.createHandler(deviceId);
@@ -69,4 +72,31 @@
         print("size %d", config.getControllers().size());
     }
 
+
+    private ControllerInfo parseCInfoString(String cInfoString) {
+        Annotations annotation;
+
+        String[] config = cInfoString.split(",");
+        if (config.length == 2) {
+            String[] pair = config[1].split("=");
+
+            if (pair.length == 2) {
+                annotation = DefaultAnnotations.builder()
+                        .set(pair[0], pair[1]).build();
+            } else {
+                print("Wrong format {}", config[1]);
+                return null;
+            }
+
+            String[] data = config[0].split(":");
+            String type = data[0];
+            IpAddress ip = IpAddress.valueOf(data[1]);
+            int port = Integer.parseInt(data[2]);
+
+            return new ControllerInfo(ip, port, type, annotation);
+        } else {
+            print(config[0]);
+            return new ControllerInfo(config[0]);
+        }
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java b/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java
index 98f2599..cc965b1 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java
@@ -17,17 +17,23 @@
 
 import com.google.common.base.Preconditions;
 import org.onlab.packet.IpAddress;
+import org.onosproject.net.Annotated;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Objects;
 
 /**
  * Represents information for a device to connect to a controller.
  */
-public class ControllerInfo {
+public class ControllerInfo implements Annotated {
 
     private IpAddress ip = IpAddress.valueOf("0.0.0.0");
     private int port = 6653;
     private String type = "error";
+    private final Annotations annotations;
 
     /**
      * Information for contacting the controller.
@@ -37,18 +43,36 @@
      * @param type the connection type
      */
     public ControllerInfo(IpAddress ip, int port, String type) {
-        this.ip = ip;
-        this.port = port;
-        this.type = type;
+        this(ip, port, type, DefaultAnnotations.EMPTY);
     }
 
     /**
+     * Information for contacting the controller.
+     *
+     * @param ip   the ip address
+     * @param port the tcp port
+     * @param type the connection type
+     * @param annotations optional key/value annotations
+     */
+    public ControllerInfo(IpAddress ip, int port, String type, Annotations annotations) {
+        this.ip = checkNotNull(ip);
+        this.port = port;
+        this.type = checkNotNull(type);
+        this.annotations = checkNotNull(annotations);
+    }
+
+    // TODO Factory method equivalent to this method
+    //      should probably live in OVSDB, NETCONF package.
+    /**
      * Information for contacting the controller, if some information
      * is not contained in the target string because it's optional
      * it's leaved as in the field declaration (default values).
      *
      * @param target column returned from ovsdb query
+     *
+     * @deprecated in Hummingbird (1.7.0)
      */
+    @Deprecated
     public ControllerInfo(String target) {
         String[] data = target.split(":");
         this.type = data[0];
@@ -69,6 +93,7 @@
                 this.port = Integer.parseInt(data[2]);
             }
         }
+        this.annotations = DefaultAnnotations.EMPTY;
     }
 
     /**
@@ -98,11 +123,24 @@
         return type;
     }
 
+    @Override
+    public Annotations annotations() {
+        return annotations;
+    }
+
+    // TODO Method equivalent to this method
+    //      should probably live in OVSDB, NETCONF package.
+    // @deprecated in Hummingbird (1.7.0)
+    @Deprecated
     public String target() {
         if (type.startsWith("p")) {
             return type + ":" + port + ":" + ip;
         } else {
-            return type + ":" + ip + ":" + port;
+            if (annotations.equals(DefaultAnnotations.EMPTY)) {
+                return type + ":" + ip + ":" + port;
+            } else {
+                return type + ":" + ip + ":" + port + ":" + annotations.toString();
+            }
         }
     }
 
@@ -115,12 +153,10 @@
     @Override
     public boolean equals(Object toBeCompared) {
         if (toBeCompared instanceof ControllerInfo) {
-            ControllerInfo controllerInfo = (ControllerInfo) toBeCompared;
-            if (controllerInfo.type().equals(this.type)
-                    && controllerInfo.ip().equals(this.ip())
-                    && controllerInfo.port() == this.port) {
-                return true;
-            }
+            ControllerInfo that = (ControllerInfo) toBeCompared;
+            return Objects.equals(this.type, that.type) &&
+                    Objects.equals(this.ip, that.ip) &&
+                    Objects.equals(this.port, that.port);
         }
         return false;
     }
diff --git a/drivers/fujitsu/src/main/java/org/onosproject/drivers/fujitsu/FujitsuVoltControllerConfig.java b/drivers/fujitsu/src/main/java/org/onosproject/drivers/fujitsu/FujitsuVoltControllerConfig.java
index b61916e..f06ad81 100644
--- a/drivers/fujitsu/src/main/java/org/onosproject/drivers/fujitsu/FujitsuVoltControllerConfig.java
+++ b/drivers/fujitsu/src/main/java/org/onosproject/drivers/fujitsu/FujitsuVoltControllerConfig.java
@@ -19,18 +19,26 @@
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.configuration.HierarchicalConfiguration;
 import org.onosproject.drivers.utilities.XmlConfigParser;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
 import org.onlab.packet.IpAddress;
 import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.ControllerConfig;
 import org.onosproject.net.behaviour.ControllerInfo;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.netconf.NetconfController;
+import org.onosproject.netconf.NetconfDevice;
+import org.onosproject.netconf.NetconfException;
 import org.slf4j.Logger;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -46,8 +54,11 @@
         implements ControllerConfig {
 
     private final Logger log = getLogger(FujitsuVoltControllerConfig.class);
+    private static final String RESOURCE_XML = "voltcontrollers.xml";
 
     private static final String DOT = ".";
+    private static final String L_ANGLE_BR = "<";
+    private static final String R_ANGLE_BR = "/>";
     private static final String VOLT_NE_NAMESPACE =
             "xmlns=\"http://fujitsu.com/ns/volt/1.1\"";
     private static final String DATA = "data";
@@ -60,14 +71,35 @@
     private static final String IP_ADDRESS = "ip-address";
     private static final String PORT = "port";
     private static final String PROTOCOL = "protocol";
+    private static final String CONFIG = "config";
+    private static final String OFCONFIG_ID = "ofconfig-id";
+    private static final String EDIT_CONFIG = "edit-config";
+    private static final String TARGET = "target";
+    private static final String RUNNING = "running";
+    private static final String MERGE = "merge";
+    private static final String DEFAULT_OPERATION = "default-operation";
 
     private static final String VOLT_NE_OPEN = "<" + VOLT_NE + " ";
     private static final String VOLT_NE_CLOSE = "</" + VOLT_NE + ">";
     private static final String VOLT_OFCONFIG_EL = "<" + VOLT_OFCONFIG + "/>\n";
+    private static final String TARGET_OPEN = "<" + TARGET + ">";
+    private static final String TARGET_CLOSE = "</" + TARGET + ">";
+    private static final String END_LICENSE_HEADER = "-->";
 
     private static final String VOLT_DATACONFIG = DATA + DOT + VOLT_NE + DOT +
             VOLT_OFCONFIG + DOT + OF_CONTROLLERS + DOT + OF_CONTROLLER;
 
+    private static final String EDIT_CONFIG_TG = EDIT_CONFIG + DOT + TARGET;
+    private static final String EDIT_CONFIG_DO = EDIT_CONFIG + DOT + DEFAULT_OPERATION;
+    private static final String CONTROLLER_INFO_ID = CONTROLLER_INFO + DOT + "id";
+    private static final String CONTROLLER_INFO_IP = CONTROLLER_INFO + DOT + IP_ADDRESS;
+    private static final String CONTROLLER_INFO_PORT = CONTROLLER_INFO + DOT + PORT;
+    private static final String CONTROLLER_INFO_PROTOCOL = CONTROLLER_INFO + DOT + PROTOCOL;
+
+    private static final String VOLT_EDITCONFIG = EDIT_CONFIG + DOT +
+            CONFIG + DOT + VOLT_NE + DOT + VOLT_OFCONFIG + DOT + OF_CONTROLLERS;
+
+
     @Override
     public List<ControllerInfo> getControllers() {
         DriverHandler handler = handler();
@@ -85,11 +117,11 @@
 
                 String reply;
                 reply = controller.
-                    getDevicesMap().get(ncDeviceId).getSession().
-                    get(request.toString(), REPORT_ALL);
+                        getDevicesMap().get(ncDeviceId).getSession().
+                        get(request.toString(), REPORT_ALL);
                 log.debug("Reply XML {}", reply);
                 controllers.addAll(parseStreamVoltControllers(XmlConfigParser.
-                    loadXml(new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8)))));
+                        loadXml(new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8)))));
             } catch (IOException e) {
                 log.error("Cannot communicate to device {} ", ncDeviceId);
             }
@@ -103,10 +135,31 @@
 
     @Override
     public void setControllers(List<ControllerInfo> controllers) {
-        // TODO update later
-        log.warn("Operation not supported");
+        DriverHandler handler = handler();
+        NetconfController controller = handler.get(NetconfController.class);
+        MastershipService mastershipService = handler.get(MastershipService.class);
+        DeviceId ncdeviceId = handler.data().deviceId();
+        checkNotNull(controller, "Netconf controller is null");
+        if (mastershipService.isLocalMaster(ncdeviceId)) {
+            try {
+                NetconfDevice device = controller.getNetconfDevice(ncdeviceId);
+                String config = createVoltControllersConfig(
+                        XmlConfigParser.loadXml(getClass().
+                                getResourceAsStream(RESOURCE_XML)),
+                        RUNNING, MERGE, controllers);
+                device.getSession().editConfig(config.substring(
+                        config.indexOf(END_LICENSE_HEADER) + END_LICENSE_HEADER.length()));
+            } catch (NetconfException e) {
+                log.error("Cannot communicate to device {} , exception ", ncdeviceId, e);
+            }
+        } else {
+            log.warn("I'm not master for {} please use master, {} to execute command",
+                     ncdeviceId,
+                     mastershipService.getMasterFor(ncdeviceId));
+        }
     }
 
+
     /**
      * Parses XML string to get controller information.
      *
@@ -123,17 +176,67 @@
                     sub.configurationsAt(CONTROLLER_INFO);
 
             for (HierarchicalConfiguration child : childFields) {
+                Annotations annotations = DefaultAnnotations.builder()
+                        .set(OFCONFIG_ID, sub.getString(OFCONFIG_ID)).build();
                 ControllerInfo controller = new ControllerInfo(
                         IpAddress.valueOf(child.getString(IP_ADDRESS)),
                         Integer.parseInt(child.getString(PORT)),
-                        child.getString(PROTOCOL));
+                        child.getString(PROTOCOL), annotations);
 
-                log.debug("VOLT: OFCONTROLLER:  PROTOCOL={}, IP={}, PORT={} ",
-                          controller.type(), controller.ip(), controller.port());
+                log.debug("VOLT: OFCONTROLLER: PROTOCOL={}, IP={}, PORT={}, ID={} ",
+                          controller.type(), controller.ip(),
+                          controller.port(), controller.annotations().value(OFCONFIG_ID));
                 controllers.add(controller);
             }
         }
         return controllers;
     }
 
+    /**
+     * Forms XML string to change controller information.
+     *
+     * @param cfg a hierarchical configuration
+     * @param target the type of configuration
+     * @param netconfOperation operation type
+     * @param controllers list of controllers
+     * @return XML string
+     */
+    public static String createVoltControllersConfig(HierarchicalConfiguration cfg,
+                                                     String target, String netconfOperation,
+                                                     List<ControllerInfo> controllers) {
+        XMLConfiguration editcfg = null;
+
+        cfg.setProperty(EDIT_CONFIG_TG, target);
+        cfg.setProperty(EDIT_CONFIG_DO, netconfOperation);
+
+        List<ConfigurationNode> newControllers = new ArrayList<>();
+        for (ControllerInfo ci : controllers) {
+            XMLConfiguration controller = new XMLConfiguration();
+            controller.setRoot(new HierarchicalConfiguration.Node(OF_CONTROLLER));
+            controller.setProperty(OFCONFIG_ID, ci.annotations().value(OFCONFIG_ID));
+            controller.setProperty(CONTROLLER_INFO_ID, ci.annotations().value(OFCONFIG_ID));
+            controller.setProperty(CONTROLLER_INFO_IP, ci.ip());
+            controller.setProperty(CONTROLLER_INFO_PORT, ci.port());
+            controller.setProperty(CONTROLLER_INFO_PROTOCOL, ci.type());
+            newControllers.add(controller.getRootNode());
+        }
+        cfg.addNodes(VOLT_EDITCONFIG, newControllers);
+
+        try {
+             editcfg = (XMLConfiguration) cfg;
+        } catch (ClassCastException e) {
+            e.printStackTrace();
+        }
+        StringWriter stringWriter = new StringWriter();
+        try {
+            editcfg.save(stringWriter);
+        } catch (ConfigurationException e) {
+            e.printStackTrace();
+        }
+        String s = stringWriter.toString();
+        s = s.replace(TARGET_OPEN + target + TARGET_CLOSE,
+                      TARGET_OPEN + L_ANGLE_BR + target + R_ANGLE_BR + TARGET_CLOSE);
+        return s;
+    }
+
 }
diff --git a/drivers/fujitsu/src/main/resources/org/onosproject/drivers/fujitsu/voltcontrollers.xml b/drivers/fujitsu/src/main/resources/org/onosproject/drivers/fujitsu/voltcontrollers.xml
new file mode 100644
index 0000000..5403a35
--- /dev/null
+++ b/drivers/fujitsu/src/main/resources/org/onosproject/drivers/fujitsu/voltcontrollers.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  -->
+
+<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <edit-config>
+        <target>
+        </target>
+        <default-operation>
+        </default-operation>
+        <config>
+            <volt-ne xmlns="http://fujitsu.com/ns/volt/1.1">
+                <volt-ofconfig>
+                    <of-controllers>
+                    </of-controllers>
+                </volt-ofconfig>
+            </volt-ne>
+        </config>
+    </edit-config>
+</rpc>