Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
index 62b0b84..49a420f 100644
--- a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
+++ b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
@@ -1,5 +1,10 @@
 package org.onlab.onos.fwd;
 
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Dictionary;
+import java.util.Set;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -30,11 +35,6 @@
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
-import java.util.Dictionary;
-import java.util.Set;
-
-import static org.slf4j.LoggerFactory.getLogger;
-
 /**
  * Sample reactive forwarding application.
  */
@@ -206,7 +206,7 @@
         treat.setOutput(portNumber);
 
         FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
-                                         builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
+                                         builder.build(), treat.build(), PRIORITY, appId, TIMEOUT, false);
 
         flowRuleService.applyFlowRules(f);
     }
diff --git a/cli/src/main/java/org/onlab/onos/cli/RolesCommand.java b/cli/src/main/java/org/onlab/onos/cli/RolesCommand.java
index b81a0c2..55f59d5 100644
--- a/cli/src/main/java/org/onlab/onos/cli/RolesCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/RolesCommand.java
@@ -21,7 +21,7 @@
         description = "Lists mastership roles of nodes for each device.")
 public class RolesCommand extends AbstractShellCommand {
 
-    private static final String FMT_HDR = "%s: master=%s, standbys=%s";
+    private static final String FMT_HDR = "%s: master=%s, standbys=[ %s]";
 
     @Override
     protected void execute() {
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
index e200cd0..ab9fb83 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
@@ -1,10 +1,26 @@
 package org.onlab.onos.cli.net;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
 import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.NetworkResource;
+import org.onlab.onos.net.intent.ConnectivityIntent;
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.LinkCollectionIntent;
+import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.intent.SinglePointToMultiPointIntent;
+
+import java.util.List;
+import java.util.Set;
 
 /**
  * Lists the inventory of intents and their states.
@@ -16,11 +32,137 @@
     @Override
     protected void execute() {
         IntentService service = get(IntentService.class);
-        for (Intent intent : service.getIntents()) {
-            IntentState state = service.getIntentState(intent.id());
-            print("id=%s, state=%s, appId=%s, %s",
-                  intent.id(), state, intent.appId().name(), intent);
+        if (outputJson()) {
+            print("%s", json(service, service.getIntents()));
+        } else {
+            for (Intent intent : service.getIntents()) {
+                IntentState state = service.getIntentState(intent.id());
+                print("id=%s, state=%s, type=%s, appId=%s",
+                      intent.id(), state, intent.getClass().getSimpleName(),
+                      intent.appId().name());
+                printDetails(service, intent);
+            }
         }
     }
 
+    private void printDetails(IntentService service, Intent intent) {
+        if (intent.resources() != null && !intent.resources().isEmpty()) {
+            print("    resources=%s", intent.resources());
+        }
+        if (intent instanceof ConnectivityIntent) {
+            ConnectivityIntent ci = (ConnectivityIntent) intent;
+            if (!ci.selector().criteria().isEmpty()) {
+                print("    selector=%s", ci.selector().criteria());
+            }
+            if (!ci.treatment().instructions().isEmpty()) {
+                print("    treatment=%s", ci.treatment().instructions());
+            }
+        }
+
+        if (intent instanceof PointToPointIntent) {
+            PointToPointIntent pi = (PointToPointIntent) intent;
+            print("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoint());
+        } else if (intent instanceof MultiPointToSinglePointIntent) {
+            MultiPointToSinglePointIntent pi = (MultiPointToSinglePointIntent) intent;
+            print("    ingress=%s, egress=%s", pi.ingressPoints(), pi.egressPoint());
+        } else if (intent instanceof SinglePointToMultiPointIntent) {
+            SinglePointToMultiPointIntent pi = (SinglePointToMultiPointIntent) intent;
+            print("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoints());
+        } else if (intent instanceof PathIntent) {
+            PathIntent pi = (PathIntent) intent;
+            print("    path=%s, cost=%d", pi.path().links(), pi.path().cost());
+        } else if (intent instanceof LinkCollectionIntent) {
+            LinkCollectionIntent li = (LinkCollectionIntent) intent;
+            print("    links=%s", li.links());
+            print("    egress=%s", li.egressPoint());
+        }
+
+        List<Intent> installable = service.getInstallableIntents(intent.id());
+        if (installable != null && !installable.isEmpty()) {
+            print("    installable=%s", installable);
+        }
+    }
+
+    // Produces JSON array of the specified intents.
+    private JsonNode json(IntentService service, Iterable<Intent> intents) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        for (Intent intent : intents) {
+            result.add(json(service, mapper, intent));
+        }
+        return result;
+    }
+
+    private JsonNode json(IntentService service, ObjectMapper mapper, Intent intent) {
+        ObjectNode result = mapper.createObjectNode()
+                .put("id", intent.id().toString())
+                .put("type", intent.getClass().getSimpleName())
+                .put("appId", intent.appId().name());
+
+        IntentState state = service.getIntentState(intent.id());
+        if (state != null) {
+            result.put("state", state.toString());
+        }
+
+        if (intent.resources() != null && !intent.resources().isEmpty()) {
+            ArrayNode rnode = mapper.createArrayNode();
+            for (NetworkResource resource : intent.resources()) {
+                rnode.add(resource.toString());
+            }
+            result.set("resources", rnode);
+        }
+
+        if (intent instanceof ConnectivityIntent) {
+            ConnectivityIntent ci = (ConnectivityIntent) intent;
+            if (!ci.selector().criteria().isEmpty()) {
+                result.put("selector", ci.selector().criteria().toString());
+            }
+            if (!ci.treatment().instructions().isEmpty()) {
+                result.put("treatment", ci.treatment().instructions().toString());
+            }
+        }
+
+        if (intent instanceof PathIntent) {
+            PathIntent pi = (PathIntent) intent;
+            ArrayNode pnode = mapper.createArrayNode();
+            for (Link link : pi.path().links()) {
+                pnode.add(link.toString());
+            }
+            result.set("path", pnode);
+
+        } else if (intent instanceof PointToPointIntent) {
+            PointToPointIntent pi = (PointToPointIntent) intent;
+            result.set("ingress", LinksListCommand.json(mapper, pi.ingressPoint()));
+            result.set("egress", LinksListCommand.json(mapper, pi.egressPoint()));
+
+        } else if (intent instanceof MultiPointToSinglePointIntent) {
+            MultiPointToSinglePointIntent pi = (MultiPointToSinglePointIntent) intent;
+            result.set("ingress", json(mapper, pi.ingressPoints()));
+            result.set("egress", LinksListCommand.json(mapper, pi.egressPoint()));
+
+        } else if (intent instanceof SinglePointToMultiPointIntent) {
+            SinglePointToMultiPointIntent pi = (SinglePointToMultiPointIntent) intent;
+            result.set("ingress", LinksListCommand.json(mapper, pi.ingressPoint()));
+            result.set("egress", json(mapper, pi.egressPoints()));
+
+        } else if (intent instanceof LinkCollectionIntent) {
+            LinkCollectionIntent li = (LinkCollectionIntent) intent;
+            result.set("links", LinksListCommand.json(li.links()));
+        }
+
+        List<Intent> installable = service.getInstallableIntents(intent.id());
+        if (installable != null && !installable.isEmpty()) {
+            result.set("installable", json(service, installable));
+        }
+        return result;
+    }
+
+    private JsonNode json(ObjectMapper mapper, Set<ConnectPoint> connectPoints) {
+        ArrayNode result = mapper.createArrayNode();
+        for (ConnectPoint cp : connectPoints) {
+            result.add(LinksListCommand.json(mapper, cp));
+        }
+        return result;
+    }
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java b/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
index 45b96ab..767884d 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
@@ -3,10 +3,11 @@
 import java.util.List;
 import java.util.Objects;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 
 /**
- * A container for detailed role information for a device,
+ * An immutable container for role information for a device,
  * within the current cluster. Role attributes include current
  * master and a preference-ordered list of backup nodes.
  */
@@ -52,12 +53,9 @@
 
     @Override
     public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append("master:").append(master).append(",");
-        builder.append("backups:");
-        for (NodeId n : backups) {
-            builder.append(" ").append(n);
-        }
-        return builder.toString();
+        return MoreObjects.toStringHelper(this.getClass())
+            .add("master", master)
+            .add("backups", backups)
+            .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
index 9f75fc4..dcb2d95 100644
--- a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.mastership;
 
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.event.AbstractEvent;
 import org.onlab.onos.net.DeviceId;
 
@@ -9,9 +10,8 @@
  */
 public class MastershipEvent extends AbstractEvent<MastershipEvent.Type, DeviceId> {
 
-    //do we worry about explicitly setting slaves/equals? probably not,
-    //to keep it simple
-    NodeId node;
+    //Contains master and standby information.
+    RoleInfo roleInfo;
 
     /**
      * Type of mastership events.
@@ -29,16 +29,16 @@
     }
 
     /**
-     * Creates an event of a given type and for the specified device, master,
-     * and the current time.
+     * Creates an event of a given type and for the specified device,
+     * role information, and the current time.
      *
      * @param type   device event type
      * @param device event device subject
-     * @param node master ID subject
+     * @param info mastership role information subject
      */
-    public MastershipEvent(Type type, DeviceId device, NodeId node) {
+    public MastershipEvent(Type type, DeviceId device, RoleInfo info) {
         super(type, device);
-        this.node = node;
+        this.roleInfo = info;
     }
 
     /**
@@ -50,9 +50,9 @@
      * @param master master ID subject
      * @param time   occurrence time
      */
-    public MastershipEvent(Type type, DeviceId device, NodeId master, long time) {
+    public MastershipEvent(Type type, DeviceId device, RoleInfo info, long time) {
         super(type, device, time);
-        this.node = master;
+        this.roleInfo = info;
     }
 
     /**
@@ -63,7 +63,17 @@
      *
      * @return node ID as a subject
      */
+    //XXX to-be removed - or keep for convenience?
     public NodeId node() {
-        return node;
+        return roleInfo.master();
+    }
+
+    /**
+     * Returns the current role state for the subject.
+     *
+     * @return RoleInfo associated with Device ID subject
+     */
+    public RoleInfo roleInfo() {
+        return roleInfo;
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
index 905469f..cf448cb 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
@@ -27,7 +27,7 @@
             TrafficTreatment treatment, int priority, FlowEntryState state,
             long life, long packets, long bytes, long flowId,
             int timeout) {
-        super(deviceId, selector, treatment, priority, flowId, timeout);
+        super(deviceId, selector, treatment, priority, flowId, timeout, false);
         this.state = state;
         this.life = life;
         this.packets = packets;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
index 6ecbbbc..a6593a8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
@@ -24,16 +24,18 @@
     private final short appId;
 
     private final int timeout;
+    private final boolean permanent;
 
 
     public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
             TrafficTreatment treatment, int priority, long flowId,
-            int timeout) {
+            int timeout, boolean permanent) {
         this.deviceId = deviceId;
         this.priority = priority;
         this.selector = selector;
         this.treatment = treatment;
         this.timeout = timeout;
+        this.permanent = permanent;
         this.created = System.currentTimeMillis();
 
         this.appId = (short) (flowId >>> 48);
@@ -42,7 +44,7 @@
 
     public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
             TrafficTreatment treatement, int priority, ApplicationId appId,
-            int timeout) {
+            int timeout, boolean permanent) {
 
         if (priority < FlowRule.MIN_PRIORITY) {
             throw new IllegalArgumentException("Priority cannot be less than " + MIN_PRIORITY);
@@ -54,6 +56,7 @@
         this.treatment = treatement;
         this.appId = appId.id();
         this.timeout = timeout;
+        this.permanent = permanent;
         this.created = System.currentTimeMillis();
 
         this.id = FlowId.valueOf((((long) this.appId) << 48) | (this.hash() & 0x0000ffffffffL));
@@ -67,6 +70,7 @@
         this.appId = rule.appId();
         this.id = rule.id();
         this.timeout = rule.timeout();
+        this.permanent = rule.isPermanent();
         this.created = System.currentTimeMillis();
 
     }
@@ -157,4 +161,9 @@
         return timeout;
     }
 
+    @Override
+    public boolean isPermanent() {
+        return permanent;
+    }
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
index f51d75e..f175bd3 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
@@ -1,6 +1,10 @@
 package org.onlab.onos.net.flow;
 
-import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.criteria.Criteria;
 import org.onlab.onos.net.flow.criteria.Criterion;
@@ -8,10 +12,8 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Default traffic selector implementation.
@@ -52,6 +54,13 @@
         return false;
     }
 
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("criteria", criteria)
+                .toString();
+    }
+
     /**
      * Returns a new traffic selector builder.
      *
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
index d202217..d3fb7d9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
@@ -1,5 +1,9 @@
 package org.onlab.onos.net.flow;
 
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.instructions.Instruction;
 import org.onlab.onos.net.flow.instructions.Instructions;
@@ -7,12 +11,9 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
 /**
  * Default traffic treatment implementation.
  */
@@ -62,6 +63,13 @@
         return false;
     }
 
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("instructions", instructions)
+                .toString();
+    }
+
     /**
      * Builds a list of treatments following the following order.
      * Modifications -> Group -> Output (including drop)
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
index c63f247..3ac277d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
@@ -59,8 +59,16 @@
 
     /**
      * Returns the timeout for this flow requested by an application.
+     *
      * @return integer value of the timeout
      */
     int timeout();
 
+    /**
+     * Returns whether the flow is permanent i.e. does not time out.
+     *
+     * @return true if the flow is permanent, otherwise false
+     */
+    boolean isPermanent();
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
index c3aae54..700066d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
@@ -1,6 +1,8 @@
 package org.onlab.onos.net.intent;
 
 
+import java.util.List;
+
 /**
  * Service for application submitting or withdrawing their intents.
  */
@@ -68,6 +70,15 @@
     IntentState getIntentState(IntentId id);
 
     /**
+     * Returns the list of the installable events associated with the specified
+     * top-level intent.
+     *
+     * @param intentId top-level intent identifier
+     * @return compiled installable intents
+     */
+    List<Intent> getInstallableIntents(IntentId intentId);
+
+    /**
      * Adds the specified listener for intent events.
      *
      * @param listener listener to be added
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
index 4b3fd37..5020459 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -196,6 +196,11 @@
     }
 
     @Override
+    public List<Intent> getInstallableIntents(IntentId intentId) {
+        return installables.get(intentId);
+    }
+
+    @Override
     public void addListener(IntentListener listener) {
         listeners.add(listener);
     }
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index 525946e..67e0867 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -327,6 +327,10 @@
             if (storedRule == null) {
                 return false;
             }
+            if (storedRule.isPermanent()) {
+                return true;
+            }
+
             final long timeout = storedRule.timeout() * 1000;
             final long currentTime = System.currentTimeMillis();
             if (storedRule.packets() != swRule.packets()) {
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
index 03d5ce8..bfdc57e 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -153,6 +153,12 @@
     }
 
     @Override
+    public List<Intent> getInstallableIntents(IntentId intentId) {
+        checkNotNull(intentId, INTENT_ID_NULL);
+        return store.getInstallableIntents(intentId);
+    }
+
+    @Override
     public void addListener(IntentListener listener) {
         listenerRegistry.addListener(listener);
     }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
index 2deb837..b3eb0f5 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
@@ -117,7 +117,7 @@
         TrafficTreatment treatment = builder().setOutput(outPort).build();
 
         FlowRule rule = new DefaultFlowRule(deviceId,
-                selector, treatment, 123, appId, 600);
+                selector, treatment, 123, appId, 0, true);
 
         return new FlowRuleBatchEntry(operation, rule);
     }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
index 0baea5a..a7381b7 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -73,7 +73,7 @@
 
             FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
                     builder.build(), treatment,
-                    123, appId, 15);
+                    123, appId, 0, true);
             rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
             prev = link.dst();
         }
@@ -95,7 +95,7 @@
                     .setOutput(link.src().port()).build();
             FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
                     builder.build(), treatment,
-                    123, appId, 600);
+                    123, appId, 0, true);
             rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
             prev = link.dst();
         }
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index dff740a..ca7cc07 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -1,7 +1,11 @@
 package org.onlab.onos.net.flow.impl;
 
 import static java.util.Collections.EMPTY_LIST;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
@@ -115,7 +119,7 @@
     private FlowRule flowRule(int tsval, int trval) {
         TestSelector ts = new TestSelector(tsval);
         TestTreatment tr = new TestTreatment(trval);
-        return new DefaultFlowRule(DID, ts, tr, 10, appId, TIMEOUT);
+        return new DefaultFlowRule(DID, ts, tr, 10, appId, TIMEOUT, false);
     }
 
 
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java
index 105e37b..e2a3ddf 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java
@@ -5,6 +5,7 @@
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.LinkedList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -12,6 +13,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.DefaultEventSinkRegistry;
 import org.onlab.onos.event.Event;
@@ -87,7 +89,8 @@
         service.addListener(new MasterNodeCheck(latch, DID1, NID1));
 
         // fake MastershipEvent
-        eventDispatcher.post(new MastershipEvent(Type.MASTER_CHANGED, DID1, NID1));
+        eventDispatcher.post(new MastershipEvent(Type.MASTER_CHANGED, DID1,
+                new RoleInfo(NID1, new LinkedList<NodeId>())));
 
         assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index aaf056c..74ca8cd 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -136,13 +136,13 @@
                     rv.reassign(nodeId, STANDBY, NONE);
                     roleMap.put(deviceId, rv);
                     updateTerm(deviceId);
-                    return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
+                    return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                 case NONE:
                     rv.add(MASTER, nodeId);
                     rv.reassign(nodeId, STANDBY, NONE);
                     roleMap.put(deviceId, rv);
                     updateTerm(deviceId);
-                    return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
+                    return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                 default:
                     log.warn("unknown Mastership Role {}", role);
                     return null;
@@ -306,7 +306,7 @@
             roleMap.put(deviceId, rv);
             Integer term = terms.get(deviceId);
             terms.put(deviceId, ++term);
-            return new MastershipEvent(MASTER_CHANGED, deviceId, backup);
+            return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
         }
     }
 
@@ -373,7 +373,7 @@
                 return;
             }
             notifyDelegate(new MastershipEvent(
-                    MASTER_CHANGED, event.getKey(), event.getValue().get(MASTER)));
+                    MASTER_CHANGED, event.getKey(), event.getValue().roleInfo()));
         }
 
         @Override
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
index 1ccee6b..c156143 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
@@ -10,6 +10,9 @@
 import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.net.MastershipRole;
 
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+
 /**
  * A structure that holds node mastership roles associated with a
  * {@link DeviceId}. This structure needs to be locked through IMap.
@@ -109,14 +112,10 @@
 
     @Override
     public String toString() {
-        final StringBuilder builder = new StringBuilder();
+        ToStringHelper helper = MoreObjects.toStringHelper(this.getClass());
         for (Map.Entry<MastershipRole, List<NodeId>> el : value.entrySet()) {
-            builder.append(el.getKey().toString()).append(": [");
-            for (NodeId n : el.getValue()) {
-                builder.append(n);
-            }
-            builder.append("]\n");
+            helper.add(el.getKey().toString(), el.getValue());
         }
-        return builder.toString();
+        return helper.toString();
     }
 }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
index fe34959..709c95a 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
@@ -29,6 +29,8 @@
 import org.onlab.packet.IpPrefix;
 import org.slf4j.Logger;
 
+import com.google.common.collect.Lists;
+
 import static org.onlab.onos.mastership.MastershipEvent.Type.*;
 
 /**
@@ -89,7 +91,8 @@
             }
         }
 
-        return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
+        return new MastershipEvent(MASTER_CHANGED, deviceId,
+                new RoleInfo(nodeId, Lists.newLinkedList(backups)));
     }
 
     @Override
@@ -196,7 +199,8 @@
                     } else {
                         masterMap.put(deviceId, backup);
                         termMap.get(deviceId).incrementAndGet();
-                        return new MastershipEvent(MASTER_CHANGED, deviceId, backup);
+                        return new MastershipEvent(MASTER_CHANGED, deviceId,
+                                new RoleInfo(backup, Lists.newLinkedList(backups)));
                     }
                 case STANDBY:
                 case NONE:
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index 9924a71..cfc3134 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -78,7 +78,7 @@
         if (addedRule) {
             FlowRule rule = new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
                     buildSelector(), buildTreatment(), stat.getPriority(),
-                    stat.getCookie().getValue(), stat.getIdleTimeout());
+                    stat.getCookie().getValue(), stat.getIdleTimeout(), false);
             return new DefaultFlowEntry(rule, FlowEntryState.ADDED,
                     stat.getDurationSec(), stat.getPacketCount().getValue(),
                     stat.getByteCount().getValue());
@@ -86,7 +86,7 @@
         } else {
             FlowRule rule = new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
                     buildSelector(), null, removed.getPriority(),
-                   removed.getCookie().getValue(), removed.getIdleTimeout());
+                   removed.getCookie().getValue(), removed.getIdleTimeout(), false);
             return new DefaultFlowEntry(rule, FlowEntryState.REMOVED, removed.getDurationSec(),
                     removed.getPacketCount().getValue(), removed.getByteCount().getValue());
         }
diff --git a/tools/test/bin/onos-batch b/tools/test/bin/onos-batch
new file mode 100755
index 0000000..67864a2
--- /dev/null
+++ b/tools/test/bin/onos-batch
@@ -0,0 +1,17 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Executes selected set of ONOS commands using the batch mode.
+# -----------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+node=${1:-$OCI}
+
+commands="${2:-summary,intents,flows,hosts}"
+
+aux=/tmp/onos-batch.$$
+trap "rm -f $aux" EXIT
+
+echo "$commands" | tr ',' '\n' > $aux
+onos $node -b <$aux