package org.infinispan.partitionhandling; import static org.infinispan.test.TestingUtil.extractComponentRegistry; import static org.testng.Assert.assertEquals; import org.infinispan.Cache; import org.infinispan.distribution.MagicKey; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.PartitionStatusChanged; import org.infinispan.notifications.cachelistener.event.PartitionStatusChangedEvent; import org.infinispan.statetransfer.StateTransferManager; import org.infinispan.test.Exceptions; import org.infinispan.test.concurrent.StateSequencer; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; @Test(groups = "functional", testName = "partitionhandling.DelayedAvailabilityUpdateTest") public class DelayedAvailabilityUpdateTest extends BasePartitionHandlingTest { private static final Log log = LogFactory.getLog(DelayedAvailabilityUpdateTest.class); public void testDelayedAvailabilityUpdate0() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(0, 1), new PartitionDescriptor(2, 3)); } public void testDelayedAvailabilityUpdate1() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(0, 2), new PartitionDescriptor(1, 3)); } public void testDelayedAvailabilityUpdate2() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(0, 3), new PartitionDescriptor(1, 2)); } public void testDelayedAvailabilityUpdate3() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(1, 2), new PartitionDescriptor(0, 3)); } public void testDelayedAvailabilityUpdate4() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(1, 3), new PartitionDescriptor(0, 2)); } public void testDelayedAvailabilityUpdate5() throws Exception { testDelayedAvailabilityUpdate(new PartitionDescriptor(2, 3), new PartitionDescriptor(0, 1)); } private void testDelayedAvailabilityUpdate(PartitionDescriptor p0, PartitionDescriptor p1) throws Exception { Object k0Existing = new MagicKey("k0Existing", cache(p0.node(0)), cache(p0.node(1))); Object k1Existing = new MagicKey("k1Existing", cache(p0.node(1)), cache(p1.node(0))); Object k2Existing = new MagicKey("k2Existing", cache(p1.node(0)), cache(p1.node(1))); Object k3Existing = new MagicKey("k3Existing", cache(p1.node(1)), cache(p0.node(0))); Object k0Missing = new MagicKey("k0Missing", cache(p0.node(0)), cache(p0.node(1))); Object k1Missing = new MagicKey("k1Missing", cache(p0.node(1)), cache(p1.node(0))); Object k2Missing = new MagicKey("k2Missing", cache(p1.node(0)), cache(p1.node(1))); Object k3Missing = new MagicKey("k3Missing", cache(p1.node(1)), cache(p0.node(0))); Cache<Object, Object> cacheP0N0 = cache(p0.node(0)); cacheP0N0.put(k0Existing, "v0"); cacheP0N0.put(k1Existing, "v1"); cacheP0N0.put(k2Existing, "v2"); cacheP0N0.put(k3Existing, "v3"); StateSequencer ss = new StateSequencer(); ss.logicalThread("main", "main:block_availability_update_p0n0", "main:after_availability_update_p0n1", "main:check_availability", "main:resume_availability_update_p0n0"); log.debugf("Delaying the availability mode update on node %s", address(p0.node(0))); cache(p0.node(0)).addListener(new BlockAvailabilityChangeListener(true, ss, "main:block_availability_update_p0n0", "main:resume_availability_update_p0n0")); cache(p0.node(1)).addListener(new BlockAvailabilityChangeListener(false, ss, "main:after_availability_update_p0n1")); splitCluster(p0.getNodes(), p1.getNodes()); ss.enter("main:check_availability"); // Keys stay available in between the availability mode update and the topology update StateTransferManager stmP0N1 = extractComponentRegistry(cache(p0.node(1))).getStateTransferManager(); eventuallyEquals(2, () -> stmP0N1.getCacheTopology().getActualMembers().size()); assertEquals(AvailabilityMode.AVAILABLE, partitionHandlingManager(p0.node(0)).getAvailabilityMode()); // The availability didn't change on p0.node0, check that keys owned by p1 are not accessible partition(0).assertKeyAvailableForRead(k0Existing, "v0"); partition(0).assertKeysNotAvailableForRead(k1Existing, k2Existing); // k3 is an exception, since p0.node0 is a backup owner it returns the local value assertPartiallyAvailable(p0, k3Existing, "v3"); partition(0).assertKeyAvailableForRead(k0Missing, null); partition(0).assertKeysNotAvailableForRead(k1Missing, k2Missing); // k3 is an exception, since p0.node0 is a backup owner it returns the local value assertPartiallyAvailable(p0, k3Missing, null); // Allow the partition handling manager on p0.node0 to update the availability mode ss.exit("main:check_availability"); partition(0).assertDegradedMode(); partition(1).assertDegradedMode(); } private void assertPartiallyAvailable(PartitionDescriptor p0, Object k3Existing, Object value) { assertEquals(value, cache(p0.node(0)).get(k3Existing)); Exceptions.expectException(AvailabilityException.class, () -> cache(p0.node(1)).get(k3Existing)); } @Listener public static class BlockAvailabilityChangeListener { private boolean blockPre; private StateSequencer ss; private String[] states; BlockAvailabilityChangeListener(boolean blockPre, StateSequencer ss, String... states) { this.blockPre = blockPre; this.ss = ss; this.states = states; } @PartitionStatusChanged public void onPartitionStatusChange(PartitionStatusChangedEvent e) throws Exception { if (blockPre == e.isPre()) { for (String state : states) { ss.advance(state); } } } } }