├── .gitignore ├── .mvn ├── jvm.config ├── maven.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .settings.xml ├── .travis.yml ├── Guardfile ├── LICENSE ├── LICENSE.txt ├── README.adoc ├── README.md ├── docs ├── pom.xml └── src │ └── main │ ├── asciidoc │ ├── README.adoc │ ├── ghpages.sh │ ├── intro.adoc │ ├── leaderelection.adoc │ └── spring-cloud-cluster.adoc │ └── ruby │ └── generate_readme.sh ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spring-cloud-cluster-autoconfigure ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── springframework │ │ │ └── cloud │ │ │ └── cluster │ │ │ └── autoconfigure │ │ │ ├── leader │ │ │ ├── EtcdLeaderAutoConfiguration.java │ │ │ ├── HazelcastLeaderAutoConfiguration.java │ │ │ ├── LeaderAutoConfiguration.java │ │ │ └── ZookeeperLeaderAutoConfiguration.java │ │ │ └── lock │ │ │ └── RedisLockServiceAutoConfiguration.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ ├── java │ └── org │ │ └── springframework │ │ └── cloud │ │ └── cluster │ │ └── autoconfigure │ │ ├── TestUtils.java │ │ ├── leader │ │ ├── AbstractLeaderAutoConfigurationTests.java │ │ ├── EtcdLeaderAutoConfigurationTests.java │ │ ├── HazelcastLeaderAutoConfigurationTests.java │ │ ├── LeaderAutoConfigurationTests.java │ │ └── ZookeeperLeaderAutoConfigurationTests.java │ │ └── lock │ │ ├── AbstractLockAutoConfigurationTests.java │ │ └── RedisLockServiceAutoConfigurationTests.java │ └── resources │ └── foobar.xml ├── spring-cloud-cluster-core ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── springframework │ │ └── cloud │ │ └── cluster │ │ ├── leader │ │ ├── AbstractCandidate.java │ │ ├── Candidate.java │ │ ├── Context.java │ │ ├── DefaultCandidate.java │ │ ├── LeaderElectionProperties.java │ │ └── event │ │ │ ├── AbstractLeaderEvent.java │ │ │ ├── DefaultLeaderEventPublisher.java │ │ │ ├── LeaderEventPublisher.java │ │ │ ├── LeaderEventPublisherConfiguration.java │ │ │ ├── LoggingListener.java │ │ │ ├── OnGrantedEvent.java │ │ │ └── OnRevokedEvent.java │ │ └── lock │ │ ├── DistributedLock.java │ │ ├── DistributedLockProperties.java │ │ ├── LockRegistry.java │ │ ├── LockService.java │ │ ├── LockServiceLocator.java │ │ ├── LockingException.java │ │ └── support │ │ ├── AbstractDistributedLock.java │ │ ├── DefaultLockRegistry.java │ │ ├── DefaultLockServiceLocator.java │ │ └── DelegatingDistributedLock.java │ └── test │ └── java │ └── org │ └── springframework │ └── cloud │ └── cluster │ ├── TestUtils.java │ └── lock │ ├── AbstractLockingTests.java │ └── support │ ├── DefaultLockRegistryTests.java │ └── DefaultLockServiceLocatorTests.java ├── spring-cloud-cluster-dependencies └── pom.xml ├── spring-cloud-cluster-etcd ├── docker-compose.yml ├── pom.xml ├── scripts │ └── run_etcd.sh └── src │ ├── main │ └── java │ │ └── org │ │ └── springframework │ │ └── cloud │ │ └── cluster │ │ └── etcd │ │ ├── EtcdClusterProperties.java │ │ └── leader │ │ └── LeaderInitiator.java │ └── test │ └── java │ └── org │ └── springframework │ └── cloud │ └── cluster │ └── etcd │ └── leader │ └── EtcdTests.java ├── spring-cloud-cluster-hazelcast ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── springframework │ │ └── cloud │ │ └── cluster │ │ └── hazelcast │ │ ├── HazelcastClusterProperties.java │ │ └── leader │ │ └── LeaderInitiator.java │ └── test │ └── java │ └── org │ └── springframework │ └── cloud │ └── cluster │ └── hazelcast │ └── leader │ └── HazelcastTests.java ├── spring-cloud-cluster-redis ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── springframework │ │ └── cloud │ │ └── cluster │ │ └── redis │ │ ├── RedisClusterProperties.java │ │ └── lock │ │ └── RedisLockService.java │ └── test │ └── java │ └── org │ └── springframework │ └── cloud │ └── cluster │ └── redis │ └── lock │ └── RedisIT.java └── spring-cloud-cluster-zookeeper ├── .gitignore ├── pom.xml └── src ├── main └── java │ └── org │ └── springframework │ └── cloud │ └── cluster │ └── zk │ ├── ZookeeperClusterProperties.java │ ├── curator │ └── CuratorFrameworkFactoryBean.java │ └── leader │ └── LeaderInitiator.java └── test └── java └── org └── springframework └── cloud └── cluster └── zk ├── curator └── CuratorFrameworkFactoryBeanTests.java └── leader └── ZookeeperTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | *# 4 | .#* 5 | .classpath 6 | .project 7 | .settings 8 | .springBeans 9 | .gradle 10 | build 11 | bin 12 | target 13 | *.swp 14 | .idea 15 | *.iml 16 | .factorypath 17 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-cluster/d6779ff522f50715a0d406ddd76d21b776174f4f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip -------------------------------------------------------------------------------- /.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | repo.spring.io 6 | ${env.CI_DEPLOY_USERNAME} 7 | ${env.CI_DEPLOY_PASSWORD} 8 | 9 | 10 | 11 | 12 | 18 | spring 19 | true 20 | 21 | 22 | spring-snapshots 23 | Spring Snapshots 24 | https://repo.spring.io/libs-snapshot-local 25 | 26 | true 27 | 28 | 29 | 30 | spring-milestones 31 | Spring Milestones 32 | https://repo.spring.io/libs-milestone-local 33 | 34 | false 35 | 36 | 37 | 38 | spring-releases 39 | Spring Releases 40 | https://repo.spring.io/release 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | spring-snapshots 49 | Spring Snapshots 50 | https://repo.spring.io/libs-snapshot-local 51 | 52 | true 53 | 54 | 55 | 56 | spring-milestones 57 | Spring Milestones 58 | https://repo.spring.io/libs-milestone-local 59 | 60 | false 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: 3 | directories: 4 | - $HOME/.m2 5 | language: java 6 | jdk: 7 | - oraclejdk8 8 | services: 9 | - redis 10 | - docker 11 | before_install: 12 | - git config user.name "$GIT_NAME" 13 | - git config user.email "$GIT_EMAIL" 14 | - git config credential.helper "store --file=.git/credentials" 15 | - echo "https://$GH_TOKEN:@github.com" > .git/credentials 16 | - gem install asciidoctor 17 | - docker-compose -f spring-cloud-cluster-etcd/docker-compose.yml start 18 | install: 19 | - ./mvnw install -P docs -q -U -DskipTests=true -Dmaven.test.redirectTestOutputToFile=true 20 | - '[ "${MVN_GOAL}" == "deploy" ] && ./docs/src/main/asciidoc/ghpages.sh || echo "Not updating docs"' 21 | script: 22 | - './mvnw -s .settings.xml $MVN_GOAL $MVN_PROFILE -nsu -Dmaven.test.redirectTestOutputToFile=true' 23 | env: 24 | global: 25 | - GIT_NAME="Dave Syer" 26 | - GIT_EMAIL=dsyer@pivotal.io 27 | - CI_DEPLOY_USERNAME=buildmaster 28 | - FEATURE_BRANCH=$(echo ${TRAVIS_BRANCH} | grep -q "^.*/.*$" && echo true || echo false) 29 | - SPRING_CLOUD_BUILD=$(echo ${TRAVIS_REPO_SLUG} | grep -q "^spring-cloud/.*$" && echo true || echo false) 30 | - MVN_GOAL=$([ "${TRAVIS_PULL_REQUEST}" == "false" -a "${TRAVIS_TAG}" == "" -a "${FEATURE_BRANCH}" == "false" -a "${SPRING_CLOUD_BUILD}" == "true" ] && echo deploy || echo install) 31 | - VERSION=$(mvn validate | grep Building | head -1 | sed -e 's/.* //') 32 | - MILESTONE=$(echo ${VERSION} | egrep 'M|RC' && echo true || echo false) 33 | - MVN_PROFILE=$([ "${MILESTONE}" == "true" ] && echo -P milestone) 34 | - secure: "pMlvX73tFb+cUYIlinof7HkNH6iYsCRv3xsT/GrVoMY8gFhYy6x1Ki/prCHr+ssSDjTlLi/HlecJz5kH4HHYZyoxDoO52cE5tHDyvjRrkLZiGq7p0T5m2FLLiVk62KunGQP+KjCUQfLnMPKz1FxDRO2lDHRZw4VhLxLmj1awKFU=" 35 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | require 'asciidoctor' 2 | require 'erb' 3 | 4 | options = {:mkdirs => true, :safe => :unsafe, :attributes => ['linkcss', 'allow-uri-read']} 5 | 6 | guard 'shell' do 7 | watch(/^[A-Z-a-z][^#]*\.adoc$/) {|m| 8 | Asciidoctor.render_file('src/main/asciidoc/README.adoc', options.merge(:to_file => './README.md')) 9 | Asciidoctor.render_file('src/main/asciidoc/spring-cloud-cluster.adoc', options.merge(:to_dir => 'target/generated-docs')) 10 | } 11 | end 12 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | // Do not edit this file (e.g. go instead to src/main/asciidoc) 2 | 3 | # spring-cloud-cluster is no longer actively maintained by VMware, Inc. 4 | 5 | 6 | 7 | This project is now superseded by code in Spring Integration. All the interfaces 8 | here have counterparts in the main Spring libraries, and the implementations are 9 | mostly parallel too (etcd is an exception because of the lack of support for 10 | v3 in third party libraries). The 1.0.1 release deprecates everything in favour 11 | of Spring Integration. 12 | 13 | N.B. For Hazelcast the support is in 14 | https://github.com/spring-projects/spring-integration-extensions/tree/master/spring-integration-hazelcast[an 15 | extension project]. 16 | 17 | Spring Cloud Cluster offers a set of primitives for building "cluster" 18 | features into a distributed system. Example are leadership election, 19 | consistent storage of cluster state, global locks and one-time tokens. 20 | 21 | == Building 22 | 23 | :jdkversion: 1.7 24 | 25 | === Basic Compile and Test 26 | 27 | To build the source you will need to install JDK {jdkversion}. 28 | 29 | Spring Cloud uses Maven for most build-related activities, and you 30 | should be able to get off the ground quite quickly by cloning the 31 | project you are interested in and typing 32 | 33 | ---- 34 | $ ./mvnw install 35 | ---- 36 | 37 | NOTE: You can also install Maven (>=3.3.3) yourself and run the `mvn` command 38 | in place of `./mvnw` in the examples below. If you do that you also 39 | might need to add `-P spring` if your local Maven settings do not 40 | contain repository declarations for spring pre-release artifacts. 41 | 42 | NOTE: Be aware that you might need to increase the amount of memory 43 | available to Maven by setting a `MAVEN_OPTS` environment variable with 44 | a value like `-Xmx512m -XX:MaxPermSize=128m`. We try to cover this in 45 | the `.mvn` configuration, so if you find you have to do it to make a 46 | build succeed, please raise a ticket to get the settings added to 47 | source control. 48 | 49 | For hints on how to build the project look in `.travis.yml` if there 50 | is one. There should be a "script" and maybe "install" command. Also 51 | look at the "services" section to see if any services need to be 52 | running locally (e.g. mongo or rabbit). Ignore the git-related bits 53 | that you might find in "before_install" since they're related to setting git 54 | credentials and you already have those. 55 | 56 | The projects that require middleware generally include a 57 | `docker-compose.yml`, so consider using 58 | http://compose.docker.io/[Docker Compose] to run the middeware servers 59 | in Docker containers. See the README in the 60 | https://github.com/spring-cloud-samples/scripts[scripts demo 61 | repository] for specific instructions about the common cases of mongo, 62 | rabbit and redis. 63 | 64 | NOTE: If all else fails, build with the command from `.travis.yml` (usually 65 | `./mvnw install`). 66 | 67 | === Documentation 68 | 69 | The spring-cloud-build module has a "docs" profile, and if you switch 70 | that on it will try to build asciidoc sources from 71 | `src/main/asciidoc`. As part of that process it will look for a 72 | `README.adoc` and process it by loading all the includes, but not 73 | parsing or rendering it, just copying it to `${main.basedir}` 74 | (defaults to `${basedir}`, i.e. the root of the project). If there are 75 | any changes in the README it will then show up after a Maven build as 76 | a modified file in the correct place. Just commit it and push the change. 77 | 78 | === Working with the code 79 | If you don't have an IDE preference we would recommend that you use 80 | http://www.springsource.com/developer/sts[Spring Tools Suite] or 81 | http://eclipse.org[Eclipse] when working with the code. We use the 82 | http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools 83 | should also work without issue as long as they use Maven 3.3.3 or better. 84 | 85 | ==== Importing into eclipse with m2eclipse 86 | We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with 87 | eclipse. If you don't already have m2eclipse installed it is available from the "eclipse 88 | marketplace". 89 | 90 | NOTE: Older versions of m2e do not support Maven 3.3, so once the 91 | projects are imported into Eclipse you will also need to tell 92 | m2eclipse to use the right profile for the projects. If you 93 | see many different errors related to the POMs in the projects, check 94 | that you have an up to date installation. If you can't upgrade m2e, 95 | add the "spring" profile to your `settings.xml`. Alternatively you can 96 | copy the repository settings from the "spring" profile of the parent 97 | pom into your `settings.xml`. 98 | 99 | ==== Importing into eclipse without m2eclipse 100 | If you prefer not to use m2eclipse you can generate eclipse project metadata using the 101 | following command: 102 | 103 | [indent=0] 104 | ---- 105 | $ ./mvnw eclipse:eclipse 106 | ---- 107 | 108 | The generated eclipse projects can be imported by selecting `import existing projects` 109 | from the `file` menu. 110 | 111 | 112 | == Contributing 113 | 114 | Spring Cloud is released under the non-restrictive Apache 2.0 license, 115 | and follows a very standard Github development process, using Github 116 | tracker for issues and merging pull requests into master. If you want 117 | to contribute even something trivial please do not hesitate, but 118 | follow the guidelines below. 119 | 120 | === Sign the Contributor License Agreement 121 | Before we accept a non-trivial patch or pull request we will need you to sign the 122 | https://cla.pivotal.io/sign/spring[Contributor License Agreement]. 123 | Signing the contributor's agreement does not grant anyone commit rights to the main 124 | repository, but it does mean that we can accept your contributions, and you will get an 125 | author credit if we do. Active contributors might be asked to join the core team, and 126 | given the ability to merge pull requests. 127 | 128 | === Code of Conduct 129 | This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc[code of 130 | conduct]. By participating, you are expected to uphold this code. Please report 131 | unacceptable behavior to spring-code-of-conduct@pivotal.io. 132 | 133 | === Code Conventions and Housekeeping 134 | None of these is essential for a pull request, but they will all help. They can also be 135 | added after the original pull request but before a merge. 136 | 137 | * Use the Spring Framework code format conventions. If you use Eclipse 138 | you can import formatter settings using the 139 | `eclipse-code-formatter.xml` file from the 140 | https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring 141 | Cloud Build] project. If using IntelliJ, you can use the 142 | http://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter 143 | Plugin] to import the same file. 144 | * Make sure all new `.java` files to have a simple Javadoc class comment with at least an 145 | `@author` tag identifying you, and preferably at least a paragraph on what the class is 146 | for. 147 | * Add the ASF license header comment to all new `.java` files (copy from existing files 148 | in the project) 149 | * Add yourself as an `@author` to the .java files that you modify substantially (more 150 | than cosmetic changes). 151 | * Add some Javadocs and, if you change the namespace, some XSD doc elements. 152 | * A few unit tests would help a lot as well -- someone has to do it. 153 | * If no-one else is using your branch, please rebase it against the current master (or 154 | other target branch in the main project). 155 | * When writing a commit message please follow http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], 156 | if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit 157 | message (where XXXX is the issue number). 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-cloud-cluster is no longer actively maintained by VMware, Inc. 2 | 3 | -------------------------------------------------------------------------------- /docs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.springframework.cloud 5 | spring-cloud-cluster-docs 6 | 7 | org.springframework.cloud 8 | spring-cloud-cluster 9 | 1.0.2.RELEASE 10 | 11 | jar 12 | spring-cloud-cluster-docs 13 | Spring Cloud Docs 14 | 15 | 16 | spring-cloud-cluster 17 | ${basedir}/.. 18 | 1.0.x 19 | 20 | 21 | 22 | 23 | docs 24 | 25 | 26 | 27 | org.asciidoctor 28 | asciidoctor-maven-plugin 29 | false 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-antrun-plugin 34 | false 35 | 36 | 37 | org.codehaus.mojo 38 | build-helper-maven-plugin 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/README.adoc: -------------------------------------------------------------------------------- 1 | This project is now superseded by code in Spring Integration. All the interfaces 2 | here have counterparts in the main Spring libraries, and the implementations are 3 | mostly parallel too (etcd is an exception because of the lack of support for 4 | v3 in third party libraries). The 1.0.1 release deprecates everything in favour 5 | of Spring Integration. 6 | 7 | N.B. For Hazelcast the support is in 8 | https://github.com/spring-projects/spring-integration-extensions/tree/master/spring-integration-hazelcast[an 9 | extension project]. 10 | 11 | include::intro.adoc[] 12 | 13 | == Building 14 | 15 | include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[] 16 | 17 | == Contributing 18 | 19 | include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[] 20 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/intro.adoc: -------------------------------------------------------------------------------- 1 | Spring Cloud Cluster offers a set of primitives for building "cluster" 2 | features into a distributed system. Example are leadership election, 3 | consistent storage of cluster state, global locks and one-time tokens. 4 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/leaderelection.adoc: -------------------------------------------------------------------------------- 1 | == Leader Election 2 | 3 | Leader election allows application to work together with other 4 | applications to coordinate a cluster leadership via a third party system. 5 | Currently we provide integrations with `zookeeper`, `hazelcast` and `etcd`. 6 | 7 | From user perspective election is working via interfaces 8 | `org.springframework.cloud.cluster.leader.Candidate` and 9 | `org.springframework.cloud.cluster.leader.Context`. `Candidate` 10 | contains access to leadership's `role` and `id` and also have methods 11 | `onGranted` and `onRevoked`. These callback methods are useful if 12 | default `Candidate` implementation is changed. 13 | 14 | Leader election is auto-configured if `spring-cloud-cluster-autoconfigure` 15 | and either `spring-cloud-cluster-hazelcast`, `spring-cloud-cluster-zookeeper` 16 | or `spring-cloud-cluster-etcd` jars are found from a classpath. In 17 | case where both jars are found leader election is created using both 18 | systems. See sections <>, 19 | <> and 20 | <> for more 21 | information about a created beans. 22 | 23 | Default `Candidate` created from auto-configuration is 24 | `org.springframework.cloud.cluster.leader.DefaultCandidate` which 25 | currently only logs granted and revoked events. 26 | 27 | If there's a need for disable all leader related auto-configuration, 28 | a `spring.cloud.cluster.leader.enabled` can be set to false which 29 | then allows to do manual configuration even if the jars an on a 30 | classpath. Properties `spring.cloud.cluster.leader.id` and 31 | `spring.cloud.cluster.leader.role` can be used to set default 32 | identifier and role. 33 | 34 | If you are interested to simple get notification of granted and 35 | revoked events one option is to attach event listener into spring 36 | application context. Events `OnGrantedEvent` and `OnRevokedEvent` are 37 | sent as spring event objects. 38 | 39 | Simply create your own event listener class: 40 | [source,java] 41 | ---- 42 | class MyEventListener implements ApplicationListener { 43 | 44 | @Override 45 | public void onApplicationEvent(AbstractLeaderEvent event) { 46 | // do something with OnGrantedEvent or OnRevokedEvent 47 | } 48 | } 49 | ---- 50 | 51 | and then create it as a bean. 52 | 53 | [source,java] 54 | ---- 55 | @Configuration 56 | static class Config { 57 | @Bean 58 | public MyEventListener myEventListener() { 59 | return new MyEventListener(); 60 | } 61 | } 62 | ---- 63 | 64 | For simply log events you can also use a utility class 65 | `LoggingListener` which allows easy configuration. 66 | 67 | [source,java] 68 | ---- 69 | import org.springframework.cloud.cluster.leader.event.LoggingListener; 70 | 71 | @Configuration 72 | static class Config { 73 | @Bean 74 | public LoggingListener loggingListener() { 75 | return new LoggingListener("info"); 76 | } 77 | } 78 | ---- 79 | 80 | [[spring-cloud-cluster-leaderelection-zookeeper]] 81 | === Zookeeper 82 | `Candidate` implementation for zookeeper is created with a bean name 83 | `zookeeperLeaderCandidate` which can be used to override the one 84 | created during auto-configuration. 85 | 86 | Zookeeper based election can be explicitly disabled using property 87 | `spring.cloud.cluster.zookeeper.leader.enabled`. 88 | 89 | Other properties `spring.cloud.cluster.zookeeper.namespace` and 90 | `spring.cloud.cluster.zookeeper.connect` can be used to set the 91 | zookeeper base namespace path and connect string. 92 | 93 | [[spring-cloud-cluster-leaderelection-hazelcast]] 94 | === Hazelcast 95 | `Candidate` implementation for hazelcast is created with a bean name 96 | `hazelcastLeaderCandidate` which can be used to override the one 97 | created during auto-configuration. 98 | 99 | Hazelcast based election can be explicitly disabled using property 100 | `spring.cloud.cluster.hazelcast.leader.enabled`. If you want to provide xml 101 | based configuration for Hazelcast instance use property 102 | `spring.cloud.cluster.hazelcast.config-location` to tell location of a 103 | Hazelcast xml configuration file. `config-location` is a normal spring 104 | `Resource`. 105 | 106 | [[spring-cloud-cluster-leaderelection-etcd]] 107 | === Etcd 108 | `Candidate` implementation for etcd is created with a bean name 109 | `etcdLeaderCandidate` which can be used to override the one 110 | created during auto-configuration. 111 | 112 | Etcd based election can be explicitly disabled using property 113 | `spring.cloud.cluster.etcd.leader.enabled`. 114 | 115 | Multiple etcd cluster uris can be specified using property 116 | `spring.cloud.cluster.etcd.connect` 117 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/spring-cloud-cluster.adoc: -------------------------------------------------------------------------------- 1 | = Spring Cloud Cluster 2 | :nofooter: 3 | 4 | include::intro.adoc[] 5 | 6 | include::leaderelection.adoc[] 7 | -------------------------------------------------------------------------------- /docs/src/main/ruby/generate_readme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | base_dir = File.join(File.dirname(__FILE__),'../../..') 4 | src_dir = File.join(base_dir, "/src/main/asciidoc") 5 | require 'asciidoctor' 6 | require 'optparse' 7 | 8 | options = {} 9 | file = "#{src_dir}/README.adoc" 10 | 11 | OptionParser.new do |o| 12 | o.on('-o OUTPUT_FILE', 'Output file (default is stdout)') { |file| options[:to_file] = file unless file=='-' } 13 | o.on('-h', '--help') { puts o; exit } 14 | o.parse! 15 | end 16 | 17 | file = ARGV[0] if ARGV.length>0 18 | 19 | # Copied from https://github.com/asciidoctor/asciidoctor-extensions-lab/blob/master/scripts/asciidoc-coalescer.rb 20 | doc = Asciidoctor.load_file file, safe: :unsafe, header_only: true, attributes: options[:attributes] 21 | header_attr_names = (doc.instance_variable_get :@attributes_modified).to_a 22 | header_attr_names.each {|k| doc.attributes[%(#{k}!)] = '' unless doc.attr? k } 23 | attrs = doc.attributes 24 | attrs['allow-uri-read'] = true 25 | puts attrs 26 | 27 | out = "// Do not edit this file (e.g. go instead to src/main/asciidoc)\n\n" 28 | doc = Asciidoctor.load_file file, safe: :unsafe, parse: false, attributes: attrs 29 | out << doc.reader.read 30 | 31 | unless options[:to_file] 32 | puts out 33 | else 34 | File.open(options[:to_file],'w+') do |file| 35 | file.write(out) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | echo "Running version check" 230 | VERSION=$( sed '\!//' -e 's!.*$!!' ) 231 | echo "The found version is [${VERSION}]" 232 | 233 | if echo $VERSION | egrep -q 'M|RC'; then 234 | echo Activating \"milestone\" profile for version=\"$VERSION\" 235 | echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" 236 | else 237 | echo Deactivating \"milestone\" profile for version=\"$VERSION\" 238 | echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') 239 | fi 240 | 241 | exec "$JAVACMD" \ 242 | $MAVEN_OPTS \ 243 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 244 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 245 | ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" 246 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% 146 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | spring-cloud-cluster 7 | pom 8 | spring-cloud-cluster 9 | Spring Cloud Cluster 10 | 1.0.2.RELEASE 11 | 12 | 13 | org.springframework.cloud 14 | spring-cloud-build 15 | 1.1.1.RELEASE 16 | 17 | 18 | 19 | 20 | 21 | spring-cloud-cluster-dependencies 22 | spring-cloud-cluster-core 23 | spring-cloud-cluster-zookeeper 24 | spring-cloud-cluster-hazelcast 25 | spring-cloud-cluster-etcd 26 | spring-cloud-cluster-redis 27 | spring-cloud-cluster-autoconfigure 28 | docs 29 | 30 | 31 | 32 | https://github.com/spring-cloud/spring-cloud-cluster 33 | scm:git:git://github.com/spring-cloud/spring-cloud-cluster.git 34 | scm:git:ssh://git@github.com/spring-cloud/spring-cloud-cluster.git 35 | HEAD 36 | 37 | 38 | 39 | UTF-8 40 | 1.7 41 | 2.8.0 42 | 3.3 43 | 2.7.0 44 | true 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.cloud 51 | spring-cloud-cluster-dependencies 52 | ${project.version} 53 | pom 54 | import 55 | 56 | 57 | org.apache.curator 58 | curator-recipes 59 | ${curator.version} 60 | 61 | 62 | org.apache.curator 63 | curator-test 64 | ${curator.version} 65 | 66 | 67 | hazelcast 68 | com.hazelcast 69 | ${hazelcast.version} 70 | 71 | 72 | hazelcast-spring 73 | com.hazelcast 74 | ${hazelcast.version} 75 | 76 | 77 | org.mousio 78 | etcd4j 79 | ${etcd4j.version} 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | maven-failsafe-plugin 88 | 89 | ${skipITs} 90 | 91 | 92 | 93 | package 94 | 95 | integration-test 96 | verify 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | spring 107 | 108 | 109 | spring-snapshots 110 | Spring Snapshots 111 | https://repo.spring.io/libs-snapshot-local 112 | 113 | true 114 | 115 | 116 | false 117 | 118 | 119 | 120 | spring-milestones 121 | Spring Milestones 122 | https://repo.spring.io/libs-milestone-local 123 | 124 | false 125 | 126 | 127 | 128 | spring-releases 129 | Spring Releases 130 | https://repo.spring.io/release 131 | 132 | false 133 | 134 | 135 | 136 | 137 | 138 | spring-snapshots 139 | Spring Snapshots 140 | https://repo.spring.io/libs-snapshot-local 141 | 142 | true 143 | 144 | 145 | false 146 | 147 | 148 | 149 | spring-milestones 150 | Spring Milestones 151 | https://repo.spring.io/libs-milestone-local 152 | 153 | false 154 | 155 | 156 | 157 | spring-releases 158 | Spring Releases 159 | https://repo.spring.io/libs-release-local 160 | 161 | false 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/.gitignore: -------------------------------------------------------------------------------- 1 | /.apt_generated/ 2 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-cluster-autoconfigure 8 | jar 9 | 10 | spring-cloud-cluster-autoconfigure 11 | Spring Cloud Cluster AutoConfigure 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-cluster 16 | 1.0.2.RELEASE 17 | .. 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-cluster-zookeeper 24 | true 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-cluster-hazelcast 29 | true 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-cluster-etcd 34 | true 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-cluster-redis 39 | true 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-configuration-processor 44 | true 45 | 46 | 47 | org.apache.curator 48 | curator-test 49 | test 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/java/org/springframework/cloud/cluster/autoconfigure/leader/EtcdLeaderAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import java.net.URI; 19 | 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.cloud.cluster.etcd.EtcdClusterProperties; 27 | import org.springframework.cloud.cluster.etcd.leader.LeaderInitiator; 28 | import org.springframework.cloud.cluster.leader.Candidate; 29 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 30 | import org.springframework.cloud.cluster.leader.LeaderElectionProperties; 31 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 32 | import org.springframework.context.annotation.Bean; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.util.StringUtils; 35 | 36 | import mousio.etcd4j.EtcdClient; 37 | 38 | /** 39 | * Auto-configuration for etcd leader election. 40 | * 41 | * @author Venil Noronha 42 | */ 43 | @Configuration 44 | @ConditionalOnClass(LeaderInitiator.class) 45 | @ConditionalOnProperty(value = { "spring.cloud.cluster.leader.enabled", 46 | "spring.cloud.cluster.etcd.leader.enabled" }, matchIfMissing = true) 47 | @ConditionalOnMissingBean(name = "etcdLeaderInitiator") 48 | @EnableConfigurationProperties({ LeaderElectionProperties.class, 49 | EtcdClusterProperties.class }) 50 | @AutoConfigureAfter(LeaderAutoConfiguration.class) 51 | public class EtcdLeaderAutoConfiguration { 52 | 53 | @Autowired 54 | private LeaderElectionProperties lep; 55 | 56 | @Autowired 57 | private EtcdClusterProperties ecp; 58 | 59 | @Autowired 60 | private LeaderEventPublisher publisher; 61 | 62 | @Bean 63 | public Candidate etcdLeaderCandidate() { 64 | return new DefaultCandidate(lep.getId(), lep.getRole()); 65 | } 66 | 67 | @Bean 68 | public EtcdClient etcdInstance() { 69 | String[] uriList = StringUtils.commaDelimitedListToStringArray(ecp.getConnect()); 70 | URI[] uris = new URI[uriList.length]; 71 | for (int i = 0; i < uriList.length; i ++) { 72 | uris[i] = URI.create(uriList[i]); 73 | } 74 | return new EtcdClient(uris); 75 | } 76 | 77 | @Bean 78 | public LeaderInitiator etcdLeaderInitiator() { 79 | LeaderInitiator initiator = new LeaderInitiator(etcdInstance(), etcdLeaderCandidate(), ecp.getNamespace()); 80 | initiator.setLeaderEventPublisher(publisher); 81 | return initiator; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/java/org/springframework/cloud/cluster/autoconfigure/leader/HazelcastLeaderAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import java.io.IOException; 19 | 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.cloud.cluster.hazelcast.HazelcastClusterProperties; 27 | import org.springframework.cloud.cluster.hazelcast.leader.LeaderInitiator; 28 | import org.springframework.cloud.cluster.leader.Candidate; 29 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 30 | import org.springframework.cloud.cluster.leader.LeaderElectionProperties; 31 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 32 | import org.springframework.context.annotation.Bean; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.core.io.Resource; 35 | 36 | import com.hazelcast.config.Config; 37 | import com.hazelcast.config.XmlConfigBuilder; 38 | import com.hazelcast.core.Hazelcast; 39 | import com.hazelcast.core.HazelcastInstance; 40 | 41 | /** 42 | * Auto-configuration for hazelcast leader election. 43 | * 44 | * @author Janne Valkealahti 45 | * 46 | */ 47 | @Configuration 48 | @ConditionalOnClass(LeaderInitiator.class) 49 | @ConditionalOnProperty(value = { "spring.cloud.cluster.leader.enabled", 50 | "spring.cloud.cluster.hazelcast.leader.enabled" }, matchIfMissing = true) 51 | @ConditionalOnMissingBean(name = "hazelcastLeaderInitiator") 52 | @EnableConfigurationProperties({ LeaderElectionProperties.class, 53 | HazelcastClusterProperties.class }) 54 | @AutoConfigureAfter(LeaderAutoConfiguration.class) 55 | public class HazelcastLeaderAutoConfiguration { 56 | 57 | @Autowired 58 | private LeaderElectionProperties lep; 59 | 60 | @Autowired 61 | private HazelcastClusterProperties hp; 62 | 63 | @Autowired 64 | private LeaderEventPublisher publisher; 65 | 66 | @Bean 67 | public Candidate hazelcastLeaderCandidate() { 68 | return new DefaultCandidate(lep.getId(), lep.getRole()); 69 | } 70 | 71 | @Bean 72 | public HazelcastInstance hazelcastInstance() { 73 | return Hazelcast.newHazelcastInstance(hazelcastConfig()); 74 | } 75 | 76 | @Bean 77 | public LeaderInitiator hazelcastLeaderInitiator() { 78 | LeaderInitiator initiator = new LeaderInitiator(hazelcastInstance(), 79 | hazelcastLeaderCandidate()); 80 | initiator.setLeaderEventPublisher(publisher); 81 | return initiator; 82 | } 83 | 84 | @Bean 85 | public Config hazelcastConfig() { 86 | Resource location = hp.getConfigLocation(); 87 | if (location != null && location.exists()) { 88 | try { 89 | return new XmlConfigBuilder(hp.getConfigLocation() 90 | .getInputStream()).build(); 91 | } catch (IOException e) { 92 | throw new IllegalArgumentException( 93 | "Unable to use config location " + location, e); 94 | } 95 | } else { 96 | return new Config(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/java/org/springframework/cloud/cluster/autoconfigure/leader/LeaderAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 20 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 21 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisherConfiguration; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.Import; 24 | 25 | /** 26 | * Auto-configuration for generic leader election components. 27 | * 28 | * @author Janne Valkealahti 29 | * 30 | */ 31 | @Configuration 32 | @ConditionalOnClass(LeaderEventPublisher.class) 33 | @ConditionalOnProperty(value = { "spring.cloud.cluster.leader.enabled" }, matchIfMissing = true) 34 | @Import(LeaderEventPublisherConfiguration.class) 35 | public class LeaderAutoConfiguration { 36 | } 37 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/java/org/springframework/cloud/cluster/autoconfigure/leader/ZookeeperLeaderAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import org.apache.curator.framework.CuratorFramework; 19 | import org.apache.curator.framework.CuratorFrameworkFactory; 20 | import org.apache.curator.retry.ExponentialBackoffRetry; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.cloud.cluster.leader.Candidate; 28 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 29 | import org.springframework.cloud.cluster.leader.LeaderElectionProperties; 30 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 31 | import org.springframework.cloud.cluster.zk.ZookeeperClusterProperties; 32 | import org.springframework.cloud.cluster.zk.leader.LeaderInitiator; 33 | import org.springframework.context.annotation.Bean; 34 | import org.springframework.context.annotation.Configuration; 35 | 36 | /** 37 | * Auto-configuration for zookeeper leader election. 38 | * 39 | * @author Janne Valkealahti 40 | * 41 | */ 42 | @Configuration 43 | @ConditionalOnClass(LeaderInitiator.class) 44 | @ConditionalOnProperty(value = { "spring.cloud.cluster.zookeeper.leader.enabled", 45 | "spring.cloud.cluster.leader.enabled" }, matchIfMissing = true) 46 | @ConditionalOnMissingBean(name = "zookeeperLeaderInitiator") 47 | @EnableConfigurationProperties({ LeaderElectionProperties.class, 48 | ZookeeperClusterProperties.class }) 49 | @AutoConfigureAfter(LeaderAutoConfiguration.class) 50 | public class ZookeeperLeaderAutoConfiguration { 51 | 52 | @Autowired 53 | private LeaderElectionProperties lep; 54 | 55 | @Autowired 56 | private ZookeeperClusterProperties zkp; 57 | 58 | @Autowired 59 | private LeaderEventPublisher publisher; 60 | 61 | @Bean 62 | public Candidate zookeeperLeaderCandidate() { 63 | return new DefaultCandidate(lep.getId(), lep.getRole()); 64 | } 65 | 66 | @Bean(initMethod = "start", destroyMethod = "close") 67 | public CuratorFramework zookeeperLeaderCuratorClient() throws Exception { 68 | CuratorFramework client = CuratorFrameworkFactory.builder() 69 | .defaultData(new byte[0]) 70 | .retryPolicy(new ExponentialBackoffRetry(1000, 3)) 71 | .connectString(zkp.getConnect()).build(); 72 | return client; 73 | } 74 | 75 | @Bean 76 | public LeaderInitiator zookeeperLeaderInitiator() throws Exception { 77 | LeaderInitiator initiator = new LeaderInitiator(zookeeperLeaderCuratorClient(), 78 | zookeeperLeaderCandidate(), zkp.getNamespace()); 79 | initiator.setLeaderEventPublisher(publisher); 80 | return initiator; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/java/org/springframework/cloud/cluster/autoconfigure/lock/RedisLockServiceAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.lock; 17 | 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.cloud.cluster.lock.DistributedLockProperties; 24 | import org.springframework.cloud.cluster.redis.RedisClusterProperties; 25 | import org.springframework.cloud.cluster.redis.lock.RedisLockService; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | import org.springframework.data.redis.connection.RedisConnectionFactory; 29 | 30 | /** 31 | * Auto-configuration for {@link RedisLockService}. 32 | * 33 | * @author Janne Valkealahti 34 | * 35 | */ 36 | @Configuration 37 | @ConditionalOnClass(RedisLockService.class) 38 | @ConditionalOnProperty(value = { "spring.cloud.cluster.redis.lock.enabled", 39 | "spring.cloud.cluster.lock.enabled" }, matchIfMissing = true) 40 | @EnableConfigurationProperties({ DistributedLockProperties.class, 41 | RedisClusterProperties.class }) 42 | @ConditionalOnBean(RedisConnectionFactory.class) 43 | public class RedisLockServiceAutoConfiguration { 44 | 45 | @Autowired 46 | private RedisConnectionFactory redisConnectionFactory; 47 | 48 | @Autowired 49 | private DistributedLockProperties distributedLockProperties; 50 | 51 | @Autowired 52 | private RedisClusterProperties redisClusterProperties; 53 | 54 | @Bean 55 | public RedisLockService redisLockService() { 56 | String role = distributedLockProperties.getRole(); 57 | Long expire = redisClusterProperties.getLock().getExpireAfter(); 58 | if (role != null && expire != null) { 59 | return new RedisLockService(redisConnectionFactory, role, expire); 60 | } else if (role != null) { 61 | return new RedisLockService(redisConnectionFactory, role); 62 | } else if (expire != null) { 63 | return new RedisLockService(redisConnectionFactory, expire); 64 | } else { 65 | return new RedisLockService(redisConnectionFactory); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | org.springframework.cloud.cluster.autoconfigure.leader.LeaderAutoConfiguration,\ 3 | org.springframework.cloud.cluster.autoconfigure.leader.ZookeeperLeaderAutoConfiguration,\ 4 | org.springframework.cloud.cluster.autoconfigure.leader.HazelcastLeaderAutoConfiguration,\ 5 | org.springframework.cloud.cluster.autoconfigure.leader.EtcdLeaderAutoConfiguration,\ 6 | org.springframework.cloud.cluster.autoconfigure.lock.RedisLockServiceAutoConfiguration 7 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure; 17 | 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Method; 20 | 21 | import org.springframework.util.ReflectionUtils; 22 | 23 | /** 24 | * Utils for tests. 25 | * 26 | * @author Janne Valkealahti 27 | * 28 | */ 29 | public class TestUtils { 30 | 31 | @SuppressWarnings("unchecked") 32 | public static T readField(String name, Object target) throws Exception { 33 | Field field = null; 34 | Class clazz = target.getClass(); 35 | do { 36 | try { 37 | field = clazz.getDeclaredField(name); 38 | } catch (Exception ex) { 39 | } 40 | 41 | clazz = clazz.getSuperclass(); 42 | } while (field == null && !clazz.equals(Object.class)); 43 | 44 | if (field == null) 45 | throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " 46 | + target.getClass()); 47 | field.setAccessible(true); 48 | return (T) field.get(target); 49 | } 50 | 51 | @SuppressWarnings("unchecked") 52 | public static T callMethod(String name, Object target) throws Exception { 53 | Class clazz = target.getClass(); 54 | Method method = ReflectionUtils.findMethod(clazz, name); 55 | 56 | if (method == null) 57 | throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " 58 | + target.getClass()); 59 | method.setAccessible(true); 60 | return (T) ReflectionUtils.invokeMethod(method, target); 61 | } 62 | 63 | public static void setField(String name, Object target, Object value) throws Exception { 64 | Field field = null; 65 | Class clazz = target.getClass(); 66 | do { 67 | try { 68 | field = clazz.getDeclaredField(name); 69 | } catch (Exception ex) { 70 | } 71 | 72 | clazz = clazz.getSuperclass(); 73 | } while (field == null && !clazz.equals(Object.class)); 74 | 75 | if (field == null) 76 | throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " 77 | + target.getClass()); 78 | field.setAccessible(true); 79 | field.set(target, value); 80 | } 81 | 82 | @SuppressWarnings("unchecked") 83 | public static T callMethod(String name, Object target, Object[] args, Class[] argsTypes) throws Exception { 84 | Class clazz = target.getClass(); 85 | Method method = ReflectionUtils.findMethod(clazz, name, argsTypes); 86 | 87 | if (method == null) 88 | throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " 89 | + target.getClass()); 90 | method.setAccessible(true); 91 | return (T) ReflectionUtils.invokeMethod(method, target, args); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/leader/AbstractLeaderAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import java.io.IOException; 19 | 20 | import org.apache.curator.test.TestingServer; 21 | import org.junit.After; 22 | import org.junit.Before; 23 | import org.springframework.beans.factory.DisposableBean; 24 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 25 | 26 | /** 27 | * Shared stuff for leader auto-configuration tests. 28 | * 29 | * @author Janne Valkealahti 30 | * 31 | */ 32 | public abstract class AbstractLeaderAutoConfigurationTests { 33 | 34 | protected AnnotationConfigApplicationContext context; 35 | 36 | protected ZookeeperTestingServerWrapper zookeeper; 37 | 38 | @Before 39 | public void setup() throws Exception { 40 | zookeeper = setupZookeeperTestingServer(); 41 | context = setupContext(); 42 | } 43 | 44 | @After 45 | public void close() { 46 | if (context != null) { 47 | context.close(); 48 | } 49 | if (zookeeper != null) { 50 | try { 51 | zookeeper.destroy(); 52 | } catch (Exception e) { 53 | } 54 | zookeeper = null; 55 | } 56 | } 57 | 58 | protected ZookeeperTestingServerWrapper setupZookeeperTestingServer() throws Exception { 59 | return null; 60 | } 61 | 62 | protected AnnotationConfigApplicationContext setupContext() { 63 | return new AnnotationConfigApplicationContext(); 64 | } 65 | 66 | static class ZookeeperTestingServerWrapper implements DisposableBean { 67 | 68 | TestingServer testingServer; 69 | 70 | public ZookeeperTestingServerWrapper() throws Exception { 71 | this.testingServer = new TestingServer(true); 72 | } 73 | 74 | @Override 75 | public void destroy() throws Exception { 76 | try { 77 | testingServer.close(); 78 | } 79 | catch (IOException e) { 80 | } 81 | } 82 | 83 | public int getPort() { 84 | return testingServer.getPort(); 85 | } 86 | 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/leader/EtcdLeaderAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.springframework.boot.test.EnvironmentTestUtils; 23 | 24 | /** 25 | * Tests for {@link EtcdLeaderAutoConfiguration}. 26 | * 27 | * @author Venil Noronha 28 | */ 29 | public class EtcdLeaderAutoConfigurationTests extends AbstractLeaderAutoConfigurationTests { 30 | 31 | @Test 32 | public void testDefaults() throws Exception { 33 | EnvironmentTestUtils.addEnvironment(this.context); 34 | context.register(LeaderAutoConfiguration.class, EtcdLeaderAutoConfiguration.class); 35 | context.refresh(); 36 | 37 | assertThat(context.containsBean("etcdInstance"), is(true)); 38 | assertThat(context.containsBean("etcdLeaderInitiator"), is(true)); 39 | assertThat(context.containsBean("etcdLeaderCandidate"), is(true)); 40 | } 41 | 42 | @Test 43 | public void testDisabled() throws Exception { 44 | EnvironmentTestUtils.addEnvironment(this.context, 45 | "spring.cloud.cluster.etcd.leader.enabled:false" 46 | ); 47 | context.register(LeaderAutoConfiguration.class, EtcdLeaderAutoConfiguration.class); 48 | context.refresh(); 49 | 50 | assertThat(context.containsBean("etcdInstance"), is(false)); 51 | assertThat(context.containsBean("etcdLeaderInitiator"), is(false)); 52 | assertThat(context.containsBean("etcdLeaderCandidate"), is(false)); 53 | } 54 | 55 | @Test 56 | public void testGlobalLeaderDisabled() throws Exception { 57 | EnvironmentTestUtils.addEnvironment(this.context, 58 | "spring.cloud.cluster.leader.enabled:false", 59 | "spring.cloud.cluster.etcd.leader.enabled:true" 60 | ); 61 | context.register(LeaderAutoConfiguration.class, EtcdLeaderAutoConfiguration.class); 62 | context.refresh(); 63 | 64 | assertThat(context.containsBean("etcdInstance"), is(false)); 65 | assertThat(context.containsBean("etcdLeaderInitiator"), is(false)); 66 | assertThat(context.containsBean("etcdLeaderCandidate"), is(false)); 67 | } 68 | 69 | @Test 70 | public void testEnabled() throws Exception { 71 | EnvironmentTestUtils.addEnvironment(this.context, 72 | "spring.cloud.cluster.etcd.leader.enabled:true" 73 | ); 74 | context.register(LeaderAutoConfiguration.class, EtcdLeaderAutoConfiguration.class); 75 | context.refresh(); 76 | 77 | assertThat(context.containsBean("etcdInstance"), is(true)); 78 | assertThat(context.containsBean("etcdLeaderInitiator"), is(true)); 79 | assertThat(context.containsBean("etcdLeaderCandidate"), is(true)); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/leader/HazelcastLeaderAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import static org.hamcrest.CoreMatchers.notNullValue; 19 | import static org.hamcrest.CoreMatchers.nullValue; 20 | import static org.hamcrest.Matchers.is; 21 | import static org.junit.Assert.assertThat; 22 | 23 | import org.junit.Test; 24 | import org.springframework.boot.test.EnvironmentTestUtils; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | 28 | import com.hazelcast.config.Config; 29 | 30 | /** 31 | * Tests for {@link HazelcastLeaderAutoConfiguration}. 32 | * 33 | * @author Janne Valkealahti 34 | * 35 | */ 36 | public class HazelcastLeaderAutoConfigurationTests extends AbstractLeaderAutoConfigurationTests { 37 | 38 | @Test 39 | public void testDefaults() throws Exception { 40 | EnvironmentTestUtils.addEnvironment(this.context); 41 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class); 42 | context.refresh(); 43 | 44 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(true)); 45 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(true)); 46 | } 47 | 48 | @Test 49 | public void testDisabled() throws Exception { 50 | EnvironmentTestUtils.addEnvironment(this.context, 51 | "spring.cloud.cluster.hazelcast.leader.enabled:false"); 52 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class); 53 | context.refresh(); 54 | 55 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(false)); 56 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(false)); 57 | } 58 | 59 | @Test 60 | public void testGlobalLeaderDisabled() throws Exception { 61 | EnvironmentTestUtils 62 | .addEnvironment( 63 | this.context, 64 | "spring.cloud.cluster.leader.enabled:false", 65 | "spring.cloud.cluster.hazelcast.leader.enabled:true"); 66 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class); 67 | context.refresh(); 68 | 69 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(false)); 70 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(false)); 71 | } 72 | 73 | @Test 74 | public void testEnabled() throws Exception { 75 | EnvironmentTestUtils 76 | .addEnvironment( 77 | this.context, 78 | "spring.cloud.cluster.hazelcast.leader.enabled:true"); 79 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class); 80 | context.refresh(); 81 | 82 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(true)); 83 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(true)); 84 | } 85 | 86 | @Test 87 | public void testOverrideConfig() throws Exception { 88 | EnvironmentTestUtils.addEnvironment(this.context); 89 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class, OverrideConfig.class); 90 | context.refresh(); 91 | 92 | Config config = context.getBean("hazelcastConfig", Config.class); 93 | assertThat(config, notNullValue()); 94 | assertThat(config.getProperty("foo"), is("bar")); 95 | assertThat(config.getProperty("bar"), nullValue()); 96 | 97 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(true)); 98 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(true)); 99 | } 100 | 101 | @Test 102 | public void testXmlConfig() throws Exception { 103 | EnvironmentTestUtils.addEnvironment(this.context, 104 | "spring.cloud.cluster.hazelcast.config-location:classpath:/foobar.xml"); 105 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class); 106 | context.refresh(); 107 | 108 | Config config = context.getBean("hazelcastConfig", Config.class); 109 | assertThat(config, notNullValue()); 110 | assertThat(config.getProperty("foo"), is("bar")); 111 | assertThat(config.getProperty("bar"), is("foo")); 112 | 113 | assertThat(context.containsBean("hazelcastLeaderInitiator"), is(true)); 114 | assertThat(context.containsBean("hazelcastLeaderCandidate"), is(true)); 115 | } 116 | 117 | @Configuration 118 | protected static class OverrideConfig { 119 | 120 | @Bean 121 | public Config hazelcastConfig() { 122 | Config config = new Config(); 123 | config.setProperty("foo", "bar"); 124 | return config; 125 | } 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/leader/LeaderAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import static org.hamcrest.Matchers.notNullValue; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.beans.factory.annotation.Qualifier; 24 | import org.springframework.boot.test.EnvironmentTestUtils; 25 | import org.springframework.cloud.cluster.leader.Candidate; 26 | 27 | /** 28 | * Tests for common leadership concepts. 29 | * 30 | * @author Janne Valkealahti 31 | * @author Venil Noronha 32 | */ 33 | public class LeaderAutoConfigurationTests extends AbstractLeaderAutoConfigurationTests { 34 | 35 | @Override 36 | protected ZookeeperTestingServerWrapper setupZookeeperTestingServer() throws Exception { 37 | return new ZookeeperTestingServerWrapper(); 38 | } 39 | 40 | @Test 41 | public void testAutowireSingleZookeeperCandidate() { 42 | EnvironmentTestUtils.addEnvironment(this.context); 43 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class, Config1.class); 44 | context.refresh(); 45 | 46 | Config1 config1 = context.getBean(Config1.class); 47 | assertThat(config1, notNullValue()); 48 | assertThat(config1.candidate, notNullValue()); 49 | } 50 | 51 | @Test 52 | public void testAutowireSingleHazelcastCandidate() { 53 | EnvironmentTestUtils.addEnvironment(this.context); 54 | context.register(LeaderAutoConfiguration.class, HazelcastLeaderAutoConfiguration.class, Config1.class); 55 | context.refresh(); 56 | 57 | Config1 config1 = context.getBean(Config1.class); 58 | assertThat(config1, notNullValue()); 59 | assertThat(config1.candidate, notNullValue()); 60 | } 61 | 62 | @Test 63 | public void testAutowireSingleEtcdCandidate() { 64 | EnvironmentTestUtils.addEnvironment(this.context); 65 | context.register(LeaderAutoConfiguration.class, EtcdLeaderAutoConfiguration.class, Config1.class); 66 | context.refresh(); 67 | 68 | Config1 config1 = context.getBean(Config1.class); 69 | assertThat(config1, notNullValue()); 70 | assertThat(config1.candidate, notNullValue()); 71 | } 72 | 73 | @Test 74 | public void testAutowireMultipleCandidates() { 75 | EnvironmentTestUtils.addEnvironment(this.context); 76 | context.register(LeaderAutoConfiguration.class, 77 | ZookeeperLeaderAutoConfiguration.class, 78 | HazelcastLeaderAutoConfiguration.class, 79 | EtcdLeaderAutoConfiguration.class, Config2.class); 80 | context.refresh(); 81 | 82 | Config2 config2 = context.getBean(Config2.class); 83 | assertThat(config2, notNullValue()); 84 | assertThat(config2.zookeeperLeaderCandidate, notNullValue()); 85 | assertThat(config2.hazelcastLeaderCandidate, notNullValue()); 86 | assertThat(config2.etcdLeaderCandidate, notNullValue()); 87 | } 88 | 89 | static class Config1 { 90 | 91 | @Autowired 92 | Candidate candidate; 93 | } 94 | 95 | static class Config2 { 96 | 97 | @Autowired 98 | @Qualifier("zookeeperLeaderCandidate") 99 | Candidate zookeeperLeaderCandidate; 100 | 101 | @Autowired 102 | @Qualifier("hazelcastLeaderCandidate") 103 | Candidate hazelcastLeaderCandidate; 104 | 105 | @Autowired 106 | @Qualifier("etcdLeaderCandidate") 107 | Candidate etcdLeaderCandidate; 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/leader/ZookeeperLeaderAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.leader; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.springframework.boot.test.EnvironmentTestUtils; 23 | 24 | /** 25 | * Tests for {@link ZookeeperLeaderAutoConfiguration}. 26 | * 27 | * @author Janne Valkealahti 28 | * 29 | */ 30 | public class ZookeeperLeaderAutoConfigurationTests extends AbstractLeaderAutoConfigurationTests { 31 | 32 | @Override 33 | protected ZookeeperTestingServerWrapper setupZookeeperTestingServer() throws Exception { 34 | return new ZookeeperTestingServerWrapper(); 35 | } 36 | 37 | @Test 38 | public void testDefaults() throws Exception { 39 | EnvironmentTestUtils.addEnvironment(this.context); 40 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class); 41 | context.refresh(); 42 | 43 | assertThat(context.containsBean("zookeeperLeaderInitiator"), is(true)); 44 | assertThat(context.containsBean("zookeeperLeaderCandidate"), is(true)); 45 | } 46 | 47 | @Test 48 | public void testEnabled() throws Exception { 49 | EnvironmentTestUtils 50 | .addEnvironment( 51 | this.context, 52 | "spring.cloud.cluster.zookeeper.leader.enabled:true", 53 | "spring.cloud.cluster.zookeeper.connect:localhost:" + zookeeper.getPort()); 54 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class); 55 | context.refresh(); 56 | 57 | assertThat(context.containsBean("zookeeperLeaderInitiator"), is(true)); 58 | assertThat(context.containsBean("zookeeperLeaderCandidate"), is(true)); 59 | } 60 | 61 | @Test 62 | public void testDisabled() throws Exception { 63 | EnvironmentTestUtils 64 | .addEnvironment( 65 | this.context, 66 | "spring.cloud.cluster.zookeeper.leader.enabled:false"); 67 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class); 68 | context.refresh(); 69 | 70 | assertThat(context.containsBean("zookeeperLeaderInitiator"), is(false)); 71 | assertThat(context.containsBean("zookeeperLeaderCandidate"), is(false)); 72 | } 73 | 74 | @Test 75 | public void testGlobalLeaderDisabled() throws Exception { 76 | EnvironmentTestUtils 77 | .addEnvironment( 78 | this.context, 79 | "spring.cloud.cluster.leader.enabled:false", 80 | "spring.cloud.cluster.zookeeper.leader.enabled:true"); 81 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class); 82 | context.refresh(); 83 | 84 | assertThat(context.containsBean("zookeeperLeaderInitiator"), is(false)); 85 | assertThat(context.containsBean("zookeeperLeaderCandidate"), is(false)); 86 | } 87 | 88 | @Test 89 | public void testGlobalLeaderDisabledZkEnabled() throws Exception { 90 | EnvironmentTestUtils 91 | .addEnvironment( 92 | this.context, 93 | "spring.cloud.cluster.leader.enabled:false"); 94 | context.register(LeaderAutoConfiguration.class, ZookeeperLeaderAutoConfiguration.class); 95 | context.refresh(); 96 | 97 | assertThat(context.containsBean("zookeeperLeaderInitiator"), is(false)); 98 | assertThat(context.containsBean("zookeeperLeaderCandidate"), is(false)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/lock/AbstractLockAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.lock; 17 | 18 | import org.junit.After; 19 | import org.junit.Before; 20 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 21 | 22 | /** 23 | * Shared stuff for locking auto-configuration tests. 24 | * 25 | * @author Janne Valkealahti 26 | * 27 | */ 28 | public abstract class AbstractLockAutoConfigurationTests { 29 | 30 | protected AnnotationConfigApplicationContext context; 31 | 32 | @Before 33 | public void setup() { 34 | context = setupContext(); 35 | } 36 | 37 | @After 38 | public void close() { 39 | if (context != null) { 40 | context.close(); 41 | } 42 | } 43 | 44 | protected AnnotationConfigApplicationContext setupContext() { 45 | return new AnnotationConfigApplicationContext(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/java/org/springframework/cloud/cluster/autoconfigure/lock/RedisLockServiceAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.autoconfigure.lock; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 23 | import org.springframework.boot.test.EnvironmentTestUtils; 24 | import org.springframework.cloud.cluster.autoconfigure.TestUtils; 25 | import org.springframework.cloud.cluster.redis.lock.RedisLockService; 26 | import org.springframework.integration.redis.util.RedisLockRegistry; 27 | 28 | /** 29 | * Tests for {@link RedisLockServiceAutoConfiguration}. 30 | * 31 | * @author Janne Valkealahti 32 | * 33 | */ 34 | public class RedisLockServiceAutoConfigurationTests extends AbstractLockAutoConfigurationTests { 35 | 36 | @Test 37 | public void testDefaults() { 38 | EnvironmentTestUtils.addEnvironment(this.context); 39 | context.register(RedisAutoConfiguration.class, RedisLockServiceAutoConfiguration.class); 40 | context.refresh(); 41 | 42 | assertThat(context.containsBean("redisLockService"), is(true)); 43 | } 44 | 45 | @Test 46 | public void testDisabled() throws Exception { 47 | EnvironmentTestUtils 48 | .addEnvironment( 49 | this.context, 50 | "spring.cloud.cluster.redis.lock.enabled:false"); 51 | context.register(RedisAutoConfiguration.class, RedisLockServiceAutoConfiguration.class); 52 | context.refresh(); 53 | 54 | assertThat(context.containsBean("redisLockService"), is(false)); 55 | } 56 | 57 | @Test 58 | public void testGlobalLeaderDisabled() throws Exception { 59 | EnvironmentTestUtils 60 | .addEnvironment( 61 | this.context, 62 | "spring.cloud.cluster.lock.enabled:false", 63 | "spring.cloud.cluster.redis.lock.enabled:true"); 64 | context.register(RedisAutoConfiguration.class, RedisLockServiceAutoConfiguration.class); 65 | context.refresh(); 66 | 67 | assertThat(context.containsBean("redisLockService"), is(false)); 68 | } 69 | 70 | @Test 71 | public void testChangeRole() throws Exception { 72 | EnvironmentTestUtils 73 | .addEnvironment( 74 | this.context, 75 | "spring.cloud.cluster.lock.role:foo"); 76 | context.register(RedisAutoConfiguration.class, RedisLockServiceAutoConfiguration.class); 77 | context.refresh(); 78 | 79 | RedisLockService service = context.getBean(RedisLockService.class); 80 | RedisLockRegistry redisLockRegistry = TestUtils.readField("redisLockRegistry", service); 81 | String registryKey = TestUtils.readField("registryKey", redisLockRegistry); 82 | 83 | assertThat(registryKey, is("foo")); 84 | } 85 | 86 | @Test 87 | public void testChangeTimeout() throws Exception { 88 | EnvironmentTestUtils 89 | .addEnvironment( 90 | this.context, 91 | "spring.cloud.cluster.redis.lock.expireAfter:1234"); 92 | context.register(RedisAutoConfiguration.class, RedisLockServiceAutoConfiguration.class); 93 | context.refresh(); 94 | 95 | RedisLockService service = context.getBean(RedisLockService.class); 96 | RedisLockRegistry redisLockRegistry = TestUtils.readField("redisLockRegistry", service); 97 | String registryKey = TestUtils.readField("registryKey", redisLockRegistry); 98 | Long expireAfter = TestUtils.readField("expireAfter", redisLockRegistry); 99 | 100 | assertThat(registryKey, is(RedisLockService.DEFAULT_REGISTRY_KEY)); 101 | assertThat(expireAfter, is(1234l)); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /spring-cloud-cluster-autoconfigure/src/test/resources/foobar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | bar 8 | foo 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/.gitignore: -------------------------------------------------------------------------------- 1 | /.apt_generated/ 2 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-cluster-core 8 | jar 9 | 10 | spring-cloud-cluster-core 11 | Spring Cloud Cluster Core 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-cluster 16 | 1.0.2.RELEASE 17 | .. 18 | 19 | 20 | 21 | 22 | org.springframework 23 | spring-tx 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-configuration-processor 32 | true 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/AbstractCandidate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader; 17 | 18 | import java.util.UUID; 19 | 20 | import org.springframework.util.StringUtils; 21 | 22 | /** 23 | * Base implementation of a {@link Candidate}. 24 | * 25 | * @author Janne Valkealahti 26 | * 27 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 28 | */ 29 | @Deprecated 30 | public abstract class AbstractCandidate implements Candidate { 31 | 32 | private static final String DEFAULT_ROLE = "leader"; 33 | 34 | private final String id; 35 | 36 | private final String role; 37 | 38 | /** 39 | * Instantiate a abstract candidate. 40 | */ 41 | public AbstractCandidate() { 42 | this(null, null); 43 | } 44 | 45 | /** 46 | * Instantiate a abstract candidate. 47 | * 48 | * @param id the identifier 49 | * @param role the role 50 | */ 51 | public AbstractCandidate(String id, String role) { 52 | this.id = StringUtils.hasText(id) ? id : UUID.randomUUID().toString(); 53 | this.role = StringUtils.hasText(role) ? role : DEFAULT_ROLE; 54 | } 55 | 56 | @Override 57 | public String getRole() { 58 | return this.role; 59 | } 60 | 61 | @Override 62 | public String getId() { 63 | return this.id; 64 | } 65 | 66 | @Override 67 | public abstract void onGranted(Context ctx) throws InterruptedException; 68 | 69 | @Override 70 | public abstract void onRevoked(Context ctx); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/Candidate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader; 17 | 18 | /** 19 | * Interface that defines the contract for candidates to participate 20 | * in a leader election. The callback methods {@link #onGranted(Context)} 21 | * and {@link #onRevoked(Context)} are invoked when leadership is 22 | * granted and revoked. 23 | * 24 | * @author Patrick Peralta 25 | * @author Janne Valkealahti 26 | * 27 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 28 | */ 29 | @Deprecated 30 | public interface Candidate { 31 | 32 | /** 33 | * Gets the role. 34 | * 35 | * @return a string indicating the name of the leadership role 36 | * this candidate is participating in; other candidates 37 | * present in the system with the same name will contend 38 | * for leadership 39 | */ 40 | String getRole(); 41 | 42 | /** 43 | * Gets the identifier. 44 | * 45 | * @return a unique ID for this candidate; no other candidate for 46 | * leader election should return the same id 47 | */ 48 | String getId(); 49 | 50 | /** 51 | * Callback method invoked when this candidate is elected leader. 52 | * Implementations may chose to launch a background thread to 53 | * perform leadership roles and return immediately. Another option 54 | * is for implementations to perform all leadership work in the 55 | * thread invoking this method. In the latter case, the 56 | * method must respond to thread interrupts by throwing 57 | * {@link java.lang.InterruptedException}. When the thread 58 | * is interrupted, this indicates that this candidate is no 59 | * longer leader. 60 | * 61 | * @param ctx leadership context 62 | * @throws InterruptedException when this candidate is no longer leader 63 | */ 64 | void onGranted(Context ctx) throws InterruptedException; 65 | 66 | /** 67 | * Callback method invoked when this candidate is no longer leader. 68 | * Implementations should use this to shut down any resources 69 | * (threads, network connections, etc) used to perform leadership work. 70 | * 71 | * @param ctx leadership context 72 | */ 73 | void onRevoked(Context ctx); 74 | } 75 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/Context.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader; 17 | 18 | /** 19 | * Interface that defines the context for candidate leadership. 20 | * Instances of this object are passed to {@link Candidate candidates} 21 | * upon granting and revoking of leadership. 22 | * 23 | * @author Patrick Peralta 24 | * @author Janne Valkealahti 25 | * 26 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 27 | */ 28 | @Deprecated 29 | public interface Context { 30 | 31 | /** 32 | * Checks if the {@link Candidate} this context was 33 | * passed to is the leader. 34 | * 35 | * @return true if the {@link Candidate} this context was 36 | * passed to is the leader 37 | */ 38 | boolean isLeader(); 39 | 40 | /** 41 | * Causes the {@link Candidate} this context was passed to 42 | * to relinquish leadership. This method has no effect 43 | * if the candidate is not currently the leader. 44 | */ 45 | void yield(); 46 | } 47 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/DefaultCandidate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * Simple {@link org.springframework.cloud.cluster.leader.Candidate} for leadership. 23 | * This implementation simply logs when it is elected and when its leadership is revoked. 24 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 25 | */ 26 | @Deprecated 27 | public class DefaultCandidate extends AbstractCandidate { 28 | 29 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 30 | 31 | private volatile Context leaderContext; 32 | 33 | /** 34 | * Instantiate a default candidate. 35 | */ 36 | public DefaultCandidate() { 37 | super(); 38 | } 39 | 40 | /** 41 | * Instantiate a default candidate. 42 | * 43 | * @param id the identifier 44 | * @param role the role 45 | */ 46 | public DefaultCandidate(String id, String role) { 47 | super(id, role); 48 | } 49 | 50 | @Override 51 | public void onGranted(Context ctx) { 52 | this.logger.info("{} has been granted leadership; context: {}", this, ctx); 53 | this.leaderContext = ctx; 54 | } 55 | 56 | @Override 57 | public void onRevoked(Context ctx) { 58 | this.logger.info("{} leadership has been revoked", this, ctx); 59 | } 60 | 61 | /** 62 | * Voluntarily yield leadership if held. If leader context is not 63 | * yet known this method does nothing. Leader context becomes available 64 | * only after {@link #onGranted(Context)} method is called by the 65 | * leader initiator. 66 | */ 67 | public void yieldLeadership() { 68 | if (this.leaderContext != null) { 69 | this.leaderContext.yield(); 70 | } 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return String.format("DefaultCandidate{role=%s, id=%s}", getRole(), getId()); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/LeaderElectionProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | /** 21 | * Generic configuration properties for leader election. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | @ConfigurationProperties(value = "spring.cloud.cluster.leader") 27 | public class LeaderElectionProperties { 28 | 29 | /** if leader election is enabled globally. */ 30 | private boolean enabled = true; 31 | 32 | /** leader election candidate identifier. */ 33 | private String id; 34 | 35 | /** leader election candidate role. */ 36 | private String role; 37 | 38 | public boolean isEnabled() { 39 | return enabled; 40 | } 41 | 42 | public void setEnabled(boolean enabled) { 43 | this.enabled = enabled; 44 | } 45 | 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | public void setId(String id) { 51 | this.id = id; 52 | } 53 | 54 | public String getRole() { 55 | return role; 56 | } 57 | 58 | public void setRole(String role) { 59 | this.role = role; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/AbstractLeaderEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.cloud.cluster.leader.Context; 19 | import org.springframework.context.ApplicationEvent; 20 | 21 | /** 22 | * Base {@link ApplicationEvent} class for leader based events. All custom event 23 | * classes should be derived from this class. 24 | * 25 | * @author Janne Valkealahti 26 | * @author Gary Russell 27 | * 28 | */ 29 | @SuppressWarnings("serial") 30 | public abstract class AbstractLeaderEvent extends ApplicationEvent { 31 | 32 | private final Context context; 33 | 34 | private final String role; 35 | 36 | /** 37 | * Create a new ApplicationEvent. 38 | * 39 | * @param source the component that published the event (never {@code null}) 40 | */ 41 | public AbstractLeaderEvent(Object source) { 42 | this(source, null, null); 43 | } 44 | 45 | /** 46 | * Create a new ApplicationEvent. 47 | * 48 | * @param source the component that published the event (never {@code null}) 49 | * @param context the context associated with this event 50 | * @param role the role of the leader 51 | */ 52 | public AbstractLeaderEvent(Object source, Context context, String role) { 53 | super(source); 54 | this.context = context; 55 | this.role = role; 56 | } 57 | 58 | /** 59 | * Get the {@link Context} associated with this event. 60 | * 61 | * @return the context 62 | */ 63 | public Context getContext() { 64 | return context; 65 | } 66 | 67 | /** 68 | * Get the role of the leader. 69 | * 70 | * @return the role 71 | */ 72 | public String getRole() { 73 | return role; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "AbstractLeaderEvent [role=" + role + ", context=" + context + ", source=" + source 79 | + "]"; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/DefaultLeaderEventPublisher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.cloud.cluster.leader.Context; 19 | import org.springframework.context.ApplicationEventPublisher; 20 | import org.springframework.context.ApplicationEventPublisherAware; 21 | 22 | /** 23 | * Default implementation of {@link LeaderEventPublisher}. 24 | * 25 | * @author Janne Valkealahti 26 | * @author Gary Russell 27 | * 28 | */ 29 | public class DefaultLeaderEventPublisher implements LeaderEventPublisher, ApplicationEventPublisherAware { 30 | 31 | private ApplicationEventPublisher applicationEventPublisher; 32 | 33 | /** 34 | * Instantiates a new leader event publisher. 35 | */ 36 | public DefaultLeaderEventPublisher() { 37 | } 38 | 39 | /** 40 | * Instantiates a new leader event publisher. 41 | * 42 | * @param applicationEventPublisher the application event publisher 43 | */ 44 | public DefaultLeaderEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 45 | this.applicationEventPublisher = applicationEventPublisher; 46 | } 47 | 48 | @Override 49 | public void publishOnGranted(Object source, Context context, String role) { 50 | if (applicationEventPublisher != null) { 51 | applicationEventPublisher.publishEvent(new OnGrantedEvent(source, context, role)); 52 | } 53 | } 54 | 55 | @Override 56 | public void publishOnRevoked(Object source, Context context, String role) { 57 | if (applicationEventPublisher != null) { 58 | applicationEventPublisher.publishEvent(new OnRevokedEvent(source, context, role)); 59 | } 60 | } 61 | 62 | @Override 63 | public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 64 | this.applicationEventPublisher = applicationEventPublisher; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/LeaderEventPublisher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.cloud.cluster.leader.Context; 19 | 20 | /** 21 | * Interface for publishing leader based application events. 22 | * 23 | * @author Janne Valkealahti 24 | * @author Gary Russell 25 | * 26 | */ 27 | public interface LeaderEventPublisher { 28 | 29 | /** 30 | * Publish a granted event. 31 | * 32 | * @param source the component generated this event 33 | * @param context the context associated with event 34 | * @param role the role of the leader 35 | */ 36 | void publishOnGranted(Object source, Context context, String role); 37 | 38 | /** 39 | * Publish a revoked event. 40 | * 41 | * @param source the component generated this event 42 | * @param context the context associated with event 43 | * @param role the role of the leader 44 | */ 45 | void publishOnRevoked(Object source, Context context, String role); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/LeaderEventPublisherConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | 21 | /** 22 | * Configuration for common {@link LeaderEventPublisher}. 23 | * 24 | * @author Janne Valkealahti 25 | * 26 | */ 27 | @Configuration 28 | public class LeaderEventPublisherConfiguration { 29 | 30 | @Bean 31 | public LeaderEventPublisher leaderEventPublisher() { 32 | return new DefaultLeaderEventPublisher(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/LoggingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.context.ApplicationListener; 21 | import org.springframework.util.StringUtils; 22 | 23 | /** 24 | * Simple {@link ApplicationListener} which logs all events 25 | * based on {@link AbstractLeaderEvent} using a log level 26 | * set during the construction. 27 | * 28 | * @author Janne Valkealahti 29 | * 30 | */ 31 | public class LoggingListener implements ApplicationListener { 32 | 33 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 34 | 35 | /** Internal enums to match the log level */ 36 | private static enum Level { 37 | ERROR, WARN, INFO, DEBUG, TRACE 38 | } 39 | 40 | /** Level to use */ 41 | private final Level level; 42 | 43 | /** 44 | * Constructs Logger listener with debug level. 45 | */ 46 | public LoggingListener() { 47 | level = Level.DEBUG; 48 | } 49 | 50 | /** 51 | * Constructs Logger listener with given level. 52 | * 53 | * @param level the level string 54 | */ 55 | public LoggingListener(String level) { 56 | try { 57 | this.level = Level.valueOf(level.toUpperCase()); 58 | } 59 | catch (IllegalArgumentException e) { 60 | throw new IllegalArgumentException("Invalid log level '" + level 61 | + "'. The (case-insensitive) supported values are: " 62 | + StringUtils.arrayToCommaDelimitedString(Level.values())); 63 | } 64 | } 65 | 66 | @Override 67 | public void onApplicationEvent(AbstractLeaderEvent event) { 68 | switch (this.level) { 69 | case ERROR: 70 | if (logger.isErrorEnabled()) { 71 | logger.error(event.toString()); 72 | } 73 | break; 74 | case WARN: 75 | if (logger.isWarnEnabled()) { 76 | logger.warn(event.toString()); 77 | } 78 | break; 79 | case INFO: 80 | if (logger.isInfoEnabled()) { 81 | logger.info(event.toString()); 82 | } 83 | break; 84 | case DEBUG: 85 | if (logger.isDebugEnabled()) { 86 | logger.debug(event.toString()); 87 | } 88 | break; 89 | case TRACE: 90 | if (logger.isTraceEnabled()) { 91 | logger.trace(event.toString()); 92 | } 93 | break; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/OnGrantedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.cloud.cluster.leader.Context; 19 | 20 | /** 21 | * Generic event representing that leader has been granted. 22 | * 23 | * @author Janne Valkealahti 24 | * @author Gary Russell 25 | * 26 | */ 27 | @SuppressWarnings("serial") 28 | public class OnGrantedEvent extends AbstractLeaderEvent { 29 | 30 | /** 31 | * Instantiates a new granted event. 32 | * 33 | * @param source the component that published the event (never {@code null}) 34 | * @param context the context associated with this event 35 | * @param role the role of the leader 36 | */ 37 | public OnGrantedEvent(Object source, Context context, String role) { 38 | super(source, context, role); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/leader/event/OnRevokedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.leader.event; 17 | 18 | import org.springframework.cloud.cluster.leader.Context; 19 | 20 | /** 21 | * Generic event representing that leader has been revoked. 22 | * 23 | * @author Janne Valkealahti 24 | * @author Gary Russell 25 | * 26 | */ 27 | @SuppressWarnings("serial") 28 | public class OnRevokedEvent extends AbstractLeaderEvent { 29 | 30 | /** 31 | * Instantiates a new revoked event. 32 | * 33 | * @param source the component that published the event (never {@code null}) 34 | * @param context the context associated with this event 35 | * @param role the role of the leader 36 | */ 37 | public OnRevokedEvent(Object source, Context context, String role) { 38 | super(source, context, role); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/DistributedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | import java.util.concurrent.locks.Lock; 19 | 20 | /** 21 | * Distributed implementation of a {@link Lock}. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 26 | */ 27 | @Deprecated 28 | public interface DistributedLock extends Lock { 29 | 30 | /** 31 | * Gets the associated lock key. 32 | * 33 | * @return associated lock key 34 | */ 35 | String getLockKey(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/DistributedLockProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | /** 21 | * Generic configuration properties for distributed locking. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | @ConfigurationProperties(value = "spring.cloud.cluster.lock") 27 | public class DistributedLockProperties { 28 | 29 | /** if distributed locking is enabled globally. */ 30 | private boolean enabled = true; 31 | 32 | /** distributed locking role. */ 33 | private String role; 34 | 35 | public boolean isEnabled() { 36 | return enabled; 37 | } 38 | 39 | public void setEnabled(boolean enabled) { 40 | this.enabled = enabled; 41 | } 42 | 43 | public String getRole() { 44 | return role; 45 | } 46 | 47 | public void setRole(String role) { 48 | this.role = role; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/LockRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | /** 19 | * {@code LockRegistry} implementations provide high 20 | * level access to obtain {@link DistributedLock}s 21 | * from a system using a locking key. 22 | * 23 | *

