/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License 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 org.apache.aurora.common.zookeeper; import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import org.apache.aurora.common.base.Command; import org.apache.aurora.common.net.pool.DynamicHostSet; import org.apache.aurora.common.thrift.Endpoint; import org.apache.aurora.common.thrift.ServiceInstance; import org.apache.aurora.common.thrift.Status; import org.apache.aurora.common.zookeeper.Group.JoinException; import org.apache.aurora.common.zookeeper.testing.BaseZooKeeperClientTest; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import org.easymock.IMocksControl; import org.junit.Before; import org.junit.Test; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.createControl; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * * TODO(William Farner): Change this to remove thrift dependency. */ public class ServerSetImplTest extends BaseZooKeeperClientTest { private static final List<ACL> ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE; private static final String SERVICE = "/twitter/services/puffin_hosebird"; private LinkedBlockingQueue<ImmutableSet<ServiceInstance>> serverSetBuffer; private DynamicHostSet.HostChangeMonitor<ServiceInstance> serverSetMonitor; @Before public void mySetUp() throws IOException { serverSetBuffer = new LinkedBlockingQueue<>(); serverSetMonitor = serverSetBuffer::offer; } private ServerSetImpl createServerSet() throws IOException { return new ServerSetImpl(createZkClient(), ACL, SERVICE); } @Test public void testLifecycle() throws Exception { ServerSetImpl client = createServerSet(); client.watch(serverSetMonitor); assertChangeFiredEmpty(); ServerSetImpl server = createServerSet(); ServerSet.EndpointStatus status = server.join( InetSocketAddress.createUnresolved("foo", 1234), makePortMap("http-admin", 8080)); ServiceInstance serviceInstance = new ServiceInstance( new Endpoint("foo", 1234), ImmutableMap.of("http-admin", new Endpoint("foo", 8080)), Status.ALIVE); assertChangeFired(serviceInstance); status.leave(); assertChangeFiredEmpty(); assertTrue(serverSetBuffer.isEmpty()); } @Test public void testMembershipChanges() throws Exception { ServerSetImpl client = createServerSet(); client.watch(serverSetMonitor); assertChangeFiredEmpty(); ServerSetImpl server = createServerSet(); ServerSet.EndpointStatus foo = join(server, "foo"); assertChangeFired("foo"); expireSession(client.getZkClient()); ServerSet.EndpointStatus bar = join(server, "bar"); // We should've auto re-monitored membership, but not been notifed of "foo" since this was not a // change, just "foo", "bar" since this was an addition. assertChangeFired("foo", "bar"); foo.leave(); assertChangeFired("bar"); ServerSet.EndpointStatus baz = join(server, "baz"); assertChangeFired("bar", "baz"); baz.leave(); assertChangeFired("bar"); bar.leave(); assertChangeFiredEmpty(); assertTrue(serverSetBuffer.isEmpty()); } @Test public void testStopMonitoring() throws Exception { ServerSetImpl client = createServerSet(); Command stopMonitoring = client.watch(serverSetMonitor); assertChangeFiredEmpty(); ServerSetImpl server = createServerSet(); ServerSet.EndpointStatus foo = join(server, "foo"); assertChangeFired("foo"); ServerSet.EndpointStatus bar = join(server, "bar"); assertChangeFired("foo", "bar"); stopMonitoring.execute(); // No new updates should be received since monitoring has stopped. foo.leave(); assertTrue(serverSetBuffer.isEmpty()); // Expiration event. assertTrue(serverSetBuffer.isEmpty()); } @Test public void testOrdering() throws Exception { ServerSetImpl client = createServerSet(); client.watch(serverSetMonitor); assertChangeFiredEmpty(); Map<String, InetSocketAddress> server1Ports = makePortMap("http-admin1", 8080); Map<String, InetSocketAddress> server2Ports = makePortMap("http-admin2", 8081); Map<String, InetSocketAddress> server3Ports = makePortMap("http-admin3", 8082); ServerSetImpl server1 = createServerSet(); ServerSetImpl server2 = createServerSet(); ServerSetImpl server3 = createServerSet(); ServiceInstance instance1 = new ServiceInstance( new Endpoint("foo", 1000), ImmutableMap.of("http-admin1", new Endpoint("foo", 8080)), Status.ALIVE); ServiceInstance instance2 = new ServiceInstance( new Endpoint("foo", 1001), ImmutableMap.of("http-admin2", new Endpoint("foo", 8081)), Status.ALIVE); ServiceInstance instance3 = new ServiceInstance( new Endpoint("foo", 1002), ImmutableMap.of("http-admin3", new Endpoint("foo", 8082)), Status.ALIVE); server1.join(InetSocketAddress.createUnresolved("foo", 1000), server1Ports); assertEquals(ImmutableList.of(instance1), ImmutableList.copyOf(serverSetBuffer.take())); ServerSet.EndpointStatus status2 = server2.join( InetSocketAddress.createUnresolved("foo", 1001), server2Ports); assertEquals(ImmutableList.of(instance1, instance2), ImmutableList.copyOf(serverSetBuffer.take())); server3.join(InetSocketAddress.createUnresolved("foo", 1002), server3Ports); assertEquals(ImmutableList.of(instance1, instance2, instance3), ImmutableList.copyOf(serverSetBuffer.take())); status2.leave(); assertEquals(ImmutableList.of(instance1, instance3), ImmutableList.copyOf(serverSetBuffer.take())); } @Test public void testUnwatchOnException() throws Exception { IMocksControl control = createControl(); ZooKeeperClient zkClient = control.createMock(ZooKeeperClient.class); Watcher onExpirationWatcher = control.createMock(Watcher.class); expect(zkClient.registerExpirationHandler(anyObject(Command.class))) .andReturn(onExpirationWatcher); expect(zkClient.get()).andThrow(new InterruptedException()); // See interrupted() note below. expect(zkClient.unregister(onExpirationWatcher)).andReturn(true); control.replay(); Group group = new Group(zkClient, ZooDefs.Ids.OPEN_ACL_UNSAFE, "/blabla"); ServerSetImpl serverset = new ServerSetImpl(zkClient, group); try { serverset.watch(hostSet -> {}); fail("Expected MonitorException"); } catch (DynamicHostSet.MonitorException e) { // NB: The assert is not important to this test, but the call to `Thread.interrupted()` is. // That call both returns the current interrupted status as well as clearing it. The clearing // is crucial depending on the order tests are run in this class. If this test runs before // one of the tests above that uses a `ZooKeeperClient` for example, those tests will fail // executing `ZooKeeperClient.get` which internally blocks on s sync-point that takes part in // the interruption mechanism and so immediately throws `InterruptedException` based on the // un-cleared interrupted bit. assertTrue(Thread.interrupted()); } control.verify(); } private static Map<String, InetSocketAddress> makePortMap(String name, int port) { return ImmutableMap.of(name, InetSocketAddress.createUnresolved("foo", port)); } private ServerSet.EndpointStatus join(ServerSet serverSet, String host) throws JoinException, InterruptedException { return serverSet.join( InetSocketAddress.createUnresolved(host, 42), ImmutableMap.<String, InetSocketAddress>of()); } private void assertChangeFired(String... serviceHosts) throws InterruptedException { assertChangeFired(ImmutableSet.copyOf(Iterables.transform(ImmutableSet.copyOf(serviceHosts), serviceHost -> new ServiceInstance(new Endpoint(serviceHost, 42), ImmutableMap.<String, Endpoint>of(), Status.ALIVE)))); } protected void assertChangeFiredEmpty() throws InterruptedException { assertChangeFired(ImmutableSet.<ServiceInstance>of()); } protected void assertChangeFired(ServiceInstance... serviceInstances) throws InterruptedException { assertChangeFired(ImmutableSet.copyOf(serviceInstances)); } protected void assertChangeFired(ImmutableSet<ServiceInstance> serviceInstances) throws InterruptedException { assertEquals(serviceInstances, serverSetBuffer.take()); } }