Event history service and CLI

- Tool created while debugging ONOS-3509

  Usage Example: (See recent Mastership and Device events)
   onos> events -m -d

Change-Id: I87aceaf8fe61732a61c2d1e39399d0f10a729b54
diff --git a/apps/events/src/main/java/org/onosproject/events/EventsCommand.java b/apps/events/src/main/java/org/onosproject/events/EventsCommand.java
new file mode 100644
index 0000000..c6f15de
--- /dev/null
+++ b/apps/events/src/main/java/org/onosproject/events/EventsCommand.java
@@ -0,0 +1,250 @@
+/*
+ * 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.events;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.joda.time.LocalDateTime;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.event.Event;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyEvent;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Command to print history of instance local ONOS Events.
+ */
+@Command(scope = "onos", name = "events",
+         description = "Command to print history of instance local ONOS Events")
+public class EventsCommand
+    extends AbstractShellCommand {
+
+    @Option(name = "--all", aliases = "-a",
+            description = "Include all Events (default behavior)",
+            required = false)
+    private boolean all = false;
+
+    @Option(name = "--mastership", aliases = "-m",
+            description = "Include MastershipEvent",
+            required = false)
+    private boolean mastership = false;
+
+    @Option(name = "--device", aliases = "-d",
+            description = "Include DeviceEvent",
+            required = false)
+    private boolean device = false;
+
+    @Option(name = "--link", aliases = "-l",
+            description = "Include LinkEvent",
+            required = false)
+    private boolean link = false;
+
+    @Option(name = "--topology", aliases = "-t",
+            description = "Include TopologyEvent",
+            required = false)
+    private boolean topology = false;
+
+    @Option(name = "--host", aliases = "-t",
+            description = "Include HostEvent",
+            required = false)
+    private boolean host = false;
+
+    @Option(name = "--cluster", aliases = "-c",
+            description = "Include ClusterEvent",
+            required = false)
+    private boolean cluster = false;
+
+    @Option(name = "--max-events", aliases = "-n",
+            description = "Maximum number of events to print",
+            required = false,
+            valueToShowInHelp = "-1 [no limit]")
+    private long maxSize = -1;
+
+    @Override
+    protected void execute() {
+        EventHistoryService eventHistoryService = get(EventHistoryService.class);
+
+        Stream<Event<?, ?>> events = eventHistoryService.history().stream();
+
+        boolean dumpAll = all || !(mastership || device || link || topology || host);
+
+        if (!dumpAll) {
+            Predicate<Event<?, ?>> filter = (defaultIs) -> false;
+
+            if (mastership) {
+                filter = filter.or(evt -> evt instanceof MastershipEvent);
+            }
+            if (device) {
+                filter = filter.or(evt -> evt instanceof DeviceEvent);
+            }
+            if (link) {
+                filter = filter.or(evt -> evt instanceof LinkEvent);
+            }
+            if (topology) {
+                filter = filter.or(evt -> evt instanceof TopologyEvent);
+            }
+            if (host) {
+                filter = filter.or(evt -> evt instanceof HostEvent);
+            }
+            if (cluster) {
+                filter = filter.or(evt -> evt instanceof ClusterEvent);
+            }
+
+            events = events.filter(filter);
+        }
+
+        if (maxSize > 0) {
+            events = events.limit(maxSize);
+        }
+
+        if (outputJson()) {
+            ArrayNode jsonEvents = events.map(this::json).collect(toArrayNode());
+            printJson(jsonEvents);
+        } else {
+            events.forEach(this::printEvent);
+        }
+
+    }
+
+    private Collector<JsonNode, ArrayNode, ArrayNode> toArrayNode() {
+        return Collector.of(() -> mapper().createArrayNode(),
+                            ArrayNode::add,
+                            ArrayNode::addAll);
+    }
+
+    private ObjectNode json(Event<?, ?> event) {
+        ObjectNode result = mapper().createObjectNode();
+
+        result.put("time", event.time())
+              .put("type", event.type().toString())
+              .put("event", event.toString());
+
+        return result;
+    }
+
+    /**
+     * Print JsonNode using default pretty printer.
+     *
+     * @param json JSON node to print
+     */
+    private void printJson(JsonNode json) {
+        try {
+            print("%s", mapper().writerWithDefaultPrettyPrinter().writeValueAsString(json));
+        } catch (JsonProcessingException e) {
+            StringWriter sw = new StringWriter();
+            e.printStackTrace(new PrintWriter(sw));
+            print("[ERROR] %s\n%s", e.getMessage(), sw.toString());
+        }
+    }
+
+    private void printEvent(Event<?, ?> event) {
+        if (event instanceof DeviceEvent) {
+            DeviceEvent deviceEvent = (DeviceEvent) event;
+            if (event.type().toString().startsWith("PORT")) {
+                // Port event
+                print("%s %s\t%s/%s [%s]",
+                      new LocalDateTime(event.time()),
+                      event.type(),
+                      deviceEvent.subject().id(), deviceEvent.port().number(),
+                      deviceEvent.port()
+                  );
+            } else {
+                // Device event
+                print("%s %s\t%s [%s]",
+                      new LocalDateTime(event.time()),
+                      event.type(),
+                      deviceEvent.subject().id(),
+                      deviceEvent.subject()
+                  );
+            }
+
+        } else if (event instanceof MastershipEvent) {
+            print("%s %s\t%s [%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  event.subject(),
+                  ((MastershipEvent) event).roleInfo());
+
+        } else if (event instanceof LinkEvent) {
+            LinkEvent linkEvent = (LinkEvent) event;
+            Link link = linkEvent.subject();
+            print("%s %s\t%s/%s-%s/%s [%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  link.src().deviceId(), link.src().port(), link.dst().deviceId(), link.dst().port(),
+                  link);
+
+        } else if (event instanceof HostEvent) {
+            HostEvent hostEvent = (HostEvent) event;
+            print("%s %s\t%s [%s->%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  hostEvent.subject().id(),
+                  hostEvent.prevSubject(), hostEvent.subject());
+
+        } else if (event instanceof TopologyEvent) {
+            TopologyEvent topoEvent = (TopologyEvent) event;
+            List<Event> reasons = MoreObjects.firstNonNull(topoEvent.reasons(), ImmutableList.of());
+            Topology topo = topoEvent.subject();
+            String summary = String.format("(d=%d,l=%d,c=%d)",
+                                           topo.deviceCount(),
+                                           topo.linkCount(),
+                                           topo.clusterCount());
+            print("%s %s%s [%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  summary,
+                  reasons.stream().map(e -> e.type()).collect(toList()));
+
+        } else if (event instanceof ClusterEvent) {
+            print("%s %s\t%s [%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  ((ClusterEvent) event).subject().id(),
+                  event.subject());
+
+        } else {
+            // Unknown Event?
+            print("%s %s\t%s [%s]",
+                  new LocalDateTime(event.time()),
+                  event.type(),
+                  event.subject(),
+                  event);
+        }
+    }
+
+}