// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ================================================================================================= package com.twitter.common.zookeeper; import com.google.common.testing.TearDown; import com.twitter.common.zookeeper.Group.GroupChangeListener; import com.twitter.common.zookeeper.Group.JoinException; import com.twitter.common.zookeeper.Group.Membership; import com.twitter.common.zookeeper.Partitioner.Partition; import com.twitter.common.zookeeper.testing.BaseZooKeeperTest; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import org.junit.Test; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author John Sirois */ public class PartitionerTest extends BaseZooKeeperTest { private static final List<ACL> ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE; private static final String PARTITION_NAMESPACE = "/twitter/puffin/hosebird"; @Test public void testHeterogeneousPartitionGroup() throws Exception { ZooKeeperClient zkClient = createZkClient(); ZooKeeperUtils.ensurePath(zkClient, ACL, PARTITION_NAMESPACE + "/not-a-partition-node"); Partitioner partitioner = new Partitioner(zkClient, ACL, PARTITION_NAMESPACE); join(partitioner); assertEquals("Expected Partitioner to be tolerant of foreign nodes", 1, partitioner.getGroupSize()); } private static class InstrumentedPartitioner extends Partitioner { private final AtomicInteger myViewOfGroupSize = new AtomicInteger(); public InstrumentedPartitioner(ZooKeeperClient zkClient) throws IOException { super(zkClient, ACL, PARTITION_NAMESPACE); } @Override GroupChangeListener createGroupChangeListener(Membership membership) { final GroupChangeListener listener = super.createGroupChangeListener(membership); return new GroupChangeListener() { @Override public void onGroupChange(Iterable<String> memberIds) { listener.onGroupChange(memberIds); synchronized (myViewOfGroupSize) { myViewOfGroupSize.set(getGroupSize()); myViewOfGroupSize.notify(); } } }; } public void observeGroupSize(int expectedSize) throws InterruptedException { while (expectedSize != myViewOfGroupSize.get()) { synchronized (myViewOfGroupSize) { myViewOfGroupSize.wait(); } } } } @Test public void testJoin() throws Exception { // Test that the 1st member of the partition group owns the whole space. InstrumentedPartitioner firstPartitioner = new InstrumentedPartitioner(createZkClient()); Partition firstPartition = join(firstPartitioner); assertTrue(firstPartition.isMember(0L)); assertTrue(firstPartition.isMember(1L)); assertTrue(firstPartition.isMember(2L)); // Test that when additional members join partitions are added and existing partitions shrink. InstrumentedPartitioner secondPartitioner = new InstrumentedPartitioner(createZkClient()); Partition secondPartition = join(secondPartitioner); firstPartitioner.observeGroupSize(2); assertTrue(firstPartition.isMember(0L)); assertFalse(secondPartition.isMember(0L)); assertFalse(firstPartition.isMember(1L)); assertTrue(secondPartition.isMember(1L)); assertTrue(firstPartition.isMember(2L)); assertFalse(secondPartition.isMember(2L)); InstrumentedPartitioner thirdPartitioner = new InstrumentedPartitioner(createZkClient()); Partition thirdPartition = join(thirdPartitioner); firstPartitioner.observeGroupSize(3); secondPartitioner.observeGroupSize(3); assertTrue(firstPartition.isMember(0L)); assertFalse(secondPartition.isMember(0L)); assertFalse(thirdPartition.isMember(0L)); assertFalse(firstPartition.isMember(1L)); assertTrue(secondPartition.isMember(1L)); assertFalse(thirdPartition.isMember(1L)); assertFalse(firstPartition.isMember(2L)); assertFalse(secondPartition.isMember(2L)); assertTrue(thirdPartition.isMember(2L)); assertTrue(firstPartition.isMember(3L)); assertFalse(secondPartition.isMember(3L)); assertFalse(thirdPartition.isMember(3L)); // Test that members leaving the partition group results in the partitions being merged. firstPartition.cancel(); secondPartitioner.observeGroupSize(2); thirdPartitioner.observeGroupSize(2); assertTrue(secondPartition.isMember(0L)); assertFalse(thirdPartition.isMember(0L)); assertFalse(secondPartition.isMember(1L)); assertTrue(thirdPartition.isMember(1L)); assertTrue(secondPartition.isMember(2L)); assertFalse(thirdPartition.isMember(2L)); thirdPartition.cancel(); secondPartitioner.observeGroupSize(1); assertTrue(secondPartition.isMember(0L)); assertTrue(secondPartition.isMember(1L)); assertTrue(secondPartition.isMember(2L)); } private Partition join(Partitioner partitioner) throws JoinException, InterruptedException { final Partition partition = partitioner.join(); addTearDown(new TearDown() { @Override public void tearDown() throws JoinException { partition.cancel(); } }); return partition; } }