ONOS-5503 Added CLIs for administering OFAgent

Change-Id: I58256316e2054952da9dce04bf927901761807e5
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgent.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgent.java
index 0db4489..659d6db 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgent.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgent.java
@@ -71,6 +71,15 @@
          */
         OFAgent build();
 
+
+        /**
+         * Returns OF agent builder with the supplied OF agent.
+         *
+         * @param ofAgent ofagent
+         * @return of agent builder
+         */
+        Builder from(OFAgent ofAgent);
+
         /**
          * Returns OF agent builder with the supplied network ID.
          *
@@ -88,6 +97,22 @@
         Builder controllers(Set<OFController> controllers);
 
         /**
+         * Returns OF agent builder with the supplied additional controller.
+         *
+         * @param controller additional controller
+         * @return of agent builder
+         */
+        Builder addController(OFController controller);
+
+        /**
+         * Returns OF agent builder with the supplied controller removed.
+         *
+         * @param controller controller to delete
+         * @return of agent builder
+         */
+        Builder deleteController(OFController controller);
+
+        /**
          * Returns OF agent builder with the supplied state.
          *
          * @param state state of the agent
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgentAdminService.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgentAdminService.java
index 0c6667a..859215d 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgentAdminService.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFAgentAdminService.java
@@ -40,8 +40,9 @@
      * Removes the OpenFlow agent for the given virtual network.
      *
      * @param networkId virtual network identifier
+     * @return removed ofagent; null if it fails
      */
