/** * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG * 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 com.sixt.service.framework.rpc; import org.junit.Before; import org.junit.Test; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static com.sixt.service.framework.rpc.CircuitBreakerState.*; import static com.sixt.service.framework.rpc.CircuitBreakerState.State.*; import static org.assertj.core.api.Assertions.assertThat; public class CircuitBreakerStateIntegrationTest { private CircuitBreakerState breaker; private StubExecutor executor; @Before public void setup() { executor = new StubExecutor(2); breaker = new CircuitBreakerState(executor); } @Test public void toHellAndBack() { for (int i = 0; i < 3; i++) { assertThat(breaker.getState()).isEqualTo(PRIMARY_HEALTHY); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(PRIMARY_HEALTHY); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(PRIMARY_TRIPPED); executor.tick(PRIMARY_TRIP_TIME - 1); assertThat(breaker.getState()).isEqualTo(PRIMARY_TRIPPED); executor.tick(1); assertThat(breaker.getState()).isEqualTo(PRIMARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(SECONDARY_TRIPPED); executor.tick(SECONDARY_TRIP_TIME - 1); assertThat(breaker.getState()).isEqualTo(SECONDARY_TRIPPED); executor.tick(1); assertThat(breaker.getState()).isEqualTo(SECONDARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); executor.tick(TERTIARY_TRIP_TIME - 1); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); executor.tick(1); assertThat(breaker.getState()).isEqualTo(TERTIARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); executor.tick(TERTIARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(TERTIARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(TERTIARY_HEALTHY); executor.tick(TERTIARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(SECONDARY_HEALTHY); executor.tick(SECONDARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(PRIMARY_HEALTHY); } } @Test public void healthyToTripped() { breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(PRIMARY_TRIPPED); executor.tick(PRIMARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(PRIMARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(SECONDARY_TRIPPED); executor.tick(SECONDARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(SECONDARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(SECONDARY_HEALTHY); breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(SECONDARY_TRIPPED); executor.tick(SECONDARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(SECONDARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); executor.tick(TERTIARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(TERTIARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(TERTIARY_HEALTHY); breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); } @Test public void primaryProbeToHealthy() { breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(PRIMARY_TRIPPED); executor.tick(PRIMARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(PRIMARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(SECONDARY_HEALTHY); executor.tick(PRIMARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(PRIMARY_HEALTHY); } @Test public void stateChangerRespectsFailures() { breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(PRIMARY_TRIPPED); executor.tick(PRIMARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(PRIMARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(SECONDARY_TRIPPED); executor.tick(SECONDARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(SECONDARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(SECONDARY_HEALTHY); breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); executor.tick(PRIMARY_TRIP_TIME); assertThat(executor.scheduledTasks).hasSize(2); executor.tick(PRIMARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(SECONDARY_PROBE); breaker.requestComplete(false); assertThat(breaker.getState()).isEqualTo(TERTIARY_TRIPPED); executor.tick(TERTIARY_TRIP_TIME); assertThat(breaker.getState()).isEqualTo(TERTIARY_PROBE); breaker.requestComplete(true); assertThat(breaker.getState()).isEqualTo(TERTIARY_HEALTHY); breaker.requestComplete(false); breaker.requestComplete(false); breaker.requestComplete(false); executor.tick(TERTIARY_TRIP_TIME); } private class StubExecutor extends ScheduledThreadPoolExecutor { private int currentTime = 0; private List<ScheduledTask> scheduledTasks = new LinkedList<>(); public StubExecutor(int corePoolSize) { super(corePoolSize); } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { scheduledTasks.add(new ScheduledTask(currentTime + (int)delay, command)); return null; } public void tick(int ticks) { for (int i = 0; i < ticks; i++) { currentTime++; while (!scheduledTasks.isEmpty() && scheduledTasks.get(0).startTime == currentTime) { ScheduledTask task = scheduledTasks.remove(0); task.command.run(); } } } private class ScheduledTask { int startTime; Runnable command; ScheduledTask(int startTime, Runnable command) { this.startTime = startTime; this.command = command; } } } }