Skip to content
Permalink
Browse files
GERONIMO-6584 - Adding support for circuit breakers.
Adding tests for combined retry + circuit breaker behavior.
  • Loading branch information
johnament committed Sep 10, 2017
1 parent 31bcd92 commit cf5a00023b4c65d49b7df0b377c2fd89dd4fdca9
Showing 30 changed files with 1,162 additions and 117 deletions.
@@ -150,6 +150,12 @@
<version>${arquillian-weld-embedded.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.testng</groupId>
<artifactId>arquillian-testng-container</artifactId>
<version>${arquillian.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

@@ -0,0 +1,25 @@
/*
* 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.safeguard.api.circuitbreaker;

public interface CircuitBreaker {
CircuitBreakerDefinition getDefinition();
CircuitBreakerState getState();
}
@@ -0,0 +1,36 @@
/*
* 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.safeguard.api.circuitbreaker;

import org.apache.safeguard.api.retry.RetryBuilder;
import org.apache.safeguard.api.retry.RetryDefinition;

import java.time.Duration;

public interface CircuitBreakerBuilder {
CircuitBreakerBuilder withDelay(Duration delay);
CircuitBreakerBuilder withFailureCount(int failureCount);
CircuitBreakerBuilder withFailures(int failureCount, int requestCount);
CircuitBreakerBuilder withSuccessCount(int successCount);
CircuitBreakerBuilder withSuccesses(int successCount, int requestCount);
CircuitBreakerBuilder withFailOn(Class<? extends Throwable>... failOn);

CircuitBreakerDefinition build();
}
@@ -0,0 +1,39 @@
/*
* 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.safeguard.api.circuitbreaker;

import javax.enterprise.util.Nonbinding;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collection;

public interface CircuitBreakerDefinition {
Collection<Class<? extends Throwable>> getFailOn();

Duration getDelay();

int getRequestVolumeThreshold();

double getFailureRatio();

int getSuccessThreshold();

double getSuccessRatio();
}
@@ -0,0 +1,25 @@
/*
* 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.safeguard.api.circuitbreaker;

public interface CircuitBreakerManager {
CircuitBreakerBuilder newCircuitBreaker(String name);
CircuitBreaker getCircuitBreaker(String name);
}
@@ -0,0 +1,26 @@
/*
* 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.safeguard.api.circuitbreaker;

public enum CircuitBreakerState {
OPEN,
CLOSED,
HALF_OPEN
}
@@ -42,8 +42,6 @@
<dependency>
<groupId>org.jboss.arquillian.testng</groupId>
<artifactId>arquillian-testng-container</artifactId>
<version>${arquillian.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
@@ -0,0 +1,61 @@
/*
* 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.safeguard.impl;

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.SyncFailsafe;
import org.apache.safeguard.impl.circuitbreaker.FailsafeCircuitBreaker;
import org.apache.safeguard.impl.retry.FailsafeRetryDefinition;

import java.time.Duration;
import java.util.concurrent.Callable;

class ExecutionPlan {
private final Duration timeout;
private final boolean isAsynchronous;
private final FailsafeRetryDefinition retryDefinition;
private final FailsafeCircuitBreaker failsafeCircuitBreaker;

public ExecutionPlan(Duration timeout, boolean isAsynchronous, FailsafeRetryDefinition retryDefinition, FailsafeCircuitBreaker failsafeCircuitBreaker) {
this.timeout = timeout;
this.isAsynchronous = isAsynchronous;
this.retryDefinition = retryDefinition;
this.failsafeCircuitBreaker = failsafeCircuitBreaker;
if(retryDefinition == null && failsafeCircuitBreaker == null && !isAsynchronous) {
throw new IllegalStateException("For non-async invocations, must have at least one of RetryDefintion or CircuitBreaker defined");
}
}

public <T> T execute(Callable<T> callable) {
SyncFailsafe<?> syncFailsafe;
if(retryDefinition == null) {
syncFailsafe = Failsafe.with(failsafeCircuitBreaker.getDefinition().getCircuitBreaker());
}
else {
if(failsafeCircuitBreaker == null) {
syncFailsafe = Failsafe.with(retryDefinition.getRetryPolicy());
}
else {
syncFailsafe = Failsafe.with(retryDefinition.getRetryPolicy()).with(failsafeCircuitBreaker.getDefinition().getCircuitBreaker());
}
}
return syncFailsafe.get(callable);
}
}
@@ -0,0 +1,90 @@
/*
* 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.safeguard.impl;

import org.apache.safeguard.impl.circuitbreaker.FailsafeCircuitBreaker;
import org.apache.safeguard.impl.circuitbreaker.FailsafeCircuitBreakerBuilder;
import org.apache.safeguard.impl.circuitbreaker.FailsafeCircuitBreakerManager;
import org.apache.safeguard.impl.retry.FailsafeRetryBuilder;
import org.apache.safeguard.impl.retry.FailsafeRetryDefinition;
import org.apache.safeguard.impl.retry.FailsafeRetryManager;
import org.apache.safeguard.impl.util.AnnotationUtil;
import org.apache.safeguard.impl.util.NamingUtil;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.eclipse.microprofile.faulttolerance.Retry;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import static org.apache.safeguard.impl.MicroprofileAnnotationMapper.mapCircuitBreaker;
import static org.apache.safeguard.impl.MicroprofileAnnotationMapper.mapRetry;

public class ExecutionPlanFactory {
private final FailsafeCircuitBreakerManager circuitBreakerManager;
private final FailsafeRetryManager retryManager;
private Map<String, ExecutionPlan> executionPlanMap = new HashMap<>();

public ExecutionPlanFactory(FailsafeCircuitBreakerManager circuitBreakerManager, FailsafeRetryManager retryManager) {
this.circuitBreakerManager = circuitBreakerManager;
this.retryManager = retryManager;
}

ExecutionPlan locateExecutionPlan(String name) {
return executionPlanMap.computeIfAbsent(name, name1 -> {
FailsafeCircuitBreaker circuitBreaker = circuitBreakerManager.getCircuitBreaker(name1);
FailsafeRetryDefinition retryDefinition = retryManager.getRetryDefinition(name1);
return new ExecutionPlan(null, false, retryDefinition, circuitBreaker);
});
}

ExecutionPlan locateExecutionPlan(Method method) {
final String name = NamingUtil.createName(method);
return executionPlanMap.computeIfAbsent(name, name1 -> {
FailsafeCircuitBreaker circuitBreaker = circuitBreakerManager.getCircuitBreaker(name1);
if(circuitBreaker == null) {
circuitBreaker = createCBDefinition(name, method);
}
FailsafeRetryDefinition retryDefinition = retryManager.getRetryDefinition(name1);
if(retryDefinition == null) {
retryDefinition =createDefinition(name, method);
}
return new ExecutionPlan(null, false, retryDefinition, circuitBreaker);
});
}

private FailsafeRetryDefinition createDefinition(String name, Method method) {
Retry retry = AnnotationUtil.getAnnotation(method, Retry.class);
if (retry == null) {
return null;
}
FailsafeRetryBuilder retryBuilder = retryManager.newRetryDefinition(name);
return mapRetry(retry, retryBuilder);
}

private FailsafeCircuitBreaker createCBDefinition(String name, Method method) {
CircuitBreaker circuitBreaker = AnnotationUtil.getAnnotation(method, CircuitBreaker.class);
if (circuitBreaker == null) {
return null;
}
FailsafeCircuitBreakerBuilder circuitBreakerBuilder = this.circuitBreakerManager.newCircuitBreaker(name);
return new FailsafeCircuitBreaker(mapCircuitBreaker(circuitBreaker, circuitBreakerBuilder));
}
}

0 comments on commit cf5a000

Please sign in to comment.