package netflix.ocelli; import java.util.Comparator; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import netflix.ocelli.InstanceQuarantiner.IncarnationFactory; import netflix.ocelli.functions.Delays; import netflix.ocelli.loadbalancer.ChoiceOfTwoLoadBalancer; import netflix.ocelli.loadbalancer.RoundRobinLoadBalancer; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.functions.Func1; import rx.schedulers.TestScheduler; public class InstanceQuarantinerTest { public static interface EventListener { public void onBegin(); public void onSuccess(); public void onFailed(); } final static TestScheduler scheduler = new TestScheduler(); public static class Client implements EventListener { public static Func1<Instance<Integer>, Instance<Client>> connector() { return new Func1<Instance<Integer>, Instance<Client>>() { @Override public Instance<Client> call(Instance<Integer> t1) { return Instance.create(new Client(t1.getValue(), t1.getLifecycle()), t1.getLifecycle()); } }; } public static IncarnationFactory<Client> incarnationFactory() { return new IncarnationFactory<Client>() { @Override public Client create( Client value, InstanceEventListener listener, Observable<Void> lifecycle) { return new Client(value, listener, lifecycle); } }; } private Integer address; private final Observable<Void> lifecycle; private final AtomicInteger counter; private AtomicInteger score = new AtomicInteger(); private final InstanceEventListener listener; private final int id; public Client(Integer address, Observable<Void> lifecycle) { this.address = address; this.counter = new AtomicInteger(); this.lifecycle = lifecycle; this.listener = null; id = 0; } Client(Client client, InstanceEventListener listener, Observable<Void> lifecycle) { this.address = client.address; this.counter = client.counter; this.lifecycle = lifecycle; this.listener = listener; id = this.counter.incrementAndGet(); } @Override public void onBegin() { } @Override public void onSuccess() { listener.onEvent(InstanceEvent.EXECUTION_SUCCESS, 0, TimeUnit.SECONDS, null, null); } @Override public void onFailed() { listener.onEvent(InstanceEvent.EXECUTION_FAILED, 0, TimeUnit.SECONDS, new Exception("Failed"), null); } public Integer getValue() { return address; } public int getId() { return id; } public String toString() { return address.toString() + "[" + id + "]"; } public static Comparator<Client> compareByMetric() { return new Comparator<Client>() { @Override public int compare(Client o1, Client o2) { return o1.score.get() - o2.score.get(); } }; } public Observable<Void> getLifecycle() { return lifecycle; } } @Test public void basicTest() { final InstanceManager<Integer> instances = InstanceManager.create(); final LoadBalancer<Client> lb = LoadBalancer .fromSource(instances.map(Client.connector())) .withQuarantiner(Client.incarnationFactory(), Delays.fixed(1, TimeUnit.SECONDS), scheduler) .build(RoundRobinLoadBalancer.<Client>create()); instances.add(1); // Load balancer now has one instance Client c = lb.next(); Assert.assertNotNull("Load balancer should have an active intance", c); Assert.assertEquals(1, c.getId()); // Force the instance to fail c.onFailed(); // Load balancer is now empty try { c = lb.next(); Assert.fail("Load balancer should be empty"); } catch (NoSuchElementException e) { } // Advance past quarantine time scheduler.advanceTimeBy(2, TimeUnit.SECONDS); c = lb.next(); Assert.assertNotNull("Load balancer should have an active intance", c); Assert.assertEquals(2, c.getId()); // Force the instance to fail c.onFailed(); // Load balancer is now empty try { c = lb.next(); Assert.fail("Load balancer should be empty"); } catch (NoSuchElementException e) { } // Advance past quarantine time scheduler.advanceTimeBy(2, TimeUnit.SECONDS); c = lb.next(); Assert.assertNotNull("Load balancer should have an active intance", c); Assert.assertEquals(3, c.counter.get()); // Remove the instance entirely instances.remove(1); try { c = lb.next(); Assert.fail(); } catch (NoSuchElementException e) { } System.out.println(c); } @Test @Ignore public void test() { final InstanceManager<Integer> instances = InstanceManager.create(); final LoadBalancer<Client> lb = LoadBalancer .fromSource(instances.map(Client.connector())) .withQuarantiner(Client.incarnationFactory(), Delays.fixed(1, TimeUnit.SECONDS), scheduler) .build(RoundRobinLoadBalancer.<Client>create()); // Add to the load balancer instances.add(1); instances.add(2); // Perform 10 operations List<String> result = Observable .interval(100, TimeUnit.MILLISECONDS) .concatMap(new Func1<Long, Observable<String>>() { @Override public Observable<String> call(final Long counter) { return Observable.just(lb.next()) .concatMap(new Func1<Client, Observable<String>>() { @Override public Observable<String> call(Client instance) { instance.onBegin(); if (1 == instance.getValue()) { instance.onFailed(); return Observable.error(new Exception("Failed")); } instance.onSuccess(); return Observable.just(instance + "-" + counter); } }) .retry(1); } }) .take(10) .toList() .toBlocking() .first() ; } @Test public void integrationTest() { final InstanceManager<Integer> instances = InstanceManager.create(); final LoadBalancer<Client> lb = LoadBalancer .fromSource(instances.map(Client.connector())) .withQuarantiner(Client.incarnationFactory(), Delays.fixed(1, TimeUnit.SECONDS), scheduler) .build(ChoiceOfTwoLoadBalancer.<Client>create(Client.compareByMetric())); instances.add(1); Client client = lb.next(); instances.add(2); client = lb.next(); } }