Purpose of this interface is to decouple lock 24 | * access from their implementing system. Registry may 25 | * return locks from different systems while {@link LockService} 26 | * is always bound to a one system. Also implementation may 27 | * choose to use {@link LockServiceLocator} to find the backing 28 | * {@link LockService} implementation. 29 | * 30 | * @author Janne Valkealahti 31 | * 32 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 33 | */ 34 | @Deprecated 35 | public interface LockRegistry { 36 | 37 | /** 38 | * Gets a {@link DistributedLock} from a registry. 39 | * 40 | * @param lockKey the locking key 41 | * @return distributed lock 42 | */ 43 | DistributedLock get(String lockKey); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/LockService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | /** 19 | * {@code LockService} implementations provide low 20 | * level access to a particular locking service. This 21 | * service is always backed by a real locking service 22 | * bound to a real distributed system like zookeeper 23 | * or redis. 24 | * 25 | * @author Janne Valkealahti 26 | * 27 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 28 | */ 29 | @Deprecated 30 | public interface LockService { 31 | 32 | /** 33 | * Obtains a {@link DistributedLock} from a service. Obtaining 34 | * a lock should not do any locking operations like trying 35 | * a lock. All possible locking actions should be left to a user 36 | * to be executed via {@link DistributedLock}. 37 | * 38 | * @param lockKey the locking key 39 | * @return distributed lock 40 | */ 41 | DistributedLock obtain(String lockKey); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/LockServiceLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | /** 19 | * Lock service locator defines a contract matching a lock 20 | * key to a {@link LockService}. 21 | * 22 | *

Implementation is free to implement this interface as 23 | * it wish. Possible implementation can i.e. choose to locate 24 | * different services based on path matching or any other means 25 | * which creates a consistent resolving to a service. 26 | * 27 | * @author Janne Valkealahti 28 | * 29 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 30 | */ 31 | @Deprecated 32 | public interface LockServiceLocator { 33 | 34 | /** 35 | * Locates a bound {@link LockService} for a locking key. 36 | * 37 | *

Locate algorithm has to be consistent among different 38 | * JVM's meaning that in all cases a locking key has to resolve 39 | * back to same {@link LockService} where {@link DistributedLock} 40 | * either already exists or will exist once it is created. 41 | * 42 | * @param lockKey the locking key 43 | * @return lock service or null if key is not bound to any service 44 | */ 45 | LockService locate(String lockKey); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/LockingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | import org.springframework.dao.NonTransientDataAccessException; 19 | 20 | /** 21 | * Generic runtime exception for locking system. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | public class LockingException extends NonTransientDataAccessException { 27 | 28 | private static final long serialVersionUID = 285133760957296884L; 29 | 30 | /** 31 | * Instantiates a new locking exception. 32 | * 33 | * @param msg the msg 34 | * @param cause the cause 35 | */ 36 | public LockingException(String msg, Throwable cause) { 37 | super(msg, cause); 38 | } 39 | 40 | /** 41 | * Instantiates a new locking exception. 42 | * 43 | * @param msg the msg 44 | */ 45 | public LockingException(String msg) { 46 | super(msg); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/support/AbstractDistributedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import org.springframework.cloud.cluster.lock.DistributedLock; 19 | 20 | /** 21 | * Base implementation of {@link DistributedLock}. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | public abstract class AbstractDistributedLock implements DistributedLock { 27 | 28 | private final String lockKey; 29 | 30 | /** 31 | * Instantiates a new abstract distributed lock. 32 | * 33 | * @param lockKey the locking key 34 | */ 35 | public AbstractDistributedLock(String lockKey) { 36 | this.lockKey = lockKey; 37 | } 38 | 39 | @Override 40 | public String getLockKey() { 41 | return lockKey; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/support/DefaultLockRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import org.springframework.cloud.cluster.lock.DistributedLock; 19 | import org.springframework.cloud.cluster.lock.LockRegistry; 20 | import org.springframework.cloud.cluster.lock.LockService; 21 | import org.springframework.cloud.cluster.lock.LockServiceLocator; 22 | import org.springframework.cloud.cluster.lock.LockingException; 23 | import org.springframework.util.Assert; 24 | 25 | /** 26 | * Default implementation of a {@link LockRegistry} delegating 27 | * to a {@link LockServiceLocator}. 28 | * 29 | * @author Janne Valkealahti 30 | * 31 | */ 32 | public class DefaultLockRegistry implements LockRegistry { 33 | 34 | private final LockServiceLocator lockServiceLocator; 35 | 36 | /** 37 | * Instantiates a new default lock registry. 38 | * 39 | * @param lockServiceLocator the lock service locator 40 | */ 41 | public DefaultLockRegistry(LockServiceLocator lockServiceLocator) { 42 | Assert.notNull(lockServiceLocator, "Lock service locator must be set"); 43 | this.lockServiceLocator = lockServiceLocator; 44 | } 45 | 46 | @Override 47 | public DistributedLock get(String lockKey) { 48 | LockService service = lockServiceLocator.locate(lockKey); 49 | if (service == null) { 50 | throw new LockingException("Unable to find lockservice for key=[" 51 | + lockKey + "]"); 52 | } 53 | return service.obtain(lockKey); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/support/DefaultLockServiceLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.CopyOnWriteArrayList; 20 | 21 | import org.springframework.cloud.cluster.lock.LockService; 22 | import org.springframework.cloud.cluster.lock.LockServiceLocator; 23 | import org.springframework.util.AntPathMatcher; 24 | import org.springframework.util.Assert; 25 | import org.springframework.util.PathMatcher; 26 | 27 | /** 28 | * Default implementation of a {@link LockServiceLocator} which uses a set of 29 | * {@link LockService}s where matching happens using a simple {@link PathMatcher}. 30 | * 31 | * @author Janne Valkealahti 32 | * 33 | */ 34 | public class DefaultLockServiceLocator implements LockServiceLocator { 35 | 36 | private final List mappings = new CopyOnWriteArrayList(); 37 | 38 | private final PathMatcher matcher = new AntPathMatcher(); 39 | 40 | private final LockService fallback; 41 | 42 | /** 43 | * Instantiates a new default lock service locator. 44 | * 45 | * @param fallback the primary lock service 46 | */ 47 | public DefaultLockServiceLocator(LockService fallback) { 48 | Assert.notNull(fallback, "Fallback lock service must be set"); 49 | this.fallback = fallback; 50 | } 51 | 52 | @Override 53 | public LockService locate(String lockKey) { 54 | LockService match = match(lockKey); 55 | return match != null ? match : fallback; 56 | } 57 | 58 | /** 59 | * Adds a mapping path for lock service. 60 | * 61 | * @param path the path 62 | * @param lockService the lock service 63 | */ 64 | public void addMapping(String path, LockService lockService) { 65 | Assert.notNull(lockService, "Lock service must not be null"); 66 | mappings.add(new PathMapping(path, lockService)); 67 | } 68 | 69 | private LockService match(String path) { 70 | for (PathMapping m : mappings) { 71 | if (matcher.match(m.getPath(), path)) { 72 | return m.getLockService(); 73 | } 74 | } 75 | return null; 76 | } 77 | 78 | private static class PathMapping { 79 | 80 | private String path; 81 | 82 | private LockService lockService; 83 | 84 | public PathMapping(String path, LockService lockService) { 85 | this.path = path; 86 | this.lockService = lockService; 87 | } 88 | 89 | public String getPath() { 90 | return path; 91 | } 92 | 93 | public LockService getLockService() { 94 | return lockService; 95 | } 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/main/java/org/springframework/cloud/cluster/lock/support/DelegatingDistributedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.concurrent.locks.Condition; 20 | import java.util.concurrent.locks.Lock; 21 | 22 | import org.springframework.cloud.cluster.lock.DistributedLock; 23 | import org.springframework.util.Assert; 24 | 25 | /** 26 | * {@link DistributedLock} which simply delegates to a {@link Lock}. 27 | * 28 | * @author Janne Valkealahti 29 | * 30 | */ 31 | public class DelegatingDistributedLock extends AbstractDistributedLock { 32 | 33 | private final Lock lock; 34 | 35 | /** 36 | * Instantiates a new delegating distributed lock. 37 | * 38 | * @param lockKey the locking key 39 | * @param lock the lock 40 | */ 41 | public DelegatingDistributedLock(String lockKey, Lock lock) { 42 | super(lockKey); 43 | Assert.notNull(lock, "Lock must be set"); 44 | this.lock = lock; 45 | } 46 | 47 | @Override 48 | public void lock() { 49 | lock.lock(); 50 | } 51 | 52 | @Override 53 | public void lockInterruptibly() throws InterruptedException { 54 | lock.lockInterruptibly(); 55 | } 56 | 57 | @Override 58 | public boolean tryLock() { 59 | return lock.tryLock(); 60 | } 61 | 62 | @Override 63 | public boolean tryLock(long time, TimeUnit unit) 64 | throws InterruptedException { 65 | return lock.tryLock(time, unit); 66 | } 67 | 68 | @Override 69 | public void unlock() { 70 | lock.unlock(); 71 | } 72 | 73 | @Override 74 | public Condition newCondition() { 75 | return lock.newCondition(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/test/java/org/springframework/cloud/cluster/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster; 17 | 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Method; 20 | 21 | import org.springframework.util.ReflectionUtils; 22 | 23 | /** 24 | * Utils for tests. 25 | * 26 | * @author Janne Valkealahti 27 | * 28 | */ 29 | public class TestUtils { 30 | 31 | @SuppressWarnings("unchecked") 32 | public static T readField(String name, Object target) throws Exception { 33 | Field field = null; 34 | Class clazz = target.getClass(); 35 | do { 36 | try { 37 | field = clazz.getDeclaredField(name); 38 | } catch (Exception ex) { 39 | } 40 | 41 | clazz = clazz.getSuperclass(); 42 | } while (field == null && !clazz.equals(Object.class)); 43 | 44 | if (field == null) 45 | throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " 46 | + target.getClass()); 47 | field.setAccessible(true); 48 | return (T) field.get(target); 49 | } 50 | 51 | @SuppressWarnings("unchecked") 52 | public static T callMethod(String name, Object target) throws Exception { 53 | Class clazz = target.getClass(); 54 | Method method = ReflectionUtils.findMethod(clazz, name); 55 | 56 | if (method == null) 57 | throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " 58 | + target.getClass()); 59 | method.setAccessible(true); 60 | return (T) ReflectionUtils.invokeMethod(method, target); 61 | } 62 | 63 | public static void setField(String name, Object target, Object value) throws Exception { 64 | Field field = null; 65 | Class clazz = target.getClass(); 66 | do { 67 | try { 68 | field = clazz.getDeclaredField(name); 69 | } catch (Exception ex) { 70 | } 71 | 72 | clazz = clazz.getSuperclass(); 73 | } while (field == null && !clazz.equals(Object.class)); 74 | 75 | if (field == null) 76 | throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " 77 | + target.getClass()); 78 | field.setAccessible(true); 79 | field.set(target, value); 80 | } 81 | 82 | @SuppressWarnings("unchecked") 83 | public static T callMethod(String name, Object target, Object[] args, Class[] argsTypes) throws Exception { 84 | Class clazz = target.getClass(); 85 | Method method = ReflectionUtils.findMethod(clazz, name, argsTypes); 86 | 87 | if (method == null) 88 | throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " 89 | + target.getClass()); 90 | method.setAccessible(true); 91 | return (T) ReflectionUtils.invokeMethod(method, target, args); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/test/java/org/springframework/cloud/cluster/lock/AbstractLockingTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.concurrent.locks.ReentrantLock; 21 | 22 | import org.springframework.cloud.cluster.lock.support.DelegatingDistributedLock; 23 | 24 | /** 25 | * Base testing stuff for locking. 26 | * 27 | * @author Janne Valkealahti 28 | * 29 | */ 30 | public abstract class AbstractLockingTests { 31 | 32 | protected static class LockService1 extends BaseLockService { 33 | public LockService1() {} 34 | } 35 | 36 | protected static class LockService2 extends BaseLockService { 37 | public LockService2() {} 38 | } 39 | 40 | protected static class LockService3 extends BaseLockService { 41 | public LockService3() {} 42 | } 43 | 44 | protected abstract static class BaseLockService implements LockService { 45 | 46 | private Map locks = new HashMap(); 47 | 48 | @Override 49 | public synchronized DistributedLock obtain(String lockKey) { 50 | DistributedLock lock = locks.get(lockKey); 51 | if (lock == null) { 52 | ReentrantLock l = new ReentrantLock(); 53 | lock = new DelegatingDistributedLock(lockKey, l); 54 | locks.put(lockKey, lock); 55 | } 56 | return lock; 57 | } 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/test/java/org/springframework/cloud/cluster/lock/support/DefaultLockRegistryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import org.junit.Test; 19 | import org.springframework.cloud.cluster.lock.LockService; 20 | import org.springframework.cloud.cluster.lock.LockServiceLocator; 21 | import org.springframework.cloud.cluster.lock.LockingException; 22 | 23 | /** 24 | * Tests for {@link DefaultLockRegistry}. 25 | * 26 | * @author Janne Valkealahti 27 | * 28 | */ 29 | public class DefaultLockRegistryTests { 30 | 31 | @Test(expected = LockingException.class) 32 | public void testFailureWithMissingLocate() { 33 | DefaultLockRegistry registry = new DefaultLockRegistry(new AlwaysNullLockServiceLocator()); 34 | registry.get("cantfind"); 35 | } 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | public void testFailureWithNullLockService() { 39 | new DefaultLockRegistry(null); 40 | } 41 | 42 | private static class AlwaysNullLockServiceLocator implements LockServiceLocator { 43 | 44 | @Override 45 | public LockService locate(String lockKey) { 46 | return null; 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /spring-cloud-cluster-core/src/test/java/org/springframework/cloud/cluster/lock/support/DefaultLockServiceLocatorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.lock.support; 17 | 18 | import static org.hamcrest.CoreMatchers.instanceOf; 19 | import static org.hamcrest.CoreMatchers.nullValue; 20 | import static org.junit.Assert.assertThat; 21 | 22 | import org.junit.Test; 23 | import org.springframework.cloud.cluster.TestUtils; 24 | import org.springframework.cloud.cluster.lock.AbstractLockingTests; 25 | import org.springframework.cloud.cluster.lock.LockService; 26 | 27 | /** 28 | * Tests for {@link DefaultLockServiceLocator}. 29 | * 30 | * @author Janne Valkealahti 31 | * 32 | */ 33 | public class DefaultLockServiceLocatorTests extends AbstractLockingTests { 34 | 35 | @Test 36 | public void testPathMatching() throws Exception { 37 | DefaultLockServiceLocator locator = new DefaultLockServiceLocator( 38 | new LockService1()); 39 | locator.addMapping("/path2/**", new LockService2()); 40 | locator.addMapping("/path3/**", new LockService3()); 41 | 42 | LockService matched = match(locator, "/path2/dlock1"); 43 | assertThat(matched, instanceOf(LockService2.class)); 44 | 45 | matched = match(locator, "/path3/dlock1"); 46 | assertThat(matched, instanceOf(LockService3.class)); 47 | 48 | matched = match(locator, "/path1/dlock1"); 49 | assertThat(matched, nullValue()); 50 | } 51 | 52 | @Test 53 | public void testLockServiceLocate() throws Exception { 54 | DefaultLockServiceLocator locator = new DefaultLockServiceLocator( 55 | new LockService1()); 56 | locator.addMapping("/path2/**", new LockService2()); 57 | locator.addMapping("/path3/**", new LockService3()); 58 | 59 | LockService service = locator.locate("/path1/dlock1"); 60 | assertThat(service, instanceOf(LockService1.class)); 61 | 62 | service = locator.locate("xxx"); 63 | assertThat(service, instanceOf(LockService1.class)); 64 | 65 | service = locator.locate("/path2/dlock1"); 66 | assertThat(service, instanceOf(LockService2.class)); 67 | 68 | service = locator.locate("/path3/dlock1"); 69 | assertThat(service, instanceOf(LockService3.class)); 70 | } 71 | 72 | private static LockService match(Object locator, String key) 73 | throws Exception { 74 | return TestUtils.callMethod("match", locator, new String[] { key }, 75 | new Class[] { String.class }); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /spring-cloud-cluster-dependencies/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | spring-cloud-dependencies-parent 7 | org.springframework.cloud 8 | 1.1.1.RELEASE 9 | 10 | 11 | spring-cloud-cluster-dependencies 12 | 1.0.2.RELEASE 13 | pom 14 | spring-cloud-cluster-dependencies 15 | Spring Cloud Cluster Dependencies 16 | 17 | 1.0.2.RELEASE 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-cluster-core 24 | ${spring-cloud-cluster.version} 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-cluster-autoconfigure 29 | ${spring-cloud-cluster.version} 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-cluster-zookeeper 34 | ${spring-cloud-cluster.version} 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-cluster-hazelcast 39 | ${spring-cloud-cluster.version} 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-cluster-etcd 44 | ${spring-cloud-cluster.version} 45 | 46 | 47 | org.springframework.cloud 48 | spring-cloud-cluster-redis 49 | ${spring-cloud-cluster.version} 50 | 51 | 52 | 53 | 54 | 55 | spring 56 | 57 | 58 | spring-snapshots 59 | Spring Snapshots 60 | https://repo.spring.io/libs-snapshot-local 61 | 62 | true 63 | 64 | 65 | false 66 | 67 | 68 | 69 | spring-milestones 70 | Spring Milestones 71 | https://repo.spring.io/libs-milestone-local 72 | 73 | false 74 | 75 | 76 | 77 | spring-releases 78 | Spring Releases 79 | https://repo.spring.io/release 80 | 81 | false 82 | 83 | 84 | 85 | 86 | 87 | spring-snapshots 88 | Spring Snapshots 89 | https://repo.spring.io/libs-snapshot-local 90 | 91 | true 92 | 93 | 94 | false 95 | 96 | 97 | 98 | spring-milestones 99 | Spring Milestones 100 | https://repo.spring.io/libs-milestone-local 101 | 102 | false 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /spring-cloud-cluster-etcd/docker-compose.yml: -------------------------------------------------------------------------------- 1 | etcd: 2 | image: microbox/etcd 3 | ports: 4 | - "4001:4001" 5 | - "7001:7001" 6 | command: "--name spring-cloud-cluster-etcd --data-dir=/data" -------------------------------------------------------------------------------- /spring-cloud-cluster-etcd/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | spring-cloud-cluster-etcd 7 | jar 8 | 9 | spring-cloud-cluster-etcd 10 | Spring Cloud Cluster Etcd 11 | 12 | 13 | org.springframework.cloud 14 | spring-cloud-cluster 15 | 1.0.2.RELEASE 16 | .. 17 | 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-cluster-core 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-configuration-processor 27 | true 28 | 29 | 30 | org.mousio 31 | etcd4j 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /spring-cloud-cluster-etcd/scripts/run_etcd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # Resolve links: $0 may be a link 5 | PRG="$0" 6 | # Need this for relative symlinks. 7 | while [ -h "$PRG" ] ; do 8 | ls=`ls -ld "$PRG"` 9 | link=`expr "$ls" : '.*-> \(.*\)$'` 10 | if expr "$link" : '/.*' > /dev/null; then 11 | PRG="$link" 12 | else 13 | PRG=`dirname "$PRG"`"/$link" 14 | fi 15 | done 16 | cd "`dirname \"$PRG\"`/../" 17 | 18 | docker-compose up -d 19 | -------------------------------------------------------------------------------- /spring-cloud-cluster-etcd/src/main/java/org/springframework/cloud/cluster/etcd/EtcdClusterProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.etcd; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | /** 21 | * Configuration properties for etcd leader election. 22 | * 23 | * @author Venil Noronha 24 | */ 25 | @ConfigurationProperties(value = "spring.cloud.cluster.etcd") 26 | public class EtcdClusterProperties { 27 | 28 | /** etcd namespace */ 29 | private String namespace; 30 | 31 | /** comma separated connect urls for etcd */ 32 | private String connect = "http://localhost:4001"; 33 | 34 | /** etcd leader properties. */ 35 | private EtcdLeaderProperties leader = new EtcdLeaderProperties(); 36 | 37 | public String getNamespace() { 38 | return namespace; 39 | } 40 | 41 | public void setNamespace(String namespace) { 42 | this.namespace = namespace; 43 | } 44 | 45 | public String getConnect() { 46 | return connect; 47 | } 48 | 49 | public void setConnect(String connect) { 50 | this.connect = connect; 51 | } 52 | 53 | public EtcdLeaderProperties getLeader() { 54 | return leader; 55 | } 56 | 57 | public void setLeader(EtcdLeaderProperties leader) { 58 | this.leader = leader; 59 | } 60 | 61 | public static class EtcdLeaderProperties { 62 | 63 | /** if etcd leader election is enabled. */ 64 | private boolean enabled = true; 65 | 66 | public boolean isEnabled() { 67 | return enabled; 68 | } 69 | 70 | public void setEnabled(boolean enabled) { 71 | this.enabled = enabled; 72 | } 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /spring-cloud-cluster-etcd/src/test/java/org/springframework/cloud/cluster/etcd/leader/EtcdTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.etcd.leader; 17 | 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import java.io.IOException; 22 | import java.net.URI; 23 | import java.util.ArrayList; 24 | import java.util.concurrent.CountDownLatch; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.TimeoutException; 27 | 28 | import org.junit.Test; 29 | import org.slf4j.LoggerFactory; 30 | import org.springframework.cloud.cluster.leader.AbstractCandidate; 31 | import org.springframework.cloud.cluster.leader.Context; 32 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 33 | import org.springframework.cloud.cluster.leader.event.AbstractLeaderEvent; 34 | import org.springframework.cloud.cluster.leader.event.DefaultLeaderEventPublisher; 35 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 36 | import org.springframework.context.ApplicationListener; 37 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 38 | import org.springframework.context.annotation.Bean; 39 | import org.springframework.context.annotation.Configuration; 40 | 41 | import mousio.etcd4j.EtcdClient; 42 | import mousio.etcd4j.responses.EtcdException; 43 | 44 | /** 45 | * Tests for etcd leader election. 46 | * 47 | * @author Venil Noronha 48 | */ 49 | public class EtcdTests { 50 | 51 | @Test 52 | public void testSimpleLeader() throws InterruptedException { 53 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleTestConfig.class); 54 | TestCandidate candidate = ctx.getBean(TestCandidate.class); 55 | TestEventListener listener = ctx.getBean(TestEventListener.class); 56 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 57 | assertThat(listener.onEventLatch.await(5, TimeUnit.SECONDS), is(true)); 58 | assertThat(listener.events.size(), is(1)); 59 | ctx.close(); 60 | } 61 | 62 | @Test 63 | public void testLeaderYield() throws InterruptedException { 64 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(YieldTestConfig.class); 65 | YieldTestCandidate candidate = ctx.getBean(YieldTestCandidate.class); 66 | YieldTestEventListener listener = ctx.getBean(YieldTestEventListener.class); 67 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 68 | assertThat(candidate.onRevokedLatch.await(10, TimeUnit.SECONDS), is(true)); 69 | assertThat(listener.onEventsLatch.await(1, TimeUnit.MILLISECONDS), is(true)); 70 | assertThat(listener.events.size(), is(2)); 71 | ctx.close(); 72 | } 73 | 74 | @Test 75 | public void testBlockingThreadLeader() throws InterruptedException, IOException, EtcdException, TimeoutException { 76 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BlockingThreadTestConfig.class); 77 | BlockingThreadTestCandidate candidate = ctx.getBean(BlockingThreadTestCandidate.class); 78 | YieldTestEventListener listener = ctx.getBean(YieldTestEventListener.class); 79 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 80 | Thread.sleep(2000); // Let the grant-notification thread run for a while 81 | candidate.ctx.yield(); // Internally interrupts the grant notification thread 82 | assertThat(candidate.onRevokedLatch.await(10, TimeUnit.SECONDS), is(true)); 83 | assertThat(listener.onEventsLatch.await(1, TimeUnit.MILLISECONDS), is(true)); 84 | assertThat(listener.events.size(), is(2)); 85 | ctx.close(); 86 | } 87 | 88 | @Test 89 | public void testFailingCandidateGrantCallback() throws InterruptedException { 90 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FailingCandidateTestConfig.class); 91 | FailingTestCandidate candidate = ctx.getBean(FailingTestCandidate.class); 92 | YieldTestEventListener listener = ctx.getBean(YieldTestEventListener.class); 93 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 94 | assertThat(candidate.onRevokedLatch.await(10, TimeUnit.SECONDS), is(true)); 95 | assertThat(listener.onEventsLatch.await(10, TimeUnit.SECONDS), is(true)); 96 | assertThat(listener.events.size(), is(2)); 97 | ctx.close(); 98 | } 99 | 100 | @Configuration 101 | static class SimpleTestConfig { 102 | 103 | @Bean 104 | public TestCandidate candidate() { 105 | return new TestCandidate(); 106 | } 107 | 108 | @Bean 109 | public EtcdClient etcdInstance() { 110 | return new EtcdClient(URI.create("http://localhost:4001")); 111 | } 112 | 113 | @Bean 114 | public LeaderInitiator initiator() { 115 | LeaderInitiator initiator = new LeaderInitiator(etcdInstance(), candidate(), "etcd-test"); 116 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 117 | return initiator; 118 | } 119 | 120 | @Bean 121 | public LeaderEventPublisher leaderEventPublisher() { 122 | return new DefaultLeaderEventPublisher(); 123 | } 124 | 125 | @Bean 126 | public TestEventListener testEventListener() { 127 | return new TestEventListener(); 128 | } 129 | 130 | } 131 | 132 | static class TestCandidate extends DefaultCandidate { 133 | 134 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 135 | 136 | @Override 137 | public void onGranted(Context ctx) { 138 | onGrantedLatch.countDown(); 139 | super.onGranted(ctx); 140 | } 141 | 142 | } 143 | 144 | static class TestEventListener implements ApplicationListener { 145 | 146 | CountDownLatch onEventLatch = new CountDownLatch(1); 147 | 148 | ArrayList events = new ArrayList(); 149 | 150 | @Override 151 | public void onApplicationEvent(AbstractLeaderEvent event) { 152 | events.add(event); 153 | onEventLatch.countDown(); 154 | } 155 | 156 | } 157 | 158 | @Configuration 159 | static class YieldTestConfig { 160 | 161 | @Bean 162 | public YieldTestCandidate candidate() { 163 | return new YieldTestCandidate(); 164 | } 165 | 166 | @Bean 167 | public EtcdClient etcdInstance() { 168 | return new EtcdClient(URI.create("http://localhost:4001")); 169 | } 170 | 171 | @Bean 172 | public LeaderInitiator initiator() { 173 | LeaderInitiator initiator = new LeaderInitiator(etcdInstance(), candidate(), "etcd-yield-test"); 174 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 175 | return initiator; 176 | } 177 | 178 | @Bean 179 | public LeaderEventPublisher leaderEventPublisher() { 180 | return new DefaultLeaderEventPublisher(); 181 | } 182 | 183 | @Bean 184 | public YieldTestEventListener testEventListener() { 185 | return new YieldTestEventListener(); 186 | } 187 | 188 | } 189 | 190 | static class YieldTestCandidate extends DefaultCandidate { 191 | 192 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 193 | CountDownLatch onRevokedLatch = new CountDownLatch(1); 194 | 195 | @Override 196 | public void onGranted(Context ctx) { 197 | super.onGranted(ctx); 198 | onGrantedLatch.countDown(); 199 | ctx.yield(); 200 | } 201 | 202 | @Override 203 | public void onRevoked(Context ctx) { 204 | super.onRevoked(ctx); 205 | onRevokedLatch.countDown(); 206 | } 207 | 208 | } 209 | 210 | static class YieldTestEventListener implements ApplicationListener { 211 | 212 | CountDownLatch onEventsLatch = new CountDownLatch(2); 213 | 214 | ArrayList events = new ArrayList(); 215 | 216 | @Override 217 | public void onApplicationEvent(AbstractLeaderEvent event) { 218 | events.add(event); 219 | onEventsLatch.countDown(); 220 | } 221 | 222 | } 223 | 224 | @Configuration 225 | static class BlockingThreadTestConfig { 226 | 227 | @Bean 228 | public BlockingThreadTestCandidate candidate() { 229 | return new BlockingThreadTestCandidate(); 230 | } 231 | 232 | @Bean 233 | public EtcdClient etcdInstance() { 234 | return new EtcdClient(URI.create("http://localhost:4001")); 235 | } 236 | 237 | @Bean 238 | public LeaderInitiator initiator() { 239 | LeaderInitiator initiator = new LeaderInitiator(etcdInstance(), candidate(), "etcd-blocking-thread-test"); 240 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 241 | return initiator; 242 | } 243 | 244 | @Bean 245 | public LeaderEventPublisher leaderEventPublisher() { 246 | return new DefaultLeaderEventPublisher(); 247 | } 248 | 249 | @Bean 250 | public YieldTestEventListener testEventListener() { 251 | return new YieldTestEventListener(); 252 | } 253 | 254 | } 255 | 256 | static class BlockingThreadTestCandidate extends AbstractCandidate { 257 | 258 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 259 | CountDownLatch onRevokedLatch = new CountDownLatch(1); 260 | Context ctx = null; 261 | 262 | @Override 263 | public void onGranted(Context ctx) throws InterruptedException { 264 | this.ctx = ctx; 265 | LoggerFactory.getLogger(getClass()).info("{} has been granted leadership; context: {}", this, ctx); 266 | onGrantedLatch.countDown(); 267 | while (true) { 268 | LoggerFactory.getLogger(getClass()).info("{} is doing some heavy lifting", this); 269 | try { 270 | Thread.sleep(1000); // Mock heavy lifting 271 | } 272 | catch (InterruptedException e) { 273 | LoggerFactory.getLogger(getClass()).info("{} was interrupted, rethrowing the exception", this); 274 | throw e; 275 | } 276 | } 277 | } 278 | 279 | @Override 280 | public void onRevoked(Context ctx) { 281 | LoggerFactory.getLogger(getClass()).info("{} leadership has been revoked", this, ctx); 282 | onRevokedLatch.countDown(); 283 | } 284 | 285 | } 286 | 287 | @Configuration 288 | static class FailingCandidateTestConfig { 289 | 290 | @Bean 291 | public FailingTestCandidate candidate() { 292 | return new FailingTestCandidate(); 293 | } 294 | 295 | @Bean 296 | public EtcdClient etcdInstance() { 297 | return new EtcdClient(URI.create("http://localhost:4001")); 298 | } 299 | 300 | @Bean 301 | public LeaderInitiator initiator() { 302 | LeaderInitiator initiator = new LeaderInitiator(etcdInstance(), candidate(), "etcd-failing-candidate-test"); 303 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 304 | return initiator; 305 | } 306 | 307 | @Bean 308 | public LeaderEventPublisher leaderEventPublisher() { 309 | return new DefaultLeaderEventPublisher(); 310 | } 311 | 312 | @Bean 313 | public YieldTestEventListener testEventListener() { 314 | return new YieldTestEventListener(); 315 | } 316 | 317 | } 318 | 319 | static class FailingTestCandidate extends DefaultCandidate { 320 | 321 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 322 | CountDownLatch onRevokedLatch = new CountDownLatch(1); 323 | 324 | @Override 325 | public void onGranted(Context ctx) { 326 | super.onGranted(ctx); 327 | onGrantedLatch.countDown(); 328 | throw new RuntimeException("Candidate grant callback failure"); 329 | } 330 | 331 | @Override 332 | public void onRevoked(Context ctx) { 333 | super.onRevoked(ctx); 334 | onRevokedLatch.countDown(); 335 | } 336 | 337 | } 338 | 339 | } 340 | -------------------------------------------------------------------------------- /spring-cloud-cluster-hazelcast/.gitignore: -------------------------------------------------------------------------------- 1 | /.apt_generated/ 2 | -------------------------------------------------------------------------------- /spring-cloud-cluster-hazelcast/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-cluster-hazelcast 8 | jar 9 | 10 | spring-cloud-cluster-hazelcast 11 | Spring Cloud Cluster Hazelcast 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-cluster 16 | 1.0.2.RELEASE 17 | .. 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-cluster-core 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-configuration-processor 28 | true 29 | 30 | 31 | hazelcast 32 | com.hazelcast 33 | 34 | 35 | hazelcast-spring 36 | com.hazelcast 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /spring-cloud-cluster-hazelcast/src/main/java/org/springframework/cloud/cluster/hazelcast/HazelcastClusterProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.hazelcast; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | import org.springframework.core.io.Resource; 20 | 21 | /** 22 | * Configuration properties for hazelcast leader election. 23 | * 24 | * @author Janne Valkealahti 25 | * 26 | */ 27 | @ConfigurationProperties(value = "spring.cloud.cluster.hazelcast") 28 | public class HazelcastClusterProperties { 29 | 30 | /** xml config location for hazelcast configuration. */ 31 | private Resource configLocation; 32 | 33 | /** hazelcast leader properties. */ 34 | private HazelcastLeaderProperties leader = new HazelcastLeaderProperties(); 35 | 36 | public Resource getConfigLocation() { 37 | return configLocation; 38 | } 39 | 40 | public void setConfigLocation(Resource configLocation) { 41 | this.configLocation = configLocation; 42 | } 43 | 44 | public HazelcastLeaderProperties getLeader() { 45 | return leader; 46 | } 47 | 48 | public void setLeader(HazelcastLeaderProperties leader) { 49 | this.leader = leader; 50 | } 51 | 52 | public static class HazelcastLeaderProperties { 53 | 54 | /** if hazelcast leader election is enabled. */ 55 | private boolean enabled = true; 56 | 57 | public boolean isEnabled() { 58 | return enabled; 59 | } 60 | 61 | public void setEnabled(boolean enabled) { 62 | this.enabled = enabled; 63 | } 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /spring-cloud-cluster-hazelcast/src/main/java/org/springframework/cloud/cluster/hazelcast/leader/LeaderInitiator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.cluster.hazelcast.leader; 18 | 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.Future; 23 | import java.util.concurrent.ThreadFactory; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import org.springframework.beans.factory.DisposableBean; 27 | import org.springframework.beans.factory.InitializingBean; 28 | import org.springframework.cloud.cluster.leader.Candidate; 29 | import org.springframework.cloud.cluster.leader.Context; 30 | import org.springframework.cloud.cluster.leader.event.DefaultLeaderEventPublisher; 31 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 32 | import org.springframework.context.Lifecycle; 33 | import org.springframework.util.Assert; 34 | 35 | import com.hazelcast.core.HazelcastInstance; 36 | import com.hazelcast.core.IMap; 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | 40 | /** 41 | * Bootstrap leadership {@link org.springframework.cloud.cluster.leader.Candidate candidates} 42 | * with Hazelcast. Upon construction, {@link #start} must be invoked to 43 | * register the candidate for leadership election. 44 | * 45 | * @author Patrick Peralta 46 | * @author Gary Russell 47 | */ 48 | public class LeaderInitiator implements Lifecycle, InitializingBean, DisposableBean { 49 | 50 | private static final Logger logger = LoggerFactory.getLogger(LeaderInitiator.class); 51 | 52 | /** 53 | * Hazelcast client. 54 | */ 55 | private final HazelcastInstance client; 56 | 57 | /** 58 | * Candidate for leader election. 59 | */ 60 | private final Candidate candidate; 61 | 62 | /** 63 | * Executor service for running leadership daemon. 64 | */ 65 | private final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { 66 | @Override 67 | public Thread newThread(Runnable r) { 68 | Thread thread = new Thread(r, "Hazelcast leadership"); 69 | thread.setDaemon(true); 70 | return thread; 71 | } 72 | }); 73 | 74 | /** 75 | * Future returned by submitting an {@link Initiator} to {@link #executorService}. 76 | * This is used to cancel leadership. 77 | */ 78 | private volatile Future future; 79 | 80 | /** 81 | * Hazelcast distributed map used for locks. 82 | */ 83 | private volatile IMap mapLocks; 84 | 85 | /** 86 | * Flag that indicates whether the leadership election for 87 | * this {@link #candidate} is running. 88 | */ 89 | private volatile boolean running; 90 | 91 | /** 92 | * Leader event publisher. 93 | */ 94 | private volatile LeaderEventPublisher leaderEventPublisher = new DefaultLeaderEventPublisher(); 95 | 96 | /** 97 | * Construct a {@link LeaderInitiator}. 98 | * 99 | * @param client Hazelcast client 100 | * @param candidate leadership election candidate 101 | */ 102 | public LeaderInitiator(HazelcastInstance client, Candidate candidate) { 103 | this.client = client; 104 | this.candidate = candidate; 105 | } 106 | 107 | /** 108 | * Start the registration of the {@link #candidate} for leader election. 109 | */ 110 | @Override 111 | public synchronized void start() { 112 | if (!running) { 113 | mapLocks = client.getMap("spring-cloud-leader"); 114 | running = true; 115 | future = executorService.submit(new Initiator()); 116 | } 117 | } 118 | 119 | /** 120 | * Stop the registration of the {@link #candidate} for leader election. 121 | * If the candidate is currently leader, its leadership will be revoked. 122 | */ 123 | @Override 124 | public synchronized void stop() { 125 | if (running) { 126 | running = false; 127 | future.cancel(true); 128 | } 129 | } 130 | 131 | /** 132 | * @return true if leadership election for this {@link #candidate} is running 133 | */ 134 | @Override 135 | public boolean isRunning() { 136 | return running; 137 | } 138 | 139 | @Override 140 | public void afterPropertiesSet() throws Exception { 141 | start(); 142 | } 143 | 144 | @Override 145 | public void destroy() throws Exception { 146 | stop(); 147 | executorService.shutdown(); 148 | } 149 | 150 | /** 151 | * Sets the {@link LeaderEventPublisher}. 152 | * 153 | * @param leaderEventPublisher the event publisher 154 | */ 155 | public void setLeaderEventPublisher(LeaderEventPublisher leaderEventPublisher) { 156 | Assert.notNull(leaderEventPublisher); 157 | this.leaderEventPublisher = leaderEventPublisher; 158 | } 159 | 160 | /** 161 | * Callable that manages the acquisition of Hazelcast locks 162 | * for leadership election. 163 | */ 164 | class Initiator implements Callable { 165 | 166 | @Override 167 | public Void call() throws Exception { 168 | Assert.state(mapLocks != null); 169 | HazelcastContext context = new HazelcastContext(); 170 | String role = candidate.getRole(); 171 | boolean locked = false; 172 | 173 | while (running) { 174 | try { 175 | locked = mapLocks.tryLock(role, Long.MAX_VALUE, TimeUnit.MILLISECONDS); 176 | if (locked) { 177 | mapLocks.put(role, candidate.getId()); 178 | leaderEventPublisher.publishOnGranted(LeaderInitiator.this, context, candidate.getRole()); 179 | candidate.onGranted(context); 180 | Thread.sleep(Long.MAX_VALUE); 181 | } 182 | } 183 | catch (Exception e) { 184 | // Catch and log any exceptions. This prevents the 185 | // call method from exiting so that another attempt 186 | // at acquiring leadership may be made. 187 | // There is no need to reset the interrupt flag 188 | // on InterruptedException because the interrupt 189 | // is handled in the finally block below (just like 190 | // any other exception) 191 | logger.warn("Exception caught", e); 192 | } 193 | finally { 194 | if (locked) { 195 | mapLocks.remove(role); 196 | mapLocks.unlock(role); 197 | candidate.onRevoked(context); 198 | leaderEventPublisher.publishOnRevoked(LeaderInitiator.this, context, candidate.getRole()); 199 | locked = false; 200 | } 201 | } 202 | } 203 | return null; 204 | } 205 | 206 | } 207 | 208 | /** 209 | * Implementation of leadership context backed by Hazelcast. 210 | */ 211 | class HazelcastContext implements Context { 212 | 213 | @Override 214 | public boolean isLeader() { 215 | return mapLocks != null && mapLocks.isLocked(candidate.getRole()); 216 | } 217 | 218 | @Override 219 | public void yield() { 220 | if (future != null) { 221 | future.cancel(true); 222 | } 223 | } 224 | 225 | @Override 226 | public String toString() { 227 | return String.format("HazelcastContext{role=%s, id=%s, isLeader=%s}", 228 | candidate.getRole(), candidate.getId(), isLeader()); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /spring-cloud-cluster-hazelcast/src/test/java/org/springframework/cloud/cluster/hazelcast/leader/HazelcastTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.hazelcast.leader; 17 | 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import java.util.ArrayList; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import org.junit.Test; 26 | import org.springframework.cloud.cluster.leader.Context; 27 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 28 | import org.springframework.cloud.cluster.leader.event.AbstractLeaderEvent; 29 | import org.springframework.cloud.cluster.leader.event.DefaultLeaderEventPublisher; 30 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 31 | import org.springframework.context.ApplicationListener; 32 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 33 | import org.springframework.context.annotation.Bean; 34 | import org.springframework.context.annotation.Configuration; 35 | 36 | import com.hazelcast.core.Hazelcast; 37 | import com.hazelcast.core.HazelcastInstance; 38 | 39 | /** 40 | * Tests for hazelcast leader election. 41 | * 42 | * @author Janne Valkealahti 43 | * @author Patrick Peralta 44 | * 45 | */ 46 | public class HazelcastTests { 47 | 48 | @Test 49 | public void testSimpleLeader() throws InterruptedException { 50 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( 51 | Config1.class); 52 | TestCandidate candidate = ctx.getBean(TestCandidate.class); 53 | TestEventListener listener = ctx.getBean(TestEventListener.class); 54 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 55 | assertThat(listener.onEventLatch.await(5, TimeUnit.SECONDS), is(true)); 56 | assertThat(listener.events.size(), is(1)); 57 | ctx.close(); 58 | } 59 | 60 | @Configuration 61 | static class Config1 { 62 | 63 | @Bean 64 | public TestCandidate candidate() { 65 | return new TestCandidate(); 66 | } 67 | 68 | @Bean 69 | public HazelcastInstance hazelcastInstance() { 70 | return Hazelcast.newHazelcastInstance(); 71 | } 72 | 73 | @Bean 74 | public LeaderInitiator initiator() { 75 | LeaderInitiator initiator = new LeaderInitiator(hazelcastInstance(), candidate()); 76 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 77 | return initiator; 78 | } 79 | 80 | @Bean 81 | public LeaderEventPublisher leaderEventPublisher() { 82 | return new DefaultLeaderEventPublisher(); 83 | } 84 | 85 | @Bean 86 | public TestEventListener testEventListener() { 87 | return new TestEventListener(); 88 | } 89 | 90 | } 91 | 92 | static class TestCandidate extends DefaultCandidate { 93 | 94 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 95 | 96 | @Override 97 | public void onGranted(Context ctx) { 98 | onGrantedLatch.countDown(); 99 | super.onGranted(ctx); 100 | } 101 | 102 | } 103 | 104 | static class TestEventListener implements ApplicationListener { 105 | 106 | CountDownLatch onEventLatch = new CountDownLatch(1); 107 | 108 | ArrayList events = new ArrayList(); 109 | 110 | @Override 111 | public void onApplicationEvent(AbstractLeaderEvent event) { 112 | events.add(event); 113 | onEventLatch.countDown(); 114 | } 115 | 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /spring-cloud-cluster-redis/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-cluster-redis 8 | jar 9 | 10 | spring-cloud-cluster-redis 11 | Spring Cloud Cluster Redis 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-cluster 16 | 1.0.2.RELEASE 17 | .. 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-cluster-core 24 | 25 | 26 | org.springframework.integration 27 | spring-integration-redis 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-redis 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /spring-cloud-cluster-redis/src/main/java/org/springframework/cloud/cluster/redis/RedisClusterProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.redis; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | /** 21 | * Distributed locking properties for redis implementation. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | @ConfigurationProperties(value = "spring.cloud.cluster.redis") 27 | public class RedisClusterProperties { 28 | 29 | private RedisDistributedLockProperties lock = new RedisDistributedLockProperties(); 30 | 31 | public RedisDistributedLockProperties getLock() { 32 | return lock; 33 | } 34 | 35 | public void setLock(RedisDistributedLockProperties lock) { 36 | this.lock = lock; 37 | } 38 | 39 | public static class RedisDistributedLockProperties { 40 | 41 | /** if redis distributed locking is enabled. */ 42 | private boolean enabled = true; 43 | 44 | /** key expire in milliseconds */ 45 | private Long expireAfter; 46 | 47 | public boolean isEnabled() { 48 | return enabled; 49 | } 50 | 51 | public void setEnabled(boolean enabled) { 52 | this.enabled = enabled; 53 | } 54 | 55 | public Long getExpireAfter() { 56 | return expireAfter; 57 | } 58 | 59 | public void setExpireAfter(Long expireAfter) { 60 | this.expireAfter = expireAfter; 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /spring-cloud-cluster-redis/src/main/java/org/springframework/cloud/cluster/redis/lock/RedisLockService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.redis.lock; 17 | 18 | import java.util.concurrent.locks.Lock; 19 | 20 | import org.springframework.cloud.cluster.lock.DistributedLock; 21 | import org.springframework.cloud.cluster.lock.LockService; 22 | import org.springframework.cloud.cluster.lock.support.DelegatingDistributedLock; 23 | import org.springframework.data.redis.connection.RedisConnectionFactory; 24 | import org.springframework.integration.redis.util.RedisLockRegistry; 25 | 26 | /** 27 | * {@link LockService} implementation based on Redis. 28 | * 29 | *

This implementation delegates to {@link RedisLockRegistry} from Spring 30 | * Integration. 31 | * 32 | * @author Janne Valkealahti 33 | * 34 | */ 35 | public class RedisLockService implements LockService { 36 | 37 | public static final String DEFAULT_REGISTRY_KEY = "spring-cloud"; 38 | 39 | private final RedisLockRegistry redisLockRegistry; 40 | 41 | /** 42 | * Instantiates a new redis lock service. 43 | * 44 | * @param connectionFactory the redis connection factory 45 | */ 46 | public RedisLockService(RedisConnectionFactory connectionFactory) { 47 | this(connectionFactory, DEFAULT_REGISTRY_KEY); 48 | } 49 | 50 | /** 51 | * Instantiates a new redis lock service. 52 | * 53 | * @param connectionFactory the redis connection factory 54 | * @param registryKey The key prefix for locks. 55 | */ 56 | public RedisLockService(RedisConnectionFactory connectionFactory, String registryKey) { 57 | this.redisLockRegistry = new RedisLockRegistry(connectionFactory, registryKey); 58 | } 59 | 60 | /** 61 | * Instantiates a new redis lock service. 62 | * 63 | * @param connectionFactory the redis connection factory 64 | * @param expireAfter The expiration in milliseconds. 65 | */ 66 | public RedisLockService(RedisConnectionFactory connectionFactory, long expireAfter) { 67 | this.redisLockRegistry = new RedisLockRegistry(connectionFactory, DEFAULT_REGISTRY_KEY, expireAfter); 68 | } 69 | 70 | /** 71 | * Instantiates a new redis lock service. 72 | * 73 | * @param connectionFactory the redis connection factory 74 | * @param registryKey The key prefix for locks. 75 | * @param expireAfter The expiration in milliseconds. 76 | */ 77 | public RedisLockService(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) { 78 | this.redisLockRegistry = new RedisLockRegistry(connectionFactory, registryKey, expireAfter); 79 | } 80 | 81 | @Override 82 | public DistributedLock obtain(String lockKey) { 83 | Lock lock = redisLockRegistry.obtain(lockKey); 84 | return new DelegatingDistributedLock(lockKey, lock); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /spring-cloud-cluster-redis/src/test/java/org/springframework/cloud/cluster/redis/lock/RedisIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.redis.lock; 17 | 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import java.util.Set; 22 | 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 27 | import org.springframework.boot.test.EnvironmentTestUtils; 28 | import org.springframework.cloud.cluster.lock.DistributedLock; 29 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 30 | import org.springframework.data.redis.connection.RedisConnectionFactory; 31 | import org.springframework.data.redis.core.RedisTemplate; 32 | import org.springframework.data.redis.serializer.StringRedisSerializer; 33 | 34 | /** 35 | * Integration tests for redis locking using external redis server. 36 | * 37 | * @author Janne Valkealahti 38 | * 39 | */ 40 | public class RedisIT { 41 | 42 | private AnnotationConfigApplicationContext context; 43 | private RedisConnectionFactory connectionFactory; 44 | private RedisTemplate redisTemplate; 45 | 46 | @Before 47 | public void setup() { 48 | context = new AnnotationConfigApplicationContext(); 49 | EnvironmentTestUtils.addEnvironment(context); 50 | context.register(RedisAutoConfiguration.class); 51 | context.refresh(); 52 | connectionFactory = context.getBean(RedisConnectionFactory.class); 53 | redisTemplate = new RedisTemplate(); 54 | redisTemplate.setConnectionFactory(connectionFactory); 55 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 56 | redisTemplate.setValueSerializer(new StringRedisSerializer()); 57 | redisTemplate.afterPropertiesSet(); 58 | cleanLocks(); 59 | } 60 | 61 | @After 62 | public void close() { 63 | cleanLocks(); 64 | context.close(); 65 | } 66 | 67 | private void cleanLocks() { 68 | Set keys = redisTemplate.keys(RedisLockService.DEFAULT_REGISTRY_KEY + ":*"); 69 | redisTemplate.delete(keys); 70 | } 71 | 72 | @Test 73 | public void testSimpleLock() { 74 | RedisLockService lockService = new RedisLockService(connectionFactory); 75 | DistributedLock lock = lockService.obtain("lock"); 76 | lock.lock(); 77 | 78 | Set keys = redisTemplate.keys(RedisLockService.DEFAULT_REGISTRY_KEY + ":*"); 79 | assertThat(keys.size(), is(1)); 80 | assertThat(keys.iterator().next(), is(RedisLockService.DEFAULT_REGISTRY_KEY + ":lock")); 81 | 82 | lock.unlock(); 83 | } 84 | 85 | @Test 86 | public void testSecondLockSucceed() { 87 | RedisLockService lockService = new RedisLockService(connectionFactory); 88 | DistributedLock lock1 = lockService.obtain("lock"); 89 | DistributedLock lock2 = lockService.obtain("lock"); 90 | lock1.lock(); 91 | // same thread so try/lock doesn't fail 92 | assertThat(lock2.tryLock(), is(true)); 93 | lock2.lock(); 94 | 95 | Set keys = redisTemplate.keys(RedisLockService.DEFAULT_REGISTRY_KEY + ":*"); 96 | assertThat(keys.size(), is(1)); 97 | assertThat(keys.iterator().next(), is(RedisLockService.DEFAULT_REGISTRY_KEY + ":lock")); 98 | 99 | lock1.unlock(); 100 | lock2.unlock(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/.gitignore: -------------------------------------------------------------------------------- 1 | /.apt_generated/ 2 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-cluster-zookeeper 8 | jar 9 | 10 | spring-cloud-cluster-zookeeper 11 | Spring Cloud Cluster Zookeeper 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-cluster 16 | 1.0.2.RELEASE 17 | .. 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-cluster-core 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-configuration-processor 28 | true 29 | 30 | 31 | org.apache.curator 32 | curator-recipes 33 | 34 | 35 | org.apache.curator 36 | curator-test 37 | test 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/src/main/java/org/springframework/cloud/cluster/zk/ZookeeperClusterProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.zk; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | /** 21 | * Configuration properties for zookeeper leader election. 22 | * 23 | * @author Janne Valkealahti 24 | * 25 | */ 26 | @ConfigurationProperties(value = "spring.cloud.cluster.zookeeper") 27 | public class ZookeeperClusterProperties { 28 | 29 | /** base zookeeper namespace path. */ 30 | private String namespace; 31 | 32 | /** connect string for zookeeper. */ 33 | private String connect = "localhost:2181"; 34 | 35 | /** zookeeper leader properties. */ 36 | private ZookeeperLeaderProperties leader = new ZookeeperLeaderProperties(); 37 | 38 | public String getNamespace() { 39 | return namespace; 40 | } 41 | 42 | public void setNamespace(String namespace) { 43 | this.namespace = namespace; 44 | } 45 | 46 | public String getConnect() { 47 | return connect; 48 | } 49 | 50 | public void setConnect(String connect) { 51 | this.connect = connect; 52 | } 53 | 54 | public ZookeeperLeaderProperties getLeader() { 55 | return leader; 56 | } 57 | 58 | public void setLeader(ZookeeperLeaderProperties leader) { 59 | this.leader = leader; 60 | } 61 | 62 | public static class ZookeeperLeaderProperties { 63 | 64 | /** if zookeeper leader election is enabled. */ 65 | private boolean enabled = true; 66 | 67 | public boolean isEnabled() { 68 | return enabled; 69 | } 70 | 71 | public void setEnabled(boolean enabled) { 72 | this.enabled = enabled; 73 | } 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/src/main/java/org/springframework/cloud/cluster/zk/curator/CuratorFrameworkFactoryBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.zk.curator; 17 | 18 | import org.apache.curator.RetryPolicy; 19 | import org.apache.curator.framework.CuratorFramework; 20 | import org.apache.curator.framework.CuratorFrameworkFactory; 21 | import org.apache.curator.framework.imps.CuratorFrameworkState; 22 | import org.apache.curator.retry.ExponentialBackoffRetry; 23 | import org.apache.curator.utils.CloseableUtils; 24 | import org.springframework.beans.factory.FactoryBean; 25 | import org.springframework.context.SmartLifecycle; 26 | import org.springframework.util.Assert; 27 | 28 | /** 29 | * A spring-friendly way to build a {@link CuratorFramework} and implementing {@link SmartLifecycle}. 30 | * 31 | * @author Gary Russell 32 | * 33 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 34 | */ 35 | @Deprecated 36 | public class CuratorFrameworkFactoryBean implements FactoryBean, SmartLifecycle { 37 | 38 | private final Object lifecycleLock = new Object(); 39 | 40 | private final CuratorFramework client; 41 | 42 | /** 43 | * @see SmartLifecycle 44 | */ 45 | private volatile boolean autoStartup = true; 46 | 47 | /** 48 | * @see SmartLifecycle 49 | */ 50 | private volatile boolean running; 51 | 52 | /** 53 | * @see SmartLifecycle 54 | */ 55 | private volatile int phase; 56 | 57 | 58 | /** 59 | * Construct an instance using the supplied connection string and using a default 60 | * retry policy {@code new ExponentialBackoffRetry(1000, 3)}. 61 | * @param connectionString 62 | */ 63 | public CuratorFrameworkFactoryBean(String connectionString) { 64 | this(connectionString, new ExponentialBackoffRetry(1000, 3)); 65 | } 66 | 67 | /** 68 | * Construct an instance using the supplied connection string and retry policy. 69 | * @param connectionString the connection string 70 | * @param retryPolicy the retry policy 71 | */ 72 | public CuratorFrameworkFactoryBean(String connectionString, RetryPolicy retryPolicy) { 73 | Assert.notNull(connectionString, "'connectionString' cannot be null"); 74 | Assert.notNull(retryPolicy, "'retryPolicy' cannot be null"); 75 | this.client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy); 76 | } 77 | 78 | @Override 79 | public int getPhase() { 80 | return this.phase; 81 | } 82 | 83 | /** 84 | * @param phase the phase 85 | * @see SmartLifecycle 86 | */ 87 | public void setPhase(int phase) { 88 | this.phase = phase; 89 | } 90 | 91 | @Override 92 | public boolean isRunning() { 93 | return this.running; 94 | } 95 | 96 | @Override 97 | public boolean isAutoStartup() { 98 | return this.autoStartup; 99 | } 100 | 101 | /** 102 | * @param autoStartup true to automatically start 103 | * @see SmartLifecycle 104 | */ 105 | public void setAutoStartup(boolean autoStartup) { 106 | this.autoStartup = autoStartup; 107 | } 108 | 109 | @Override 110 | public void start() { 111 | synchronized (this.lifecycleLock) { 112 | if (!this.running) { 113 | if (this.client != null) { 114 | this.client.start(); 115 | } 116 | this.running = true; 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public void stop() { 123 | synchronized (this.lifecycleLock) { 124 | if (this.running) { 125 | if (this.client.getState().equals(CuratorFrameworkState.STARTED)) { 126 | CloseableUtils.closeQuietly(this.client); 127 | } 128 | } 129 | } 130 | } 131 | 132 | @Override 133 | public void stop(Runnable runnable) { 134 | stop(); 135 | runnable.run(); 136 | } 137 | 138 | @Override 139 | public CuratorFramework getObject() throws Exception { 140 | return this.client; 141 | } 142 | 143 | @Override 144 | public Class getObjectType() { 145 | return CuratorFramework.class; 146 | } 147 | 148 | @Override 149 | public boolean isSingleton() { 150 | return true; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/src/main/java/org/springframework/cloud/cluster/zk/leader/LeaderInitiator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.zk.leader; 17 | 18 | import org.apache.curator.framework.CuratorFramework; 19 | import org.apache.curator.framework.imps.CuratorFrameworkState; 20 | import org.apache.curator.framework.recipes.leader.LeaderSelector; 21 | import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter; 22 | import org.springframework.beans.factory.DisposableBean; 23 | import org.springframework.cloud.cluster.leader.Candidate; 24 | import org.springframework.cloud.cluster.leader.Context; 25 | import org.springframework.cloud.cluster.leader.event.DefaultLeaderEventPublisher; 26 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 27 | import org.springframework.context.SmartLifecycle; 28 | import org.springframework.util.Assert; 29 | import org.springframework.util.StringUtils; 30 | 31 | /** 32 | * Bootstrap leadership {@link org.springframework.cloud.cluster.leader.Candidate candidates} 33 | * with ZooKeeper/Curator. Upon construction, {@link #start} must be invoked to 34 | * register the candidate for leadership election. 35 | * 36 | * @author Patrick Peralta 37 | * @author Janne Valkealahti 38 | * 39 | * @deprecated in favour of equivalent functionality in Spring Integration 4.3 40 | */ 41 | @Deprecated 42 | public class LeaderInitiator implements SmartLifecycle, DisposableBean { 43 | 44 | private static final String DEFAULT_NAMESPACE = "/spring-cloud/leader/"; 45 | 46 | /** 47 | * Curator client. 48 | */ 49 | private final CuratorFramework client; 50 | 51 | /** 52 | * Candidate for leader election. 53 | */ 54 | private final Candidate candidate; 55 | 56 | /** 57 | * Curator utility for selecting leaders. 58 | */ 59 | private volatile LeaderSelector leaderSelector; 60 | 61 | /** 62 | * @see SmartLifecycle 63 | */ 64 | private volatile boolean autoStartup = true; 65 | 66 | /** 67 | * @see SmartLifecycle 68 | */ 69 | private volatile int phase; 70 | 71 | /** 72 | * Flag that indicates whether the leadership election for 73 | * this {@link #candidate} is running. 74 | */ 75 | private volatile boolean running; 76 | 77 | /** 78 | * Base path in Zookeeper. 79 | * */ 80 | private final String namespace; 81 | 82 | /** 83 | * Leader event publisher. 84 | */ 85 | private volatile LeaderEventPublisher leaderEventPublisher = new DefaultLeaderEventPublisher(); 86 | 87 | /** 88 | * Construct a {@link LeaderInitiator}. 89 | * 90 | * @param client Curator client 91 | * @param candidate leadership election candidate 92 | */ 93 | public LeaderInitiator(CuratorFramework client, Candidate candidate) { 94 | this(client, candidate, DEFAULT_NAMESPACE); 95 | } 96 | 97 | /** 98 | * Construct a {@link LeaderInitiator}. 99 | * 100 | * @param client Curator client 101 | * @param candidate leadership election candidate 102 | * @param namespace namespace base path in zookeeper 103 | */ 104 | public LeaderInitiator(CuratorFramework client, Candidate candidate, String namespace) { 105 | this.client = client; 106 | this.candidate = candidate; 107 | this.namespace = namespace; 108 | } 109 | 110 | /** 111 | * @return true if leadership election for this {@link #candidate} is running 112 | */ 113 | @Override 114 | public boolean isRunning() { 115 | return this.running; 116 | } 117 | 118 | @Override 119 | public int getPhase() { 120 | return this.phase; 121 | } 122 | 123 | /** 124 | * @param phase the phase 125 | * @see SmartLifecycle 126 | */ 127 | public void setPhase(int phase) { 128 | this.phase = phase; 129 | } 130 | 131 | @Override 132 | public boolean isAutoStartup() { 133 | return this.autoStartup; 134 | } 135 | 136 | /** 137 | * @param autoStartup true to start automatically 138 | * @see SmartLifecycle 139 | */ 140 | public void setAutoStartup(boolean autoStartup) { 141 | this.autoStartup = autoStartup; 142 | } 143 | 144 | /** 145 | * Start the registration of the {@link #candidate} for leader election. 146 | */ 147 | @Override 148 | public synchronized void start() { 149 | if (!this.running) { 150 | if (this.client.getState() != CuratorFrameworkState.STARTED) { 151 | // we want to do curator start here because it needs to 152 | // be started before leader selector and it gets a little 153 | // complicated to control ordering via beans so that 154 | // curator is fully started. 155 | this.client.start(); 156 | } 157 | this.leaderSelector = new LeaderSelector(this.client, buildLeaderPath(), new LeaderListener()); 158 | this.leaderSelector.setId(this.candidate.getId()); 159 | this.leaderSelector.autoRequeue(); 160 | this.leaderSelector.start(); 161 | 162 | this.running = true; 163 | } 164 | } 165 | 166 | /** 167 | * Stop the registration of the {@link #candidate} for leader election. 168 | * If the candidate is currently leader, its leadership will be revoked. 169 | */ 170 | @Override 171 | public synchronized void stop() { 172 | if (this.running) { 173 | this.leaderSelector.close(); 174 | this.running = false; 175 | } 176 | } 177 | 178 | @Override 179 | public void stop(Runnable runnable) { 180 | stop(); 181 | runnable.run(); 182 | } 183 | 184 | @Override 185 | public void destroy() throws Exception { 186 | stop(); 187 | } 188 | 189 | /** 190 | * Sets the {@link LeaderEventPublisher}. 191 | * 192 | * @param leaderEventPublisher the event publisher 193 | */ 194 | public void setLeaderEventPublisher(LeaderEventPublisher leaderEventPublisher) { 195 | Assert.notNull(leaderEventPublisher); 196 | this.leaderEventPublisher = leaderEventPublisher; 197 | } 198 | 199 | /** 200 | * @return the ZooKeeper path used for leadership election by Curator 201 | */ 202 | private String buildLeaderPath() { 203 | String ns = StringUtils.hasText(this.namespace) ? this.namespace : DEFAULT_NAMESPACE; 204 | if (!ns.startsWith("/")) { 205 | ns = "/" + ns; 206 | } 207 | if (!ns.endsWith("/")) { 208 | ns = ns + "/"; 209 | } 210 | return String.format(ns + "%s", this.candidate.getRole()); 211 | } 212 | 213 | /** 214 | * Implementation of Curator leadership election listener. 215 | */ 216 | class LeaderListener extends LeaderSelectorListenerAdapter { 217 | 218 | @Override 219 | public void takeLeadership(CuratorFramework framework) throws Exception { 220 | CuratorContext context = new CuratorContext(); 221 | 222 | try { 223 | LeaderInitiator.this.leaderEventPublisher.publishOnGranted(LeaderInitiator.this, context, LeaderInitiator.this.candidate.getRole()); 224 | LeaderInitiator.this.candidate.onGranted(context); 225 | 226 | // when this method exits, the leadership will be revoked; 227 | // therefore this thread needs to be held up until the 228 | // candidate is no longer leader 229 | Thread.sleep(Long.MAX_VALUE); 230 | } 231 | catch (InterruptedException e) { 232 | // InterruptedException, like any other runtime exception, 233 | // is handled by the finally block below. No need to 234 | // reset the interrupt flag as the interrupt is handled. 235 | } 236 | finally { 237 | LeaderInitiator.this.candidate.onRevoked(context); 238 | LeaderInitiator.this.leaderEventPublisher.publishOnRevoked(LeaderInitiator.this, context, LeaderInitiator.this.candidate.getRole()); 239 | } 240 | } 241 | } 242 | 243 | /** 244 | * Implementation of leadership context backed by Curator. 245 | */ 246 | class CuratorContext implements Context { 247 | 248 | @Override 249 | public boolean isLeader() { 250 | return LeaderInitiator.this.leaderSelector.hasLeadership(); 251 | } 252 | 253 | @Override 254 | public void yield() { 255 | LeaderInitiator.this.leaderSelector.interruptLeadership(); 256 | } 257 | 258 | @Override 259 | public String toString() { 260 | return String.format("CuratorContext{role=%s, id=%s, isLeader=%s}", 261 | LeaderInitiator.this.candidate.getRole(), LeaderInitiator.this.candidate.getId(), isLeader()); 262 | } 263 | 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/src/test/java/org/springframework/cloud/cluster/zk/curator/CuratorFrameworkFactoryBeanTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.zk.curator; 17 | 18 | import static org.junit.Assert.assertTrue; 19 | 20 | import org.apache.curator.framework.CuratorFramework; 21 | import org.apache.curator.framework.imps.CuratorFrameworkState; 22 | import org.apache.curator.test.TestingServer; 23 | import org.junit.Test; 24 | 25 | /** 26 | * @author Gary Russell 27 | * 28 | */ 29 | public class CuratorFrameworkFactoryBeanTests { 30 | 31 | @Test 32 | public void test() throws Exception { 33 | TestingServer testingServer = new TestingServer(); 34 | CuratorFrameworkFactoryBean fb = new CuratorFrameworkFactoryBean(testingServer.getConnectString()); 35 | CuratorFramework client = fb.getObject(); 36 | fb.start(); 37 | assertTrue(client.getState().equals(CuratorFrameworkState.STARTED)); 38 | fb.stop(); 39 | assertTrue(client.getState().equals(CuratorFrameworkState.STOPPED)); 40 | testingServer.close(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /spring-cloud-cluster-zookeeper/src/test/java/org/springframework/cloud/cluster/zk/leader/ZookeeperTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.cluster.zk.leader; 17 | 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | import java.util.concurrent.CountDownLatch; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import org.apache.curator.framework.CuratorFramework; 27 | import org.apache.curator.framework.CuratorFrameworkFactory; 28 | import org.apache.curator.retry.ExponentialBackoffRetry; 29 | import org.apache.curator.test.TestingServer; 30 | import org.junit.Test; 31 | import org.springframework.beans.factory.DisposableBean; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.cloud.cluster.leader.Context; 34 | import org.springframework.cloud.cluster.leader.DefaultCandidate; 35 | import org.springframework.cloud.cluster.leader.event.AbstractLeaderEvent; 36 | import org.springframework.cloud.cluster.leader.event.DefaultLeaderEventPublisher; 37 | import org.springframework.cloud.cluster.leader.event.LeaderEventPublisher; 38 | import org.springframework.context.ApplicationListener; 39 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 40 | import org.springframework.context.annotation.Bean; 41 | import org.springframework.context.annotation.Configuration; 42 | 43 | /** 44 | * Tests for zookeeper leader election. 45 | * 46 | * @author Janne Valkealahti 47 | * @author Patrick Peralta 48 | * 49 | */ 50 | public class ZookeeperTests { 51 | 52 | @Test 53 | public void testSimpleLeader() throws InterruptedException { 54 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( 55 | ZkServerConfig.class, Config1.class); 56 | TestCandidate candidate = ctx.getBean(TestCandidate.class); 57 | TestEventListener listener = ctx.getBean(TestEventListener.class); 58 | assertThat(candidate.onGrantedLatch.await(5, TimeUnit.SECONDS), is(true)); 59 | assertThat(listener.onEventLatch.await(5, TimeUnit.SECONDS), is(true)); 60 | assertThat(listener.events.size(), is(1)); 61 | ctx.close(); 62 | } 63 | 64 | @Configuration 65 | static class ZkServerConfig { 66 | 67 | @Bean 68 | public TestingServerWrapper testingServerWrapper() throws Exception { 69 | return new TestingServerWrapper(); 70 | } 71 | 72 | } 73 | 74 | @Configuration 75 | static class Config1 { 76 | 77 | @Autowired 78 | TestingServerWrapper testingServerWrapper; 79 | 80 | @Bean 81 | public TestCandidate candidate() { 82 | return new TestCandidate(); 83 | } 84 | 85 | @Bean(destroyMethod = "close") 86 | public CuratorFramework curatorClient() throws Exception { 87 | CuratorFramework client = CuratorFrameworkFactory.builder().defaultData(new byte[0]) 88 | .retryPolicy(new ExponentialBackoffRetry(1000, 3)) 89 | .connectString("localhost:" + testingServerWrapper.getPort()).build(); 90 | // for testing we start it here, thought initiator 91 | // is trying to start it if not already done 92 | client.start(); 93 | return client; 94 | } 95 | 96 | @Bean 97 | public LeaderInitiator initiator() throws Exception { 98 | LeaderInitiator initiator = new LeaderInitiator(curatorClient(), candidate()); 99 | initiator.setLeaderEventPublisher(leaderEventPublisher()); 100 | return initiator; 101 | } 102 | 103 | @Bean 104 | public LeaderEventPublisher leaderEventPublisher() { 105 | return new DefaultLeaderEventPublisher(); 106 | } 107 | 108 | @Bean 109 | public TestEventListener testEventListener() { 110 | return new TestEventListener(); 111 | } 112 | 113 | } 114 | 115 | static class TestingServerWrapper implements DisposableBean { 116 | 117 | TestingServer testingServer; 118 | 119 | public TestingServerWrapper() throws Exception { 120 | this.testingServer = new TestingServer(true); 121 | } 122 | 123 | @Override 124 | public void destroy() throws Exception { 125 | try { 126 | testingServer.close(); 127 | } 128 | catch (IOException e) { 129 | } 130 | } 131 | 132 | public int getPort() { 133 | return testingServer.getPort(); 134 | } 135 | 136 | } 137 | 138 | static class TestCandidate extends DefaultCandidate { 139 | 140 | CountDownLatch onGrantedLatch = new CountDownLatch(1); 141 | 142 | @Override 143 | public void onGranted(Context ctx) { 144 | onGrantedLatch.countDown(); 145 | super.onGranted(ctx); 146 | } 147 | 148 | } 149 | 150 | static class TestEventListener implements ApplicationListener { 151 | 152 | CountDownLatch onEventLatch = new CountDownLatch(1); 153 | 154 | ArrayList events = new ArrayList(); 155 | 156 | @Override 157 | public void onApplicationEvent(AbstractLeaderEvent event) { 158 | events.add(event); 159 | onEventLatch.countDown(); 160 | } 161 | 162 | } 163 | 164 | } 165 | --------------------------------------------------------------------------------