-    void removeAgent(NetworkId networkId);
+    OFAgent removeAgent(NetworkId networkId);
 
     /**
      * Starts the agent for the given network.
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentAddControllerCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentAddControllerCommand.java
new file mode 100644
index 0000000..354197b
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentAddControllerCommand.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgent;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+import org.onosproject.ofagent.api.OFAgentService;
+import org.onosproject.ofagent.impl.DefaultOFAgent;
+import org.onosproject.ofagent.impl.DefaultOFController;
+
+/**
+ * Adds a controller to the OFAgent.
+ */
+@Command(scope = "onos", name = "ofagent-controller-add",
+        description = "Add a controller to the ofagent")
+public class OFAgentAddControllerCommand extends AbstractShellCommand {
+
+    private static final String PATTERN_IP_PORT = "\\d{1,3}(?:\\.\\d{1,3}){3}(?::\\d{1,5})";
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Argument(index = 1, name = "controller",
+            description = "External controller with IP:PORT format",
+            required = true, multiValued = false)
+    private String strCtrl;
+
+    @Override
+    protected void execute() {
+        if (!isValidController(strCtrl)) {
+            error("Invalid controller string %s, must be IP:PORT", strCtrl);
+            return;
+        }
+
+        OFAgentService service = get(OFAgentService.class);
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+
+        OFAgent existing = service.agent(NetworkId.networkId(networkId));
+        if (existing == null) {
+            error("OFAgent for network %s does not exist", networkId);
+            return;
+        }
+
+        String[] temp = strCtrl.split(":");
+        OFAgent updated = DefaultOFAgent.builder()
+                .from(existing)
+                .addController(DefaultOFController.of(
+                        IpAddress.valueOf(temp[0]),
+                        TpPort.tpPort(Integer.valueOf(temp[1]))))
+                .build();
+        adminService.updateAgent(updated);
+    }
+
+    private boolean isValidController(String ctrl) {
+        if (!ctrl.matches(PATTERN_IP_PORT)) {
+            return false;
+        }
+
+        String[] temp = ctrl.split(":");
+        try {
+            IpAddress.valueOf(temp[0]);
+            TpPort.tpPort(Integer.valueOf(temp[1]));
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentCreateCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentCreateCommand.java
new file mode 100644
index 0000000..239c5ef
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentCreateCommand.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import com.google.common.collect.Sets;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgent;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+import org.onosproject.ofagent.api.OFController;
+import org.onosproject.ofagent.impl.DefaultOFAgent;
+import org.onosproject.ofagent.impl.DefaultOFController;
+
+import java.util.Set;
+
+/**
+ * Creates a new OFAagent.
+ */
+@Command(scope = "onos", name = "ofagent-create", description = "Add a new ofagent")
+public class OFAgentCreateCommand extends AbstractShellCommand {
+
+    private static final String PATTERN_IP_PORT = "\\d{1,3}(?:\\.\\d{1,3}){3}(?::\\d{1,5})";
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Argument(index = 1, name = "controllers",
+            description = "List of external controllers with IP:PORT format",
+            required = false, multiValued = true)
+    private String[] strCtrls = {};
+
+    @Override
+    protected void execute() {
+        Set<OFController> ctrls = Sets.newHashSet();
+        for (String strCtrl : strCtrls) {
+            if (!isValidController(strCtrl)) {
+                print("Invalid controller %s, ignores it.", strCtrl);
+                continue;
+            }
+            String[] temp = strCtrl.split(":");
+            ctrls.add(DefaultOFController.of(IpAddress.valueOf(temp[0]),
+                    TpPort.tpPort(Integer.valueOf(temp[1]))));
+        }
+
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+        OFAgent ofAgent = DefaultOFAgent.builder()
+                .networkId(NetworkId.networkId(networkId))
+                .controllers(ctrls)
+                .state(OFAgent.State.STOPPED)
+                .build();
+        adminService.createAgent(ofAgent);
+        print("Successfully created OFAgent for network %s", networkId);
+    }
+
+    private boolean isValidController(String ctrl) {
+        if (!ctrl.matches(PATTERN_IP_PORT)) {
+            return false;
+        }
+
+        String[] temp = ctrl.split(":");
+        try {
+            IpAddress.valueOf(temp[0]);
+            TpPort.tpPort(Integer.valueOf(temp[1]));
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentDeleteControllerCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentDeleteControllerCommand.java
new file mode 100644
index 0000000..e401798
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentDeleteControllerCommand.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgent;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+import org.onosproject.ofagent.api.OFAgentService;
+import org.onosproject.ofagent.impl.DefaultOFAgent;
+import org.onosproject.ofagent.impl.DefaultOFController;
+
+/**
+ * Removes the controller from the OFAgent.
+ */
+@Command(scope = "onos", name = "ofagent-controller-delete",
+        description = "Deletes a controller from the ofagent")
+public class OFAgentDeleteControllerCommand extends AbstractShellCommand {
+
+    private static final String PATTERN_IP_PORT = "\\d{1,3}(?:\\.\\d{1,3}){3}(?::\\d{1,5})";
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Argument(index = 1, name = "controller",
+            description = "External controller with IP:PORT format",
+            required = true, multiValued = false)
+    private String strCtrl;
+
+    @Override
+    protected void execute() {
+        if (!isValidController(strCtrl)) {
+            error("Invalid controller string %s, must be IP:PORT", strCtrl);
+            return;
+        }
+
+        OFAgentService service = get(OFAgentService.class);
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+
+        OFAgent existing = service.agent(NetworkId.networkId(networkId));
+        if (existing == null) {
+            error("OFAgent for network %s does not exist", networkId);
+            return;
+        }
+
+        String[] temp = strCtrl.split(":");
+        OFAgent updated = DefaultOFAgent.builder()
+                .from(existing)
+                .deleteController(DefaultOFController.of(
+                        IpAddress.valueOf(temp[0]),
+                        TpPort.tpPort(Integer.valueOf(temp[1]))))
+                .build();
+        adminService.updateAgent(updated);
+    }
+
+    private boolean isValidController(String ctrl) {
+        if (!ctrl.matches(PATTERN_IP_PORT)) {
+            return false;
+        }
+
+        String[] temp = ctrl.split(":");
+        try {
+            IpAddress.valueOf(temp[0]);
+            TpPort.tpPort(Integer.valueOf(temp[1]));
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentListCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentListCommand.java
new file mode 100644
index 0000000..8cceadc
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentListCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.ofagent.api.OFAgentService;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Lists the existing OFAgents.
+ */
+@Command(scope = "onos", name = "ofagents", description = "Lists all ofagents")
+public class OFAgentListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-10s%-10s%-8s";
+    private static final String CTRL = "%s:%s";
+
+    @Override
+    protected void execute() {
+        OFAgentService service = get(OFAgentService.class);
+        print(FORMAT, "Network", "Status", "Controllers");
+
+        service.agents().forEach(agent -> {
+            Set<String> ctrls = agent.controllers().stream()
+                    .map(ctrl -> String.format(CTRL, ctrl.ip(), ctrl.port()))
+                    .collect(Collectors.toSet());
+            print(FORMAT, agent.networkId(), agent.state().name(), ctrls);
+        });
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentRemoveCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentRemoveCommand.java
new file mode 100644
index 0000000..5e588f8
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentRemoveCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgent;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+
+/**
+ * Removes the existing OFAgent.
+ */
+@Command(scope = "onos", name = "ofagent-remove", description = "Removes the ofagent")
+public class OFAgentRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Override
+    protected void execute() {
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+        OFAgent removed = adminService.removeAgent(NetworkId.networkId(networkId));
+        if (removed != null) {
+            print("Successfully removed OFAgent for network %s", networkId);
+        } else {
+            print("Failed to remove OFAgent for network %s", networkId);
+        }
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStartCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStartCommand.java
new file mode 100644
index 0000000..ba9228d
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStartCommand.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+
+/**
+ * Starts the OFAgent.
+ */
+@Command(scope = "onos", name = "ofagent-start", description = "Starts the ofagent")
+public class OFAgentStartCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Override
+    protected void execute() {
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+        adminService.startAgent(NetworkId.networkId(networkId));
+        print("Successfully started OFAgent for network %s", networkId);
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStopCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStopCommand.java
new file mode 100644
index 0000000..d780bed
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFAgentStopCommand.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFAgentAdminService;
+
+/**
+ * Stops the OFAgent.
+ */
+@Command(scope = "onos", name = "ofagent-stop", description = "Stops the ofagent")
+public class OFAgentStopCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = true, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Override
+    protected void execute() {
+        OFAgentAdminService adminService = get(OFAgentAdminService.class);
+        adminService.stopAgent(NetworkId.networkId(networkId));
+        print("Successfully stopped OFAgent for network %s", networkId);
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFSwitchListCommand.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFSwitchListCommand.java
new file mode 100644
index 0000000..c3e5714
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/OFSwitchListCommand.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017-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.ofagent.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.ofagent.api.OFSwitch;
+import org.onosproject.ofagent.api.OFSwitchService;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Lists virtual OF switches.
+ */
+@Command(scope = "onos", name = "ofagent-switches", description = "Lists all OF switches")
+public class OFSwitchListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-35s%-8s";
+
+    @Argument(index = 0, name = "network", description = "Virtual network ID",
+            required = false, multiValued = false)
+    private long networkId = NetworkId.NONE.id();
+
+    @Override
+    protected void execute() {
+        OFSwitchService service = get(OFSwitchService.class);
+
+        Set<OFSwitch> ofSwitches;
+        if (networkId != NetworkId.NONE.id()) {
+            ofSwitches = service.ofSwitches(NetworkId.networkId(networkId));
+        } else {
+            ofSwitches = service.ofSwitches();
+        }
+
+        print(FORMAT, "DPID", "Connected controllers");
+        ofSwitches.forEach(ofSwitch -> {
+            Set<String> channels = ofSwitch.controllerChannels().stream()
+                    .map(channel -> channel.remoteAddress().toString())
+                    .collect(Collectors.toSet());
+            print(FORMAT, ofSwitch.dpid(), channels);
+        });
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/package-info.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/package-info.java
new file mode 100644
index 0000000..b9761ce
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * CLIs for administering OFAgents.
+ */
+package org.onosproject.ofagent.cli;
\ No newline at end of file
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFAgent.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFAgent.java
index cac1e03..586ce3a 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFAgent.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFAgent.java
@@ -17,6 +17,7 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.ofagent.api.OFAgent;
 import org.onosproject.ofagent.api.OFController;
@@ -96,23 +97,10 @@
         return new Builder();
     }
 
-    /**
-     * Returns new builder instance from the existing agent.
-     *
-     * @param ofAgent the existing agent
-     * @return default ofagent builder
-     */
-    public static Builder builder(OFAgent ofAgent) {
-        return new Builder()
-                .networkId(ofAgent.networkId())
-                .controllers(ofAgent.controllers())
-                .state(ofAgent.state());
-    }
-
     public static final class Builder implements OFAgent.Builder {
 
         private NetworkId networkId;
-        private Set<OFController> controllers;
+        private Set<OFController> controllers = Sets.newHashSet();
         private State state;
 
         private Builder() {
@@ -128,6 +116,14 @@
         }
 
         @Override
+        public Builder from(OFAgent ofAgent) {
+            this.networkId = ofAgent.networkId();
+            this.controllers = Sets.newHashSet(ofAgent.controllers());
+            this.state = ofAgent.state();
+            return this;
+        }
+
+        @Override
         public Builder networkId(NetworkId networkId) {
             this.networkId = networkId;
             return this;
@@ -140,6 +136,18 @@
         }
 
         @Override
+        public OFAgent.Builder addController(OFController controller) {
+            this.controllers.add(controller);
+            return this;
+        }
+
+        @Override
+        public OFAgent.Builder deleteController(OFController controller) {
+            this.controllers.remove(controller);
+            return this;
+        }
+
+        @Override
         public Builder state(State state) {
             this.state = state;
             return this;
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentManager.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentManager.java
index ce7c30f..454f7d7 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentManager.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentManager.java
@@ -67,6 +67,8 @@
     private static final String MSG_CREATED = "created";
     private static final String MSG_UPDATED = "updated";
     private static final String MSG_REMOVED = "removed";
+    private static final String MSG_STARTED = "started";
+    private static final String MSG_STOPPED = "stopped";
     private static final String MSG_IN_STARTED = "is already in active state, do nothing";
     private static final String MSG_IN_STOPPED = "is already in inactive state, do nothing";
 
@@ -117,7 +119,9 @@
         leadershipService.removeListener(leadershipListener);
         virtualNetService.removeListener(virtualNetListener);
         ofAgentStore.unsetDelegate(delegate);
-        ofAgentStore.ofAgents().forEach(ofAgent -> stopAgent(ofAgent.networkId()));
+        ofAgentStore.ofAgents().stream()
+                .filter(ofAgent -> ofAgent.state() == STARTED)
+                .forEach(ofAgent -> stopAgent(ofAgent.networkId()));
 
         eventExecutor.shutdown();
         leadershipService.withdraw(appId.name());
@@ -132,6 +136,7 @@
             log.warn(String.format(MSG_OFAGENT, ofAgent.networkId(), ERR_IN_USE));
             return;
         }
+        // TODO check if the virtual network exists
         ofAgentStore.createOfAgent(ofAgent);
         log.info(String.format(MSG_OFAGENT, ofAgent.networkId(), MSG_CREATED));
     }
@@ -144,7 +149,7 @@
     }
 
     @Override
-    public void removeAgent(NetworkId networkId) {
+    public OFAgent removeAgent(NetworkId networkId) {
         checkNotNull(networkId, ERR_NULL_NETID);
         synchronized (this) {
             OFAgent existing = ofAgentStore.ofAgent(networkId);
@@ -156,8 +161,8 @@
                 final String error = String.format(MSG_OFAGENT, networkId, ERR_IN_USE);
                 throw new IllegalStateException(error);
             }
-            ofAgentStore.removeOfAgent(networkId);
             log.info(String.format(MSG_OFAGENT, networkId, MSG_REMOVED));
+            return ofAgentStore.removeOfAgent(networkId);
         }
     }
 
@@ -171,11 +176,13 @@
                 throw new IllegalStateException(error);
             }
             if (existing.state() == STARTED) {
-                log.warn(String.format(MSG_OFAGENT, networkId, MSG_IN_STARTED));
-                return;
+                final String error = String.format(MSG_OFAGENT, networkId, MSG_IN_STARTED);
+                throw new IllegalStateException(error);
             }
-            OFAgent updated = DefaultOFAgent.builder(existing).state(STARTED).build();
+            OFAgent updated = DefaultOFAgent.builder()
+                    .from(existing).state(STARTED).build();
             ofAgentStore.updateOfAgent(updated);
+            log.info(String.format(MSG_OFAGENT, networkId, MSG_STARTED));
         }
     }
 
@@ -189,11 +196,13 @@
                 throw new IllegalStateException(error);
             }
             if (existing.state() == STOPPED) {
-                log.warn(String.format(MSG_OFAGENT, networkId, MSG_IN_STOPPED));
-                return;
+                final String error = String.format(MSG_OFAGENT, networkId, MSG_IN_STOPPED);
+                throw new IllegalStateException(error);
             }
-            OFAgent updated = DefaultOFAgent.builder(existing).state(STOPPED).build();
+            OFAgent updated = DefaultOFAgent.builder()
+                    .from(existing).state(STOPPED).build();
             ofAgentStore.updateOfAgent(updated);
+            log.info(String.format(MSG_OFAGENT, networkId, MSG_STOPPED));
         }
     }
 
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFConnectionHandler.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFConnectionHandler.java
index ed897ff..5853c4a 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFConnectionHandler.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFConnectionHandler.java
@@ -19,6 +19,7 @@
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelFutureListener;
 import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoop;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import org.onosproject.ofagent.api.OFController;
@@ -28,6 +29,7 @@
 
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -94,6 +96,7 @@
                     MSG_CONNECTED,
                     controller.ip(),
                     controller.port()));
+            // FIXME add close future listener to handle connection lost
         } else {
             if (retryCount.getAndIncrement() > MAX_RETRY) {
                 log.warn(String.format(MSG_STATE,
@@ -102,7 +105,8 @@
                         controller.ip(),
                         controller.port()));
             } else {
-                this.connect();
+                final EventLoop loop = future.channel().eventLoop();
+                loop.schedule(this::connect, 1L, TimeUnit.SECONDS);
             }
         }
     }
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
index f522d68..9d58115 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
@@ -24,13 +24,14 @@
 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.Tools;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
 import org.onosproject.incubator.net.virtual.VirtualNetworkService;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
@@ -66,6 +67,7 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onlab.util.BoundedThreadPool.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.ofagent.api.OFAgent.State.STARTED;
 import static org.onosproject.ofagent.api.OFAgentService.APPLICATION_NAME;
 
 /**
@@ -77,7 +79,8 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    private static final OFSwitchCapabilities DEFAULT_CAPABILITIES = DefaultOFSwitchCapabilities.builder()
+    private static final OFSwitchCapabilities DEFAULT_CAPABILITIES =
+            DefaultOFSwitchCapabilities.builder()
             .flowStats()
             .tableStats()
             .portStats()
@@ -106,6 +109,7 @@
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final OFAgentListener ofAgentListener = new InternalOFAgentListener();
+    private final VirtualNetworkListener vNetworkListener = new InternalVirtualNetworkListener();
     private final DeviceListener deviceListener = new InternalDeviceListener();
     private final FlowRuleListener flowRuleListener = new InternalFlowRuleListener();
     private final PacketProcessor packetProcessor = new InternalPacketProcessor();
@@ -119,13 +123,17 @@
         appId = coreService.registerApplication(APPLICATION_NAME);
         localId = clusterService.getLocalNode().id();
         ioWorker = new NioEventLoopGroup();
+
+        ofAgentService.agents().forEach(this::processOFAgentCreated);
         ofAgentService.addListener(ofAgentListener);
+        virtualNetService.addListener(vNetworkListener);
 
         log.info("Started");
     }
 
     @Deactivate
     protected void deactivate() {
+        virtualNetService.removeListener(vNetworkListener);
         ofAgentService.removeListener(ofAgentListener);
         ofAgentService.agents().forEach(this::processOFAgentStopped);
 
@@ -149,21 +157,25 @@
         return ImmutableSet.copyOf(ofSwitches);
     }
 
-    private void addOFSwitch(DeviceId deviceId) {
+    private void addOFSwitch(NetworkId networkId, DeviceId deviceId) {
         OFSwitch ofSwitch = DefaultOFSwitch.of(
                 dpidWithDeviceId(deviceId),
                 DEFAULT_CAPABILITIES);
         ofSwitchMap.put(deviceId, ofSwitch);
-        log.debug("Added virtual OF switch for {}", deviceId);
-        // TODO connect controllers if the agent is in started state
+        log.info("Added virtual OF switch for {}", deviceId);
+
+        OFAgent ofAgent = ofAgentService.agent(networkId);
+        if (ofAgent.state() == STARTED) {
+            connectController(ofSwitch, ofAgent.controllers());
+        }
     }
 
     private void deleteOFSwitch(DeviceId deviceId) {
-        // TODO disconnect switch if it has active connection
-        OFSwitch ofSwitch = ofSwitchMap.remove(deviceId);
-        if (ofSwitch != null) {
-            log.debug("Removed virtual OFSwitch for {}", deviceId);
-        }
+        OFSwitch ofSwitch = ofSwitchMap.get(deviceId);
+        ofSwitch.controllerChannels().forEach(ChannelOutboundInvoker::disconnect);
+
+        ofSwitchMap.remove(deviceId);
+        log.info("Removed virtual OFSwitch for {}", deviceId);
     }
 
     private void connectController(OFSwitch ofSwitch, Set<OFController> controllers) {
@@ -174,25 +186,22 @@
                     ioWorker);
             connectionHandler.connect();
         });
-        log.debug("Connection requested for {}, controller:{}", ofSwitch.dpid(), controllers);
     }
 
     private void disconnectController(OFSwitch ofSwitch, Set<OFController> controllers) {
         Set<SocketAddress> controllerAddrs = controllers.stream()
-                .map(ctrl -> new InetSocketAddress(ctrl.ip().toInetAddress(), ctrl.port().toInt()))
+                .map(ctrl -> new InetSocketAddress(
+                        ctrl.ip().toInetAddress(), ctrl.port().toInt()))
                 .collect(Collectors.toSet());
 
         ofSwitch.controllerChannels().stream()
                 .filter(channel -> controllerAddrs.contains(channel.remoteAddress()))
                 .forEach(ChannelOutboundInvoker::disconnect);
-        log.debug("Disconnection requested for {}, controller:{}", ofSwitch.dpid(), controllers);
     }
 
     private Set<DeviceId> devices(NetworkId networkId) {
-        DeviceService deviceService = virtualNetService.get(
-                networkId,
-                DeviceService.class);
-        Set<DeviceId> deviceIds = Tools.stream(deviceService.getAvailableDevices())
+        Set<DeviceId> deviceIds = virtualNetService.getVirtualDevices(networkId)
+                .stream()
                 .map(Device::id)
                 .collect(Collectors.toSet());
         return ImmutableSet.copyOf(deviceIds);
@@ -214,19 +223,13 @@
     }
 
     private void processOFAgentCreated(OFAgent ofAgent) {
-        devices(ofAgent.networkId()).forEach(this::addOFSwitch);
-        DeviceService deviceService = virtualNetService.get(
-                ofAgent.networkId(),
-                DeviceService.class);
-        deviceService.addListener(deviceListener);
+        devices(ofAgent.networkId()).forEach(deviceId -> {
+            addOFSwitch(ofAgent.networkId(), deviceId);
+        });
     }
 
     private void processOFAgentRemoved(OFAgent ofAgent) {
         devices(ofAgent.networkId()).forEach(this::deleteOFSwitch);
-        DeviceService deviceService = virtualNetService.get(
-                ofAgent.networkId(),
-                DeviceService.class);
-        deviceService.removeListener(deviceListener);
     }
 
     private void processOFAgentStarted(OFAgent ofAgent) {
@@ -237,6 +240,11 @@
             }
         });
 
+        DeviceService deviceService = virtualNetService.get(
+                ofAgent.networkId(),
+                DeviceService.class);
+        deviceService.addListener(deviceListener);
+
         PacketService packetService = virtualNetService.get(
                 ofAgent.networkId(),
                 PacketService.class);
@@ -256,6 +264,11 @@
             }
         });
 
+        DeviceService deviceService = virtualNetService.get(
+                ofAgent.networkId(),
+                DeviceService.class);
+        deviceService.removeListener(deviceListener);
+
         PacketService packetService = virtualNetService.get(
                 ofAgent.networkId(),
                 PacketService.class);
@@ -267,6 +280,43 @@
         flowRuleService.removeListener(flowRuleListener);
     }
 
+    private class InternalVirtualNetworkListener implements VirtualNetworkListener {
+
+        @Override
+        public void event(VirtualNetworkEvent event) {
+            switch (event.type()) {
+                case VIRTUAL_DEVICE_ADDED:
+                    eventExecutor.execute(() -> {
+                        log.debug("Virtual device {} added to network {}",
+                                event.virtualDevice().id(),
+                                event.subject());
+                        addOFSwitch(event.subject(), event.virtualDevice().id());
+                    });
+                    break;
+                case VIRTUAL_DEVICE_UPDATED:
+                    // TODO handle device availability updates
+                    break;
+                case VIRTUAL_DEVICE_REMOVED:
+                    eventExecutor.execute(() -> {
+                        log.debug("Virtual device {} removed from network {}",
+                                event.virtualDevice().id(),
+                                event.subject());
+                        deleteOFSwitch(event.virtualDevice().id());
+                    });
+                    break;
+                case NETWORK_UPDATED:
+                case NETWORK_REMOVED:
+                case NETWORK_ADDED:
+                case VIRTUAL_PORT_ADDED:
+                case VIRTUAL_PORT_UPDATED:
+                case VIRTUAL_PORT_REMOVED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
     private class InternalOFAgentListener implements OFAgentListener {
 
         @Override
@@ -323,24 +373,10 @@
         @Override
         public void event(DeviceEvent event) {
             switch (event.type()) {
-                case DEVICE_ADDED:
-                    eventExecutor.execute(() -> {
-                        Device device = event.subject();
-                        log.debug("Processing device added: {}", device);
-                        addOFSwitch(device.id());
-                    });
-                    break;
-                case DEVICE_REMOVED:
-                    eventExecutor.execute(() -> {
-                        Device device = event.subject();
-                        log.debug("Processing device added: {}", device);
-                        deleteOFSwitch(device.id());
-                    });
-                    break;
                 case DEVICE_AVAILABILITY_CHANGED:
-                    // TODO handle event
-                    break;
+                case DEVICE_ADDED:
                 case DEVICE_UPDATED:
+                case DEVICE_REMOVED:
                 case DEVICE_SUSPENDED:
                 case PORT_ADDED:
                     // TODO handle event
diff --git a/apps/ofagent/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/ofagent/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..2b0fe65
--- /dev/null
+++ b/apps/ofagent/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,43 @@
+<!--
+~ Copyright 2017-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.ofagent.cli.OFAgentListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentCreateCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentRemoveCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentStartCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentStopCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentAddControllerCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFAgentDeleteControllerCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ofagent.cli.OFSwitchListCommand"/>
+        </command>
+    </command-bundle>
+</blueprint>