├── .gitignore ├── .travis.yml ├── .zappr.yaml ├── CONTRIBUTIONS.md ├── LICENSE ├── MAINTAINERS ├── MIGRATION.md ├── README.md ├── SECURITY.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── zalando │ │ └── actuate │ │ ├── autoconfigure │ │ └── failsafe │ │ │ ├── CircuitBreakersEndpointAutoConfiguration.java │ │ │ └── package-info.java │ │ └── failsafe │ │ ├── CircuitBreakerView.java │ │ ├── CircuitBreakersEndpoint.java │ │ ├── Container.java │ │ └── package-info.java └── resources │ └── META-INF │ └── spring.factories └── test ├── java └── org │ └── zalando │ └── actuate │ └── failsafe │ ├── CircuitBreakerConfiguration.java │ ├── CircuitBreakersEndpointTest.java │ ├── EmptyCircuitBreakersEndpointTest.java │ ├── NoCircuitBreakersApplication.java │ ├── SampleApplication.java │ └── SampleController.java └── resources └── application.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/workspace.xml 8 | .idea/tasks.xml 9 | .idea/dictionaries 10 | .idea/vcs.xml 11 | .idea/jsLibraryMappings.xml 12 | .gradletasknamecache 13 | # Sensitive or high-churn files: 14 | .idea/dataSources.ids 15 | .idea/dataSources.xml 16 | .idea/dataSources.local.xml 17 | .idea/sqlDataSources.xml 18 | .idea/dynamic.xml 19 | .idea/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/gradle.xml 23 | .idea/libraries 24 | 25 | # Mongo Explorer plugin: 26 | .idea/mongoSettings.xml 27 | 28 | ## File-based project format: 29 | *.iws 30 | 31 | ## Plugin-specific files: 32 | 33 | # IntelliJ 34 | /out/ 35 | .idea/** 36 | # mpeltonen/sbt-idea plugin 37 | .idea_modules/ 38 | **/*.iml 39 | 40 | # JIRA plugin 41 | atlassian-ide-plugin.xml 42 | 43 | # Crashlytics plugin (for Android Studio and IntelliJ) 44 | com_crashlytics_export_strings.xml 45 | crashlytics.properties 46 | crashlytics-build.properties 47 | fabric.properties 48 | ### Java template 49 | *.class 50 | 51 | # Mobile Tools for Java (J2ME) 52 | .mtj.tmp/ 53 | 54 | # Package Files # 55 | *.jar 56 | !gradle/wrapper/gradle-wrapper.jar 57 | *.war 58 | *.ear 59 | 60 | # Gradle build-folder 61 | build 62 | .gradle 63 | 64 | target/ 65 | /failsafe-actuator.iml 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: true 3 | 4 | jdk: 5 | - oraclejdk8 6 | 7 | before_install: 8 | - sudo apt-get install jq 9 | - wget -O ~/codacy-coverage-reporter-assembly-latest.jar https://github.com/codacy/codacy-coverage-reporter/releases/download/2.0.2/codacy-coverage-reporter-2.0.2-assembly.jar 10 | script: 11 | - mvn test 12 | after_success: 13 | - java -cp ~/codacy-coverage-reporter-assembly-latest.jar com.codacy.CodacyCoverageReporter -l Java -r target/site/jacoco/jacoco.xml 14 | 15 | -------------------------------------------------------------------------------- /.zappr.yaml: -------------------------------------------------------------------------------- 1 | approvals: 2 | groups: 3 | zalando: 4 | minimum: 2 5 | from: 6 | orgs: 7 | - "zalando" 8 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We welcome pull requests. Simply fork the repository, create a feature branch with your work and follow up with a pull request against master. 4 | 5 | Unless you explicitly state otherwise in advance, any non trivial contribution intentionally submitted for inclusion in this project by you to the steward of this repository (Zalando SE, Berlin) shall be under the terms and conditions of the MIT License, without any additional copyright information, terms or conditions. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Zalando SE 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | Mahdi Modabber 4 | Malte Pickhan 5 | 6 | # Contributors 7 | 8 | - Jörg Bellmann 9 | - Michael Vitz 10 | - Rob van der Leek 11 | - Jonas Jurczok 12 | - Willi Schönborn 13 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Failsafe actuator 2.0 Migration Guide 2 | 3 | ## Breaking changes 4 | 5 | - Failsafe actuator changed endpoint id from `circuit-breakers` to `circuitbreakers` 6 | for alignment with naming constraints introduced in Spring Boot 2.1. 7 | This leads to change actuator endpoint url to `/acutuator/circuitbreakers` 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/97639870e76546cab6fd2597c583c0b1)](https://www.codacy.com/app/MALPI/failsafe-actuator?utm_source=github.com&utm_medium=referral&utm_content=zalando-incubator/failsafe-actuator&utm_campaign=badger) 2 | [![Build Status](https://travis-ci.org/zalando/failsafe-actuator.svg?branch=master)](https://travis-ci.org/zalando/failsafe-actuator) 3 | [![Maven Central](https://img.shields.io/maven-central/v/org.zalando/failsafe-actuator.svg)](https://maven-badges.herokuapp.com/maven-central/org.zalando/failsafe-actuator) 4 | 5 | # Failsafe Actuator 6 | 7 | **Failsafe Actuator** is a Java library that provides a simple monitoring interface for [Spring Boot](https://projects.spring.io/spring-boot/) 8 | applications that use the [Failsafe](https://github.com/jhalterman/failsafe) library. 9 | Using Failsafe Actuator will readily expose the state of your [Circuit Breakers](http://martinfowler.com/bliki/CircuitBreaker.html) (closed, open, half-open) 10 | to your Spring Actuator endpoint without additional effort. 11 | 12 | ## Core Technical Concepts/Inspiration 13 | 14 | Failsafe Actuator supports Spring's dependency injection to make it easier to use *Failsafe*. 15 | It allows you to monitor the state of your Circuit Breakers so that, whenever a third party that your app relies upon 16 | suddenly becomes unavailable, you can discover it immediately and take action. This is essential for applications used in production. 17 | 18 | ## Development Status/Project Roadmap 19 | This library is currently under development and used in production at [Zalando](https://jobs.zalando.com/tech/). 20 | 21 | Find more details about our development plans in the [Issues Tracker](https://github.com/zalando-incubator/failsafe-actuator/issues). 22 | 23 | We're always [looking for contributors](https://github.com/zalando-incubator/failsafe-actuator/blob/master/CONTRIBUTIONS.md), 24 | so if you find an interesting "Help Wanted" issue then please drop us a line in the related issue to claim it and begin working. 25 | 26 | Unless you explicitly state otherwise in advance, any non trivial contribution intentionally submitted for inclusion in this project by you to the steward of this repository (Zalando SE, Berlin) shall be under the terms and conditions of the MIT License, without any additional copyright information, terms or conditions. 27 | 28 | ## Getting Started 29 | 30 | ### Dependencies/Requirements 31 | * Java 8 32 | * [Spring Boot 2](http://projects.spring.io/spring-boot/) 33 | * [Failsafe](https://github.com/jhalterman/failsafe) 34 | 35 | ### Running/Using 36 | 37 | To use Failsafe Actuator, add the following dependency to your project: 38 | 39 | **Gradle:** 40 | ```groovy 41 | compile("org.zalando:failsafe-actuator:${FAILSAFE-ACTUATOR-VERSION}") 42 | ``` 43 | 44 | **Maven:** 45 | ```xml 46 | 47 | org.zalando 48 | failsafe-actuator 49 | ${failsafe-actuator.version} 50 | 51 | ``` 52 | 53 | Create your `CircuitBreaker` by defining them as a `Bean`. 54 | 55 | ```java 56 | @Configuration 57 | public class CircuitBreakerConfiguration { 58 | @Bean 59 | public CircuitBreaker myBreaker() { 60 | return new CircuitBreaker(); 61 | } 62 | } 63 | ``` 64 | 65 | You can use and configure the created `CircuitBreaker` by autowiring it in the class where it should be used. 66 | 67 | 68 | ```java 69 | @Component 70 | public class MyBean { 71 | @Autowired 72 | private CircuitBreaker myBreaker; 73 | } 74 | ``` 75 | 76 | That's it. By calling the [endpoint](http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html) via _**http://${yourAddress}/actuator/circuitbreakers**_. 77 | you will get a response which looks like the following: 78 | 79 | 80 | ```http 81 | GET /actuator/circuitbreakers 82 | 83 | HTTP/1.1 200 84 | Content-Type: application/json 85 | 86 | { 87 | "myBreaker": { 88 | "state": "OPEN" 89 | }, 90 | "otherBreaker": { 91 | "state": "CLOSED" 92 | } 93 | } 94 | ``` 95 | 96 | Individual circuit breakers can be requested via `/acutuator/circuitbreakers/{name}`: 97 | 98 | ```http 99 | GET /actuator/circuitbreakers/myBreaker 100 | 101 | HTTP/1.1 200 102 | Content-Type: application/json 103 | 104 | { 105 | "state": "OPEN" 106 | } 107 | ``` 108 | 109 | You can even modify the circuit breaker state and manually open or close them: 110 | 111 | ```http 112 | POST /actuator/circuitbreakers/myBreaker 113 | Content-Type: application/json 114 | 115 | { 116 | "state": "CLOSED" 117 | } 118 | ``` 119 | 120 | ## Example usage 121 | 122 | To see a complete example on how to use the library take a look at the 123 | [Sample Application](https://github.com/zalando/failsafe-actuator/blob/master/src/test/java/org/zalando/actuate/failsafe/SampleApplication.java). 124 | It starts a [Rest Controller](https://github.com/zalando/failsafe-actuator/blob/master/src/test/java/org/zalando/actuate/failsafe/SampleController.java) 125 | that shows how to autowire `CircuitBreaker` into your application 126 | and configure them. 127 | 128 | ## How to build on your own 129 | 130 | In order to build the JAR on your own run the following command: 131 | 132 | ```bash 133 | mvn clean install 134 | ``` 135 | 136 | ## License 137 | 138 | This code is released under the MIT license. See [License](LICENSE). 139 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you have discovered a security vulnerability, please email tech-security@zalando.de. 2 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | org.zalando 8 | failsafe-actuator 9 | 2.0.0-SNAPSHOT 10 | 11 | Failsafe-Actuator 12 | Failsafe-Actuator provides an out of the box endpoint for Failsafe 13 | https://github.com/zalando-incubator/failsafe-actuator 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 1.8 19 | ${java.version} 20 | ${java.version} 21 | 2.0.5.RELEASE 22 | 23 | 24 | 25 | 26 | com.google.code.findbugs 27 | jsr305 28 | 3.0.2 29 | provided 30 | 31 | 32 | net.jodah 33 | failsafe 34 | 1.1.0 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-actuator 39 | ${spring-boot.version} 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-actuator-autoconfigure 44 | ${spring-boot.version} 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-autoconfigure 49 | ${spring-boot.version} 50 | 51 | 52 | org.projectlombok 53 | lombok 54 | 1.18.2 55 | provided 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-test 60 | ${spring-boot.version} 61 | test 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-web 66 | ${spring-boot.version} 67 | test 68 | 69 | 70 | junit 71 | junit 72 | 4.12 73 | test 74 | 75 | 76 | com.jayway.jsonpath 77 | json-path-assert 78 | 2.2.0 79 | test 80 | 81 | 82 | commons-io 83 | commons-io 84 | 2.6 85 | test 86 | 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-compiler-plugin 94 | 3.7.0 95 | 96 | 97 | -parameters 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-surefire-plugin 104 | 2.20 105 | 106 | 107 | 108 | org.jacoco 109 | jacoco-maven-plugin 110 | 0.7.8 111 | 112 | 113 | jacoco-initialize 114 | 115 | prepare-agent 116 | 117 | 118 | 119 | report 120 | prepare-package 121 | 122 | report 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | scm:git:https://github.com/zalando-incubator/failsafe-actuator.git 132 | scm:git:https://github.com/zalando-incubator/failsafe-actuator.git 133 | https://github.com/zalando-incubator/failsafe-actuator 134 | HEAD 135 | 136 | 137 | 138 | 139 | ossrh 140 | https://oss.sonatype.org/content/repositories/snapshots 141 | 142 | 143 | ossrh 144 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 145 | 146 | 147 | 148 | 149 | Zalando SE 150 | https://tech.zalando.com/ 151 | 152 | 153 | 154 | 155 | mpickhan 156 | Malte Pickhan 157 | Zalando SE 158 | malte.pickhan@zalando.de 159 | 160 | 161 | mmodabber 162 | Mahdi Modabber 163 | Zalando SE 164 | mahdi.modabber@zalando.de 165 | 166 | 167 | 168 | 169 | 170 | MIT License 171 | http://www.opensource.org/licenses/mit-license.php 172 | repo 173 | 174 | 175 | 176 | 177 | deployment 178 | 179 | 180 | 181 | org.apache.maven.plugins 182 | maven-source-plugin 183 | 3.0.1 184 | 185 | 186 | attach-sources 187 | verify 188 | 189 | jar-no-fork 190 | 191 | 192 | 193 | 194 | 195 | org.apache.maven.plugins 196 | maven-javadoc-plugin 197 | 2.10.4 198 | 199 | 200 | attach-javadocs 201 | 202 | jar 203 | 204 | 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-gpg-plugin 210 | 1.6 211 | 212 | 213 | sign-artifacts 214 | verify 215 | 216 | sign 217 | 218 | 219 | 220 | 221 | 222 | org.apache.maven.plugins 223 | maven-release-plugin 224 | 2.5.3 225 | 226 | 227 | 228 | 229 | 230 | test-coverage 231 | 232 | 233 | 234 | org.jacoco 235 | jacoco-maven-plugin 236 | 0.6.3.201306030806 237 | 238 | ${basedir}/target/coverage-reports/jacoco-unit.exec 239 | ${basedir}/target/coverage-reports/jacoco-unit.exec 240 | 241 | 242 | 243 | prepare-unit-tests 244 | 245 | prepare-agent 246 | 247 | 248 | 249 | 250 | prepare-integration-tests 251 | 252 | prepare-agent 253 | 254 | pre-integration-test 255 | 256 | itCoverageAgent 257 | 258 | 259 | 260 | jacoco-site 261 | verify 262 | 263 | report 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/autoconfigure/failsafe/CircuitBreakersEndpointAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.autoconfigure.failsafe; 2 | 3 | import net.jodah.failsafe.CircuitBreaker; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.zalando.actuate.failsafe.CircuitBreakersEndpoint; 10 | 11 | import javax.annotation.Nullable; 12 | import java.util.Collections; 13 | import java.util.Map; 14 | 15 | @Configuration 16 | public class CircuitBreakersEndpointAutoConfiguration { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | @ConditionalOnEnabledEndpoint 21 | public CircuitBreakersEndpoint circuitBreakersEndpoint( 22 | @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") 23 | @Autowired(required = false) @Nullable final Map breakers) { 24 | return new CircuitBreakersEndpoint(breakers == null ? Collections.emptyMap() : breakers); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/autoconfigure/failsafe/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package org.zalando.actuate.autoconfigure.failsafe; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/failsafe/CircuitBreakerView.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Value; 6 | import net.jodah.failsafe.CircuitBreaker; 7 | 8 | @Value 9 | @AllArgsConstructor(onConstructor = @__(@JsonCreator)) 10 | final class CircuitBreakerView { 11 | 12 | private final CircuitBreaker.State state; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/failsafe/CircuitBreakersEndpoint.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import net.jodah.failsafe.CircuitBreaker; 4 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 5 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 6 | import org.springframework.boot.actuate.endpoint.annotation.Selector; 7 | import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Map; 11 | import java.util.function.Consumer; 12 | 13 | import static java.util.stream.Collectors.toMap; 14 | 15 | @Endpoint(id = "circuitbreakers") 16 | public class CircuitBreakersEndpoint { 17 | 18 | private final Map breakers; 19 | 20 | public CircuitBreakersEndpoint(final Map breakers) { 21 | this.breakers = breakers; 22 | } 23 | 24 | @ReadOperation 25 | public Container circuitBreakers() { 26 | final Map circuitBreakers = breakers.entrySet().stream() 27 | .collect(toMap(Map.Entry::getKey, entry -> toView(entry.getValue()))); 28 | 29 | return new Container(circuitBreakers); 30 | } 31 | 32 | @ReadOperation 33 | @Nullable 34 | public CircuitBreakerView circuitBreaker(@Selector final String name) { 35 | final CircuitBreaker breaker = breakers.get(name); 36 | 37 | if (breaker == null) { 38 | return null; 39 | } 40 | 41 | return toView(breaker); 42 | } 43 | 44 | @WriteOperation 45 | @Nullable 46 | public CircuitBreakerView transitionTo(@Selector final String name, final CircuitBreaker.State state) { 47 | final CircuitBreaker breaker = breakers.get(name); 48 | 49 | if (breaker == null) { 50 | return null; 51 | } 52 | 53 | transitioner(state).accept(breaker); 54 | 55 | return toView(breaker); 56 | } 57 | 58 | private CircuitBreakerView toView(final CircuitBreaker breaker) { 59 | return new CircuitBreakerView(breaker.getState()); 60 | } 61 | 62 | private Consumer transitioner(final CircuitBreaker.State state) { 63 | switch (state) { 64 | case CLOSED: 65 | return CircuitBreaker::close; 66 | case OPEN: 67 | return CircuitBreaker::open; 68 | case HALF_OPEN: 69 | return CircuitBreaker::halfOpen; 70 | default: 71 | throw new UnsupportedOperationException("Unknown state: " + state); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/failsafe/Container.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Value; 6 | 7 | import java.util.Map; 8 | 9 | @Value 10 | @AllArgsConstructor(onConstructor = @__(@JsonCreator)) 11 | final class Container { 12 | 13 | private final Map circuitBreakers; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/actuate/failsafe/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package org.zalando.actuate.failsafe; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | org.zalando.actuate.autoconfigure.failsafe.CircuitBreakersEndpointAutoConfiguration 3 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/CircuitBreakerConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import net.jodah.failsafe.CircuitBreaker; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class CircuitBreakerConfiguration { 9 | 10 | @Bean 11 | public CircuitBreaker test() { 12 | return new CircuitBreaker(); 13 | } 14 | 15 | @Bean 16 | public CircuitBreaker delay() { 17 | return new CircuitBreaker(); 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/CircuitBreakersEndpointTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import net.jodah.failsafe.CircuitBreaker; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.web.client.TestRestTemplate; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.web.client.DefaultResponseErrorHandler; 12 | import org.springframework.web.client.HttpClientErrorException; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.util.Map; 16 | 17 | import static net.jodah.failsafe.CircuitBreaker.State.CLOSED; 18 | import static net.jodah.failsafe.CircuitBreaker.State.HALF_OPEN; 19 | import static net.jodah.failsafe.CircuitBreaker.State.OPEN; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.junit.Assert.assertNull; 23 | import static org.junit.Assert.assertTrue; 24 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 25 | import static org.springframework.http.HttpEntity.EMPTY; 26 | import static org.springframework.http.HttpMethod.GET; 27 | 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest(classes = SampleApplication.class, webEnvironment = RANDOM_PORT) 30 | public class CircuitBreakersEndpointTest { 31 | 32 | @Autowired 33 | private TestRestTemplate http; 34 | 35 | @PostConstruct 36 | public void configure() { 37 | this.http.getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler()); 38 | } 39 | 40 | @Before 41 | public void before() { 42 | 43 | write("test", CLOSED); 44 | } 45 | 46 | @Test 47 | public void shouldReadAll() { 48 | final Map breakers = readAll().getCircuitBreakers(); 49 | 50 | assertEquals(2, breakers.size()); 51 | assertTrue(breakers.containsKey("test")); 52 | assertTrue(breakers.containsKey("delay")); 53 | 54 | final CircuitBreakerView view = breakers.get("test"); 55 | assertEquals(CLOSED, view.getState()); 56 | } 57 | 58 | @Test 59 | public void shouldReadOne() { 60 | final CircuitBreakerView view = readOne("test"); 61 | assertNotNull(view); 62 | assertEquals(CLOSED, view.getState()); 63 | } 64 | 65 | @Test(expected = HttpClientErrorException.class) 66 | public void shouldReadUnknown() { 67 | readOne("unknown"); 68 | } 69 | 70 | @Test 71 | public void shouldOpen() { 72 | verifyTransitionTo(OPEN); 73 | } 74 | 75 | @Test 76 | public void shouldHalfOpen() { 77 | verifyTransitionTo(HALF_OPEN); 78 | } 79 | 80 | @Test 81 | public void shouldClose() { 82 | write("test", OPEN); // so we have an actual state change, since closed is the default 83 | 84 | verifyTransitionTo(CLOSED); 85 | } 86 | 87 | @Test 88 | public void shouldWriteUnknown() { 89 | assertNull(write("unknown", OPEN)); 90 | } 91 | 92 | private void verifyTransitionTo(final CircuitBreaker.State state) { 93 | final CircuitBreakerView written = write("test", state); 94 | final CircuitBreakerView read = readOne("test"); 95 | 96 | assertEquals(read, written); 97 | assertEquals(read.getState(), state); 98 | } 99 | 100 | private Container readAll() { 101 | return http.exchange("/actuator/circuitbreakers", GET, EMPTY, Container.class).getBody(); 102 | } 103 | 104 | private CircuitBreakerView readOne(final String name) { 105 | return http.getForObject("/actuator/circuitbreakers/{name}", 106 | CircuitBreakerView.class, name); 107 | } 108 | 109 | private CircuitBreakerView write(final String test, final CircuitBreaker.State state) { 110 | return http.postForObject("/actuator/circuitbreakers/{name}", 111 | new CircuitBreakerView(state), CircuitBreakerView.class, test); 112 | } 113 | 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/EmptyCircuitBreakersEndpointTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.web.client.TestRestTemplate; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import java.util.Map; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 14 | import static org.springframework.http.HttpEntity.EMPTY; 15 | import static org.springframework.http.HttpMethod.GET; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest(classes = NoCircuitBreakersApplication.class, webEnvironment = RANDOM_PORT) 19 | public class EmptyCircuitBreakersEndpointTest { 20 | 21 | @Autowired 22 | private TestRestTemplate http; 23 | 24 | @Test 25 | public void shouldReadAll() { 26 | final Map breakers = readAll().getCircuitBreakers(); 27 | 28 | assertEquals(0, breakers.size()); 29 | } 30 | 31 | private Container readAll() { 32 | return http.exchange("/actuator/circuitbreakers", GET, EMPTY, Container.class).getBody(); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/NoCircuitBreakersApplication.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import org.springframework.boot.SpringBootConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | 6 | @SpringBootConfiguration 7 | @EnableAutoConfiguration 8 | public class NoCircuitBreakersApplication { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import org.springframework.boot.Banner; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.core.env.Environment; 7 | 8 | import java.io.PrintStream; 9 | 10 | @SpringBootApplication 11 | public class SampleApplication { 12 | 13 | public static void main(final String[] args) { 14 | new SpringApplicationBuilder(SampleApplication.class) 15 | .logStartupInfo(false) 16 | .banner(new SampleApplicationBanner()) 17 | .run(args); 18 | } 19 | 20 | private static class SampleApplicationBanner implements Banner { 21 | 22 | @Override 23 | public void printBanner(final Environment environment, final Class sourceClass, final PrintStream out) { 24 | final String port = environment.getProperty("server.port"); 25 | String banner = ""; 26 | banner += "Failsafe-Actuator sample applicaton is running!\n"; 27 | banner += "\n"; 28 | banner += "See the circuit breaker status:\n"; 29 | banner += " $ curl http://127.0.0.1:" + port + "/failsafe\n"; 30 | banner += "Unreliable endpoint that fails every second invocation:\n"; 31 | banner += " $ curl http://127.0.0.1:" + port + "/unreliable\n"; 32 | banner += "Reliable endpoint using a circuit breaker and fallback:\n"; 33 | banner += " $ curl http://127.0.0.1:" + port + "/reliable\n"; 34 | banner += "Reliable endpoint using a circuit breaker with 5 second delay and fallback:\n"; 35 | banner += " $ curl http://127.0.0.1:" + port + "/reliableWithDelay\n"; 36 | out.print(banner); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/actuate/failsafe/SampleController.java: -------------------------------------------------------------------------------- 1 | package org.zalando.actuate.failsafe; 2 | 3 | import net.jodah.failsafe.CircuitBreaker; 4 | import net.jodah.failsafe.Failsafe; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | @Controller 15 | @ResponseBody 16 | public class SampleController { 17 | 18 | private final AtomicBoolean fail = new AtomicBoolean(); 19 | 20 | @Autowired 21 | private CircuitBreaker test; 22 | 23 | @Autowired 24 | private CircuitBreaker delay; 25 | 26 | @PostConstruct 27 | private void init() { 28 | delay.withDelay(5, TimeUnit.SECONDS); 29 | } 30 | 31 | @RequestMapping("/unreliable") 32 | public String sayHelloWorldUnreliable() { 33 | return getMessage(); 34 | } 35 | 36 | @RequestMapping("/reliable") 37 | public String sayHelloWorldReliable() { 38 | return Failsafe.with(test).withFallback("Service unavailable").get(this::getMessage); 39 | } 40 | 41 | @RequestMapping("/reliableWithDelay") 42 | public String sayHelloWorldReliableWithDelay() { 43 | return Failsafe.with(delay).withFallback("Service unavailable").get(this::getMessage); 44 | } 45 | 46 | private String getMessage() { 47 | if (fail.compareAndSet(true, false)) { 48 | throw new RuntimeException("Every second try fails"); 49 | } else if (fail.compareAndSet(false, true)) { 50 | return "Hello world!"; 51 | } else { 52 | throw new IllegalStateException(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server.port: 8080 2 | logging.level.root: WARN 3 | management.endpoints.web.exposure.include: '*' 4 | --------------------------------------------------------------------------------