// ================================================================================================= // 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.net.pool; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.twitter.common.base.Closure; import com.twitter.common.base.Closures; import com.twitter.common.base.Function; import com.twitter.common.collections.Pair; import com.twitter.common.thrift.testing.MockTSocket; import com.twitter.common.net.loadbalancing.LoadBalancerImpl; import com.twitter.common.net.loadbalancing.RandomStrategy; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; import com.twitter.common.thrift.TTransportConnection; import com.twitter.common.thrift.Util; import com.twitter.common.zookeeper.Group.JoinException; import com.twitter.common.zookeeper.ServerSet; import com.twitter.common.zookeeper.ServerSet.EndpointStatus; import com.twitter.common.zookeeper.ServerSetImpl; import com.twitter.common.zookeeper.testing.BaseZooKeeperTest; import com.twitter.thrift.ServiceInstance; import com.twitter.thrift.Status; import org.apache.thrift.transport.TTransport; import org.apache.zookeeper.ZooDefs; import org.easymock.EasyMock; import org.easymock.IMocksControl; import org.junit.Before; import org.junit.Test; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; import static org.easymock.EasyMock.createControl; import static org.easymock.EasyMock.expect; import static org.junit.Assert.*; /** * @author John Sirois */ public class DynamicPoolTest extends BaseZooKeeperTest { private IMocksControl control; private Function<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>> poolFactory; private DynamicPool connectionPool; private LinkedBlockingQueue<Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>>> poolRebuilds; private ServerSet serverSet; @Before public void mySetUp() throws Exception { control = createControl(); @SuppressWarnings("unchecked") Function<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>> poolFactory = control.createMock(Function.class); this.poolFactory = poolFactory; LoadBalancerImpl<InetSocketAddress> lb = LoadBalancerImpl.create(new RandomStrategy<InetSocketAddress>()); poolRebuilds = new LinkedBlockingQueue<Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>>>(); serverSet = new ServerSetImpl(createZkClient(), ZooDefs.Ids.OPEN_ACL_UNSAFE, "/test-service"); Closure<Collection<InetSocketAddress>> onBackendsChosen = Closures.noop(); Amount<Long, Time> restoreInterval = Amount.of(1L, Time.MINUTES); connectionPool = new DynamicPool<ServiceInstance, TTransport, InetSocketAddress>( serverSet, poolFactory, lb, onBackendsChosen, restoreInterval, Util.GET_ADDRESS, Util.IS_ALIVE) { @Override void poolRebuilt(Set<ObjectPool<Connection<TTransport, InetSocketAddress>>> deadPools, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>> livePools) { super.poolRebuilt(deadPools, livePools); poolRebuilds.offer(Pair.of(deadPools, livePools)); } }; } @Test public void testConstructionBlocksOnInitialPoolBuild() { assertNotNull(Iterables.getOnlyElement(poolRebuilds)); } @Test(expected = ResourceExhaustedException.class) public void testNoEndpointsAvailable() throws Exception { connectionPool.get(); } private EndpointStatus join(String host, Status status) throws JoinException, InterruptedException { return serverSet.join(InetSocketAddress.createUnresolved(host, 42), ImmutableMap.<String, InetSocketAddress>of(), status); } @Test @SuppressWarnings("unchecked") public void testPoolRebuilds() throws Exception { ConnectionFactory<Connection<TTransport, InetSocketAddress>> connectionFactory = control.createMock(ConnectionFactory.class); TTransport transport = new MockTSocket(); Connection<TTransport, InetSocketAddress> connection = new TTransportConnection(transport, InetSocketAddress.createUnresolved("jake", 1137)); expect(connectionFactory.create(EasyMock.isA(Amount.class))).andReturn(connection); ConnectionPool<Connection<TTransport, InetSocketAddress>> fooPool = new ConnectionPool<Connection<TTransport, InetSocketAddress>>(connectionFactory); expect(poolFactory.apply(InetSocketAddress.createUnresolved("foo", 42))).andReturn(fooPool); control.replay(); Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>> rebuild1 = poolRebuilds.take(); assertTrue("Should not have any dead pools on initial rebuild", rebuild1.getFirst().isEmpty()); assertNoLivePools(rebuild1); EndpointStatus fooStatus = join("foo", Status.ALIVE); Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>> rebuild2 = poolRebuilds.take(); assertTrue("The NULL pool should never be tracked as dead", rebuild2.getFirst().isEmpty()); assertEquals(transport, connectionPool.get().get()); fooStatus.update(Status.STOPPING); Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>> rebuild3 = poolRebuilds.take(); assertSame("Expected foo pool to be discarded", fooPool, Iterables.getOnlyElement(rebuild3.getFirst())); assertNoLivePools(rebuild1); control.verify(); } private void assertNoLivePools(Pair<Set<ObjectPool<Connection<TTransport, InetSocketAddress>>>, Map<InetSocketAddress, ObjectPool<Connection<TTransport, InetSocketAddress>>>> rebuild) throws TimeoutException { assertTrue("Expected no live pools to be set", rebuild.getSecond().isEmpty()); try { connectionPool.get(); fail("Expected server set to be exhausted with no endpoints"); } catch (ResourceExhaustedException e) { // expected } } }