/* * Copyright 2015 the original author or authors. * * 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.springframework.retry.policy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import org.springframework.classify.BinaryExceptionClassifier; import org.springframework.retry.RecoveryCallback; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.policy.CircuitBreakerRetryPolicy.CircuitBreakerRetryContext; import org.springframework.retry.support.DefaultRetryState; import org.springframework.retry.support.RetryTemplate; /** * @author Dave Syer * */ public class CircuitBreakerRetryTemplateTests { private static final String RECOVERED = "RECOVERED"; private static final String RESULT = "RESULT"; private RetryTemplate retryTemplate; private RecoveryCallback<Object> recovery; private MockRetryCallback callback; private DefaultRetryState state; @Before public void init() { this.callback = new MockRetryCallback(); this.recovery = new RecoveryCallback<Object>() { @Override public Object recover(RetryContext context) throws Exception { return RECOVERED; } }; this.retryTemplate = new RetryTemplate(); this.callback.setAttemptsBeforeSuccess(1); // No rollback by default (so exceptions are not rethrown) this.state = new DefaultRetryState("retry", new BinaryExceptionClassifier(false)); } @Test public void testCircuitOpenWhenNotRetryable() throws Throwable { this.retryTemplate .setRetryPolicy(new CircuitBreakerRetryPolicy(new NeverRetryPolicy())); Object result = this.retryTemplate.execute(this.callback, this.recovery, this.state); assertEquals(1, this.callback.getAttempts()); assertEquals(RECOVERED, result); result = this.retryTemplate.execute(this.callback, this.recovery, this.state); // circuit is now open so no more attempts assertEquals(1, this.callback.getAttempts()); assertEquals(RECOVERED, result); } @Test public void testCircuitOpenWithNoRecovery() throws Throwable { this.retryTemplate .setRetryPolicy(new CircuitBreakerRetryPolicy(new NeverRetryPolicy())); this.retryTemplate.setThrowLastExceptionOnExhausted(true); try { this.retryTemplate.execute(this.callback, this.state); } catch (Exception e) { assertEquals(this.callback.exceptionToThrow, e); assertEquals(1, this.callback.getAttempts()); } try { this.retryTemplate.execute(this.callback, this.state); } catch (Exception e) { assertEquals(this.callback.exceptionToThrow, e); // circuit is now open so no more attempts assertEquals(1, this.callback.getAttempts()); } } @Test public void testCircuitOpensWhenDelegateNotRetryable() throws Throwable { this.retryTemplate .setRetryPolicy(new CircuitBreakerRetryPolicy(new SimpleRetryPolicy())); this.callback.setAttemptsBeforeSuccess(10); Object result = this.retryTemplate.execute(this.callback, this.recovery, this.state); assertEquals(1, this.callback.getAttempts()); assertEquals(RECOVERED, result); assertFalse(this.callback.status.isOpen()); result = this.retryTemplate.execute(this.callback, this.recovery, this.state); result = this.retryTemplate.execute(this.callback, this.recovery, this.state); // circuit is now open so no more attempts assertEquals(3, this.callback.getAttempts()); assertEquals(RECOVERED, result); assertTrue(this.callback.status.isOpen()); } @Test public void testWindowResetsAfterTimeout() throws Throwable { CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy( new SimpleRetryPolicy()); this.retryTemplate.setRetryPolicy(retryPolicy); retryPolicy.setOpenTimeout(100); this.callback.setAttemptsBeforeSuccess(10); Object result = this.retryTemplate.execute(this.callback, this.recovery, this.state); assertEquals(1, this.callback.getAttempts()); assertEquals(RECOVERED, result); assertFalse(this.callback.status.isOpen()); Thread.sleep(200L); result = this.retryTemplate.execute(this.callback, this.recovery, this.state); // circuit is reset after sleep window assertEquals(2, this.callback.getAttempts()); assertEquals(RECOVERED, result); assertFalse(this.callback.status.isOpen()); } @Test public void testCircuitClosesAfterTimeout() throws Throwable { CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy( new NeverRetryPolicy()); this.retryTemplate.setRetryPolicy(retryPolicy); retryPolicy.setResetTimeout(100); Object result = this.retryTemplate.execute(this.callback, this.recovery, this.state); assertEquals(1, this.callback.getAttempts()); assertEquals(RECOVERED, result); assertTrue(this.callback.status.isOpen()); // Sleep longer than the timeout Thread.sleep(200L); assertFalse(this.callback.status.isOpen()); result = this.retryTemplate.execute(this.callback, this.recovery, this.state); // circuit closed again now assertEquals(RESULT, result); } protected static class MockRetryCallback implements RetryCallback<Object, Exception> { private int attemptsBeforeSuccess; private Exception exceptionToThrow = new Exception(); private CircuitBreakerRetryContext status; @Override public Object doWithRetry(RetryContext status) throws Exception { this.status = (CircuitBreakerRetryContext) status; int attempts = getAttempts(); attempts++; status.setAttribute("attempts", attempts); if (attempts <= this.attemptsBeforeSuccess) { throw this.exceptionToThrow; } return RESULT; } public int getAttempts() { if (!this.status.hasAttribute("attempts")) { this.status.setAttribute("attempts", 0); } int attempts = (Integer) this.status.getAttribute("attempts"); return attempts; } public void setAttemptsBeforeSuccess(int attemptsBeforeSuccess) { this.attemptsBeforeSuccess = attemptsBeforeSuccess; } public void setExceptionToThrow(Exception exceptionToThrow) { this.exceptionToThrow = exceptionToThrow; } } }