Enhancing null providers for use in demos.

Change-Id: I079d19a98fba2312bd4b17d2e275b34f4dee6f19
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/CustomTopologySimulator.java b/providers/null/src/main/java/org/onosproject/provider/nil/CustomTopologySimulator.java
new file mode 100644
index 0000000..016b187
--- /dev/null
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/CustomTopologySimulator.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 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.provider.nil;
+
+import com.google.common.collect.Maps;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+
+import java.util.Map;
+
+import static org.onlab.util.Tools.toHex;
+import static org.onosproject.provider.nil.NullProviders.SCHEME;
+
+/**
+ * Custom topology defined by a concise language.
+ */
+public class CustomTopologySimulator extends TopologySimulator {
+
+    private int nextDeviceId = 0;
+    private int nextHostId = 0;
+
+    private Map<String, DeviceId> nameToId = Maps.newConcurrentMap();
+
+    /**
+     * Returns the next device id.
+     *
+     * @return the next device id
+     */
+    public DeviceId nextDeviceId() {
+        return DeviceId.deviceId(SCHEME + ":" + toHex(++nextDeviceId));
+    }
+
+    /**
+     * Returns the next host id.
+     *
+     * @return the next host id
+     */
+    public HostId nextHostId() {
+        return HostId.hostId(MacAddress.valueOf(nextHostId), VlanId.NONE);
+    }
+
+    /**
+     * Returns the identifier of the device with the specified alias.
+     *
+     * @param name device name
+     * @return device identifier
+     */
+    public DeviceId deviceId(String name) {
+        return nameToId.get(name);
+    }
+
+    /**
+     * Creates simulated device.
+     *
+     * @param id        device identifier
+     * @param name      device name
+     * @param type      device type
+     * @param portCount number of device ports
+     */
+    public void createDevice(DeviceId id, String name, Device.Type type, int portCount) {
+        int chassisId = Integer.parseInt(id.uri().getSchemeSpecificPart());
+        createDevice(id, chassisId, type, portCount);
+        nameToId.put(name, id);
+    }
+
+    @Override
+    protected void createDevices() {
+    }
+
+    @Override
+    protected void createLinks() {
+    }
+
+    @Override
+    protected void createHosts() {
+    }
+}
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java b/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
index 08be68a..b0ccf15 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
@@ -268,6 +268,15 @@
     }
 
     /**
+     * Returns the currently active topology simulator.
+     *
+     * @return current simulator; null if none is active
+     */
+    public TopologySimulator currentSimulator() {
+        return simulator;
+    }
+
+    /**
      * Severs the link between the specified end-points in both directions.
      *
      * @param one link endpoint
@@ -356,6 +365,8 @@
             return new MeshTopologySimulator();
         } else if (topoShape.matches("grid([,].*|$)")) {
             return new GridTopologySimulator();
+        } else if (topoShape.matches("custom([,].*|$)")) {
+            return new CustomTopologySimulator();
         } else {
             return new ConfiguredTopologySimulator();
         }
@@ -424,7 +435,7 @@
 
         @Override
         public boolean isReachable(DeviceId deviceId) {
-            return topoShape.equals("configured") ||
+            return topoShape.equals("custom") ||
                     (simulator != null && simulator.contains(deviceId) &&
                             topologyMutationDriver.isReachable(deviceId));
         }
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java b/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java
index b89f94a..2edd3c8 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java
@@ -185,16 +185,28 @@
     /**
      * Creates simulated device.
      *
-     * @param id device identifier
+     * @param id        device identifier
      * @param chassisId chassis identifier number
      */
