[SDFAB-933] Integrate atomix-3.1.12 and expose demote API
Additionally, this patch adds unit tests for demote and
updates a bunch of testing tools
Change-Id: I6c4046730707fa3ac88bbd2bcaf2f60250baa899
diff --git a/core/api/src/main/java/org/onosproject/cluster/LeadershipAdminService.java b/core/api/src/main/java/org/onosproject/cluster/LeadershipAdminService.java
index fe9868a..bac18ad 100644
--- a/core/api/src/main/java/org/onosproject/cluster/LeadershipAdminService.java
+++ b/core/api/src/main/java/org/onosproject/cluster/LeadershipAdminService.java
@@ -54,4 +54,15 @@
* @return mapping from topic to leadership info.
*/
Map<String, Leadership> getLeaderBoard();
+
+ /**
+ * Attempts to demote a node to the bottom of the candidate list. It is not allowed
+ * to demote the current leader
+ *
+ * @param topic leadership topic
+ * @param nodeId identifier of node to be demoted
+ * @return {@code true} if nodeId is now the bottom candidate. This method returns {@code false}
+ * if {@code nodeId} is not one of the candidates for the topic or if it is the leader.
+ */
+ boolean demote(String topic, NodeId nodeId);
}
diff --git a/core/api/src/main/java/org/onosproject/cluster/LeadershipStore.java b/core/api/src/main/java/org/onosproject/cluster/LeadershipStore.java
index 0953e38..ebc6b4c 100644
--- a/core/api/src/main/java/org/onosproject/cluster/LeadershipStore.java
+++ b/core/api/src/main/java/org/onosproject/cluster/LeadershipStore.java
@@ -16,6 +16,7 @@
package org.onosproject.cluster;
import java.util.Map;
+
import org.onosproject.store.Store;
/**
@@ -79,4 +80,15 @@
* @return topic to leadership mapping
*/
Map<String, Leadership> getLeaderships();
+
+ /**
+ * Attempts to demote a node to the bottom of the candidate list. It is not allowed
+ * to demote the current leader
+ *
+ * @param topic leadership topic
+ * @param nodeId identifier of node to be demoted
+ * @return {@code true} if nodeId is now the bottom candidate. This method returns {@code false}
+ * if {@code nodeId} is not one of the candidates for the topic or if it is the leader.
+ */
+ boolean demote(String topic, NodeId nodeId);
}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java b/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java
index 8e0679b..9af5e2f 100644
--- a/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java
+++ b/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java
@@ -58,4 +58,13 @@
*/
void balanceRoles();
+ /**
+ * Attempts to demote a node to the bottom of the backup list. It is not allowed
+ * to demote the current master
+ *
+ * @param instance controller instance identifier
+ * @param deviceId device identifier
+ */
+ void demote(NodeId instance, DeviceId deviceId);
+
}
diff --git a/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java b/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java
index 7f43a15..6a9fbf8 100644
--- a/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java
+++ b/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java
@@ -129,4 +129,13 @@
* @param nodeId the controller instance identifier
*/
void relinquishAllRole(NodeId nodeId);
+
+ /**
+ * Attempts to demote a node to the bottom of the backup list. It is not allowed
+ * to demote the current master
+ *
+ * @param instance controller instance identifier
+ * @param deviceId device identifier
+ */
+ void demote(NodeId instance, DeviceId deviceId);
}
diff --git a/core/api/src/main/java/org/onosproject/store/primitives/DefaultLeaderElector.java b/core/api/src/main/java/org/onosproject/store/primitives/DefaultLeaderElector.java
index 6275a4f..be67325 100644
--- a/core/api/src/main/java/org/onosproject/store/primitives/DefaultLeaderElector.java
+++ b/core/api/src/main/java/org/onosproject/store/primitives/DefaultLeaderElector.java
@@ -91,6 +91,11 @@
}
@Override
+ public boolean demote(String topic, NodeId nodeId) {
+ return complete(asyncElector.demote(topic, nodeId));
+ }
+
+ @Override
public void addStatusChangeListener(Consumer<Status> listener) {
asyncElector.addStatusChangeListener(listener);
}
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncLeaderElector.java b/core/api/src/main/java/org/onosproject/store/service/AsyncLeaderElector.java
index b253e2a..7aee61f 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncLeaderElector.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncLeaderElector.java
@@ -144,4 +144,16 @@
default LeaderElector asLeaderElector() {
return asLeaderElector(DistributedPrimitive.DEFAULT_OPERATION_TIMEOUT_MILLIS);
}
+
+ /**
+ * Attempts to demote a node to the bottom of the candidate list. It is not allowed
+ * to demote the current leader
+ *
+ * @param topic leadership topic
+ * @param nodeId identifier of node to be demoted
+ * @return CompletableFuture that is completed with a boolean when the operation is done. Boolean is true if
+ * node is now the bottom candidate. This operation can fail (i.e. return false) if the node
+ * is not registered to run for election for the topic or it is leader
+ */
+ CompletableFuture<Boolean> demote(String topic, NodeId nodeId);
}
diff --git a/core/api/src/main/java/org/onosproject/store/service/LeaderElector.java b/core/api/src/main/java/org/onosproject/store/service/LeaderElector.java
index 9ac1a6e..e7604eb 100644
--- a/core/api/src/main/java/org/onosproject/store/service/LeaderElector.java
+++ b/core/api/src/main/java/org/onosproject/store/service/LeaderElector.java
@@ -102,4 +102,15 @@
* @param consumer listener to remove
*/
void removeChangeListener(Consumer<Change<Leadership>> consumer);
+
+ /**
+ * Attempts to demote a node to the bottom of the candidate list. It is not allowed
+ * to demote the current leader
+ *
+ * @param topic leadership topic
+ * @param nodeId identifier of node to be demoted
+ * @return {@code true} if nodeId is now the bottom candidate. This method returns {@code false}
+ * if {@code nodeId} is not one of the candidates for the topic or if it is the leader.
+ */
+ boolean demote(String topic, NodeId nodeId);
}
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
index 4536dda..0f00a5f 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
@@ -412,4 +412,22 @@
future.whenComplete((event, error) -> notifyDelegate(event));
});
}
+
+ @Override
+ public void demote(NodeId instance, DeviceId deviceId) {
+ if (instance == null) {
+ return;
+ }
+ NodeId master = masterMap.get(deviceId);
+ if (master.equals(instance)) {
+ return;
+ }
+ List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+ if (!stbys.contains(instance)) {
+ return;
+ }
+ stbys.remove(instance);
+ stbys.add(instance);
+ backups.put(deviceId, stbys);
+ }
}
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java
index 24c7a47..cee2431 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java
@@ -20,6 +20,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import com.google.common.collect.Lists;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -49,6 +50,8 @@
private static final NodeId N1 = new NodeId("local");
private static final NodeId N2 = new NodeId("other");
+ private static final NodeId N3 = new NodeId("other2");
+ private static final NodeId N4 = new NodeId("other3");
private SimpleMastershipStore sms;
@@ -170,6 +173,30 @@
assertEquals("wrong master", N2, event.roleInfo().master());
}
+ @Test
+ public void demote() {
+ put(DID1, N1, true, false);
+ put(DID1, N2, false, true);
+ put(DID1, N3, false, true);
+ List<NodeId> stdbys = Lists.newArrayList(N2, N3);
+ // N1 master, N2 and N3 backups
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+ assertEquals("wrong backups", stdbys, sms.backups.getOrDefault(DID1, new ArrayList<>()));
+ // No effect, it is the master
+ sms.demote(N1, DID1);
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+ assertEquals("wrong backups", stdbys, sms.backups.getOrDefault(DID1, new ArrayList<>()));
+ // No effect, it is not part of the mastership
+ sms.demote(N4, DID1);
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+ assertEquals("wrong backups", stdbys, sms.backups.getOrDefault(DID1, new ArrayList<>()));
+ // Demote N2
+ stdbys = Lists.newArrayList(N3, N2);
+ sms.demote(N2, DID1);
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+ assertEquals("wrong backups", stdbys, sms.backups.getOrDefault(DID1, new ArrayList<>()));
+ }
+
//helper to populate master/backup structures
private void put(DeviceId dev, NodeId node, boolean master, boolean backup) {
if (master) {
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/LeadershipManager.java b/core/net/src/main/java/org/onosproject/cluster/impl/LeadershipManager.java
index 5d02b5d..f582f30 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/LeadershipManager.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/LeadershipManager.java
@@ -103,6 +103,11 @@
}
@Override
+ public boolean demote(String topic, NodeId nodeId) {
+ return store.demote(topic, nodeId);
+ }
+
+ @Override
public boolean transferLeadership(String topic, NodeId to) {
return store.moveLeadership(topic, to);
}
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java b/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java
index 442aaef..d6a7eae 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java
@@ -305,6 +305,15 @@
Futures.getUnchecked(balanceRolesFuture);
}
+ @Override
+ public void demote(NodeId instance, DeviceId deviceId) {
+ checkNotNull(instance, NODE_ID_NULL);
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ checkPermission(CLUSTER_WRITE);
+
+ store.demote(instance, deviceId);
+ }
+
/**
* Balances the nodes specified in controllerDevices.
*
diff --git a/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java b/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java
index 50fc377..2666910 100644
--- a/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java
@@ -21,6 +21,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
@@ -33,6 +34,7 @@
import org.onosproject.cluster.DefaultControllerNode;
import org.onosproject.cluster.NodeId;
import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.mastership.MastershipInfo;
import org.onosproject.mastership.MastershipService;
import org.onosproject.mastership.MastershipStore;
import org.onosproject.mastership.MastershipTermService;
@@ -354,6 +356,32 @@
checkDeviceMasters(deviceIds, expectedMasters);
}
+ @Test
+ public void demote() {
+ mgr.setRole(NID1, DID1, MASTER);
+ mgr.setRole(NID2, DID1, STANDBY);
+ mgr.setRole(NID3, DID1, STANDBY);
+ List<NodeId> stdbys = Lists.newArrayList(NID2, NID3);
+ MastershipInfo mastershipInfo = mgr.getMastershipFor(DID1);
+ assertTrue(mastershipInfo.master().isPresent());
+ assertEquals("wrong role", NID1, mastershipInfo.master().get());
+ assertEquals("wrong backups", stdbys, mastershipInfo.backups());
+ // No effect, it is the master
+ mgr.demote(NID1, DID1);
+ assertEquals("wrong role", NID1, mastershipInfo.master().get());
+ assertEquals("wrong backups", stdbys, mastershipInfo.backups());
+ // No effect, it is not part of the mastership
+ mgr.demote(NID4, DID1);
+ assertEquals("wrong role", NID1, mastershipInfo.master().get());
+ assertEquals("wrong backups", stdbys, mastershipInfo.backups());
+ // Demote N2
+ mgr.demote(NID2, DID1);
+ stdbys = Lists.newArrayList(NID3, NID2);
+ mastershipInfo = mgr.getMastershipFor(DID1);
+ assertEquals("wrong role", NID1, mastershipInfo.master().get());
+ assertEquals("wrong backups", stdbys, mastershipInfo.backups());
+ }
+
private void checkDeviceMasters(Set<DeviceId> deviceIds, Set<NodeId> expectedMasters) {
checkDeviceMasters(deviceIds, expectedMasters, null);
}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedLeadershipStore.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedLeadershipStore.java
index 43959de..3e4b68f 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedLeadershipStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedLeadershipStore.java
@@ -263,6 +263,11 @@
e -> new Leadership(parseTopic(e.getKey()), e.getValue().leader(), e.getValue().candidates())));
}
+ @Override
+ public boolean demote(String topic, NodeId nodeId) {
+ return leaderElector.demote(getTopicFor(topic, nodeId), nodeId);
+ }
+
/**
* Returns a leader elector topic namespaced with the local node's version.
*
diff --git a/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java b/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java
index 3c27df2..1b54024 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java
@@ -300,6 +300,12 @@
// Noop. LeadershipService already takes care of detecting and purging stale locks.
}
+ @Override
+ public void demote(NodeId instance, DeviceId deviceId) {
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ leadershipAdminService.demote(leadershipTopic, instance);
+ }
+
private MastershipInfo buildMastershipFromLeadership(Leadership leadership) {
ImmutableMap.Builder<NodeId, MastershipRole> builder = ImmutableMap.builder();
if (leadership.leaderNodeId() != null) {
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixLeaderElector.java b/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixLeaderElector.java
index e92eafb..b296da0 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixLeaderElector.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixLeaderElector.java
@@ -105,6 +105,11 @@
return CompletableFuture.completedFuture(null);
}
+ @Override
+ public CompletableFuture<Boolean> demote(String topic, NodeId nodeId) {
+ return adaptFuture(atomixElector.demote(topic, nodeId));
+ }
+
private Leadership toLeadership(String topic, io.atomix.core.election.Leadership<NodeId> leadership) {
return leadership != null
? new Leadership(topic, toLeader(leadership.leader()), leadership.candidates())