Adding commands for managing layouts.

Adding support for wiping out regions and layouts.
Refactored UI layout manager to allow public knowledge of default layout id.
Enhanced test topo for layout hierarchy.

Change-Id: I254fd494175e753f542f12a664618c1a83f72127
diff --git a/cli/src/main/java/org/onosproject/cli/net/LayoutAddCommand.java b/cli/src/main/java/org/onosproject/cli/net/LayoutAddCommand.java
new file mode 100644
index 0000000..5d160bd
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/LayoutAddCommand.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.region.Region;
+import org.onosproject.net.region.RegionService;
+import org.onosproject.ui.UiTopoLayoutService;
+import org.onosproject.ui.model.topo.UiTopoLayout;
+import org.onosproject.ui.model.topo.UiTopoLayoutId;
+
+import static org.onosproject.net.region.RegionId.regionId;
+import static org.onosproject.ui.model.topo.UiTopoLayoutId.layoutId;
+
+/**
+ * Creates a new UI layout.
+ */
+@Command(scope = "onos", name = "layout-add",
+        description = "Creates a new UI layout")
+public class LayoutAddCommand extends AbstractShellCommand {
+
+    private static final String FMT = "id=%s, name=%s, type=%s";
+    private static final String FMT_MASTER = "  master=%s";
+
+    @Argument(index = 0, name = "id", description = "Layout ID",
+            required = true, multiValued = false)
+    String id = null;
+
+    @Argument(index = 1, name = "id", description = "Region ID (optional)",
+            required = false, multiValued = false)
+    String regionId = null;
+
+    @Argument(index = 2, name = "id", description = "Parent layout ID (optional)",
+            required = false, multiValued = false)
+    String parentId = null;
+
+    private RegionService regionService;
+
+    @Override
+    protected void execute() {
+        UiTopoLayoutService service = get(UiTopoLayoutService.class);
+        RegionService regionService = get(RegionService.class);
+
+        Region region = regionId == null ? null : regionService.getRegion(regionId(regionId));
+        UiTopoLayoutId pid = parentId == null ? UiTopoLayoutId.DEFAULT_ID : layoutId(parentId);
+
+        UiTopoLayout layout = new UiTopoLayout(layoutId(id), region, pid);
+        service.addLayout(layout);
+    }
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/LayoutListCommand.java b/cli/src/main/java/org/onosproject/cli/net/LayoutListCommand.java
new file mode 100644
index 0000000..014717d
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/LayoutListCommand.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.ui.UiTopoLayoutService;
+import org.onosproject.ui.model.topo.UiTopoLayout;
+import org.onosproject.ui.model.topo.UiTopoLayoutId;
+import org.onosproject.utils.Comparators;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * List layout details.
+ */
+@Command(scope = "onos", name = "layouts",
+        description = "List layout details")
+public class LayoutListCommand extends AbstractShellCommand {
+
+    private static final String FMT = "id=%s, region=%s, parent=%s";
+
+    @Argument(index = 0, name = "id", description = "Layout ID",
+            required = false, multiValued = false)
+    String id = null;
+
+    private UiTopoLayoutService layoutService;
+
+    @Override
+    protected void execute() {
+        layoutService = get(UiTopoLayoutService.class);
+        if (id == null) {
+            for (UiTopoLayout layout : getSortedLayouts(layoutService)) {
+                printLayout(layout);
+            }
+        } else {
+            UiTopoLayout layout = layoutService.getLayout(UiTopoLayoutId.layoutId(id));
+            if (layout == null) {
+                error("No such region %s", id);
+            } else {
+                printLayout(layout);
+            }
+        }
+    }
+
+    private List<UiTopoLayout> getSortedLayouts(UiTopoLayoutService service) {
+        List<UiTopoLayout> layouts = newArrayList(service.getLayouts());
+        Collections.sort(layouts, Comparators.LAYOUT_COMPARATOR);
+        return layouts;
+    }
+
+    private void printLayout(UiTopoLayout layout) {
+        print(FMT, layout.id(), layout.regionId(),
+              layout.parent() != null ? layout.parent().id() : "none");
+    }
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/WipeOutCommand.java b/cli/src/main/java/org/onosproject/cli/net/WipeOutCommand.java
index f64a304..ddd9970 100644
--- a/cli/src/main/java/org/onosproject/cli/net/WipeOutCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/WipeOutCommand.java
@@ -26,6 +26,8 @@
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.link.LinkAdminService;
+import org.onosproject.net.region.RegionAdminService;
+import org.onosproject.ui.UiTopoLayoutService;
 
 /**
  * Wipes-out the entire network information base, i.e. devices, links, hosts, intents.
@@ -51,6 +53,9 @@
         wipeOutHosts();
         wipeOutDevices();
         wipeOutLinks();
+
+        wipeOutLayouts();
+        wipeOutRegions();
     }
 
     private void wipeOutIntents() {
@@ -106,4 +111,16 @@
             }
         }
     }
+
+    private void wipeOutLayouts() {
+        print("Wiping UI layouts");
+        UiTopoLayoutService service = get(UiTopoLayoutService.class);
+        service.getLayouts().forEach(service::removeLayout);
+    }
+
+    private void wipeOutRegions() {
+        print("Wiping regions");
+        RegionAdminService service = get(RegionAdminService.class);
+        service.getRegions().forEach(r -> service.removeRegion(r.id()));
+    }
 }
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index c767289..5e3577e 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -631,6 +631,19 @@
             </completers>
         </command>
 
+        <!-- UI Layout commands -->
+        <command>
+            <action class="org.onosproject.cli.net.LayoutListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.cli.net.LayoutAddCommand"/>
+        </command>
+        <!--TODO: add this + completers
+        <command>
+            <action class="org.onosproject.cli.net.LayoutRemoveCommand"/>
+        </command>
+        -->
+
         <!--virtual network commands -->
         <command>
             <action class="org.onosproject.cli.net.vnet.TenantListCommand"/>
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayoutId.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayoutId.java
index 2fa8717..289cacd 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayoutId.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayoutId.java
@@ -23,6 +23,11 @@
  */
 public final class UiTopoLayoutId extends Identifier<String> {
 
+    /**
+     * Default topology layout identifier.
+     */
+    public static final UiTopoLayoutId DEFAULT_ID = UiTopoLayoutId.layoutId("_default_");
+
     // For serialization
     private UiTopoLayoutId() {
     }
diff --git a/core/common/src/main/java/org/onosproject/utils/Comparators.java b/core/common/src/main/java/org/onosproject/utils/Comparators.java
index c3cd07c..0f8d7d7 100644
--- a/core/common/src/main/java/org/onosproject/utils/Comparators.java
+++ b/core/common/src/main/java/org/onosproject/utils/Comparators.java
@@ -33,6 +33,7 @@
 import org.onosproject.net.region.Region;
 import org.onosproject.net.statistic.TypedFlowEntryWithLoad;
 import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.ui.model.topo.UiTopoLayout;
 
 import java.util.Comparator;
 
@@ -154,6 +155,12 @@
         }
     };
 
+    public static final Comparator<UiTopoLayout> LAYOUT_COMPARATOR = new Comparator<UiTopoLayout>() {
+        @Override
+        public int compare(UiTopoLayout l1, UiTopoLayout l2) {
+            return l1.id().toString().compareTo(l2.id().toString());
+        }
+    };
     public static final Comparator<TenantId> TENANT_ID_COMPARATOR = new Comparator<TenantId>() {
         @Override
         public int compare(TenantId tenant1, TenantId tenant2) {
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 10cd72f..67d9df1 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -195,6 +195,9 @@
 import org.onosproject.net.packet.DefaultPacketRequest;
 import org.onosproject.net.packet.PacketPriority;
 import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.region.DefaultRegion;
+import org.onosproject.net.region.Region;
+import org.onosproject.net.region.RegionId;
 import org.onosproject.net.resource.ContinuousResource;
 import org.onosproject.net.resource.ContinuousResourceId;
 import org.onosproject.net.resource.DiscreteResource;
@@ -504,6 +507,10 @@
             .register(new AnnotationsSerializer(), DefaultAnnotations.class)
             .register(new ExtensionInstructionSerializer(), Instructions.ExtensionInstructionWrapper.class)
             .register(new ExtensionCriterionSerializer(), ExtensionCriterion.class)
+            .register(Region.class)
+            .register(Region.Type.class)
+            .register(RegionId.class)
+            .register(DefaultRegion.class)
             .register(ExtensionSelectorType.class)
             .register(ExtensionTreatmentType.class)
             .register(TransactionId.class)
diff --git a/tools/test/topos/regions-topo-2 b/tools/test/topos/regions-topo-2
index bf380ef..5a18c91 100755
--- a/tools/test/topos/regions-topo-2
+++ b/tools/test/topos/regions-topo-2
@@ -1,4 +1,5 @@
 #!/bin/bash
+# Test topology
 
 host=${1:-127.0.0.1}
 
@@ -6,8 +7,6 @@
 onos ${host} wipe-out please
 onos ${host} null-simulation start custom
 
-sleep 1
-
 onos ${host} <<-EOF
 
 null-create-device switch s1 10 0 0
@@ -35,8 +34,6 @@
 
 EOF
 
-
-
 onos ${host} <<-EOF
 
 region-add r1 Region1 METRO ${host}
@@ -58,4 +55,10 @@
     of:0000000000000009
 
 regions
+
+layout-add l1 r1
+layout-add l2 r2
+layout-add l3 r3 l2
+
+layouts
 EOF
\ No newline at end of file
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
index 60797f4..a597f24 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
@@ -40,6 +40,7 @@
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.ui.model.topo.UiTopoLayoutId.DEFAULT_ID;
 
 /**
  * Manages the user interface topology layouts.
@@ -54,8 +55,6 @@
     private static final String ID_NULL = "Layout ID cannot be null";
     private static final String LAYOUT_NULL = "Layout cannot be null";
 
-    private static final UiTopoLayoutId DEFAULT_ID = UiTopoLayoutId.layoutId("_default_");
-
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;