[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/WORKSPACE b/WORKSPACE
index 29dca06..e70e347 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -42,8 +42,8 @@
 
 # Use this to build against locally built Atomix
 #local_atomix(
-#    path = "/home/developer/atomix",
-#    version = "3.1.9-SNAPSHOT",
+#    path = "/home/sdn/atomix",
+#    version = "3.1.12-SNAPSHOT",
 #)
 
 # Use this to build against locally built YANG tools
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
index 57c0774..2989952 100644
--- a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
@@ -204,6 +204,11 @@
         Futures.getUnchecked(balanceRolesFuture);
     }
 
+    @Override
+    public void demote(NodeId instance, DeviceId deviceId) {
+        throw new UnsupportedOperationException("VN mastership does not support promote api.");
+    }
+
     public class InternalDelegate implements MastershipStoreDelegate {
         @Override
         public void notify(MastershipEvent event) {
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())
diff --git a/deps/deps.json b/deps/deps.json
index 042ca40..d235951 100644
--- a/deps/deps.json
+++ b/deps/deps.json
@@ -126,13 +126,13 @@
     "aopalliance-repackaged": "mvn:org.glassfish.hk2.external:aopalliance-repackaged:2.5.0-b42",
     "amqp-client": "mvn:com.rabbitmq:amqp-client:jar:3.6.1",
     "asm": "mvn:org.ow2.asm:asm:5.2",
-    "atomix": "mvn:io.atomix:atomix:3.1.9",
-    "atomix-cluster": "mvn:io.atomix:atomix-cluster:3.1.9",
-    "atomix-primary-backup": "mvn:io.atomix:atomix-primary-backup:3.1.9",
-    "atomix-primitive": "mvn:io.atomix:atomix-primitive:3.1.9",
-    "atomix-raft": "mvn:io.atomix:atomix-raft:3.1.9",
-    "atomix-storage": "mvn:io.atomix:atomix-storage:3.1.9",
-    "atomix-utils": "mvn:io.atomix:atomix-utils:3.1.9",
+    "atomix": "mvn:io.atomix:atomix:3.1.12",
+    "atomix-cluster": "mvn:io.atomix:atomix-cluster:3.1.12",
+    "atomix-primary-backup": "mvn:io.atomix:atomix-primary-backup:3.1.12",
+    "atomix-primitive": "mvn:io.atomix:atomix-primitive:3.1.12",
+    "atomix-raft": "mvn:io.atomix:atomix-raft:3.1.12",
+    "atomix-storage": "mvn:io.atomix:atomix-storage:3.1.12",
+    "atomix-utils": "mvn:io.atomix:atomix-utils:3.1.12",
     "classgraph": "mvn:io.github.classgraph:classgraph:4.2.3",
     "commons-codec": "mvn:commons-codec:commons-codec:1.10",
     "commons-cli": "mvn:commons-cli:commons-cli:1.3",
diff --git a/tools/build/bazel/generate_workspace.bzl b/tools/build/bazel/generate_workspace.bzl
index 5ebcd10..0944a76 100644
--- a/tools/build/bazel/generate_workspace.bzl
+++ b/tools/build/bazel/generate_workspace.bzl
@@ -1,4 +1,4 @@
-# ***** This file was auto-generated at Thu, 6 Jan 2022 08:36:09 GMT. Do not edit this file manually. *****
+# ***** This file was auto-generated at Tue, 8 Feb 2022 21:28:04 GMT. Do not edit this file manually. *****
 # ***** Use onos-lib-gen *****
 
 load("//tools/build/bazel:variables.bzl", "ONOS_GROUP_ID", "ONOS_VERSION")
@@ -145,45 +145,45 @@
     if "atomix" not in native.existing_rules():
         java_import_external(
             name = "atomix",
-            jar_sha256 = "e4700e1acf329747fc41f49279d9066c368877f1381f609fcb4c0ce63824e0c6",
+            jar_sha256 = "d5163b005977b5da5f27835af528fce4a71127a1c795f5350f05a5767324e019",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix/3.1.9/atomix-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix/3.1.12/atomix-3.1.12.jar"],        )
     if "atomix_cluster" not in native.existing_rules():
         java_import_external(
             name = "atomix_cluster",
-            jar_sha256 = "38c3ee00cdc48fa00012c543abbd7cf5975568233ce25dbbb7d4c422b7579b57",
+            jar_sha256 = "4e3cbd01f23414934ffe5ccfafa0b16d4009c3d497390fcf8711236f6055fda2",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-cluster/3.1.9/atomix-cluster-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-cluster/3.1.12/atomix-cluster-3.1.12.jar"],        )
     if "atomix_primary_backup" not in native.existing_rules():
         java_import_external(
             name = "atomix_primary_backup",
-            jar_sha256 = "f87527364ac10e006b714f55ce1a6d92a1f27fa27e8813de82454665876838d6",
+            jar_sha256 = "afa5191c3a1ab79e007c208c9ef44f4b7c6e0904a1d7f576200d3cacd90c3f4b",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-primary-backup/3.1.9/atomix-primary-backup-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-primary-backup/3.1.12/atomix-primary-backup-3.1.12.jar"],        )
     if "atomix_primitive" not in native.existing_rules():
         java_import_external(
             name = "atomix_primitive",
-            jar_sha256 = "12c7ff4e65ad683419906d86d320250ce4ea160fbb394b90a577116563cb81b4",
+            jar_sha256 = "a2a4002824db7b80308f8cedd83a5abe67ebdb9723b6c50fa99ab935c49c2641",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-primitive/3.1.9/atomix-primitive-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-primitive/3.1.12/atomix-primitive-3.1.12.jar"],        )
     if "atomix_raft" not in native.existing_rules():
         java_import_external(
             name = "atomix_raft",
-            jar_sha256 = "69a3188bc34d5f5629b6321be04c10ca3064faecbb6d139015c1fd5bcaac713f",
+            jar_sha256 = "9583123d7cfc60edb786964087b92f568aafdf00be1b52d4e4737a6cf9d4ca31",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-raft/3.1.9/atomix-raft-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-raft/3.1.12/atomix-raft-3.1.12.jar"],        )
     if "atomix_storage" not in native.existing_rules():
         java_import_external(
             name = "atomix_storage",
-            jar_sha256 = "a049ae8a671bcacea4cbc8db31e78aa8c4f0bca4b3871929e06644d70d33e857",
+            jar_sha256 = "d81895846ca70202f38a13c9627b8fc3674e363a3670f7f1221e5444188bbaf3",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-storage/3.1.9/atomix-storage-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-storage/3.1.12/atomix-storage-3.1.12.jar"],        )
     if "atomix_utils" not in native.existing_rules():
         java_import_external(
             name = "atomix_utils",
-            jar_sha256 = "dba1d7b0fa900f39cee3160045e3cd624ffcaf18f9756fdadadb1c876ef331e0",
+            jar_sha256 = "8d8b177488bf7665e008e892b33a5fbde92d6233dc83cf350d51f6bc3f5344d8",
             licenses = ["notice"],
-            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-utils/3.1.9/atomix-utils-3.1.9.jar"],        )
+            jar_urls = ["https://repo1.maven.org/maven2/io/atomix/atomix-utils/3.1.12/atomix-utils-3.1.12.jar"],        )
     if "classgraph" not in native.existing_rules():
         java_import_external(
             name = "classgraph",
@@ -1346,13 +1346,13 @@
 artifact_map["@aopalliance_repackaged//:aopalliance_repackaged"] = "mvn:org.glassfish.hk2.external:aopalliance-repackaged:jar:2.5.0-b42"
 artifact_map["@amqp_client//:amqp_client"] = "mvn:com.rabbitmq:amqp-client:jar:3.6.1"
 artifact_map["@asm//:asm"] = "mvn:org.ow2.asm:asm:jar:5.2"
-artifact_map["@atomix//:atomix"] = "mvn:io.atomix:atomix:jar:3.1.9"
-artifact_map["@atomix_cluster//:atomix_cluster"] = "mvn:io.atomix:atomix-cluster:jar:3.1.9"
-artifact_map["@atomix_primary_backup//:atomix_primary_backup"] = "mvn:io.atomix:atomix-primary-backup:jar:3.1.9"
-artifact_map["@atomix_primitive//:atomix_primitive"] = "mvn:io.atomix:atomix-primitive:jar:3.1.9"
-artifact_map["@atomix_raft//:atomix_raft"] = "mvn:io.atomix:atomix-raft:jar:3.1.9"
-artifact_map["@atomix_storage//:atomix_storage"] = "mvn:io.atomix:atomix-storage:jar:3.1.9"
-artifact_map["@atomix_utils//:atomix_utils"] = "mvn:io.atomix:atomix-utils:jar:3.1.9"
+artifact_map["@atomix//:atomix"] = "mvn:io.atomix:atomix:jar:3.1.12"
+artifact_map["@atomix_cluster//:atomix_cluster"] = "mvn:io.atomix:atomix-cluster:jar:3.1.12"
+artifact_map["@atomix_primary_backup//:atomix_primary_backup"] = "mvn:io.atomix:atomix-primary-backup:jar:3.1.12"
+artifact_map["@atomix_primitive//:atomix_primitive"] = "mvn:io.atomix:atomix-primitive:jar:3.1.12"
+artifact_map["@atomix_raft//:atomix_raft"] = "mvn:io.atomix:atomix-raft:jar:3.1.12"
+artifact_map["@atomix_storage//:atomix_storage"] = "mvn:io.atomix:atomix-storage:jar:3.1.12"
+artifact_map["@atomix_utils//:atomix_utils"] = "mvn:io.atomix:atomix-utils:jar:3.1.12"
 artifact_map["@classgraph//:classgraph"] = "mvn:io.github.classgraph:classgraph:jar:4.2.3"
 artifact_map["@commons_codec//:commons_codec"] = "mvn:commons-codec:commons-codec:jar:1.10"
 artifact_map["@commons_cli//:commons_cli"] = "mvn:commons-cli:commons-cli:jar:1.3"
diff --git a/tools/dev/docker/Dockerfile-sshd b/tools/dev/docker/Dockerfile-sshd
index 2229787..6f0c00c 100644
--- a/tools/dev/docker/Dockerfile-sshd
+++ b/tools/dev/docker/Dockerfile-sshd
@@ -2,9 +2,9 @@
 LABEL maintainer="Eric Tang <qcorba at gmail.com>"
 
 ARG ATOMIX_VERSION
-ENV ENV_ATOMIX_VERSION=${ATOMIX_VERSION:-3.1.9}
+ENV ENV_ATOMIX_VERSION=${ATOMIX_VERSION:-3.1.12}
 
-RUN apt-get update 
+RUN apt-get update
 RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \
     --no-install-recommends \
     openjdk-11-jre \
@@ -66,7 +66,7 @@
 
 # Install Atomix
 RUN set -eux; \
-#   curl -o /tmp/atomix.tar.gz -XGET https://oss.sonatype.org/content/repositories/releases/io/atomix/atomix-dist/3.1.9/atomix-dist-3.1.9.tar.gz; \
+#   curl -o /tmp/atomix.tar.gz -XGET https://oss.sonatype.org/content/repositories/releases/io/atomix/atomix-dist/3.1.12/atomix-dist-3.1.12.tar.gz; \
     curl -o /tmp/atomix.tar.gz https://repo1.maven.org/maven2/io/atomix/atomix-dist/$ENV_ATOMIX_VERSION/atomix-dist-$ENV_ATOMIX_VERSION.tar.gz; \
     mkdir /opt/atomix; \
     tar zxmf /tmp/atomix.tar.gz -C /opt/atomix; \
diff --git a/tools/test/bin/atomix-push-bits b/tools/test/bin/atomix-push-bits
index d82321c..5eb9191 100755
--- a/tools/test/bin/atomix-push-bits
+++ b/tools/test/bin/atomix-push-bits
@@ -26,7 +26,7 @@
 
 . $ONOS_ROOT/tools/build/envDefaults
 
-ATOMIX_VERSION=${ATOMIX_VERSION:-3.1.9}
+ATOMIX_VERSION=${ATOMIX_VERSION:-3.1.12}
 ATOMIX_MAVEN=~/.m2/repository/io/atomix/atomix-dist/$ATOMIX_VERSION/atomix-dist-$ATOMIX_VERSION.tar.gz
 ATOMIX_LOCAL=/tmp/atomix-$ATOMIX_VERSION.tar.gz
 ATOMIX_REMOTE=https://oss.sonatype.org/content/repositories/releases/io/atomix/atomix-dist/$ATOMIX_VERSION/atomix-dist-$ATOMIX_VERSION.tar.gz
diff --git a/tools/test/bin/atomix-service b/tools/test/bin/atomix-service
index 5910a8c..8eb6a5e 100755
--- a/tools/test/bin/atomix-service
+++ b/tools/test/bin/atomix-service
@@ -40,11 +40,9 @@
 fi
 
 # Define the java options for atomix
-# FIXME atomix-agent already provides a gc algorithm. Once we fix atomix-agent we can allow this
-# JAVA_OPTS="${JAVA_OPTS:--XX:+UseG1GC -XX:MaxGCPauseMillis=200}"
+JAVA_OPTS="${JAVA_OPTS:--XX:+UseG1GC -XX:MaxGCPauseMillis=200}"
 if [ ! -z "$ONOS_YOURKIT" ]; then
-    #JAVA_OPTS+=" -agentpath:$ATOMIX_INSTALL_DIR/libyjpagent.so=listen=all"
-    JAVA_OPTS="${JAVA_OPTS:--agentpath:$ATOMIX_INSTALL_DIR/libyjpagent.so=listen=all}"
+    JAVA_OPTS+=" -agentpath:$ATOMIX_INSTALL_DIR/libyjpagent.so=listen=all"
 fi
 
 case $2 in
diff --git a/tools/test/scenarios/odtn/createOdtnCell.sh b/tools/test/scenarios/odtn/createOdtnCell.sh
index 8e5f74d..fcb81ba 100755
--- a/tools/test/scenarios/odtn/createOdtnCell.sh
+++ b/tools/test/scenarios/odtn/createOdtnCell.sh
@@ -16,7 +16,7 @@
 echo "The public key in local host is: $SSH_KEY"
 
 # Create Atomix cluster using Atomix docker image
-ATOMIX_IMAGE=atomix/atomix:3.1.9
+ATOMIX_IMAGE=atomix/atomix:3.1.12
 for i in {1..3}; do
     echo "Setting up atomix-$i..."
     docker container run --detach --name atomix-$i --hostname atomix-$i \