package org.cloudname.service; import org.cloudname.backends.memory.MemoryBackend; import org.cloudname.core.BackendManager; import org.cloudname.core.CloudnameBackend; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * Test persistent services functions. */ public class CloudnameServicePermanentTest { private static final String SERVICE_COORDINATE = "myoldskoolserver.test.local"; private static final CloudnameBackend memoryBackend = BackendManager.getBackend("memory://"); private static final Endpoint DEFAULT_ENDPOINT = new Endpoint("serviceport", "localhost", 80); private final ServiceCoordinate serviceCoordinate = ServiceCoordinate.parse(SERVICE_COORDINATE); @BeforeClass public static void createServiceRegistration() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { assertThat( cloudnameService.createPermanentService( ServiceCoordinate.parse(SERVICE_COORDINATE), DEFAULT_ENDPOINT), is(true)); } } @Test public void testPersistentServiceChanges() throws InterruptedException { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { final CountDownLatch callCounter = new CountDownLatch(2); final int secondsToWait = 1; // ...a listener on the service will trigger when there's a change plus the initial // onCreate call. cloudnameService.addPermanentServiceListener(serviceCoordinate, new PermanentServiceListener() { private final AtomicInteger createCount = new AtomicInteger(0); private final AtomicInteger changeCount = new AtomicInteger(0); @Override public void onServiceCreated(Endpoint endpoint) { // Expect this to be called once and only once, even on updates assertThat(createCount.incrementAndGet(), is(1)); callCounter.countDown(); } @Override public void onServiceChanged(Endpoint endpoint) { // This will be called when the endpoint changes assertThat(changeCount.incrementAndGet(), is(1)); callCounter.countDown(); } @Override public void onServiceRemoved() { // This won't be called fail("Did not expect onServiceRemoved to be called"); } }); // Updating with invalid endpoint name fails assertThat(cloudnameService.updatePermanentService(serviceCoordinate, new Endpoint("wrongep", DEFAULT_ENDPOINT.getHost(), 81)), is(false)); // Using the right one, however, does work assertThat(cloudnameService.updatePermanentService(serviceCoordinate, new Endpoint( DEFAULT_ENDPOINT.getName(), DEFAULT_ENDPOINT.getHost(), 81)), is(true)); // Wait for notifications callCounter.await(secondsToWait, TimeUnit.SECONDS); } // At this point the service created above is closed; changes to the service won't // trigger errors in the listener declared. Just do one change to make sure. final CloudnameService cloudnameService = new CloudnameService(memoryBackend); assertThat(cloudnameService.updatePermanentService( ServiceCoordinate.parse(SERVICE_COORDINATE), DEFAULT_ENDPOINT), is(true)); } @Test public void testDuplicateRegistration() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { // Creating the same permanent service will fail assertThat("Can't create two identical permanent services", cloudnameService.createPermanentService(serviceCoordinate, DEFAULT_ENDPOINT), is(false)); } } @Test (expected = IllegalArgumentException.class) public void testNullCoordinateRegistration() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { cloudnameService.createPermanentService(null, DEFAULT_ENDPOINT); } } @Test (expected = IllegalArgumentException.class) public void testInvalidEndpoint() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { cloudnameService.createPermanentService(serviceCoordinate, null); } } @Test public void testListenerOnServiceThatDoesntExist() throws InterruptedException { final String anotherServiceCoordinate = "someother.service.coordinate"; // It should be possible to listen for a permanent service that doesn't exist yet. Once the // service is created it must trigger a callback to the clients listening. try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { final CountDownLatch createCalls = new CountDownLatch(1); final CountDownLatch removeCalls = new CountDownLatch(1); final CountDownLatch updateCalls = new CountDownLatch(1); cloudnameService.addPermanentServiceListener( ServiceCoordinate.parse(anotherServiceCoordinate), new PermanentServiceListener() { final AtomicInteger order = new AtomicInteger(0); @Override public void onServiceCreated(Endpoint endpoint) { createCalls.countDown(); assertThat(order.incrementAndGet(), is(1)); } @Override public void onServiceChanged(Endpoint endpoint) { updateCalls.countDown(); assertThat(order.incrementAndGet(), is(2)); } @Override public void onServiceRemoved() { removeCalls.countDown(); assertThat(order.incrementAndGet(), is(3)); } }); // Create the new service registration, change the endpoint, then remove it. The // count down latches should count down and the order should be create, change, remove final ServiceCoordinate another = ServiceCoordinate.parse(anotherServiceCoordinate); cloudnameService.createPermanentService(another, DEFAULT_ENDPOINT); cloudnameService.updatePermanentService(another, new Endpoint(DEFAULT_ENDPOINT.getName(), "otherhost", 4711)); cloudnameService.removePermanentService(another); final int secondsToWait = 1; assertTrue("Expected callback for create to trigger but it didn't", createCalls.await(secondsToWait, TimeUnit.SECONDS)); assertTrue("Expected callback for update to trigger but it didn't", updateCalls.await(secondsToWait, TimeUnit.SECONDS)); assertTrue("Expected callback for remove to trigger but it didn't", removeCalls.await(secondsToWait, TimeUnit.SECONDS)); } } @Test public void testLeaseUpdateOnLeaseThatDoesntExist() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { assertThat("Can't update a service that doesn't exist", cloudnameService.updatePermanentService( ServiceCoordinate.parse("foo.bar.baz"), DEFAULT_ENDPOINT), is(false)); } } @Test public void testRemoveServiceThatDoesntExist() { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { assertThat("Can't remove a service that doesn't exist", cloudnameService.removePermanentService(ServiceCoordinate.parse("foo.bar.baz")), is(false)); } } @AfterClass public static void removeServiceRegistration() throws InterruptedException { try (final CloudnameService cloudnameService = new CloudnameService(memoryBackend)) { final ServiceCoordinate serviceCoordinate = ServiceCoordinate.parse(SERVICE_COORDINATE); final CountDownLatch callCounter = new CountDownLatch(2); final int secondsToWait = 1; cloudnameService.addPermanentServiceListener(serviceCoordinate, new PermanentServiceListener() { private final AtomicInteger createCount = new AtomicInteger(0); private final AtomicInteger removeCount = new AtomicInteger(0); @Override public void onServiceCreated(final Endpoint endpoint) { // This will be called once and only once assertThat("Did not onServiceCreated to be called multiple times", createCount.incrementAndGet(), is(1)); callCounter.countDown(); } @Override public void onServiceChanged(final Endpoint endpoint) { fail("Did not expect any calls to onServiceChanged"); } @Override public void onServiceRemoved() { assertThat("Did not expect onServiceRemoved to be called multiple" + " times", removeCount.incrementAndGet(), is(1)); callCounter.countDown(); } }); // Remove the service created in the setup. assertThat(cloudnameService.removePermanentService(serviceCoordinate), is(true)); assertTrue("Did not receive the expected number of calls to listener. " + callCounter.getCount() + " calls remaining.", callCounter.await(secondsToWait, TimeUnit.SECONDS)); // Removing it twice will fail. assertThat(cloudnameService.removePermanentService(serviceCoordinate), is(false)); } } private final ServiceCoordinate coordinate = ServiceCoordinate.parse("service.tag.region"); @Test (expected = IllegalArgumentException.class) public void coordinateCanNotBeNullWhenUpdatingService() { new CloudnameService(memoryBackend).updatePermanentService(null, null); } @Test (expected = IllegalArgumentException.class) public void endpointCanNotBeNullWhenUpdatingService() { new CloudnameService(memoryBackend).updatePermanentService(coordinate, null); } @Test (expected = IllegalArgumentException.class) public void coordinateCanNotBeNullWhenRemovingService() { new CloudnameService(memoryBackend).removePermanentService(null); } @Test (expected = IllegalArgumentException.class) public void coordinateCanNotBeNullWhenAddingListener() { new CloudnameService(memoryBackend).addPermanentServiceListener(null, null); } @Test (expected = IllegalArgumentException.class) public void listenerCanNotBeNullWhenAddingListener() { new CloudnameService(memoryBackend).addPermanentServiceListener(coordinate, null); } }