-    protected void createDevice(DeviceId id, int chassisId) {
+    public void createDevice(DeviceId id, int chassisId) {
+        createDevice(id, chassisId, Device.Type.SWITCH, hostCount + infrastructurePorts);
+    }
+
+    /**
+     * Creates simulated device.
+     *
+     * @param id        device identifier
+     * @param chassisId chassis identifier number
+     * @param type      device type
+     * @param portCount number of device ports
+     */
+    public void createDevice(DeviceId id, int chassisId, Device.Type type, int portCount) {
         DeviceDescription desc =
-                new DefaultDeviceDescription(id.uri(), Device.Type.SWITCH,
+                new DefaultDeviceDescription(id.uri(), type,
                                              "ON.Lab", "0.1", "0.1", "1234",
                                              new ChassisId(chassisId));
         deviceProviderService.deviceConnected(id, desc);
-        deviceProviderService.updatePorts(id, buildPorts(hostCount + infrastructurePorts));
+        deviceProviderService.updatePorts(id, buildPorts(portCount));
     }
 
     /**
@@ -205,7 +217,7 @@
      * @param pi port number of i-th device
      * @param pj port number of j-th device
      */
-    protected void createLink(int i, int j, int pi, int pj) {
+    public void createLink(int i, int j, int pi, int pj) {
         ConnectPoint one = new ConnectPoint(deviceIds.get(i), PortNumber.portNumber(pi));
         ConnectPoint two = new ConnectPoint(deviceIds.get(j), PortNumber.portNumber(pj));
         createLink(one, two);
@@ -214,12 +226,26 @@
     /**
      * Creates simulated link between two connection points.
      *
-     * @param one  one connection point
-     * @param two  another connection point
+     * @param one one connection point
+     * @param two another connection point
      */
-    protected void createLink(ConnectPoint one, ConnectPoint two) {
-        linkProviderService.linkDetected(new DefaultLinkDescription(one, two, DIRECT));
-        linkProviderService.linkDetected(new DefaultLinkDescription(two, one, DIRECT));
+    public void createLink(ConnectPoint one, ConnectPoint two) {
+        createLink(one, two, DIRECT, true);
+    }
+
+    /**
+     * Creates simulated link between two connection points.
+     *
+     * @param one             one connection point
+     * @param two             another connection point
+     * @param type            link type
+     * @param isBidirectional true if link is bidirectional
+     */
+    public void createLink(ConnectPoint one, ConnectPoint two, Link.Type type, boolean isBidirectional) {
+        linkProviderService.linkDetected(new DefaultLinkDescription(one, two, type));
+        if (isBidirectional) {
+            linkProviderService.linkDetected(new DefaultLinkDescription(two, one, type));
+        }
     }
 
     /**
@@ -228,7 +254,7 @@
      * @param deviceId   device identifier
      * @param portOffset port offset where to start attaching hosts
      */
-    protected void createHosts(DeviceId deviceId, int portOffset) {
+    public void createHosts(DeviceId deviceId, int portOffset) {
         String s = deviceId.toString();
         byte dByte = Byte.parseByte(s.substring(s.length() - 2), 16);
         // TODO: this limits the simulation to 256 devices & 256 hosts/device.
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullDevice.java b/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullDevice.java
new file mode 100644
index 0000000..86b12f0
--- /dev/null
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullDevice.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 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.provider.nil.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.provider.nil.CustomTopologySimulator;
+import org.onosproject.provider.nil.NullProviders;
+import org.onosproject.provider.nil.TopologySimulator;
+
+/**
+ * Adds a simulated device to the custom topology simulation.
+ */
+@Command(scope = "onos", name = "null-create-device",
+        description = "Adds a simulated device to the custom topology simulation")
+public class CreateNullDevice extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "type", description = "Device type, e.g. switch, roadm",
+            required = true, multiValued = false)
+    String type = null;
+
+    @Argument(index = 1, name = "name", description = "Device name",
+            required = true, multiValued = false)
+    String name = null;
+
+    @Argument(index = 2, name = "portCount", description = "Port count",
+            required = true, multiValued = false)
+    Integer portCount = null;
+
+    @Argument(index = 3, name = "latitude", description = "Geo latitude",
+            required = true, multiValued = false)
+    Double latitude = null;
+
+    @Argument(index = 4, name = "longitude", description = "Geo longitude",
+            required = true, multiValued = false)
+    Double longitude = null;
+
+    @Override
+    protected void execute() {
+        NullProviders service = get(NullProviders.class);
+        NetworkConfigService cfgService = get(NetworkConfigService.class);
+
+        TopologySimulator simulator = service.currentSimulator();
+        if (!(simulator instanceof CustomTopologySimulator)) {
+            error("Custom topology simulator is not active.");
+            return;
+        }
+
+        CustomTopologySimulator sim = (CustomTopologySimulator) simulator;
+        DeviceId deviceId = sim.nextDeviceId();
+        BasicDeviceConfig cfg = cfgService.addConfig(deviceId, BasicDeviceConfig.class);
+        cfg.name(name);
+        cfg.latitude(latitude);
+        cfg.longitude(longitude);
+        cfg.apply();
+
+        sim.createDevice(deviceId, name, Device.Type.valueOf(type.toUpperCase()), portCount);
+    }
+
+}
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullLink.java b/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullLink.java
new file mode 100644
index 0000000..575319d
--- /dev/null
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/cli/CreateNullLink.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 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.provider.nil.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.edge.EdgePortService;
+import org.onosproject.provider.nil.CustomTopologySimulator;
+import org.onosproject.provider.nil.NullProviders;
+import org.onosproject.provider.nil.TopologySimulator;
+
+import java.util.Iterator;
+
+/**
+ * Adds a simulated link to the custom topology simulation.
+ */
+@Command(scope = "onos", name = "null-create-link",
+        description = "Adds a simulated link to the custom topology simulation")
+public class CreateNullLink extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "type", description = "Link type, e.g. direct, indirect, optical",
+            required = true, multiValued = false)
+    String type = null;
+
+    @Argument(index = 1, name = "src", description = "Source device name",
+            required = true, multiValued = false)
+    String src = null;
+
+    @Argument(index = 2, name = "dst", description = "Destination device name",
+            required = true, multiValued = false)
+    String dst = null;
+
+    @Option(name = "-u", aliases = "--unidirectional", description = "Unidirectional link only",
+            required = false, multiValued = false)
+    private boolean unidirectional = false;
+
+    @Override
+    protected void execute() {
+        NullProviders service = get(NullProviders.class);
+
+        TopologySimulator simulator = service.currentSimulator();
+        if (!(simulator instanceof CustomTopologySimulator)) {
+            error("Custom topology simulator is not active.");
+            return;
+        }
+
+        CustomTopologySimulator sim = (CustomTopologySimulator) simulator;
+        ConnectPoint one = findAvailablePort(sim.deviceId(src));
+        ConnectPoint two = findAvailablePort(sim.deviceId(dst));
+        sim.createLink(one, two, Link.Type.valueOf(type.toUpperCase()), !unidirectional);
+    }
+
+    private ConnectPoint findAvailablePort(DeviceId deviceId) {
+        EdgePortService eps = get(EdgePortService.class);
+        Iterator<ConnectPoint> points = eps.getEdgePoints(deviceId).iterator();
+        return points.hasNext() ? points.next() : null;
+    }
+
+}
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullDeviceCommand.java b/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullDeviceCommand.java
index bf08232..ef1a70e 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullDeviceCommand.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullDeviceCommand.java
@@ -25,10 +25,10 @@
 import static org.onosproject.cli.UpDownCompleter.UP;
 
 /**
- * Servers or repairs a simulated link.
+ * Downs or repairs a simulated device.
  */
 @Command(scope = "onos", name = "null-device",
-        description = "Severs or repairs a simulated link")
+        description = "Downs or repairs a simulated device")
 public class NullDeviceCommand extends AbstractShellCommand {
 
     @Argument(index = 0, name = "id", description = "Device identifier",
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java b/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java
index b857dc8..8e8f198 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java
@@ -25,7 +25,7 @@
 import static org.onosproject.cli.UpDownCompleter.UP;
 
 /**
- * Servers or repairs a simulated link.
+ * Severs or repairs a simulated link.
  */
 @Command(scope = "onos", name = "null-link",
         description = "Severs or repairs a simulated link")
diff --git a/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 74bf0d2..0ac6e09 100644
--- a/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -41,6 +41,12 @@
                 <null/>
             </completers>
         </command>
+        <command>
+            <action class="org.onosproject.provider.nil.cli.CreateNullDevice"/>
+        </command>
+        <command>
+            <action class="org.onosproject.provider.nil.cli.CreateNullLink"/>
+        </command>
     </command-bundle>
 
     <bean id="startStopCompleter" class="org.onosproject.cli.StartStopCompleter"/>