/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.hadoop.io.retry; import static org.junit.Assert.*; import java.io.IOException; import org.apache.hadoop.io.retry.UnreliableImplementation.TypeOfExceptionToFailWith; import org.apache.hadoop.io.retry.UnreliableInterface.UnreliableException; import org.apache.hadoop.ipc.StandbyException; import org.junit.Test; public class TestFailoverProxy { public static class FlipFlopProxyProvider implements FailoverProxyProvider { private Class<?> iface; private Object currentlyActive; private Object impl1; private Object impl2; public FlipFlopProxyProvider(Class<?> iface, Object activeImpl, Object standbyImpl) { this.iface = iface; this.impl1 = activeImpl; this.impl2 = standbyImpl; currentlyActive = impl1; } @Override public Object getProxy() { return currentlyActive; } @Override public void performFailover(Object currentProxy) { currentlyActive = impl1 == currentProxy ? impl2 : impl1; } @Override public Class<?> getInterface() { return iface; } } public static class FailOverOnceOnAnyExceptionPolicy implements RetryPolicy { @Override public RetryAction shouldRetry(Exception e, int retries, int failovers, boolean isMethodIdempotent) { return failovers < 1 ? RetryAction.FAILOVER_AND_RETRY : RetryAction.FAIL; } } @Test public void testSuccedsOnceThenFailOver() throws UnreliableException, IOException, StandbyException { UnreliableInterface unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1"), new UnreliableImplementation("impl2")), new FailOverOnceOnAnyExceptionPolicy()); assertEquals("impl1", unreliable.succeedsOnceThenFailsReturningString()); assertEquals("impl2", unreliable.succeedsOnceThenFailsReturningString()); try { unreliable.succeedsOnceThenFailsReturningString(); fail("should not have succeeded more than twice"); } catch (UnreliableException e) { // expected } } @Test public void testSucceedsTenTimesThenFailOver() throws UnreliableException, IOException, StandbyException { UnreliableInterface unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1"), new UnreliableImplementation("impl2")), new FailOverOnceOnAnyExceptionPolicy()); for (int i = 0; i < 10; i++) { assertEquals("impl1", unreliable.succeedsTenTimesThenFailsReturningString()); } assertEquals("impl2", unreliable.succeedsTenTimesThenFailsReturningString()); } @Test public void testNeverFailOver() throws UnreliableException, IOException, StandbyException { UnreliableInterface unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1"), new UnreliableImplementation("impl2")), RetryPolicies.TRY_ONCE_DONT_FAIL); unreliable.succeedsOnceThenFailsReturningString(); try { unreliable.succeedsOnceThenFailsReturningString(); fail("should not have succeeded twice"); } catch (UnreliableException e) { assertEquals("impl1", e.getMessage()); } } @Test public void testFailoverOnStandbyException() throws UnreliableException, IOException, StandbyException { UnreliableInterface unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1"), new UnreliableImplementation("impl2")), RetryPolicies.failoverOnNetworkException(1)); assertEquals("impl1", unreliable.succeedsOnceThenFailsReturningString()); try { unreliable.succeedsOnceThenFailsReturningString(); fail("should not have succeeded twice"); } catch (UnreliableException e) { // Make sure there was no failover on normal exception. assertEquals("impl1", e.getMessage()); } unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1", TypeOfExceptionToFailWith.STANDBY_EXCEPTION), new UnreliableImplementation("impl2", TypeOfExceptionToFailWith.UNRELIABLE_EXCEPTION)), RetryPolicies.failoverOnNetworkException(1)); assertEquals("impl1", unreliable.succeedsOnceThenFailsReturningString()); // Make sure we fail over since the first implementation threw a StandbyException assertEquals("impl2", unreliable.succeedsOnceThenFailsReturningString()); } @Test public void testFailoverOnNetworkExceptionIdempotentOperation() throws UnreliableException, IOException, StandbyException { UnreliableInterface unreliable = (UnreliableInterface)RetryProxy .create(UnreliableInterface.class, new FlipFlopProxyProvider(UnreliableInterface.class, new UnreliableImplementation("impl1", TypeOfExceptionToFailWith.IO_EXCEPTION), new UnreliableImplementation("impl2", TypeOfExceptionToFailWith.UNRELIABLE_EXCEPTION)), RetryPolicies.failoverOnNetworkException(1)); assertEquals("impl1", unreliable.succeedsOnceThenFailsReturningString()); try { assertEquals("impl2", unreliable.succeedsOnceThenFailsReturningString()); fail("should not have succeeded twice"); } catch (IOException e) { // Make sure we *don't* fail over since the first implementation threw an // IOException and this method is not idempotent assertEquals("impl1", e.getMessage()); } assertEquals("impl1", unreliable.succeedsOnceThenFailsReturningStringIdempotent()); // Make sure we fail over since the first implementation threw an // IOException and this method is idempotent. assertEquals("impl2", unreliable.succeedsOnceThenFailsReturningStringIdempotent()); } }