[AETHER-76] Impelentation of a new Trellis Troubleshoot Tool (T3) for offline mode

- For the performance improvement, T3 offline mode uses snapshots of the network states
called Network Information Base (NIB) instead of runtime interactions with ONOS core
during troubleshooting a Trellis system.
- Partially tested with some mininet topos for trellis
(https://github.com/opennetworkinglab/routing/tree/master/trellis).
- Usage instruction docs (https://docs.trellisfabric.org/troubleshooting.html).

Change-Id: Ice608f77aa96bfbcadfff34991c4a1b6d93125b6
(cherry picked from commit eaa6329aba67c2577fdca7d3ddf230611e82f9f7)
diff --git a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
index 7ab5da0..2261378 100644
--- a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
+++ b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.mcast.cli;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.Completion;
 import org.apache.karaf.shell.api.action.Option;
@@ -29,7 +31,6 @@
 import java.util.Comparator;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 
@@ -41,7 +42,9 @@
 public class McastShowHostCommand extends AbstractShellCommand {
 
     // Format for group line
-    private static final String FORMAT_MAPPING = "origin=%s, group=%s, source IP=%s, sources=%s, sinks=%s";
+    private static final String FORMAT_MAPPING = "origin=%s, group=%s, source IP=%s, sources=%s, sinks=%s\n";
+    private StringBuilder routesBuilder = new StringBuilder();
+    private ArrayNode routesNode = mapper().createArrayNode();
 
     @Option(name = "-gAddr", aliases = "--groupAddress",
             description = "IP Address of the multicast group",
@@ -65,41 +68,43 @@
                     .findAny().orElse(null);
             // If it exists
             if (mcastRoute != null) {
-                printRoute(mcastService, mcastRoute);
+                prepareResult(mcastService, mcastRoute);
             }
-            return;
+        } else {
+            routes.stream()
+                    .filter(mcastRoute -> mcastRoute.group().isIp4())
+                    .sorted(Comparator.comparing(McastRoute::group))
+                    .forEach(route -> {
+                        prepareResult(mcastService, route);
+                    });
+            routes.stream()
+                    .filter(mcastRoute -> mcastRoute.group().isIp6())
+                    .sorted(Comparator.comparing(McastRoute::group))
+                    .forEach(route -> {
+                        prepareResult(mcastService, route);
+                    });
         }
-        // Filter ipv4
-        Set<McastRoute> ipv4Routes = routes.stream()
-                .filter(mcastRoute -> mcastRoute.group().isIp4())
-                .collect(Collectors.toSet());
-        // Print ipv4 first
-        ipv4Routes.stream()
-                .sorted(Comparator.comparing(McastRoute::group))
-                .forEach(route -> {
-                    printRoute(mcastService, route);
-                });
-        // Filter ipv6
-        Set<McastRoute> ipv6Routes = routes.stream()
-                .filter(mcastRoute -> mcastRoute.group().isIp6())
-                .collect(Collectors.toSet());
-        // Then print ipv6
-        ipv6Routes.stream()
-                .sorted(Comparator.comparing(McastRoute::group))
-                .forEach(route -> {
-                    printRoute(mcastService, route);
-                });
+        if (outputJson()) {
+            print("%s", routesNode);
+        } else {
+            print("%s", routesBuilder.toString());
+        }
     }
 
-    private void printRoute(MulticastRouteService mcastService, McastRoute route) {
-        Map<HostId, Set<ConnectPoint>> sinks = mcastService.routeData(route).sinks();
-        Map<HostId, Set<ConnectPoint>> sources = mcastService.routeData(route).sources();
-        String srcIp = "*";
-        if (route.source().isPresent()) {
-            srcIp = route.source().get().toString();
+    private void prepareResult(MulticastRouteService mcastService, McastRoute route) {
+        if (outputJson()) {
+            // McastHostRouteCodec is used to encode McastRoute
+            ObjectNode routeNode = jsonForEntity(route, McastRoute.class);
+            routesNode.add(routeNode);
+        } else {
+            Map<HostId, Set<ConnectPoint>> sinks = mcastService.routeData(route).sinks();
+            Map<HostId, Set<ConnectPoint>> sources = mcastService.routeData(route).sources();
+            String srcIp = "*";
+            if (route.source().isPresent()) {
+                srcIp = route.source().get().toString();
+            }
+            routesBuilder.append(String.format(FORMAT_MAPPING, route.type(), route.group(), srcIp, sources, sinks));
         }
-
-        print(FORMAT_MAPPING, route.type(), route.group(), srcIp, sources, sinks);
     }
 
 }