MasteshipService, store, and CLI commands use RoleInfo

Change-Id: Ibc569498a67d33d088e5c9f89c6bb1f45eadc26e
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
new file mode 100644
index 0000000..bf7442e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
@@ -0,0 +1,67 @@
+package org.onlab.onos.cluster;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A container for detailed role information for a device,
+ * within the current cluster. Role attributes include current
+ * master and a preference-ordered list of backup nodes.
+ */
+public class RoleInfo {
+    private final NodeId master;
+    private final List<NodeId> backups;
+
+    public RoleInfo(NodeId master, List<NodeId> backups) {
+        this.master = master;
+        this.backups = new LinkedList<>();
+
+        this.backups.addAll(checkNotNull(backups));
+    }
+
+    public NodeId master() {
+        return master;
+    }
+
+    public List<NodeId> backups() {
+        return Collections.unmodifiableList(backups);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof RoleInfo)) {
+            return false;
+        }
+        RoleInfo that = (RoleInfo) other;
+        if (!Objects.equals(this.master, that.master)) {
+            return false;
+        }
+        if (!Objects.equals(this.backups, that.backups)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(master, backups.hashCode());
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("master: \n\t").append(master).append("\n");
+        builder.append("backups: \n");
+        for (NodeId n : backups) {
+            builder.append("\t").append(n).append("\n");
+        }
+        return builder.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 7b8531c..9f75fc4 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
@@ -56,7 +56,10 @@
     }
 
     /**
-     * Returns the NodeID of the node responsible for triggering the event.
+     * Returns the NodeID of the node associated with the event.
+     * For MASTER_CHANGED this is the newly elected master, and for
+     * BACKUPS_CHANGED, this is the node that was newly added, removed, or
+     * whose position was changed in the list.
      *
      * @return node ID as a subject
      */
diff --git a/core/api/src/main/java/org/onlab/onos/mastership/MastershipService.java b/core/api/src/main/java/org/onlab/onos/mastership/MastershipService.java
index 224bc05..30cb545 100644
--- a/core/api/src/main/java/org/onlab/onos/mastership/MastershipService.java
+++ b/core/api/src/main/java/org/onlab/onos/mastership/MastershipService.java
@@ -1,9 +1,9 @@
 package org.onlab.onos.mastership;
 
-import java.util.List;
 import java.util.Set;
 
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.MastershipRole;
 
@@ -57,7 +57,7 @@
      * @param deviceId the identifier of the device
      * @return a list of controller IDs
      */
-    List<NodeId> getNodesFor(DeviceId deviceId);
+    RoleInfo getNodesFor(DeviceId deviceId);
 
     /**
      * Returns the devices for which a controller is master.
diff --git a/core/api/src/main/java/org/onlab/onos/mastership/MastershipStore.java b/core/api/src/main/java/org/onlab/onos/mastership/MastershipStore.java
index 5e7b0e4..bd7a354 100644
--- a/core/api/src/main/java/org/onlab/onos/mastership/MastershipStore.java
+++ b/core/api/src/main/java/org/onlab/onos/mastership/MastershipStore.java
@@ -1,9 +1,9 @@
 package org.onlab.onos.mastership;
 
-import java.util.List;
 import java.util.Set;
 
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.MastershipRole;
 import org.onlab.onos.store.Store;
@@ -42,13 +42,12 @@
     NodeId getMaster(DeviceId deviceId);
 
     /**
-     * Returns the controllers connected to a device, in mastership-
-     * preference order.
+     * Returns the master and backup nodes for a device.
      *
      * @param deviceId the device identifier
-     * @return an ordered list of controller IDs
+     * @return a RoleInfo containing controller IDs
      */
-    List<NodeId> getNodes(DeviceId deviceId);
+    RoleInfo getNodes(DeviceId deviceId);
 
     /**
      * Returns the devices that a controller instance is master of.
diff --git a/core/api/src/test/java/org/onlab/onos/cluster/RoleInfoTest.java b/core/api/src/test/java/org/onlab/onos/cluster/RoleInfoTest.java
new file mode 100644
index 0000000..46ab02e
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/cluster/RoleInfoTest.java
@@ -0,0 +1,39 @@
+package org.onlab.onos.cluster;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Test to check behavioral correctness of the RoleInfo structure.
+ */
+public class RoleInfoTest {
+    private static final NodeId N1 = new NodeId("n1");
+    private static final NodeId N2 = new NodeId("n2");
+    private static final NodeId N3 = new NodeId("n3");
+    private static final NodeId N4 = new NodeId("n4");
+
+    private static final List<NodeId> BKUP1 = Lists.newArrayList(N2, N3);
+    private static final List<NodeId> BKUP2 = Lists.newArrayList(N3, N4);
+
+    private static final RoleInfo RI1 = new RoleInfo(N1, BKUP1);
+    private static final RoleInfo RI2 = new RoleInfo(N1, BKUP2);
+    private static final RoleInfo RI3 = new RoleInfo(N2, BKUP1);
+
+    @Test
+    public void basics() {
+        assertEquals("wrong master", new NodeId("n1"), RI1.master());
+        assertEquals("wrong Backups", RI1.backups(), Lists.newArrayList(N2, N3));
+
+        assertNotEquals("equals() broken", RI1, RI2);
+        assertNotEquals("equals() broken", RI1, RI3);
+
+        List<NodeId> bkup3 = Lists.newArrayList(N3, new NodeId("n4"));
+        assertEquals("equals() broken", new RoleInfo(N1, bkup3), RI2);
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/mastership/MastershipServiceAdapter.java b/core/api/src/test/java/org/onlab/onos/mastership/MastershipServiceAdapter.java
index af376e8..8169dfb 100644
--- a/core/api/src/test/java/org/onlab/onos/mastership/MastershipServiceAdapter.java
+++ b/core/api/src/test/java/org/onlab/onos/mastership/MastershipServiceAdapter.java
@@ -1,10 +1,10 @@
 package org.onlab.onos.mastership;
 
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.MastershipRole;
 
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -49,7 +49,7 @@
     }
 
     @Override
-    public List<NodeId> getNodesFor(DeviceId deviceId) {
+    public RoleInfo getNodesFor(DeviceId deviceId) {
         return null;
     }
 }