├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── cd
├── deploy.sh
├── mvnsettings.xml
├── tag.sh
└── version.sh
├── gpg.asc.enc
├── images
└── result.png
├── pom.xml
└── src
├── main
├── java
│ └── technology
│ │ └── dice
│ │ └── dicefairlink
│ │ ├── AuroraReadonlyEndpoint.java
│ │ ├── ParsedUrl.java
│ │ ├── config
│ │ ├── AwsApiDiscoveryAuthMode.java
│ │ ├── FairlinkConfiguration.java
│ │ └── ReplicasDiscoveryMode.java
│ │ ├── discovery
│ │ ├── members
│ │ │ ├── ClusterInfo.java
│ │ │ ├── FairlinkMemberFinder.java
│ │ │ ├── JdbcConnectionValidator.java
│ │ │ ├── MemberFinder.java
│ │ │ ├── MemberFinderMethod.java
│ │ │ ├── ReplicaValidator.java
│ │ │ ├── awsapi
│ │ │ │ └── AwsApiReplicasFinder.java
│ │ │ └── sql
│ │ │ │ ├── DatabaseInstance.java
│ │ │ │ ├── DatabaseInstanceRole.java
│ │ │ │ ├── MySQLReplicasFinder.java
│ │ │ │ └── PostgresSQLReplicasFinder.java
│ │ └── tags
│ │ │ ├── ExclusionTag.java
│ │ │ ├── TagFilter.java
│ │ │ └── awsapi
│ │ │ └── ResourceGroupApiTagDiscovery.java
│ │ ├── driver
│ │ ├── AuroraReadReplicasDriver.java
│ │ ├── Constants.java
│ │ └── FairlinkConnectionString.java
│ │ └── iterators
│ │ ├── CyclicIterator.java
│ │ ├── RandomisedCyclicIterator.java
│ │ └── SizedIterator.java
└── resources
│ └── META-INF
│ └── services
│ └── java.sql.Driver
└── test
├── bash
└── readReplicasOnLoop.bash
├── java
└── technology
│ └── dice
│ └── dicefairlink
│ ├── AuroraReadonlyEndpointTest.java
│ ├── StepByStepExecutor.java
│ ├── config
│ └── FairlinkConfigurationTest.java
│ ├── discovery
│ ├── members
│ │ ├── ClusterInfoTest.java
│ │ ├── FairlinkMemberFinderTest.java
│ │ ├── JdbcConnectionValidatorTest.java
│ │ ├── awsapi
│ │ │ ├── AwsApiReplicasFinderTest.java
│ │ │ └── AwsApiReplicasFinderTestIT.java
│ │ └── sql
│ │ │ ├── MySqlReplicasFinderTest.java
│ │ │ └── PostgresSqlReplicaFinderTest.java
│ └── tags
│ │ └── awsapi
│ │ ├── ResourceGroupApiResponse.java
│ │ ├── ResourceGroupApiTagDiscoveryTest.java
│ │ └── ResourceGroupApiTagDiscoveryTestIT.java
│ ├── driver
│ ├── AuroraReadReplicasDriverEndToEndTest.java
│ ├── AuroraReadReplicasDriverTest.java
│ ├── Data.java
│ └── FairlinkConnectionStringTest.java
│ ├── iterators
│ └── RandomisedCyclicIteratorTest.java
│ └── support
│ ├── discovery
│ ├── members
│ │ ├── FailingReplicasFinder.java
│ │ ├── FixedMemberFinder.java
│ │ └── FixedSetReplicasFinder.java
│ └── tags
│ │ ├── FailingExcludedReplicasFinder.java
│ │ └── FixedSetExcludedReplicasFinder.java
│ ├── driver
│ └── TestDriver.java
│ └── iterators
│ └── TestCyclicIterator.java
└── resources
├── logback.xml
└── technology
└── dice
└── dicefairlink
└── discovery
└── members
├── awsapi
├── allAvailableDbInstances.xml
├── emptyResponse.xml
├── oneReplicaDeletingInstances.xml
├── withMembers.xml
└── withoutMembers.xml
└── sql
├── mysql
├── 2replicas.sql
├── 3replicasPorts.sql
├── masterData.sql
├── schema.sql
├── slave1Data.sql
└── slave2Data.sql
└── postgresql
├── 2replicas.sql
├── drop_function.sql
└── schema.sql
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout Git repo (with tags)
15 | uses: actions/checkout@v3
16 | with:
17 | fetch-depth: 0
18 | - name: Cache Maven packages
19 | uses: actions/cache@v3
20 | with:
21 | key: ${{ runner.os }}-1
22 | path: |
23 | ~/.m2/repository
24 | - name: Set up JDK 8
25 | uses: actions/setup-java@v3
26 | with:
27 | java-version: '8'
28 | distribution: 'temurin'
29 | - name: Build with Maven
30 | run: mvn --batch-mode --update-snapshots install
31 | - name: Run Codecov
32 | uses: codecov/codecov-action@v3
33 | with:
34 | token: ${{ secrets.CODECOV_TOKEN }}
35 | - name: Set version, sign artifacts and deploy to Maven Central
36 | if: ${{ github.ref == 'refs/heads/master' && github.repository == 'DiceTechnology/dice-fairlink' }}
37 | run: cd/deploy.sh
38 | env:
39 | GPG_KEY: ${{ secrets.GPG_KEY }}
40 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
41 | CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
42 | CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
43 | SLACK_URL: ${{ secrets.SLACK_URL }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Package Files #
2 | *.jar
3 | *.war
4 | *.nar
5 | *.ear
6 | *.zip
7 | *.tar.gz
8 | *.rar
9 |
10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
11 | hs_err_pid*
12 | *.class
13 | *.og
14 | target/
15 | .idea/
16 | *.iml
17 | .DS_Store
18 | *.mmdb
19 | /nbproject/
20 | /mavenproject1/nbproject/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Dice Technology
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/cd/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set +x
4 | echo $GPG_KEY | base64 --decode | gpg --batch --fast-import
5 | set -x
6 |
7 | ./cd/version.sh && \
8 | ./cd/tag.sh && \
9 | mvn deploy -P publish -DskipTests=true --settings cd/mvnsettings.xml
10 |
--------------------------------------------------------------------------------
/cd/mvnsettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | central
5 | ${env.CENTRAL_USERNAME}
6 | ${env.CENTRAL_TOKEN}
7 |
8 |
9 |
10 |
11 | central
12 |
13 | true
14 |
15 |
16 | gpg
17 | 690636EA9C9CEBE2
18 | ${env.GPG_PASSPHRASE}
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/cd/tag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Script to tag the GIT repository with a specific version taken from the POM file
3 |
4 | set -x
5 |
6 | function slack {
7 | local PAYLOAD="{\"text\":\"$1\"}"
8 | echo Sending message to slack
9 | set +x
10 | curl -o /dev/null -s -w "%{http_code}\n" -X POST -H 'Content-type: application/json' --data "$PAYLOAD" $SLACK_URL
11 | set -x
12 | }
13 |
14 | # Get VERSION from top level POM
15 | VERSION_POM=$( mvn help:evaluate -Dexpression=project.version | grep -v '\[.*' | tail -n1 )
16 |
17 | # Get ARTIFACT_ID from top level POM
18 | ARTIFACT_ID_POM=$( mvn help:evaluate -Dexpression=project.artifactId | grep -v '\[.*' | tail -n1 )
19 |
20 | # Setup Git Configuration
21 | git config --global user.email "build@dice.technology"
22 | git config --global user.name "DiceTech CI"
23 |
24 | git tag "${VERSION_POM}" -m "[GH] Released ${VERSION_POM}" 2>/dev/null && \
25 | git push origin --tags 2>/dev/null && \
26 | echo "Tagged $ARTIFACT_ID_POM with version $VERSION_POM" && \
27 | slack "Tagged $ARTIFACT_ID_POM with version $VERSION_POM"
28 |
--------------------------------------------------------------------------------
/cd/version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Script to create a semver version as MAJOR.MINOR.PATCH (https://semver.org/) where:
3 | # - MAJOR.MINOR is taken from top level POM
4 | # - PATCH is different for each branch:
5 | # - master branch, will be based on latest tag like MAJOR.MINOR.PREVIOUS_PATCH and return PREVIOUS_PATCH + 1
6 | # - other branches, will be the Git Hash
7 |
8 | set -x
9 |
10 | # Get PATCH from git latest tag starting with major.minor, otherwise 0
11 | function getPatchFromGitTag {
12 | local VERSION_TAG=$( git tag -l "${MAJOR}.${MINOR}*" | sort -t . -k 3 -g | tail -n1 )
13 | local VERSION_TAG_BITS=(${VERSION_TAG//./ })
14 |
15 | local PATCH=0
16 | if [ ! -z "${VERSION_TAG_BITS[2]}" ]
17 | then
18 | PATCH=$((${VERSION_TAG_BITS[2]} + 1))
19 | fi
20 |
21 | echo "$PATCH"
22 | }
23 |
24 | function getPatchFromGitHash {
25 | local GIT_HASH=$( git rev-parse HEAD | cut -c 1-7 )
26 | local PATCH="0-$GIT_HASH"
27 | echo "$PATCH"
28 | }
29 |
30 | # Get MAJOR and MINOR from top level POM
31 | VERSION_POM=$( mvn help:evaluate -Dexpression=project.version | grep -v '\[.*' | tail -n1 )
32 | VERSION_POM_BITS=(${VERSION_POM//./ })
33 |
34 | MAJOR=${VERSION_POM_BITS[0]}
35 | MINOR=${VERSION_POM_BITS[1]}
36 | PATCH=$(getPatchFromGitTag)
37 | VERSION="${MAJOR}.${MINOR}.${PATCH}"
38 |
39 | echo "Setting version to ${VERSION}"
40 | echo "NEW_VERSION=${VERSION}" >> $GITHUB_ENV
41 |
42 | # Set the new version in POM
43 | mvn versions:set -DgenerateBackupPoms=false -DnewVersion=${VERSION}
44 |
--------------------------------------------------------------------------------
/gpg.asc.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DiceTechnology/dice-fairlink/147eb658581766876dba9d47d44757a5553d7f1a/gpg.asc.enc
--------------------------------------------------------------------------------
/images/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DiceTechnology/dice-fairlink/147eb658581766876dba9d47d44757a5553d7f1a/images/result.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | technology.dice.open
9 | dice-fairlink
10 | 2.1.0
11 | dice-fairlink
12 |
13 | jar
14 |
15 | dice-fairlink is a JDBC driver design to evenly connect to the read replicas of an
16 | AWS Aurora cluster
17 |
18 | http://open.dice.technology/dice-fairlink
19 |
20 |
21 | scm:git:git@github.com:DiceTechnology/dice-fairlink.git
22 | scm:git:ssh://github.com:DiceTechnology/dice-fairlink.git
23 |
24 | http://github.com/DiceTechnology/dice-fairlink/tree/master
25 |
26 |
27 |
28 |
29 | MIT License
30 | http://www.opensource.org/licenses/mit-license.php
31 | repo
32 |
33 |
34 |
35 |
36 |
37 | Goncalo Luiz
38 | gedl
39 | gluiz at endeavorco dot com
40 | DiceTechnology
41 | http://open.dice.technology
42 |
43 |
44 | Andrey Lebedenko
45 | AndreyLebedenko
46 | andrey dot lebedenko at gmail dot com
47 | DiceTechnology
48 | http://open.dice.technology
49 |
50 |
51 |
52 |
53 | UTF-8
54 | 1.8
55 | 1.8
56 |
57 |
58 |
59 |
60 | software.amazon.awssdk
61 | rds
62 | 2.20.3
63 | provided
64 |
65 |
66 | software.amazon.awssdk
67 | resourcegroupstaggingapi
68 | 2.20.3
69 | provided
70 |
71 |
72 | junit
73 | junit
74 | 4.13.1
75 | test
76 |
77 |
78 | org.assertj
79 | assertj-core
80 | 3.9.1
81 | test
82 |
83 |
84 | org.powermock
85 | powermock-module-junit4
86 | 1.7.3
87 | test
88 |
89 |
90 | org.powermock
91 | powermock-api-mockito2
92 | 1.7.3
93 | test
94 |
95 |
96 | org.easymock
97 | easymock
98 | 3.6
99 | test
100 |
101 |
102 | org.powermock
103 | powermock-api-easymock
104 | 1.7.3
105 | test
106 |
107 |
108 | com.google.guava
109 | guava
110 | 33.2.1-jre
111 | test
112 |
113 |
114 | org.jacoco
115 | jacoco-maven-plugin
116 | 0.8.8
117 | test
118 |
119 |
120 | org.testcontainers
121 | testcontainers
122 | 1.21.0
123 | test
124 |
125 |
126 | org.testcontainers
127 | mysql
128 | 1.21.0
129 | test
130 |
131 |
132 | mysql
133 | mysql-connector-java
134 | 8.0.28
135 | test
136 |
137 |
138 | org.testcontainers
139 | postgresql
140 | 1.21.0
141 | test
142 |
143 |
144 | org.postgresql
145 | postgresql
146 | 42.7.2
147 | test
148 |
149 |
150 | org.slf4j
151 | jul-to-slf4j
152 | 1.7.28
153 | test
154 |
155 |
156 | org.slf4j
157 | slf4j-api
158 | 1.7.28
159 | test
160 |
161 |
162 | ch.qos.logback
163 | logback-classic
164 | 1.2.13
165 | test
166 |
167 |
168 | com.github.tomakehurst
169 | wiremock-jre8
170 | 2.35.1
171 | test
172 |
173 |
174 |
175 |
176 |
177 |
178 | org.kuali.maven.wagons
179 | maven-s3-wagon
180 | 1.2.1
181 |
182 |
183 |
184 |
185 | org.apache.maven.plugins
186 | maven-source-plugin
187 | 2.4
188 |
189 |
190 | attach-sources
191 |
192 | jar-no-fork
193 |
194 |
195 |
196 |
197 |
198 | org.apache.maven.plugins
199 | maven-javadoc-plugin
200 | 2.10.3
201 |
202 |
203 | **/protobuf/*.java
204 |
205 |
206 |
207 |
208 | attach-javadocs
209 |
210 | jar
211 |
212 |
213 |
214 |
215 |
216 | org.jacoco
217 | jacoco-maven-plugin
218 | 0.8.4
219 |
220 |
221 | true
222 |
223 |
224 |
225 | default-prepare-agent
226 |
227 | prepare-agent
228 |
229 |
230 |
231 | default-report
232 | prepare-package
233 |
234 | report
235 |
236 |
237 |
238 | default-check
239 | test
240 |
241 | check
242 | report
243 |
244 |
245 |
246 |
247 |
248 | BUNDLE
249 |
250 |
251 |
252 | COMPLEXITY
253 | COVEREDRATIO
254 | 0.80
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | maven-scm-plugin
265 | 2.0.0-M3
266 |
267 | ${project.version}
268 |
269 |
270 |
271 | org.apache.maven.plugins
272 | maven-compiler-plugin
273 | 3.6.0
274 |
275 | 1.8
276 | 1.8
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 | publish
285 |
286 |
287 |
288 | org.apache.maven.plugins
289 | maven-gpg-plugin
290 | 3.0.1
291 |
292 |
293 | sign-artifacts
294 | verify
295 |
296 | sign
297 |
298 |
299 |
300 |
301 |
302 | org.sonatype.central
303 | central-publishing-maven-plugin
304 | 0.7.0
305 | true
306 |
307 | central
308 | true
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/AuroraReadonlyEndpoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink;
7 |
8 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
9 | import technology.dice.dicefairlink.discovery.members.MemberFinder;
10 | import technology.dice.dicefairlink.iterators.SizedIterator;
11 |
12 | import java.time.Duration;
13 | import java.util.concurrent.ScheduledExecutorService;
14 | import java.util.concurrent.TimeUnit;
15 | import java.util.concurrent.atomic.AtomicReference;
16 | import java.util.logging.Level;
17 | import java.util.logging.Logger;
18 |
19 | public class AuroraReadonlyEndpoint {
20 | private static final Logger LOGGER = Logger.getLogger(AuroraReadonlyEndpoint.class.getName());
21 | private final MemberFinder fairlinkMemberFinder;
22 | private volatile SizedIterator replicas;
23 | private final AtomicReference lastReplica = new AtomicReference<>();
24 |
25 | public AuroraReadonlyEndpoint(
26 | FairlinkConfiguration fairlinkConfiguration,
27 | MemberFinder fairlinkMemberFinder,
28 | ScheduledExecutorService replicaDiscoveryExecutor) {
29 |
30 | this.fairlinkMemberFinder = fairlinkMemberFinder;
31 | replicas = fairlinkMemberFinder.init();
32 | final Duration startJitter = fairlinkConfiguration.randomBoundDelay();
33 | LOGGER.log(Level.INFO, "Starting cluster member discovery with {0} delay.", startJitter);
34 | replicaDiscoveryExecutor.scheduleAtFixedRate(
35 | () -> replicas = fairlinkMemberFinder.discoverReplicas(),
36 | fairlinkConfiguration.getReplicaPollInterval().plus(startJitter).getSeconds(),
37 | fairlinkConfiguration.getReplicaPollInterval().getSeconds(),
38 | TimeUnit.SECONDS);
39 | }
40 |
41 | public String getNextReplica() {
42 | String nextReplica = replicas.next();
43 | LOGGER.finer("Obtained replica: " + nextReplica);
44 | if (nextReplica != null && nextReplica.equals(lastReplica.get()) && replicas.size() > 1) {
45 | nextReplica = replicas.next();
46 | }
47 | lastReplica.set(nextReplica);
48 | return nextReplica;
49 | }
50 |
51 | public void refresh() {
52 | replicas = this.fairlinkMemberFinder.discoverReplicas();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/ParsedUrl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink;
7 |
8 | public class ParsedUrl {
9 | private final String delegateProtocol;
10 | private final String delegateUrl;
11 |
12 | public ParsedUrl(String delegateProtocol, String delegateUrl) {
13 | this.delegateProtocol = delegateProtocol;
14 | this.delegateUrl = delegateUrl;
15 | }
16 |
17 | public String getDelegateProtocol() {
18 | return delegateProtocol;
19 | }
20 |
21 | public String getDelegateUrl() {
22 | return delegateUrl;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/config/AwsApiDiscoveryAuthMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.config;
7 |
8 | import java.util.Arrays;
9 | import java.util.Optional;
10 |
11 | public enum AwsApiDiscoveryAuthMode {
12 | BASIC,
13 | ENVIRONMENT,
14 | DEFAULT_CHAIN;
15 |
16 | public static Optional fromStringInsensitive(String candidate) {
17 | return Arrays.stream(AwsApiDiscoveryAuthMode.values())
18 | .filter(mode -> mode.toString().equalsIgnoreCase(candidate))
19 | .findAny();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/config/FairlinkConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.config;
7 |
8 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
9 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
10 | import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
11 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
12 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
13 | import software.amazon.awssdk.regions.Region;
14 |
15 | import java.time.Duration;
16 | import java.util.Map;
17 | import java.util.Optional;
18 | import java.util.Properties;
19 | import java.util.concurrent.ThreadLocalRandom;
20 | import java.util.logging.Level;
21 | import java.util.logging.Logger;
22 |
23 | public class FairlinkConfiguration {
24 | private static final Logger LOGGER = Logger.getLogger(FairlinkConfiguration.class.getName());
25 | private static final int MAX_START_DELAY = 10;
26 | public static final String AWS_AUTH_MODE_PROPERTY_NAME = "auroraDiscoveryAuthMode";
27 | public static final String AWS_BASIC_CREDENTIALS_KEY = "auroraDiscoveryKeyId";
28 | public static final String AWS_BASIC_CREDENTIALS_SECRET = "auroraDiscoverKeySecret";
29 | public static final String REPLICA_POLL_INTERVAL_PROPERTY_NAME = "replicaPollInterval";
30 | public static final String TAGS_INTERVAL_PROPERTY_NAME = "tagsPollInterval";
31 | public static final String REPLICA_ENDPOINT_TEMPLATE = "replicaEndpointTemplate";
32 | public static final String DISCOVERY_MODE_PROPERTY_NAME = "discoveryMode";
33 | public static final String VALIDATE_CONNECTION = "validateConnection";
34 | public static final String CLUSTER_REGION = "auroraClusterRegion";
35 | public static final String FALLBACK_ENDPOINT = "fallbackEndpoint";
36 | private static final Duration DEFAULT_POLLER_INTERVAL = Duration.ofSeconds(30);
37 | private static final Duration DEFAULT_TAG_POLL_INTERVAL = Duration.ofMinutes(2);
38 | private static final String MYSQL = "mysql";
39 | private static final String POSTGRESQL = "postgresql";
40 | private static final String AWS_ENDPOINT_OVERRIDE = "awsEndpointOverride";
41 | private final Region auroraClusterRegion;
42 | private final Optional replicaEndpointTemplate;
43 | private final Optional fallbackEndpoint;
44 | private final Optional awsEndpointOverride;
45 | private final AwsCredentialsProvider awsCredentialsProvider;
46 | private final Duration replicaPollInterval;
47 | private final ReplicasDiscoveryMode replicasDiscoveryMode;
48 | private final Map env;
49 | private final boolean validateConnection;
50 | private final Duration tagsPollerInterval;
51 |
52 | public FairlinkConfiguration(Properties properties, Map env) {
53 | this.env = env;
54 | this.auroraClusterRegion = this.resolveRegion(properties);
55 | this.awsCredentialsProvider = this.awsAuth(properties);
56 | this.tagsPollerInterval = this.resolveTagPollerInterval(properties);
57 | this.replicaPollInterval = this.resolvePollerInterval(properties);
58 | this.replicasDiscoveryMode = this.resolveDiscoveryMode(properties);
59 | this.replicaEndpointTemplate = this.resolveReplicaEndpointTemplate(properties);
60 | this.validateConnection = this.resolveValidationConnection(properties);
61 | this.fallbackEndpoint = this.resolveFallbackEndpoint(properties);
62 | this.awsEndpointOverride = this.resolveAwsEndpointOverride(properties);
63 | this.validateConfiguration();
64 | }
65 |
66 | private Optional resolveAwsEndpointOverride(Properties properties) {
67 | return Optional.ofNullable(properties.getProperty(AWS_ENDPOINT_OVERRIDE));
68 | }
69 |
70 | private Optional resolveFallbackEndpoint(Properties properties) {
71 | return Optional.ofNullable(properties.getProperty(FALLBACK_ENDPOINT));
72 | }
73 |
74 | private Optional resolveReplicaEndpointTemplate(Properties properties) {
75 | return Optional.ofNullable(properties.getProperty(REPLICA_ENDPOINT_TEMPLATE));
76 | }
77 |
78 | private boolean resolveValidationConnection(Properties properties) {
79 | return Optional.ofNullable(Boolean.parseBoolean(properties.getProperty(VALIDATE_CONNECTION)))
80 | .orElse(Boolean.TRUE);
81 | }
82 |
83 | private void validateConfiguration() {
84 | if (this.replicasDiscoveryMode == ReplicasDiscoveryMode.AWS_API) {
85 | this.validateAwsApiDiscovery();
86 | } else {
87 | this.validateSqlDiscovery();
88 | }
89 |
90 | this.replicaEndpointTemplate.orElseThrow(
91 | () ->
92 | new IllegalStateException(
93 | "Replica endpoint template mandatory. It is used for tag exclusion discovery and if an SQL discovery mode is selected"));
94 | }
95 |
96 | private void validateSqlDiscovery() {}
97 |
98 | private void validateAwsApiDiscovery() {}
99 |
100 | private ReplicasDiscoveryMode resolveDiscoveryMode(Properties properties) {
101 | return ReplicasDiscoveryMode.fromStringInsensitive(
102 | properties.getProperty(
103 | DISCOVERY_MODE_PROPERTY_NAME, ReplicasDiscoveryMode.AWS_API.name()))
104 | .orElse(ReplicasDiscoveryMode.AWS_API);
105 | }
106 |
107 | private AwsCredentialsProvider awsAuth(Properties properties) {
108 | AwsApiDiscoveryAuthMode authMode =
109 | AwsApiDiscoveryAuthMode.fromStringInsensitive(
110 | properties.getProperty(
111 | AWS_AUTH_MODE_PROPERTY_NAME, AwsApiDiscoveryAuthMode.DEFAULT_CHAIN.name()))
112 | .orElse(AwsApiDiscoveryAuthMode.DEFAULT_CHAIN);
113 | LOGGER.log(Level.FINE, "authMode: {0}", authMode);
114 | switch (authMode) {
115 | case BASIC:
116 | String key = properties.getProperty(AWS_BASIC_CREDENTIALS_KEY);
117 | String secret = properties.getProperty(AWS_BASIC_CREDENTIALS_SECRET);
118 | if (key == null || secret == null) {
119 | throw new IllegalStateException(
120 | String.format(
121 | "For basic authentication both [%s] and [%s] must both be set",
122 | AWS_BASIC_CREDENTIALS_KEY, AWS_BASIC_CREDENTIALS_SECRET));
123 | }
124 | return StaticCredentialsProvider.create(AwsBasicCredentials.create(key, secret));
125 | case ENVIRONMENT:
126 | return EnvironmentVariableCredentialsProvider.create();
127 | default:
128 | // DEFAULT_CHAIN
129 | return DefaultCredentialsProvider.create();
130 | }
131 | }
132 |
133 | private Region resolveRegion(final Properties properties) {
134 | final Optional propertyRegion =
135 | Optional.ofNullable(properties.getProperty(CLUSTER_REGION));
136 | LOGGER.log(Level.FINE, "Region from property: {0}", propertyRegion);
137 | return propertyRegion
138 | .map(r -> Region.of(r))
139 | .orElseGet(
140 | () -> {
141 | final String envRegion = env.get("AWS_DEFAULT_REGION");
142 | LOGGER.log(Level.FINE, "Region from environment: {0}", envRegion);
143 | if (envRegion != null) {
144 | return Region.of(envRegion);
145 | }
146 | throw new IllegalStateException(
147 | "Region is mandatory for exclusion tag discovery and replica discovery on AWS_API mode");
148 | });
149 | }
150 |
151 | private Duration resolvePollerInterval(Properties properties) {
152 | try {
153 | return Duration.ofSeconds(
154 | Integer.parseInt(properties.getProperty(REPLICA_POLL_INTERVAL_PROPERTY_NAME)));
155 | } catch (IllegalArgumentException | NullPointerException e) {
156 | LOGGER.warning(
157 | String.format(
158 | "No or invalid polling interval specified. Using default replica poll interval of %s",
159 | DEFAULT_POLLER_INTERVAL));
160 | return DEFAULT_POLLER_INTERVAL;
161 | }
162 | }
163 |
164 | private Duration resolveTagPollerInterval(Properties properties) {
165 | try {
166 | return Duration.ofSeconds(
167 | Integer.parseInt(properties.getProperty(TAGS_INTERVAL_PROPERTY_NAME)));
168 | } catch (IllegalArgumentException | NullPointerException e) {
169 | LOGGER.warning(
170 | String.format(
171 | "No or invalid tags polling interval specified. Using default tags poll interval of %s",
172 | DEFAULT_TAG_POLL_INTERVAL));
173 | return DEFAULT_TAG_POLL_INTERVAL;
174 | }
175 | }
176 |
177 | public Duration randomBoundDelay() {
178 | return Duration.ofMillis(
179 | new Float(ThreadLocalRandom.current().nextFloat() * MAX_START_DELAY * 1000).longValue());
180 | }
181 |
182 | public Duration getReplicaPollInterval() {
183 | return replicaPollInterval;
184 | }
185 |
186 | public Duration getTagsPollerInterval() {
187 | return tagsPollerInterval;
188 | }
189 |
190 | public Optional getAwsEndpointOverride() {
191 | return awsEndpointOverride;
192 | }
193 |
194 | public AwsCredentialsProvider getAwsCredentialsProvider() {
195 | return awsCredentialsProvider;
196 | }
197 |
198 | public Optional getFallbackEndpoint() {
199 | return fallbackEndpoint;
200 | }
201 |
202 | public Region getAuroraClusterRegion() {
203 | return auroraClusterRegion;
204 | }
205 |
206 | public String hostname(String fromDbIdentifier) {
207 | return String.format(replicaEndpointTemplate.get(), fromDbIdentifier);
208 | }
209 |
210 | public boolean isValidateConnection() {
211 | return validateConnection;
212 | }
213 |
214 | public ReplicasDiscoveryMode getReplicasDiscoveryMode() {
215 | return replicasDiscoveryMode;
216 | }
217 |
218 | public boolean isDiscoveryModeValidForDelegate(String delegateProtocol) {
219 | return (this.getReplicasDiscoveryMode() != ReplicasDiscoveryMode.SQL_MYSQL || delegateProtocol.equalsIgnoreCase(MYSQL))
220 | && (this.getReplicasDiscoveryMode() != ReplicasDiscoveryMode.SQL_POSTGRES || delegateProtocol.equalsIgnoreCase(POSTGRESQL));
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/config/ReplicasDiscoveryMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.config;
7 |
8 | import java.util.Arrays;
9 | import java.util.Optional;
10 |
11 | public enum ReplicasDiscoveryMode {
12 | AWS_API,
13 | SQL_MYSQL,
14 | SQL_POSTGRES;
15 |
16 | public static Optional fromStringInsensitive(String candidate) {
17 | return Arrays.stream(ReplicasDiscoveryMode.values())
18 | .filter(mode -> mode.toString().equalsIgnoreCase(candidate))
19 | .findAny();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/ClusterInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | import java.util.Objects;
9 | import java.util.Set;
10 |
11 | public final class ClusterInfo {
12 | private final String readonlyEndpoint;
13 | private final Set replicas;
14 |
15 | public ClusterInfo(String readonlyEndpoint, Set replicas) {
16 | if ("".equals(readonlyEndpoint) || readonlyEndpoint == null) {
17 | throw new IllegalArgumentException("Read only endpoint must not be null");
18 | }
19 | if (replicas == null) {
20 | throw new IllegalArgumentException("Set of replicas must not be mull");
21 | }
22 | this.readonlyEndpoint = readonlyEndpoint;
23 | this.replicas = replicas;
24 | }
25 |
26 | public String getReadonlyEndpoint() {
27 | return readonlyEndpoint;
28 | }
29 |
30 | public Set getReplicas() {
31 | return replicas;
32 | }
33 |
34 | @Override
35 | public boolean equals(Object o) {
36 | if (this == o) {
37 | return true;
38 | }
39 | if (!(o instanceof ClusterInfo)) {
40 | return false;
41 | }
42 | ClusterInfo that = (ClusterInfo) o;
43 | return Objects.equals(getReadonlyEndpoint(), that.getReadonlyEndpoint())
44 | && Objects.equals(getReplicas(), that.getReplicas());
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(getReadonlyEndpoint(), getReplicas());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/FairlinkMemberFinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
9 | import technology.dice.dicefairlink.discovery.tags.ExclusionTag;
10 | import technology.dice.dicefairlink.discovery.tags.TagFilter;
11 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
12 | import technology.dice.dicefairlink.iterators.SizedIterator;
13 |
14 | import java.net.URISyntaxException;
15 | import java.time.Duration;
16 | import java.util.Collection;
17 | import java.util.Collections;
18 | import java.util.HashSet;
19 | import java.util.Optional;
20 | import java.util.Set;
21 | import java.util.concurrent.ScheduledExecutorService;
22 | import java.util.concurrent.TimeUnit;
23 | import java.util.function.Function;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 | import java.util.stream.Collectors;
27 |
28 | public class FairlinkMemberFinder implements MemberFinder {
29 | private static final Logger LOGGER = Logger.getLogger(FairlinkMemberFinder.class.getName());
30 | private static final ExclusionTag EXCLUSION_TAG = new ExclusionTag("Fairlink-Exclude", "true");
31 | private static final Set EMPTY_SET = Collections.unmodifiableSet(new HashSet<>(0));
32 |
33 | private final FairlinkConfiguration fairlinkConfiguration;
34 | private final MemberFinderMethod memberFinder;
35 | private final ReplicaValidator replicaValidator;
36 | private final Function, SizedIterator> iteratorBuilder;
37 | private volatile Optional fallbackEndpoint = Optional.empty();
38 | private volatile Collection excludedInstanceIds =
39 | Collections.unmodifiableCollection(new HashSet<>(0));
40 | protected final FairlinkConnectionString fairlinkConnectionString;
41 | protected final TagFilter tagFilter;
42 |
43 | public FairlinkMemberFinder(
44 | FairlinkConfiguration fairlinkConfiguration,
45 | FairlinkConnectionString fairlinkConnectionString,
46 | ScheduledExecutorService tagsPollingExecutor,
47 | TagFilter excludedInstancesFinder,
48 | MemberFinderMethod memberFinder,
49 | Function, SizedIterator> stringSizedIteratorBuilder,
50 | ReplicaValidator replicaValidator) {
51 | this.fairlinkConnectionString = fairlinkConnectionString;
52 | this.fairlinkConfiguration = fairlinkConfiguration;
53 | this.tagFilter = excludedInstancesFinder;
54 | this.memberFinder = memberFinder;
55 | this.replicaValidator = replicaValidator;
56 | this.iteratorBuilder = stringSizedIteratorBuilder;
57 | this.fallbackEndpoint = fairlinkConfiguration.getFallbackEndpoint();
58 | final Duration startJitter = fairlinkConfiguration.randomBoundDelay();
59 | LOGGER.info("Starting excluded members discovery with " + startJitter + " delay.");
60 | tagsPollingExecutor.scheduleAtFixedRate(
61 | () -> excludedInstanceIds = safeExclusionsDiscovery(),
62 | startJitter.getSeconds(),
63 | fairlinkConfiguration.getTagsPollerInterval().getSeconds(),
64 | TimeUnit.SECONDS);
65 | }
66 |
67 | private Set safeExclusionsDiscovery() {
68 | try {
69 | return tagFilter.listExcludedInstances(EXCLUSION_TAG);
70 | } catch (Exception e) {
71 | LOGGER.log(
72 | Level.SEVERE, "Could not discover exclusions; including all discovered instances", e);
73 | return EMPTY_SET;
74 | }
75 | }
76 |
77 | public final SizedIterator discoverReplicas() {
78 | try {
79 | long before = System.currentTimeMillis();
80 | final ClusterInfo clusterInfo = this.memberFinder.discoverCluster();
81 | this.fallbackEndpoint =
82 | Optional.of(
83 | fairlinkConfiguration
84 | .getFallbackEndpoint()
85 | .orElse(clusterInfo.getReadonlyEndpoint()));
86 | final Set filteredReplicas =
87 | clusterInfo.getReplicas().stream()
88 | .filter(db -> !excludedInstanceIds.contains(db))
89 | .filter(
90 | dbIdentifier ->
91 | (!this.fairlinkConfiguration.isValidateConnection())
92 | || this.validate(fairlinkConfiguration.hostname(dbIdentifier)))
93 | .map(fairlinkConfiguration::hostname)
94 | .collect(Collectors.toSet());
95 | final SizedIterator result =
96 | filteredReplicas.isEmpty()
97 | ? this.iteratorBuilder.apply(
98 | this.setOf(this.fallbackEndpoint.orElse(clusterInfo.getReadonlyEndpoint())))
99 | : this.iteratorBuilder.apply(filteredReplicas);
100 | long after = System.currentTimeMillis();
101 | LOGGER.info(
102 | "Updated list of replicas in "
103 | + (after - before)
104 | + " ms. Found "
105 | + filteredReplicas.size()
106 | + " good, active, non-excluded replica"
107 | + (filteredReplicas.size() != 1 ? "s" : "")
108 | + " (validation "
109 | + (fairlinkConfiguration.isValidateConnection() ? "" : "NOT ")
110 | + "done). Excluded "
111 | + this.excludedInstanceIds.size()
112 | + " instance"
113 | + (excludedInstanceIds.size() != 1 ? "s" : "")
114 | + (this.excludedInstanceIds.size() != 1 ? "" : "s")
115 | + ". Next update in "
116 | + this.fairlinkConfiguration.getReplicaPollInterval());
117 | return result;
118 | } catch (Exception e) {
119 | LOGGER.log(
120 | Level.WARNING,
121 | "Error discovering cluster identified by ["
122 | + this.fairlinkConnectionString.getFairlinkUri()
123 | + "]. Will return fallback endpoint "
124 | + this.fallbackEndpoint.orElse("N/A")
125 | + " if available",
126 | e);
127 | if (!this.fallbackEndpoint.isPresent()) {
128 | LOGGER.log(
129 | Level.SEVERE,
130 | "Fallback endpoint not available. This means the cluster has never been successfully discovered. This is probably a permanent error condition");
131 | }
132 | return fallbackEndpoint
133 | .map(fallbackEndpoint -> this.iteratorBuilder.apply(this.setOf(fallbackEndpoint)))
134 | .orElseThrow(
135 | () -> new RuntimeException(
136 | "Could not discover cluster identified by ["
137 | + fairlinkConnectionString.getFairlinkUri()
138 | + "] and a fallback reader endpoint is not available"));
139 | }
140 | }
141 |
142 | private Set setOf(String entry) {
143 | Set set = new HashSet<>(1);
144 | set.add(entry);
145 | return Collections.unmodifiableSet(set);
146 | }
147 |
148 | private boolean validate(String host) {
149 | try {
150 | return this.replicaValidator.isValid(
151 | fairlinkConnectionString.delegateConnectionString(host),
152 | fairlinkConnectionString.getProperties());
153 | } catch (URISyntaxException e) {
154 | return false;
155 | }
156 | }
157 |
158 | public final SizedIterator init() {
159 | this.excludedInstanceIds = safeExclusionsDiscovery();
160 | final SizedIterator replicasIterator = this.discoverReplicas();
161 | LOGGER.log(
162 | Level.INFO,
163 | String.format(
164 | "Initialised driver for cluster identified by [%s with [%d] replicas]. List will be refreshed every [%s]",
165 | fairlinkConnectionString.getHost(),
166 | replicasIterator.size(),
167 | fairlinkConfiguration.getReplicaPollInterval()));
168 | return replicasIterator;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/JdbcConnectionValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | import java.sql.Connection;
9 | import java.sql.Driver;
10 | import java.util.Properties;
11 |
12 | public class JdbcConnectionValidator implements ReplicaValidator {
13 | private final Driver driver;
14 |
15 | public JdbcConnectionValidator(Driver driver) {
16 | this.driver = driver;
17 | }
18 |
19 | @Override
20 | public boolean isValid(String host, Properties properties) {
21 | try (Connection c = driver.connect(host, properties)) {
22 | c.createStatement().executeQuery("SELECT 1");
23 | } catch (Exception e) {
24 | return false;
25 | }
26 | return true;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/MemberFinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | import technology.dice.dicefairlink.iterators.SizedIterator;
9 |
10 | public interface MemberFinder {
11 | SizedIterator discoverReplicas();
12 |
13 | default SizedIterator init() {
14 | return this.discoverReplicas();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/MemberFinderMethod.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | @FunctionalInterface
9 | public interface MemberFinderMethod {
10 | ClusterInfo discoverCluster();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/ReplicaValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members;
7 |
8 | import java.util.Properties;
9 |
10 | @FunctionalInterface
11 | public interface ReplicaValidator {
12 | boolean isValid(String host, Properties properties);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/awsapi/AwsApiReplicasFinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members.awsapi;
7 |
8 | import java.net.URI;
9 | import java.util.Collections;
10 | import java.util.HashSet;
11 | import java.util.Optional;
12 | import java.util.Set;
13 | import java.util.logging.Level;
14 | import java.util.logging.Logger;
15 | import java.util.stream.Collectors;
16 | import software.amazon.awssdk.services.rds.RdsClient;
17 | import software.amazon.awssdk.services.rds.RdsClientBuilder;
18 | import software.amazon.awssdk.services.rds.model.DBCluster;
19 | import software.amazon.awssdk.services.rds.model.DBClusterMember;
20 | import software.amazon.awssdk.services.rds.model.DBInstance;
21 | import software.amazon.awssdk.services.rds.model.DescribeDbClustersRequest;
22 | import software.amazon.awssdk.services.rds.model.DescribeDbClustersResponse;
23 | import software.amazon.awssdk.services.rds.model.DescribeDbInstancesRequest;
24 | import software.amazon.awssdk.services.rds.model.Filter;
25 | import software.amazon.awssdk.services.rds.paginators.DescribeDBInstancesIterable;
26 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
27 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
28 | import technology.dice.dicefairlink.discovery.members.MemberFinderMethod;
29 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
30 |
31 | public class AwsApiReplicasFinder implements MemberFinderMethod {
32 | private static final Logger LOGGER = Logger.getLogger(AwsApiReplicasFinder.class.getName());
33 | private static final String ACTIVE_STATUS = "available";
34 | public static final String DB_CLUSTER_ID_FILTER = "db-cluster-id";
35 | private static final Set EMPTY_SET = Collections.unmodifiableSet(new HashSet<>(0));
36 | private final String clusterId;
37 | private final RdsClient client;
38 |
39 | public AwsApiReplicasFinder(
40 | FairlinkConfiguration fairlinkConfiguration,
41 | FairlinkConnectionString fairlinkConnectionString) {
42 |
43 | this.clusterId = fairlinkConnectionString.getHost();
44 | LOGGER.log(Level.INFO, "Cluster ID: {0}", fairlinkConnectionString.getHost());
45 | LOGGER.log(Level.INFO, "AWS Region: {0}", fairlinkConfiguration.getAuroraClusterRegion());
46 | final RdsClientBuilder clientBuilder =
47 | RdsClient.builder()
48 | .region(fairlinkConfiguration.getAuroraClusterRegion())
49 | .credentialsProvider(fairlinkConfiguration.getAwsCredentialsProvider());
50 | fairlinkConfiguration
51 | .getAwsEndpointOverride()
52 | .ifPresent(o -> clientBuilder.endpointOverride(URI.create(o)));
53 | this.client = clientBuilder.build();
54 | }
55 |
56 | private DBCluster describeCluster(String clusterId) {
57 | final DescribeDbClustersResponse describeDbClustersResponse =
58 | client.describeDBClusters(
59 | DescribeDbClustersRequest.builder().dbClusterIdentifier(clusterId).build());
60 | return describeDbClustersResponse.dbClusters().stream()
61 | .findFirst()
62 | .orElseThrow(
63 | () ->
64 | new RuntimeException(
65 | String.format(
66 | "Could not find exactly one cluster with cluster id [%s]", clusterId)));
67 | }
68 |
69 | private Set replicaMembersOf(DBCluster cluster) {
70 | try {
71 | DescribeDbInstancesRequest request =
72 | DescribeDbInstancesRequest.builder()
73 | .filters(
74 | Filter.builder()
75 | .name(DB_CLUSTER_ID_FILTER)
76 | .values(cluster.dbClusterIdentifier())
77 | .build())
78 | .build();
79 | final Optional writer =
80 | cluster.dbClusterMembers().stream().filter(member -> member.isClusterWriter()).findAny();
81 | final DescribeDBInstancesIterable describeInstancesPaginator =
82 | client.describeDBInstancesPaginator(request);
83 | final Set replicaIds =
84 | describeInstancesPaginator.stream()
85 | .flatMap(
86 | pageOfInstances ->
87 | pageOfInstances.dbInstances().stream()
88 | .filter(AwsApiReplicasFinder::isActive)
89 | .filter(dbInstance -> isDbReader(writer, dbInstance))
90 | .map(dbInstance -> dbInstance.dbInstanceIdentifier()))
91 | .collect(Collectors.toSet());
92 |
93 | return replicaIds;
94 |
95 | } catch (Exception e) {
96 | LOGGER.log(
97 | Level.SEVERE, "Failed to list cluster replicas. Returning an empty set of replicas", e);
98 | return EMPTY_SET;
99 | }
100 | }
101 |
102 | private static boolean isDbReader(Optional writer, DBInstance dbInstance) {
103 | return !writer
104 | .map(w -> w.dbInstanceIdentifier().equalsIgnoreCase(dbInstance.dbInstanceIdentifier()))
105 | .orElse(false);
106 | }
107 |
108 | private static boolean isActive(DBInstance dbInstance) {
109 | return dbInstance.dbInstanceStatus().equalsIgnoreCase(ACTIVE_STATUS);
110 | }
111 |
112 | @Override
113 | public ClusterInfo discoverCluster() {
114 | final DBCluster cluster = this.describeCluster(this.clusterId);
115 | return new ClusterInfo(cluster.readerEndpoint(), replicaMembersOf(cluster));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/sql/DatabaseInstance.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members.sql;
7 |
8 | public class DatabaseInstance {
9 | private final DatabaseInstanceRole role;
10 | private final String id;
11 |
12 | public DatabaseInstance(DatabaseInstanceRole role, String id) {
13 | this.role = role;
14 | this.id = id;
15 | }
16 |
17 | public DatabaseInstanceRole getRole() {
18 | return role;
19 | }
20 |
21 | public String getId() {
22 | return id;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/sql/DatabaseInstanceRole.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members.sql;
7 |
8 | public enum DatabaseInstanceRole {
9 | READER,
10 | WRITER;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/sql/MySQLReplicasFinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.members.sql;
7 |
8 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
9 | import technology.dice.dicefairlink.discovery.members.MemberFinderMethod;
10 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
11 |
12 | import java.sql.Connection;
13 | import java.sql.Driver;
14 | import java.sql.ResultSet;
15 | import java.util.Collections;
16 | import java.util.HashSet;
17 | import java.util.Optional;
18 | import java.util.Set;
19 | import java.util.logging.Level;
20 | import java.util.logging.Logger;
21 | import java.util.stream.Collectors;
22 |
23 | public class MySQLReplicasFinder implements MemberFinderMethod {
24 | private static final Logger LOGGER = Logger.getLogger(MySQLReplicasFinder.class.getName());
25 | private static final Set EMPTY_SET =
26 | Collections.unmodifiableSet(new HashSet<>(0));
27 | private static final String DEFAULT_INFORMATION_SCHEMA_NAME = "information_schema";
28 | private static final String FIND_NODES_QUERY_TEMPLATE =
29 | "select server_id, if(session_id = 'MASTER_SESSION_ID',"
30 | + "'WRITER', 'READER') as role from "
31 | + "%s.replica_host_status;";
32 | private final Driver driverForDelegate;
33 | private final FairlinkConnectionString fairlinkConnectionString;
34 | private final String informationSchemaName;
35 |
36 | public MySQLReplicasFinder(
37 | FairlinkConnectionString fairlinkConnectionString,
38 | Driver driverForDelegate,
39 | String informationSchemaName) {
40 | this.driverForDelegate = driverForDelegate;
41 | this.fairlinkConnectionString = fairlinkConnectionString;
42 | this.informationSchemaName =
43 | Optional.ofNullable(informationSchemaName).orElse(DEFAULT_INFORMATION_SCHEMA_NAME);
44 | }
45 |
46 | protected Set findReplicas() {
47 | Set instances = new HashSet<>();
48 | try (final Connection c =
49 | this.driverForDelegate.connect(
50 | fairlinkConnectionString.delegateConnectionString(),
51 | fairlinkConnectionString.getProperties());
52 | final ResultSet resultSet =
53 | c.createStatement()
54 | .executeQuery(
55 | String.format(FIND_NODES_QUERY_TEMPLATE, this.informationSchemaName))) {
56 | while (resultSet.next()) {
57 | instances.add(
58 | new DatabaseInstance(
59 | DatabaseInstanceRole.valueOf(resultSet.getString("role")),
60 | resultSet.getString("server_id")));
61 | }
62 | } catch (Exception e) {
63 | LOGGER.log(
64 | Level.SEVERE,
65 | "Failed to obtain cluster members due to exception. Returning empty set",
66 | e);
67 | return EMPTY_SET;
68 | }
69 | return Collections.unmodifiableSet(instances);
70 | }
71 |
72 | @Override
73 | public ClusterInfo discoverCluster() {
74 | return new ClusterInfo(
75 | this.fairlinkConnectionString.delegateConnectionString(),
76 | this.findReplicas().stream()
77 | .filter(databaseInstance -> databaseInstance.getRole() == DatabaseInstanceRole.READER)
78 | .map(DatabaseInstance::getId)
79 | .collect(Collectors.toSet()));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/members/sql/PostgresSQLReplicasFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members.sql;
2 |
3 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
4 | import technology.dice.dicefairlink.discovery.members.MemberFinderMethod;
5 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
6 |
7 | import java.sql.Connection;
8 | import java.sql.Driver;
9 | import java.sql.ResultSet;
10 | import java.util.Collections;
11 | import java.util.HashSet;
12 | import java.util.Set;
13 | import java.util.logging.Level;
14 | import java.util.logging.Logger;
15 | import java.util.stream.Collectors;
16 |
17 | public class PostgresSQLReplicasFinder implements MemberFinderMethod {
18 | private static final Logger LOGGER = Logger.getLogger(PostgresSQLReplicasFinder.class.getName());
19 | private static final Set EMPTY_SET =
20 | Collections.unmodifiableSet(new HashSet<>(0));
21 |
22 | private static final String FIND_NODES_QUERY =
23 | "select server_id, "
24 | + "case when session_id = 'MASTER_SESSION_ID' then 'WRITER' else 'READER' end "
25 | + "as role from aurora_replica_status();";
26 |
27 | private final Driver driverForDelegate;
28 | private final FairlinkConnectionString fairlinkConnectionString;
29 |
30 | public PostgresSQLReplicasFinder(FairlinkConnectionString fairlinkConnectionString, Driver driverForDelegate) {
31 | this.fairlinkConnectionString = fairlinkConnectionString;
32 | this.driverForDelegate = driverForDelegate;
33 | }
34 |
35 | protected Set findReplicas() {
36 | Set instances = new HashSet<>();
37 | try (final Connection c =
38 | this.driverForDelegate.connect(
39 | fairlinkConnectionString.delegateConnectionString(),
40 | fairlinkConnectionString.getProperties());
41 | final ResultSet resultSet =
42 | c.createStatement()
43 | .executeQuery(FIND_NODES_QUERY)) {
44 | while (resultSet.next()) {
45 | instances.add(
46 | new DatabaseInstance(
47 | DatabaseInstanceRole.valueOf(resultSet.getString("role")),
48 | resultSet.getString("server_id")));
49 | }
50 | } catch (Exception e) {
51 | LOGGER.log(
52 | Level.SEVERE,
53 | "Failed to obtain cluster members due to exception. Returning empty set",
54 | e);
55 | return EMPTY_SET;
56 | }
57 | return Collections.unmodifiableSet(instances);
58 | }
59 |
60 | @Override
61 | public ClusterInfo discoverCluster() {
62 | return new ClusterInfo(
63 | this.fairlinkConnectionString.delegateConnectionString(),
64 | this.findReplicas().stream()
65 | .filter(databaseInstance -> databaseInstance.getRole() == DatabaseInstanceRole.READER)
66 | .map(DatabaseInstance::getId)
67 | .collect(Collectors.toSet()));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/tags/ExclusionTag.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.tags;
7 |
8 | public class ExclusionTag {
9 | private final String key;
10 | private final String value;
11 |
12 | public ExclusionTag(String key, String value) {
13 | this.key = key;
14 | this.value = value;
15 | }
16 |
17 | public String getKey() {
18 | return key;
19 | }
20 |
21 | public String getValue() {
22 | return value;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/tags/TagFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.tags;
7 |
8 | import java.util.Set;
9 |
10 | @FunctionalInterface
11 | public interface TagFilter {
12 | Set listExcludedInstances(ExclusionTag tag);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/discovery/tags/awsapi/ResourceGroupApiTagDiscovery.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.discovery.tags.awsapi;
7 |
8 | import java.net.URI;
9 | import software.amazon.awssdk.services.resourcegroupstaggingapi.ResourceGroupsTaggingApiClient;
10 | import software.amazon.awssdk.services.resourcegroupstaggingapi.ResourceGroupsTaggingApiClientBuilder;
11 | import software.amazon.awssdk.services.resourcegroupstaggingapi.model.GetResourcesRequest;
12 | import software.amazon.awssdk.services.resourcegroupstaggingapi.paginators.GetResourcesIterable;
13 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
14 | import technology.dice.dicefairlink.discovery.tags.ExclusionTag;
15 | import technology.dice.dicefairlink.discovery.tags.TagFilter;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Collection;
19 | import java.util.Collections;
20 | import java.util.HashSet;
21 | import java.util.Set;
22 | import java.util.logging.Level;
23 | import java.util.logging.Logger;
24 | import java.util.stream.Collectors;
25 |
26 | public class ResourceGroupApiTagDiscovery implements TagFilter {
27 | private static final Logger LOGGER =
28 | Logger.getLogger(ResourceGroupApiTagDiscovery.class.getName());
29 | private static final String RDS_DB_INSTANCE_FILTER = "rds:db";
30 |
31 | private final ResourceGroupsTaggingApiClient client;
32 | private final Collection typeFilter;
33 |
34 | public ResourceGroupApiTagDiscovery(FairlinkConfiguration fairlinkConfiguration) {
35 |
36 | final ResourceGroupsTaggingApiClientBuilder clientBuilder =
37 | ResourceGroupsTaggingApiClient.builder()
38 | .region(fairlinkConfiguration.getAuroraClusterRegion())
39 | .credentialsProvider(fairlinkConfiguration.getAwsCredentialsProvider());
40 | fairlinkConfiguration
41 | .getAwsEndpointOverride()
42 | .ifPresent(o -> clientBuilder.endpointOverride(URI.create(o)));
43 | this.client = clientBuilder.build();
44 | Collection temporaryTypeFilter = new ArrayList<>(1);
45 | temporaryTypeFilter.add(RDS_DB_INSTANCE_FILTER);
46 | this.typeFilter = Collections.unmodifiableCollection(temporaryTypeFilter);
47 | }
48 |
49 | @Override
50 | public Set listExcludedInstances(ExclusionTag tags) {
51 | try {
52 | GetResourcesRequest request =
53 | GetResourcesRequest.builder()
54 | .resourceTypeFilters(this.typeFilter)
55 | .tagFilters(
56 | software.amazon.awssdk.services.resourcegroupstaggingapi.model.TagFilter.builder()
57 | .key(tags.getKey())
58 | .values(tags.getValue())
59 | .build())
60 | .build();
61 | final GetResourcesIterable resourcesPaginator = client.getResourcesPaginator(request);
62 | final Set excludedDbInstances =
63 | resourcesPaginator.stream()
64 | .flatMap(
65 | p ->
66 | p.resourceTagMappingList().stream()
67 | .map(
68 | e -> e.resourceARN().substring(e.resourceARN().lastIndexOf(":") + 1)))
69 | .collect(Collectors.toSet());
70 | LOGGER.fine(
71 | "Found "
72 | + excludedDbInstances.size()
73 | + " excluded replica"
74 | + (excludedDbInstances.size() != 1 ? "s" : "")
75 | + " in the account, across all clusters");
76 | return excludedDbInstances;
77 | } catch (Exception e) {
78 | LOGGER.log(
79 | Level.SEVERE,
80 | "Failed to obtain excluded instances. All instances assumed not to be excluded",
81 | e);
82 | return Collections.unmodifiableSet(new HashSet<>(0));
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/driver/AuroraReadReplicasDriver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.driver;
7 |
8 | import java.net.URISyntaxException;
9 | import java.sql.Connection;
10 | import java.sql.Driver;
11 | import java.sql.DriverManager;
12 | import java.sql.DriverPropertyInfo;
13 | import java.sql.SQLException;
14 | import java.util.Collection;
15 | import java.util.HashMap;
16 | import java.util.Map;
17 | import java.util.NoSuchElementException;
18 | import java.util.Optional;
19 | import java.util.Properties;
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.ScheduledExecutorService;
22 | import java.util.function.Function;
23 | import java.util.function.Supplier;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 | import software.amazon.awssdk.regions.Region;
27 | import technology.dice.dicefairlink.AuroraReadonlyEndpoint;
28 | import technology.dice.dicefairlink.ParsedUrl;
29 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
30 | import technology.dice.dicefairlink.discovery.members.FairlinkMemberFinder;
31 | import technology.dice.dicefairlink.discovery.members.JdbcConnectionValidator;
32 | import technology.dice.dicefairlink.discovery.members.MemberFinderMethod;
33 | import technology.dice.dicefairlink.discovery.members.ReplicaValidator;
34 | import technology.dice.dicefairlink.discovery.members.awsapi.AwsApiReplicasFinder;
35 | import technology.dice.dicefairlink.discovery.members.sql.MySQLReplicasFinder;
36 | import technology.dice.dicefairlink.discovery.members.sql.PostgresSQLReplicasFinder;
37 | import technology.dice.dicefairlink.discovery.tags.TagFilter;
38 | import technology.dice.dicefairlink.discovery.tags.awsapi.ResourceGroupApiTagDiscovery;
39 | import technology.dice.dicefairlink.iterators.RandomisedCyclicIterator;
40 | import technology.dice.dicefairlink.iterators.SizedIterator;
41 |
42 | public class AuroraReadReplicasDriver implements Driver {
43 | private static final Logger LOGGER = Logger.getLogger(AuroraReadReplicasDriver.class.getName());
44 | private final Map delegates = new HashMap<>();
45 | private final Map auroraClusters = new HashMap<>();
46 |
47 | private final Supplier discoveryExecutor;
48 | private final Supplier tagPollExecutor;
49 | private final Optional> tagFilter;
50 | private final Optional> fairlinkMemberFinder;
51 | private final Optional, SizedIterator>> sizedIteratorBuilder;
52 | private final Optional> replicaValidator;
53 |
54 | static {
55 | try {
56 | DriverManager.registerDriver(new AuroraReadReplicasDriver());
57 | LOGGER.fine("AuroraReadReplicasDriver is now registered.");
58 | } catch (Exception e) {
59 | throw new RuntimeException("Can't register driver!", e);
60 | }
61 | }
62 |
63 | public AuroraReadReplicasDriver() {
64 | this(
65 | () -> Executors.newScheduledThreadPool(1),
66 | () -> Executors.newScheduledThreadPool(1),
67 | null,
68 | null,
69 | null,
70 | null);
71 | }
72 |
73 | public AuroraReadReplicasDriver(
74 | final Supplier discoveryExecutor,
75 | final Supplier tagPollExecutor,
76 | final Supplier tagFilter,
77 | final Supplier memberFinder,
78 | final Supplier replicaValidator,
79 | final Function, SizedIterator> iteratorBuilder) {
80 | LOGGER.fine("Starting...");
81 | this.discoveryExecutor = discoveryExecutor;
82 | this.tagPollExecutor = tagPollExecutor;
83 | this.tagFilter = Optional.ofNullable(tagFilter);
84 | this.replicaValidator = Optional.ofNullable(replicaValidator);
85 | this.fairlinkMemberFinder = Optional.ofNullable(memberFinder);
86 | this.sizedIteratorBuilder = Optional.ofNullable(iteratorBuilder);
87 | }
88 |
89 | @Override
90 | public boolean acceptsURL(String url) throws SQLException {
91 | if (url == null) {
92 | throw new SQLException("Url must not be null");
93 | }
94 | boolean matches = FairlinkConnectionString.accepts(url);
95 | LOGGER.info(String.format("Accepting URL: [%s] : %s", url, matches));
96 | return matches;
97 | }
98 |
99 | /** {@inheritDoc} */
100 | @Override
101 | public Connection connect(final String url, final Properties properties) throws SQLException {
102 | final Optional parsedUrlOptional = parseUrlAndCacheDriver(url, properties);
103 |
104 | if (!parsedUrlOptional.isPresent()) {
105 | return null;
106 | }
107 | return delegates
108 | .get(parsedUrlOptional.get().getDelegateProtocol())
109 | .connect(parsedUrlOptional.get().getDelegateUrl(), properties);
110 | }
111 |
112 | /** {@inheritDoc} */
113 | @Override
114 | public int getMajorVersion() {
115 | return Constants.AURORA_RO_MAJOR_VERSION;
116 | }
117 |
118 | /** {@inheritDoc} */
119 | @Override
120 | public int getMinorVersion() {
121 | return Constants.AURORA_RO_MINOR_VERSION;
122 | }
123 |
124 | /** {@inheritDoc} */
125 | @Override
126 | public Logger getParentLogger() {
127 | return LOGGER.getParent();
128 | }
129 |
130 | /** {@inheritDoc} */
131 | @Override
132 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties properties) {
133 | return new DriverPropertyInfo[0];
134 | }
135 |
136 | /** {@inheritDoc} */
137 | @Override
138 | public boolean jdbcCompliant() {
139 | return false;
140 | }
141 |
142 | private Optional parseUrlAndCacheDriver(final String url, final Properties properties)
143 | throws SQLException {
144 | LOGGER.log(Level.FINE, "URI: {0}", url);
145 | FairlinkConnectionString fairlinkConnectionString;
146 |
147 | try {
148 | try {
149 | fairlinkConnectionString =
150 | new FairlinkConnectionString(url, properties);
151 | } catch (IllegalArgumentException e) {
152 | return Optional.empty();
153 | }
154 |
155 | if (!this.auroraClusters.containsKey(fairlinkConnectionString.getFairlinkUri())) {
156 | FairlinkConfiguration fairlinkConfiguration =
157 | new FairlinkConfiguration(properties, System.getenv());
158 | if (!fairlinkConfiguration.isDiscoveryModeValidForDelegate(
159 | fairlinkConnectionString.getDelegateProtocol())) {
160 | return Optional.empty();
161 | }
162 | LOGGER.log(
163 | Level.FINE, "Delegate driver: {0}", fairlinkConnectionString.getDelegateProtocol());
164 | LOGGER.log(Level.FINE, "Driver URI: {0}", fairlinkConnectionString.getFairlinkUri());
165 | final Region region = fairlinkConfiguration.getAuroraClusterRegion();
166 | LOGGER.log(Level.FINE, "Region: {0}", region);
167 | // because AWS credentials, region and poll interval properties
168 | // are only processed once per uri, the driver does not support dynamically changing them
169 |
170 | this.addDriverForDelegate(
171 | fairlinkConnectionString.getDelegateProtocol(),
172 | fairlinkConnectionString.delegateConnectionString());
173 |
174 | final AuroraReadonlyEndpoint roEndpoint =
175 | new AuroraReadonlyEndpoint(
176 | fairlinkConfiguration,
177 | this.fairlinkMemberFinder
178 | .map(Supplier::get)
179 | .orElseGet(
180 | () ->
181 | newMemberFinder(
182 | fairlinkConnectionString, fairlinkConfiguration, properties)),
183 | this.discoveryExecutor.get());
184 |
185 | LOGGER.log(Level.FINE, "RO url: {0}", fairlinkConnectionString.getHost());
186 | this.auroraClusters.put(fairlinkConnectionString.getFairlinkUri(), roEndpoint);
187 | }
188 |
189 | final String nextReplica =
190 | auroraClusters.get(fairlinkConnectionString.getFairlinkUri()).getNextReplica();
191 | LOGGER.fine(
192 | String.format(
193 | "Obtained [%s] for the next replica to use for cluster [%s]",
194 | nextReplica, fairlinkConnectionString.getHost()));
195 | final String delegatedReplicaUri =
196 | fairlinkConnectionString.delegateConnectionString(nextReplica);
197 |
198 | LOGGER.log(Level.FINE, "URI to connect to: {0}", delegatedReplicaUri);
199 |
200 | return Optional.of(
201 | new ParsedUrl(fairlinkConnectionString.getDelegateProtocol(), delegatedReplicaUri));
202 |
203 | } catch (URISyntaxException e) {
204 | LOGGER.log(Level.FINE, "Can not get replicas for cluster URI: " + url, e);
205 | return Optional.empty();
206 | } catch (NoSuchElementException e) {
207 | return Optional.empty();
208 | }
209 | }
210 |
211 | public void refreshReplicas(String url) {
212 | final AuroraReadonlyEndpoint auroraReadonlyEndpoint = this.auroraClusters.get(url);
213 | if (auroraReadonlyEndpoint != null) {
214 | auroraReadonlyEndpoint.refresh();
215 | }
216 | }
217 |
218 | private FairlinkMemberFinder newMemberFinder(
219 | FairlinkConnectionString fairlinkConnectionString,
220 | FairlinkConfiguration fairlinkConfiguration,
221 | Properties properties) {
222 | return new FairlinkMemberFinder(
223 | fairlinkConfiguration,
224 | fairlinkConnectionString,
225 | this.tagPollExecutor.get(),
226 | this.tagFilter
227 | .map(Supplier::get)
228 | .orElseGet(() -> new ResourceGroupApiTagDiscovery(fairlinkConfiguration)),
229 | this.newMemberFinderMethod(
230 | fairlinkConfiguration,
231 | fairlinkConnectionString,
232 | this.delegates.get(fairlinkConnectionString.getDelegateProtocol()),
233 | properties),
234 | this.sizedIteratorBuilder.orElse(strings -> RandomisedCyclicIterator.of(strings)),
235 | this.replicaValidator
236 | .map(Supplier::get)
237 | .orElse(
238 | new JdbcConnectionValidator(
239 | this.delegates.get(fairlinkConnectionString.getDelegateProtocol()))));
240 | }
241 |
242 | private MemberFinderMethod newMemberFinderMethod(
243 | FairlinkConfiguration fairlinkConfiguration,
244 | FairlinkConnectionString fairlinkConnectionString,
245 | Driver driver,
246 | Properties properties) {
247 | switch (fairlinkConfiguration.getReplicasDiscoveryMode()) {
248 | case AWS_API:
249 | return new AwsApiReplicasFinder(fairlinkConfiguration, fairlinkConnectionString);
250 | case SQL_MYSQL:
251 | return new MySQLReplicasFinder(
252 | fairlinkConnectionString,
253 | driver,
254 | properties.getProperty("_fairlinkMySQLSchemaOverride"));
255 | case SQL_POSTGRES:
256 | return new PostgresSQLReplicasFinder(
257 | fairlinkConnectionString,
258 | driver);
259 | default:
260 | throw new IllegalArgumentException(
261 | fairlinkConfiguration.getReplicasDiscoveryMode().name()
262 | + "is not a valid discovery mode");
263 | }
264 | }
265 |
266 | private void addDriverForDelegate(String delegate, final String stringURI) throws SQLException {
267 | this.delegates.putIfAbsent(delegate, DriverManager.getDriver(stringURI));
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/driver/Constants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.driver;
7 |
8 | public class Constants {
9 | public static final int AURORA_RO_MAJOR_VERSION = 2;
10 | public static final int AURORA_RO_MINOR_VERSION = 1;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/driver/FairlinkConnectionString.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.driver;
7 |
8 | import java.net.URI;
9 | import java.net.URISyntaxException;
10 | import java.util.Properties;
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | public class FairlinkConnectionString {
15 | private static final String DRIVER_PROTOCOL = "fairlink";
16 | private static final String DRIVER_PROTOCOL_BACKWARD_COMPATIBILITY = "auroraro";
17 | private static final Pattern DRIVER_PATTERN =
18 | Pattern.compile("jdbc:" + DRIVER_PROTOCOL + ":(?[^:]*):(?.*\\/\\/.+)");
19 | private static final Pattern DRIVER_PATTERN_BACKWARD_COMPATIBILITY =
20 | Pattern.compile(
21 | "jdbc:"
22 | + DRIVER_PROTOCOL_BACKWARD_COMPATIBILITY
23 | + ":(?[^:]*):(?.*\\/\\/.+)");
24 | private static final String JDBC_PREFIX = "jdbc";
25 | private final String delegateProtocol;
26 | private final String fairlinKUri;
27 | private final URI delegateUri;
28 | private final Properties properties;
29 |
30 | public FairlinkConnectionString(String connectionString, Properties properties)
31 | throws URISyntaxException {
32 | this.fairlinKUri = connectionString;
33 | this.properties = properties;
34 |
35 | Matcher matcher = DRIVER_PATTERN.matcher(connectionString);
36 | Matcher matcherBackwardCompatibility =
37 | DRIVER_PATTERN_BACKWARD_COMPATIBILITY.matcher(connectionString);
38 | if (matcher.matches()) {
39 | this.delegateProtocol = matcher.group("delegate");
40 | this.delegateUri = new URI(this.delegateProtocol + ":" + matcher.group("uri"));
41 | } else if (matcherBackwardCompatibility.matches()) {
42 | this.delegateProtocol = matcherBackwardCompatibility.group("delegate");
43 | this.delegateUri =
44 | new URI(this.delegateProtocol + ":" + matcherBackwardCompatibility.group("uri"));
45 | } else {
46 | throw new IllegalArgumentException(
47 | connectionString + " is not a valid fairlink connection string");
48 | }
49 | }
50 |
51 | public String getDelegateProtocol() {
52 | return delegateProtocol;
53 | }
54 |
55 | public String delegateConnectionString() {
56 | return JDBC_PREFIX + ":" + this.delegateUri.toASCIIString();
57 | }
58 |
59 | public String delegateConnectionString(String forHost) throws URISyntaxException {
60 | final URI portExtractor = new URI(JDBC_PREFIX + "://" + forHost);
61 | return new URI(
62 | JDBC_PREFIX + ":" + delegateUri.getScheme(),
63 | delegateUri.getUserInfo(),
64 | portExtractor.getHost(),
65 | portExtractor.getPort() == -1 ? delegateUri.getPort() : portExtractor.getPort(),
66 | delegateUri.getPath(),
67 | delegateUri.getQuery(),
68 | delegateUri.getFragment())
69 | .toASCIIString();
70 | }
71 |
72 | public String getHost() {
73 | return delegateUri.getHost();
74 | }
75 |
76 | public String getFairlinkUri() {
77 | return this.fairlinKUri;
78 | }
79 |
80 | public Properties getProperties() {
81 | return properties;
82 | }
83 |
84 | public static boolean accepts(String url) {
85 | Matcher matcher = DRIVER_PATTERN.matcher(url);
86 | Matcher matcherBackwardCompatibility = DRIVER_PATTERN_BACKWARD_COMPATIBILITY.matcher(url);
87 | return matcher.matches() || matcherBackwardCompatibility.matches();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/iterators/CyclicIterator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.iterators;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.Collections;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import java.util.NoSuchElementException;
14 |
15 | public class CyclicIterator implements SizedIterator {
16 | private final List elements;
17 | private volatile Iterator iterator;
18 |
19 | protected CyclicIterator(Collection extends T> collection) {
20 | this.elements = Collections.unmodifiableList(new ArrayList(collection));
21 | this.iterator = this.elements.iterator();
22 | }
23 |
24 | @Override
25 | public boolean hasNext() {
26 | return !elements.isEmpty();
27 | }
28 |
29 | @Override
30 | public int size() {
31 | return elements.size();
32 | }
33 |
34 | @Override
35 | public synchronized T next() {
36 | if (!iterator.hasNext()) {
37 | iterator = elements.iterator();
38 | if (!iterator.hasNext()) {
39 | throw new NoSuchElementException();
40 | }
41 | }
42 | T next = iterator.next();
43 | return next;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/iterators/RandomisedCyclicIterator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.iterators;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.Collections;
11 | import java.util.concurrent.ThreadLocalRandom;
12 |
13 | public class RandomisedCyclicIterator extends CyclicIterator {
14 |
15 | protected RandomisedCyclicIterator(Collection extends T> collection) {
16 | super(shuffle(collection));
17 | }
18 |
19 | private static Collection extends T> shuffle(Collection extends T> collection) {
20 | final ArrayList extends T> copy = new ArrayList<>(collection);
21 | Collections.shuffle(copy, ThreadLocalRandom.current());
22 | return Collections.unmodifiableCollection(copy);
23 | }
24 |
25 | public static RandomisedCyclicIterator of(Collection extends T> collection) {
26 | return new RandomisedCyclicIterator<>(collection);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/technology/dice/dicefairlink/iterators/SizedIterator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.iterators;
7 |
8 | import java.util.Iterator;
9 |
10 | public interface SizedIterator extends Iterator {
11 | int size();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/java.sql.Driver:
--------------------------------------------------------------------------------
1 | technology.dice.dicefairlink.driver.AuroraReadReplicasDriver
--------------------------------------------------------------------------------
/src/test/bash/readReplicasOnLoop.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if (( $# < 5 )); then
4 | echo "usage: $0 host port username password iterations"
5 | exit 1;
6 | fi
7 |
8 | i=1
9 | host=$1
10 | port=$2
11 | username=$3
12 | password=$4
13 | end=$5
14 |
15 | while [ $i -le $end ]; do
16 | echo $(date) - iteration $i
17 | mysql -h $host -P $port -u $username -p$password -e "select server_id, if(session_id = 'MASTER_SESSION_ID','WRITER', 'READER') as role from information_schema.replica_host_status;"
18 | i=$(($i+1))
19 | done
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/AuroraReadonlyEndpointTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 | import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
6 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
7 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
8 | import technology.dice.dicefairlink.support.discovery.members.FixedMemberFinder;
9 |
10 | import java.util.ArrayList;
11 | import java.util.HashMap;
12 | import java.util.Properties;
13 |
14 | public class AuroraReadonlyEndpointTest {
15 | private Properties baseTestProperties() {
16 | Properties p = new Properties();
17 | p.setProperty("auroraClusterRegion", "eu-west-1");
18 | p.setProperty("auroraDiscoveryAuthMode", "basic");
19 | p.setProperty("auroraDiscoveryKeyId", "keyId");
20 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
21 | p.setProperty("discoveryMode", "SQL_MYSQL");
22 | p.setProperty("replicaPollInterval", "5");
23 | p.setProperty("tagsPollInterval", "10");
24 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
25 | p.setProperty("validateConnection", "true");
26 | return p;
27 | }
28 |
29 | @Test
30 | public void multipleReplicas() {
31 | AuroraReadonlyEndpoint underTest =
32 | new AuroraReadonlyEndpoint(
33 | new FairlinkConfiguration(this.baseTestProperties(), new HashMap<>()),
34 | new FixedMemberFinder(ImmutableSet.of("r1", "r2", "r3")),
35 | new StepByStepExecutor(1));
36 | Assert.assertEquals("r1", underTest.getNextReplica());
37 | Assert.assertEquals("r2", underTest.getNextReplica());
38 | Assert.assertEquals("r3", underTest.getNextReplica());
39 | Assert.assertEquals("r1", underTest.getNextReplica());
40 | }
41 |
42 | @Test
43 | public void oneReplica() {
44 | AuroraReadonlyEndpoint underTest =
45 | new AuroraReadonlyEndpoint(
46 | new FairlinkConfiguration(this.baseTestProperties(), new HashMap<>()),
47 | new FixedMemberFinder(ImmutableSet.of("r1")),
48 | new StepByStepExecutor(1));
49 | Assert.assertEquals("r1", underTest.getNextReplica());
50 | Assert.assertEquals("r1", underTest.getNextReplica());
51 | Assert.assertEquals("r1", underTest.getNextReplica());
52 | }
53 |
54 | @Test
55 | public void refresh() {
56 | final FixedMemberFinder finder = new FixedMemberFinder(ImmutableSet.of("r1"));
57 | AuroraReadonlyEndpoint underTest =
58 | new AuroraReadonlyEndpoint(
59 | new FairlinkConfiguration(this.baseTestProperties(), new HashMap<>()),
60 | finder,
61 | new StepByStepExecutor(1));
62 | Assert.assertEquals("r1", underTest.getNextReplica());
63 | Assert.assertEquals("r1", underTest.getNextReplica());
64 | Assert.assertEquals("r1", underTest.getNextReplica());
65 | finder.updateMembers(ImmutableList.of("r3", "r4"));
66 | underTest.refresh();
67 | Assert.assertEquals("r3", underTest.getNextReplica());
68 | Assert.assertEquals("r4", underTest.getNextReplica());
69 | Assert.assertEquals("r3", underTest.getNextReplica());
70 | }
71 |
72 | @Test
73 | public void triesToSkipRepeated() {
74 | AuroraReadonlyEndpoint underTest =
75 | new AuroraReadonlyEndpoint(
76 | new FairlinkConfiguration(this.baseTestProperties(), new HashMap<>()),
77 | new FixedMemberFinder(ImmutableList.of("r1", "r1", "r2")),
78 | new StepByStepExecutor(1));
79 | Assert.assertEquals("r1", underTest.getNextReplica());
80 | Assert.assertEquals("r2", underTest.getNextReplica());
81 | }
82 |
83 | @Test
84 | public void nullEntry() {
85 | ArrayList acceptsNulls = new ArrayList(3);
86 | acceptsNulls.add(null);
87 | acceptsNulls.add(null);
88 | acceptsNulls.add("r3");
89 | AuroraReadonlyEndpoint underTest =
90 | new AuroraReadonlyEndpoint(
91 | new FairlinkConfiguration(this.baseTestProperties(), new HashMap<>()),
92 | new FixedMemberFinder(acceptsNulls),
93 | new StepByStepExecutor(1));
94 | Assert.assertNull(underTest.getNextReplica());
95 | Assert.assertNull(underTest.getNextReplica());
96 | Assert.assertEquals("r3", underTest.getNextReplica());
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/StepByStepExecutor.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink;
2 |
3 | import java.util.concurrent.ScheduledFuture;
4 | import java.util.concurrent.ScheduledThreadPoolExecutor;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | public class StepByStepExecutor extends ScheduledThreadPoolExecutor {
8 |
9 | private volatile Runnable task;
10 |
11 | public StepByStepExecutor(int corePoolSize) {
12 | super(corePoolSize);
13 | }
14 |
15 | @Override
16 | public ScheduledFuture> scheduleAtFixedRate(
17 | Runnable task, long ignoredDelay, long ignoredPeriod, TimeUnit ignoredTimeUnit) {
18 | this.task = task;
19 | return null;
20 | }
21 |
22 | public void step() {
23 | task.run();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/config/FairlinkConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.config;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import com.google.common.collect.Maps;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
8 | import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
9 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
10 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
11 | import software.amazon.awssdk.regions.Region;
12 |
13 | import java.time.Duration;
14 | import java.util.Properties;
15 |
16 | public class FairlinkConfigurationTest {
17 | @Test
18 | public void goodBasicCredentials() {
19 | Properties p = new Properties();
20 | p.setProperty("auroraClusterRegion", "eu-west-1");
21 | p.setProperty("auroraDiscoveryAuthMode", "basic");
22 | p.setProperty("auroraDiscoveryKeyId", "keyId");
23 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
24 | p.setProperty("discoveryMode", "SQL_MYSQL");
25 | p.setProperty("replicaPollInterval", "5");
26 | p.setProperty("tagsPollInterval", "10");
27 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
28 | p.setProperty("validateConnection", "true");
29 | FairlinkConfiguration underTest = new FairlinkConfiguration(p, Maps.newHashMap());
30 | Assert.assertEquals(Region.EU_WEST_1, underTest.getAuroraClusterRegion());
31 | final StaticCredentialsProvider expectedCredentials =
32 | StaticCredentialsProvider.create(AwsBasicCredentials.create("keyId", "keySecret"));
33 | Assert.assertTrue(underTest.getAwsCredentialsProvider() instanceof StaticCredentialsProvider);
34 | Assert.assertEquals("keyId", expectedCredentials.resolveCredentials().accessKeyId());
35 | Assert.assertEquals("keySecret", expectedCredentials.resolveCredentials().secretAccessKey());
36 | Assert.assertEquals(ReplicasDiscoveryMode.SQL_MYSQL, underTest.getReplicasDiscoveryMode());
37 | Assert.assertEquals(Duration.ofSeconds(10), underTest.getTagsPollerInterval());
38 | Assert.assertEquals(Duration.ofSeconds(5), underTest.getReplicaPollInterval());
39 | Assert.assertTrue(underTest.isValidateConnection());
40 | Assert.assertTrue(underTest.isDiscoveryModeValidForDelegate("mysql"));
41 | Assert.assertFalse(underTest.isDiscoveryModeValidForDelegate("postgresql"));
42 | Assert.assertEquals("replica1.rest-of-myhost.name", underTest.hostname("replica1"));
43 | final Duration jitter = underTest.randomBoundDelay();
44 | Assert.assertFalse(jitter.isNegative());
45 | Assert.assertTrue(jitter.compareTo(Duration.ofSeconds(10)) <= 0);
46 | }
47 |
48 | @Test
49 | public void goodBasicCredentialsRegionFromEnvironment() {
50 | Properties p = new Properties();
51 | p.setProperty("auroraDiscoveryAuthMode", "basic");
52 | p.setProperty("auroraDiscoveryKeyId", "keyId");
53 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
54 | p.setProperty("discoveryMode", "SQL_MYSQL");
55 | p.setProperty("replicaPollInterval", "5");
56 | p.setProperty("tagsPollInterval", "10");
57 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
58 | p.setProperty("validateConnection", "true");
59 | FairlinkConfiguration underTest =
60 | new FairlinkConfiguration(
61 | p, ImmutableMap.of("AWS_DEFAULT_REGION", Region.AP_NORTHEAST_1.toString()));
62 | Assert.assertEquals(Region.AP_NORTHEAST_1, underTest.getAuroraClusterRegion());
63 | final StaticCredentialsProvider expectedCredentials =
64 | StaticCredentialsProvider.create(AwsBasicCredentials.create("keyId", "keySecret"));
65 | Assert.assertTrue(underTest.getAwsCredentialsProvider() instanceof StaticCredentialsProvider);
66 | Assert.assertEquals("keyId", expectedCredentials.resolveCredentials().accessKeyId());
67 | Assert.assertEquals("keySecret", expectedCredentials.resolveCredentials().secretAccessKey());
68 | Assert.assertEquals(ReplicasDiscoveryMode.SQL_MYSQL, underTest.getReplicasDiscoveryMode());
69 | Assert.assertEquals(Duration.ofSeconds(10), underTest.getTagsPollerInterval());
70 | Assert.assertEquals(Duration.ofSeconds(5), underTest.getReplicaPollInterval());
71 | Assert.assertTrue(underTest.isValidateConnection());
72 | Assert.assertTrue(underTest.isDiscoveryModeValidForDelegate("mysql"));
73 | Assert.assertFalse(underTest.isDiscoveryModeValidForDelegate("postgresql"));
74 | Assert.assertEquals("replica1.rest-of-myhost.name", underTest.hostname("replica1"));
75 | final Duration jitter = underTest.randomBoundDelay();
76 | Assert.assertFalse(jitter.isNegative());
77 | Assert.assertTrue(jitter.compareTo(Duration.ofSeconds(10)) <= 0);
78 | }
79 |
80 | @Test
81 | public void goodEnvironmentCredentials() {
82 | Properties p = new Properties();
83 | p.setProperty("auroraClusterRegion", "eu-west-1");
84 | p.setProperty("auroraDiscoveryAuthMode", "environment");
85 | p.setProperty("discoveryMode", "AWS_API");
86 | p.setProperty("replicaPollInterval", "5");
87 | p.setProperty("tagsPollInterval", "10");
88 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
89 | p.setProperty("validateConnection", "true");
90 | FairlinkConfiguration underTest =
91 | new FairlinkConfiguration(
92 | p, ImmutableMap.of("AWS_SECRET_KEY", "key", "AWS_SECRET_ACCESS_KEY", "secret"));
93 | Assert.assertEquals(Region.EU_WEST_1, underTest.getAuroraClusterRegion());
94 | Assert.assertTrue(
95 | underTest.getAwsCredentialsProvider() instanceof EnvironmentVariableCredentialsProvider);
96 | Assert.assertEquals(ReplicasDiscoveryMode.AWS_API, underTest.getReplicasDiscoveryMode());
97 | Assert.assertEquals(Duration.ofSeconds(10), underTest.getTagsPollerInterval());
98 | Assert.assertEquals(Duration.ofSeconds(5), underTest.getReplicaPollInterval());
99 | Assert.assertTrue(underTest.isValidateConnection());
100 | Assert.assertTrue(underTest.isDiscoveryModeValidForDelegate("mysql"));
101 | Assert.assertTrue(underTest.isDiscoveryModeValidForDelegate("postgresql"));
102 | Assert.assertEquals("replica1.rest-of-myhost.name", underTest.hostname("replica1"));
103 | final Duration jitter = underTest.randomBoundDelay();
104 | Assert.assertFalse(jitter.isNegative());
105 | Assert.assertTrue(jitter.compareTo(Duration.ofSeconds(10)) <= 0);
106 | }
107 |
108 | @Test
109 | public void goodDefaultCredentials() {
110 | Properties p = new Properties();
111 | p.setProperty("auroraClusterRegion", "eu-west-1");
112 | p.setProperty("discoveryMode", "SQL_MYSQL");
113 | p.setProperty("replicaPollInterval", "5");
114 | p.setProperty("tagsPollInterval", "10");
115 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
116 | p.setProperty("validateConnection", "true");
117 | FairlinkConfiguration underTest =
118 | new FairlinkConfiguration(
119 | p, ImmutableMap.of("AWS_SECRET_KEY", "key", "AWS_SECRET_ACCESS_KEY", "secret"));
120 | Assert.assertEquals(Region.EU_WEST_1, underTest.getAuroraClusterRegion());
121 | Assert.assertTrue(underTest.getAwsCredentialsProvider() instanceof DefaultCredentialsProvider);
122 | Assert.assertEquals(ReplicasDiscoveryMode.SQL_MYSQL, underTest.getReplicasDiscoveryMode());
123 | Assert.assertEquals(Duration.ofSeconds(10), underTest.getTagsPollerInterval());
124 | Assert.assertEquals(Duration.ofSeconds(5), underTest.getReplicaPollInterval());
125 | Assert.assertTrue(underTest.isValidateConnection());
126 | Assert.assertTrue(underTest.isDiscoveryModeValidForDelegate("mysql"));
127 | Assert.assertFalse(underTest.isDiscoveryModeValidForDelegate("postgresql"));
128 | Assert.assertEquals("replica1.rest-of-myhost.name", underTest.hostname("replica1"));
129 | final Duration jitter = underTest.randomBoundDelay();
130 | Assert.assertFalse(jitter.isNegative());
131 | Assert.assertTrue(jitter.compareTo(Duration.ofSeconds(10)) <= 0);
132 | }
133 |
134 | @Test(expected = IllegalStateException.class)
135 | public void basicWithoutKey() {
136 | Properties p = new Properties();
137 | p.setProperty("auroraClusterRegion", "eu-west-1");
138 | p.setProperty("auroraDiscoveryAuthMode", "basic");
139 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
140 | p.setProperty("discoveryMode", "SQL_MYSQL");
141 | p.setProperty("replicaPollInterval", "5");
142 | p.setProperty("tagsPollInterval", "10");
143 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
144 | p.setProperty("validateConnection", "true");
145 | new FairlinkConfiguration(p, Maps.newHashMap());
146 | }
147 |
148 | @Test(expected = IllegalStateException.class)
149 | public void basicWithoutSecret() {
150 | Properties p = new Properties();
151 | p.setProperty("auroraClusterRegion", "eu-west-1");
152 | p.setProperty("auroraDiscoveryAuthMode", "basic");
153 | p.setProperty("auroraDiscoveryKeyId", "keyId");
154 | p.setProperty("discoveryMode", "SQL_MYSQL");
155 | p.setProperty("replicaPollInterval", "5");
156 | p.setProperty("tagsPollInterval", "10");
157 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
158 | p.setProperty("validateConnection", "true");
159 | new FairlinkConfiguration(p, Maps.newHashMap());
160 | }
161 |
162 | @Test(expected = IllegalStateException.class)
163 | public void awsApiWithoutRegion() {
164 | Properties p = new Properties();
165 | p.setProperty("auroraDiscoveryAuthMode", "basic");
166 | p.setProperty("auroraDiscoveryKeyId", "keyId");
167 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
168 | p.setProperty("discoveryMode", "AWS_API");
169 | p.setProperty("replicaPollInterval", "5");
170 | p.setProperty("tagsPollInterval", "10");
171 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
172 | p.setProperty("validateConnection", "true");
173 | new FairlinkConfiguration(p, Maps.newHashMap());
174 | }
175 |
176 | @Test(expected = IllegalStateException.class)
177 | public void noReplicaTemplate() {
178 | Properties p = new Properties();
179 | p.setProperty("auroraClusterRegion", "eu-west-1");
180 | p.setProperty("auroraDiscoveryAuthMode", "basic");
181 | p.setProperty("auroraDiscoveryKeyId", "keyId");
182 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
183 | p.setProperty("discoveryMode", "SQL_MYSQL");
184 | p.setProperty("replicaPollInterval", "5");
185 | p.setProperty("tagsPollInterval", "10");
186 | p.setProperty("validateConnection", "true");
187 | new FairlinkConfiguration(p, Maps.newHashMap());
188 | }
189 |
190 | @Test
191 | public void defaultPollerDurationsAbsent() {
192 | Properties p = new Properties();
193 | p.setProperty("auroraClusterRegion", "eu-west-1");
194 | p.setProperty("auroraDiscoveryAuthMode", "basic");
195 | p.setProperty("auroraDiscoveryKeyId", "keyId");
196 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
197 | p.setProperty("discoveryMode", "SQL_MYSQL");
198 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
199 | p.setProperty("validateConnection", "true");
200 | FairlinkConfiguration underTest = new FairlinkConfiguration(p, Maps.newHashMap());
201 | Assert.assertEquals(Duration.ofMinutes(2), underTest.getTagsPollerInterval());
202 | Assert.assertEquals(Duration.ofSeconds(30), underTest.getReplicaPollInterval());
203 | }
204 |
205 | @Test
206 | public void defaultPollerDurationsInvalid() {
207 | Properties p = new Properties();
208 | p.setProperty("auroraClusterRegion", "eu-west-1");
209 | p.setProperty("auroraDiscoveryAuthMode", "basic");
210 | p.setProperty("auroraDiscoveryKeyId", "keyId");
211 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
212 | p.setProperty("discoveryMode", "SQL_MYSQL");
213 | p.setProperty("replicaPollInterval", "abc");
214 | p.setProperty("tagsPollInterval", "def");
215 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
216 | p.setProperty("validateConnection", "true");
217 | FairlinkConfiguration underTest = new FairlinkConfiguration(p, Maps.newHashMap());
218 | Assert.assertEquals(Duration.ofMinutes(2), underTest.getTagsPollerInterval());
219 | Assert.assertEquals(Duration.ofSeconds(30), underTest.getReplicaPollInterval());
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/ClusterInfoTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
6 |
7 | public class ClusterInfoTest {
8 | @Test
9 | public void hashcode() {
10 | ClusterInfo c1 = new ClusterInfo("a", ImmutableSet.of("r1", "r2"));
11 | ClusterInfo c2 = new ClusterInfo("b", ImmutableSet.of("r3", "r4"));
12 | ClusterInfo c3 = new ClusterInfo("a", ImmutableSet.of("r1", "r2"));
13 | Assert.assertEquals(c1.hashCode(), c3.hashCode());
14 | Assert.assertNotEquals(c1.hashCode(), c2.hashCode());
15 | }
16 |
17 | @Test
18 | public void equalz() {
19 | ClusterInfo c1 = new ClusterInfo("a", ImmutableSet.of("r1", "r2"));
20 | ClusterInfo c2 = new ClusterInfo("b", ImmutableSet.of("r3", "r4"));
21 | ClusterInfo c3 = new ClusterInfo("a", ImmutableSet.of("r1", "r2"));
22 | ClusterInfo c4 = new ClusterInfo("a", ImmutableSet.of("r1", "r4"));
23 | Assert.assertTrue(c1.equals(c3));
24 | Assert.assertTrue(c1.equals(c1));
25 | Assert.assertFalse(c1.equals(c2));
26 | Assert.assertFalse(c1.equals(c4));
27 | Assert.assertFalse(c1.equals("not eequals"));
28 | Assert.assertFalse(c1.equals(null));
29 | }
30 |
31 | @Test(expected = IllegalArgumentException.class)
32 | public void ctorEmptyReadOnlyEndpoint() {
33 | new ClusterInfo("", ImmutableSet.of("r1", "r2"));
34 | }
35 |
36 | @Test(expected = IllegalArgumentException.class)
37 | public void ctorNullReadOnlyEndpoint() {
38 | new ClusterInfo(null, ImmutableSet.of("r1", "r2"));
39 | }
40 |
41 | @Test(expected = IllegalArgumentException.class)
42 | public void ctorNullListOfReplicas() {
43 | new ClusterInfo("a", null);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/JdbcConnectionValidatorTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Rule;
5 | import org.junit.Test;
6 | import org.testcontainers.containers.MySQLContainer;
7 |
8 | import java.util.Properties;
9 |
10 | public class JdbcConnectionValidatorTest {
11 | @Rule public MySQLContainer mysql = new MySQLContainer();
12 |
13 | private Properties baseTestProperties() {
14 | Properties p = new Properties();
15 | p.setProperty("user", mysql.getUsername());
16 | p.setProperty("password", mysql.getPassword());
17 | return p;
18 | }
19 |
20 | @Test
21 | public void validConnection() {
22 | JdbcConnectionValidator underTest = new JdbcConnectionValidator(mysql.getJdbcDriverInstance());
23 | Assert.assertTrue(underTest.isValid(mysql.getJdbcUrl(), baseTestProperties()));
24 | }
25 |
26 | @Test
27 | public void inValidConnection() {
28 | JdbcConnectionValidator underTest = new JdbcConnectionValidator(mysql.getJdbcDriverInstance());
29 | Assert.assertFalse(underTest.isValid(mysql.getJdbcUrl(), new Properties()));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/awsapi/AwsApiReplicasFinderTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members.awsapi;
2 |
3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
4 | import static com.github.tomakehurst.wiremock.client.WireMock.post;
5 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
6 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
7 |
8 | import com.github.tomakehurst.wiremock.client.WireMock;
9 | import com.github.tomakehurst.wiremock.junit.WireMockRule;
10 | import com.google.common.base.Charsets;
11 | import com.google.common.collect.ImmutableMap;
12 | import com.google.common.collect.ImmutableSet;
13 | import com.google.common.io.CharStreams;
14 | import java.io.IOException;
15 | import java.io.InputStreamReader;
16 | import java.net.URISyntaxException;
17 | import java.util.Properties;
18 | import org.junit.Assert;
19 | import org.junit.Rule;
20 | import org.junit.Test;
21 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
22 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
23 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
24 |
25 | public class AwsApiReplicasFinderTest {
26 | @Rule public WireMockRule wireMockRule = new WireMockRule(11342);
27 |
28 | private Properties baseTestProperties() {
29 | Properties p = new Properties();
30 | p.setProperty("auroraClusterRegion", "eu-west-1");
31 | p.setProperty("auroraDiscoveryAuthMode", "basic");
32 | p.setProperty("auroraDiscoveryKeyId", "keyId");
33 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
34 | p.setProperty("discoveryMode", "AWS_API");
35 | p.setProperty("replicaPollInterval", "5");
36 | p.setProperty("tagsPollInterval", "10");
37 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
38 | p.setProperty("validateConnection", "true");
39 | p.setProperty("awsEndpointOverride", "http://localhost:11342");
40 | return p;
41 | }
42 |
43 | @Test
44 | public void withMembersAllAvailable() throws URISyntaxException, IOException {
45 | AwsApiReplicasFinder underTest =
46 | new AwsApiReplicasFinder(
47 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
48 | new FairlinkConnectionString(
49 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
50 | this.baseTestProperties()));
51 |
52 | String describeClusterResponse =
53 | CharStreams.toString(
54 | new InputStreamReader(
55 | AwsApiReplicasFinderTest.class
56 | .getClassLoader()
57 | .getResourceAsStream(
58 | "technology/dice/dicefairlink/discovery/members/awsapi/withMembers.xml"),
59 | Charsets.UTF_8));
60 |
61 | String describeInstancesResponse =
62 | CharStreams.toString(
63 | new InputStreamReader(
64 | AwsApiReplicasFinderTest.class
65 | .getClassLoader()
66 | .getResourceAsStream(
67 | "technology/dice/dicefairlink/discovery/members/awsapi/allAvailableDbInstances.xml"),
68 | Charsets.UTF_8));
69 |
70 | stubFor(
71 | post(urlEqualTo("/"))
72 | .withRequestBody(WireMock.containing("Action=DescribeDBClusters"))
73 | .willReturn(aResponse().withStatus(200).withBody(describeClusterResponse)));
74 | stubFor(
75 | post(urlEqualTo("/"))
76 | .withRequestBody(WireMock.containing("Action=DescribeDBInstances"))
77 | .willReturn(aResponse().withStatus(200).withBody(describeInstancesResponse)));
78 |
79 | final ClusterInfo actual = underTest.discoverCluster();
80 | Assert.assertEquals(
81 | new ClusterInfo(
82 | "cluster-reader-endpoint",
83 | ImmutableSet.of("my-db-cluster-agd-3", "my-db-cluster-agd-2")),
84 | actual);
85 | }
86 |
87 | @Test
88 | public void withMembersOnReaderDeleting() throws URISyntaxException, IOException {
89 | AwsApiReplicasFinder underTest =
90 | new AwsApiReplicasFinder(
91 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
92 | new FairlinkConnectionString(
93 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
94 | this.baseTestProperties()));
95 |
96 | String describeClusterResponse =
97 | CharStreams.toString(
98 | new InputStreamReader(
99 | AwsApiReplicasFinderTest.class
100 | .getClassLoader()
101 | .getResourceAsStream(
102 | "technology/dice/dicefairlink/discovery/members/awsapi/withMembers.xml"),
103 | Charsets.UTF_8));
104 |
105 | String describeInstancesResponse =
106 | CharStreams.toString(
107 | new InputStreamReader(
108 | AwsApiReplicasFinderTest.class
109 | .getClassLoader()
110 | .getResourceAsStream(
111 | "technology/dice/dicefairlink/discovery/members/awsapi/oneReplicaDeletingInstances.xml"),
112 | Charsets.UTF_8));
113 |
114 | stubFor(
115 | post(urlEqualTo("/"))
116 | .withRequestBody(WireMock.containing("Action=DescribeDBClusters"))
117 | .willReturn(aResponse().withStatus(200).withBody(describeClusterResponse)));
118 | stubFor(
119 | post(urlEqualTo("/"))
120 | .withRequestBody(WireMock.containing("Action=DescribeDBInstances"))
121 | .willReturn(aResponse().withStatus(200).withBody(describeInstancesResponse)));
122 |
123 | final ClusterInfo actual = underTest.discoverCluster();
124 | Assert.assertEquals(
125 | new ClusterInfo("cluster-reader-endpoint", ImmutableSet.of("my-db-cluster-agd-2")), actual);
126 | }
127 |
128 | @Test
129 | public void withoutMembers() throws URISyntaxException, IOException {
130 | AwsApiReplicasFinder underTest =
131 | new AwsApiReplicasFinder(
132 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
133 | new FairlinkConnectionString(
134 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
135 | this.baseTestProperties()));
136 |
137 | String response =
138 | CharStreams.toString(
139 | new InputStreamReader(
140 | AwsApiReplicasFinderTest.class
141 | .getClassLoader()
142 | .getResourceAsStream(
143 | "technology/dice/dicefairlink/discovery/members/awsapi/withoutMembers.xml"),
144 | Charsets.UTF_8));
145 |
146 | stubFor(post(urlEqualTo("/")).willReturn(aResponse().withStatus(200).withBody(response)));
147 |
148 | final ClusterInfo actual = underTest.discoverCluster();
149 | Assert.assertEquals(
150 | new ClusterInfo("sample-cluster.reader-endpoint", ImmutableSet.of()), actual);
151 | }
152 |
153 | @Test(expected = RuntimeException.class)
154 | public void serverErrorDescribingCluster() throws URISyntaxException {
155 | AwsApiReplicasFinder underTest =
156 | new AwsApiReplicasFinder(
157 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
158 | new FairlinkConnectionString(
159 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
160 | this.baseTestProperties()));
161 |
162 | stubFor(post(urlEqualTo("/")).willReturn(aResponse().withStatus(500)));
163 |
164 | underTest.discoverCluster();
165 | }
166 |
167 | @Test
168 | public void serverErrorDescribingInstances() throws URISyntaxException, IOException {
169 | AwsApiReplicasFinder underTest =
170 | new AwsApiReplicasFinder(
171 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
172 | new FairlinkConnectionString(
173 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
174 | this.baseTestProperties()));
175 |
176 | String describeClusterResponse =
177 | CharStreams.toString(
178 | new InputStreamReader(
179 | AwsApiReplicasFinderTest.class
180 | .getClassLoader()
181 | .getResourceAsStream(
182 | "technology/dice/dicefairlink/discovery/members/awsapi/withMembers.xml"),
183 | Charsets.UTF_8));
184 |
185 | stubFor(
186 | post(urlEqualTo("/"))
187 | .withRequestBody(WireMock.containing("Action=DescribeDBClusters"))
188 | .willReturn(aResponse().withStatus(200).withBody(describeClusterResponse)));
189 |
190 | stubFor(
191 | post(urlEqualTo("/"))
192 | .withRequestBody(WireMock.containing("Action=DescribeDBInstances"))
193 | .willReturn(aResponse().withStatus(500)));
194 |
195 | final ClusterInfo actual = underTest.discoverCluster();
196 | Assert.assertEquals(new ClusterInfo("cluster-reader-endpoint", ImmutableSet.of()), actual);
197 | }
198 |
199 | @Test(expected = RuntimeException.class)
200 | public void clusterNotFound() throws URISyntaxException, IOException {
201 | AwsApiReplicasFinder underTest =
202 | new AwsApiReplicasFinder(
203 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()),
204 | new FairlinkConnectionString(
205 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
206 | this.baseTestProperties()));
207 |
208 | String response =
209 | CharStreams.toString(
210 | new InputStreamReader(
211 | AwsApiReplicasFinderTest.class
212 | .getClassLoader()
213 | .getResourceAsStream(
214 | "technology/dice/dicefairlink/discovery/members/awsapi/emptyResponse.xml"),
215 | Charsets.UTF_8));
216 |
217 | stubFor(post(urlEqualTo("/")).willReturn(aResponse().withStatus(200).withBody(response)));
218 |
219 | underTest.discoverCluster();
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/awsapi/AwsApiReplicasFinderTestIT.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members.awsapi;
2 |
3 | import org.junit.Test;
4 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
5 | import software.amazon.awssdk.regions.Region;
6 | import software.amazon.awssdk.services.rds.RdsClient;
7 | import software.amazon.awssdk.services.rds.model.DescribeDbClustersRequest;
8 | import software.amazon.awssdk.services.rds.model.DescribeDbInstancesRequest;
9 | import software.amazon.awssdk.services.rds.model.Filter;
10 |
11 | public class AwsApiReplicasFinderTestIT {
12 | // Here to capture the http wire traffic. Not automatically ran as part of CI
13 | @Test
14 | public void makeDescribeApiCalls() {
15 | final RdsClient client =
16 | RdsClient.builder()
17 | .region(Region.EU_WEST_1)
18 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
19 | .build();
20 |
21 | client.describeDBClusters(
22 | DescribeDbClustersRequest.builder()
23 | .dbClusterIdentifier("cluster")
24 | .build());
25 |
26 | client.describeDBInstances(
27 | DescribeDbInstancesRequest.builder()
28 | .filters(
29 | Filter.builder().name("db-cluster-id").values("cluster").build())
30 | .build());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/sql/MySqlReplicasFinderTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members.sql;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.common.io.CharStreams;
5 | import org.junit.Assert;
6 | import org.junit.Before;
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.testcontainers.containers.JdbcDatabaseContainer;
10 | import org.testcontainers.containers.MySQLContainer;
11 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
12 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
13 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
14 |
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.io.InputStreamReader;
18 | import java.net.URISyntaxException;
19 | import java.sql.Connection;
20 | import java.sql.DriverManager;
21 | import java.sql.SQLException;
22 | import java.util.Properties;
23 |
24 | public class MySqlReplicasFinderTest {
25 | @Rule public JdbcDatabaseContainer mysql = new MySQLContainer();
26 |
27 | @Before
28 | public void before() throws IOException, SQLException {
29 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/mysql/schema.sql");
30 | }
31 |
32 | private void runScript(String path) throws IOException, SQLException {
33 | Properties p = new Properties();
34 | p.setProperty("allowMultiQueries", "true");
35 | p.setProperty("user", mysql.getUsername());
36 | p.setProperty("password", mysql.getPassword());
37 | final InputStream resourceAsStream =
38 | MySqlReplicasFinderTest.class.getClassLoader().getResourceAsStream(path);
39 | final String schemaSql =
40 | CharStreams.toString(new InputStreamReader(resourceAsStream, Charsets.UTF_8));
41 | Connection conn = DriverManager.getConnection(mysql.getJdbcUrl(), p);
42 |
43 | conn.createStatement().executeUpdate(schemaSql);
44 | conn.close();
45 | }
46 |
47 | private Properties baseTestProperties() {
48 | Properties p = new Properties();
49 | p.setProperty("auroraClusterRegion", "eu-west-1");
50 | p.setProperty("auroraDiscoveryAuthMode", "basic");
51 | p.setProperty("auroraDiscoveryKeyId", "keyId");
52 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
53 | p.setProperty("discoveryMode", "irrelevant");
54 | p.setProperty("replicaPollInterval", "5");
55 | p.setProperty("tagsPollInterval", "10");
56 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
57 | p.setProperty("validateConnection", "true");
58 | p.setProperty("user", mysql.getUsername());
59 | p.setProperty("password", mysql.getPassword());
60 | return p;
61 | }
62 |
63 | @Test
64 | public void noReplicas() throws URISyntaxException {
65 | MySQLReplicasFinder underTest =
66 | new MySQLReplicasFinder(
67 | new FairlinkConnectionString(
68 | mysql.getJdbcUrl().replace("mysql", "fairlink:mysql"), this.baseTestProperties()),
69 | mysql.getJdbcDriverInstance(),
70 | mysql.getDatabaseName());
71 | final ClusterInfo clusterInfo = underTest.discoverCluster();
72 | Assert.assertEquals(new ClusterInfo(mysql.getJdbcUrl(), ImmutableSet.of()), clusterInfo);
73 | }
74 |
75 | @Test
76 | public void withReplicas() throws URISyntaxException, IOException, SQLException {
77 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/mysql/2replicas.sql");
78 | MySQLReplicasFinder underTest =
79 | new MySQLReplicasFinder(
80 | new FairlinkConnectionString(
81 | mysql.getJdbcUrl().replace("mysql", "fairlink:mysql"), this.baseTestProperties()),
82 | mysql.getJdbcDriverInstance(),
83 | mysql.getDatabaseName());
84 | final ClusterInfo actual = underTest.discoverCluster();
85 | final ClusterInfo expected = new ClusterInfo(mysql.getJdbcUrl(), ImmutableSet.of("replica"));
86 | Assert.assertEquals(expected, actual);
87 | }
88 |
89 | @Test
90 | public void noConnectionsResultsInEmptySet()
91 | throws URISyntaxException, IOException, SQLException {
92 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/mysql/2replicas.sql");
93 | MySQLReplicasFinder underTest =
94 | new MySQLReplicasFinder(
95 | new FairlinkConnectionString(
96 | mysql.getJdbcUrl().replace("mysql", "fairlink:fairlinktestdriver"),
97 | this.baseTestProperties()),
98 | mysql.getJdbcDriverInstance(),
99 | mysql.getDatabaseName());
100 | final ClusterInfo actual = underTest.discoverCluster();
101 | final ClusterInfo expected =
102 | new ClusterInfo(
103 | mysql.getJdbcUrl().replace("mysql", "fairlinktestdriver"), ImmutableSet.of());
104 | Assert.assertEquals(expected, actual);
105 | }
106 |
107 | @Test
108 | public void noTableResultsInEmptySet() throws URISyntaxException, IOException, SQLException {
109 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/mysql/2replicas.sql");
110 | MySQLReplicasFinder underTest =
111 | new MySQLReplicasFinder(
112 | new FairlinkConnectionString(
113 | mysql.getJdbcUrl().replace("mysql", "fairlink:mysql"), this.baseTestProperties()),
114 | mysql.getJdbcDriverInstance(),
115 | "i_do_not_exist");
116 | final ClusterInfo actual = underTest.discoverCluster();
117 | final ClusterInfo expected = new ClusterInfo(mysql.getJdbcUrl(), ImmutableSet.of());
118 | Assert.assertEquals(expected, actual);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/members/sql/PostgresSqlReplicaFinderTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.members.sql;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.common.io.CharStreams;
5 | import org.junit.Assert;
6 | import org.junit.Before;
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.testcontainers.containers.PostgreSQLContainer;
10 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
11 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
12 | import technology.dice.dicefairlink.driver.FairlinkConnectionString;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.InputStreamReader;
17 | import java.net.URISyntaxException;
18 | import java.sql.Connection;
19 | import java.sql.DriverManager;
20 | import java.sql.SQLException;
21 | import java.util.Properties;
22 |
23 | /**
24 | * @author Ryan Gardner
25 | * @date 9/13/19
26 | */
27 | public class PostgresSqlReplicaFinderTest {
28 | @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer();
29 |
30 |
31 | @Before
32 | public void before() throws IOException, SQLException {
33 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/postgresql/schema.sql");
34 | }
35 |
36 | private void runScript(String path) throws IOException, SQLException {
37 | Properties p = new Properties();
38 | p.setProperty("allowMultiQueries", "true");
39 | p.setProperty("user", postgres.getUsername());
40 | p.setProperty("password", postgres.getPassword());
41 | final InputStream resourceAsStream =
42 | PostgresSqlReplicaFinderTest.class.getClassLoader().getResourceAsStream(path);
43 | final String schemaSql =
44 | CharStreams.toString(new InputStreamReader(resourceAsStream, Charsets.UTF_8));
45 | Connection conn = DriverManager.getConnection(postgres.getJdbcUrl(), p);
46 |
47 | conn.createStatement().executeUpdate(schemaSql);
48 | conn.close();
49 | }
50 |
51 | private Properties baseTestProperties() {
52 | Properties p = new Properties();
53 | p.setProperty("auroraClusterRegion", "eu-west-1");
54 | p.setProperty("auroraDiscoveryAuthMode", "basic");
55 | p.setProperty("auroraDiscoveryKeyId", "keyId");
56 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
57 | p.setProperty("discoveryMode", "irrelevant");
58 | p.setProperty("replicaPollInterval", "5");
59 | p.setProperty("tagsPollInterval", "10");
60 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
61 | p.setProperty("validateConnection", "true");
62 | p.setProperty("user", postgres.getUsername());
63 | p.setProperty("password", postgres.getPassword());
64 | return p;
65 | }
66 |
67 | @Test
68 | public void noReplicas() throws URISyntaxException {
69 | PostgresSQLReplicasFinder underTest =
70 | new PostgresSQLReplicasFinder(
71 | new FairlinkConnectionString(
72 | postgres.getJdbcUrl().replace("postgresql", "fairlink:postgresql"), this.baseTestProperties()),
73 | postgres.getJdbcDriverInstance());
74 | final ClusterInfo clusterInfo = underTest.discoverCluster();
75 | Assert.assertEquals(new ClusterInfo(postgres.getJdbcUrl(), ImmutableSet.of()), clusterInfo);
76 | }
77 |
78 | @Test
79 | public void withReplicas() throws URISyntaxException, IOException, SQLException {
80 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/postgresql/2replicas.sql");
81 | PostgresSQLReplicasFinder underTest =
82 | new PostgresSQLReplicasFinder(
83 | new FairlinkConnectionString(
84 | postgres.getJdbcUrl().replace("postgresql", "fairlink:postgresql"), this.baseTestProperties()),
85 | postgres.getJdbcDriverInstance());
86 | final ClusterInfo actual = underTest.discoverCluster();
87 | final ClusterInfo expected = new ClusterInfo(postgres.getJdbcUrl(), ImmutableSet.of("replica"));
88 | Assert.assertEquals(expected, actual);
89 | }
90 |
91 | @Test
92 | public void noConnectionsResultsInEmptySet()
93 | throws URISyntaxException, IOException, SQLException {
94 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/postgresql/2replicas.sql");
95 | PostgresSQLReplicasFinder underTest =
96 | new PostgresSQLReplicasFinder(
97 | new FairlinkConnectionString(
98 | postgres.getJdbcUrl().replace("postgresql", "fairlink:fairlinktestdriver"),
99 | this.baseTestProperties()),
100 | postgres.getJdbcDriverInstance());
101 | final ClusterInfo actual = underTest.discoverCluster();
102 | final ClusterInfo expected =
103 | new ClusterInfo(
104 | postgres.getJdbcUrl().replace("postgresql", "fairlinktestdriver"), ImmutableSet.of());
105 | Assert.assertEquals(expected, actual);
106 | }
107 |
108 | @Test
109 | public void noFunctionResultsInEmptySet() throws URISyntaxException, IOException, SQLException {
110 | this.runScript("technology/dice/dicefairlink/discovery/members/sql/postgresql/drop_function.sql");
111 | PostgresSQLReplicasFinder underTest =
112 | new PostgresSQLReplicasFinder(
113 | new FairlinkConnectionString(
114 | postgres.getJdbcUrl().replace("postgresql", "fairlink:postgresql"), this.baseTestProperties()),
115 | postgres.getJdbcDriverInstance());
116 | final ClusterInfo actual = underTest.discoverCluster();
117 | final ClusterInfo expected = new ClusterInfo(postgres.getJdbcUrl(), ImmutableSet.of());
118 | Assert.assertEquals(expected, actual);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/tags/awsapi/ResourceGroupApiResponse.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.tags.awsapi;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import java.util.List;
5 |
6 | public class ResourceGroupApiResponse {
7 | public static class Resource {
8 | public static class Tag {
9 | private final String key;
10 | private final String value;
11 |
12 | public Tag(String key, String value) {
13 | this.key = key;
14 | this.value = value;
15 | }
16 |
17 | @JsonProperty("Key")
18 | public String getKey() {
19 | return key;
20 | }
21 |
22 | @JsonProperty("Value")
23 | public String getValue() {
24 | return value;
25 | }
26 | }
27 |
28 | private final String resourceARN;
29 | private final List tags;
30 |
31 | public Resource(String resourceARN, List tags) {
32 | this.resourceARN = resourceARN;
33 | this.tags = tags;
34 | }
35 |
36 | @JsonProperty("ResourceARN")
37 | public String getResourceARN() {
38 | return resourceARN;
39 | }
40 |
41 | @JsonProperty("Tags")
42 | public List getTags() {
43 | return tags;
44 | }
45 | }
46 |
47 | private final List taggedResources;
48 | private final String taginationToken;
49 |
50 | public ResourceGroupApiResponse(List taggedResources, String taginationToken) {
51 | this.taggedResources = taggedResources;
52 | this.taginationToken = taginationToken;
53 | }
54 |
55 | @JsonProperty("ResourceTagMappingList")
56 | public List getTaggedResources() {
57 | return taggedResources;
58 | }
59 |
60 | @JsonProperty("PaginationToken")
61 | public String getTaginationToken() {
62 | return taginationToken;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/tags/awsapi/ResourceGroupApiTagDiscoveryTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.tags.awsapi;
2 |
3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
4 | import static com.github.tomakehurst.wiremock.client.WireMock.post;
5 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
6 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
7 |
8 | import com.fasterxml.jackson.core.JsonProcessingException;
9 | import com.fasterxml.jackson.databind.ObjectMapper;
10 | import com.github.tomakehurst.wiremock.junit.WireMockRule;
11 | import com.google.common.collect.ImmutableList;
12 | import com.google.common.collect.ImmutableMap;
13 | import com.google.common.collect.ImmutableSet;
14 | import java.util.Properties;
15 | import java.util.Set;
16 | import org.junit.Assert;
17 | import org.junit.Rule;
18 | import org.junit.Test;
19 | import technology.dice.dicefairlink.config.FairlinkConfiguration;
20 | import technology.dice.dicefairlink.discovery.tags.ExclusionTag;
21 | import technology.dice.dicefairlink.discovery.tags.awsapi.ResourceGroupApiResponse.Resource;
22 | import technology.dice.dicefairlink.discovery.tags.awsapi.ResourceGroupApiResponse.Resource.Tag;
23 |
24 | public class ResourceGroupApiTagDiscoveryTest {
25 | @Rule public WireMockRule wireMockRule = new WireMockRule(11342);
26 | private final ObjectMapper mapper = new ObjectMapper();
27 |
28 | private Properties baseTestProperties() {
29 | Properties p = new Properties();
30 | p.setProperty("auroraClusterRegion", "eu-west-1");
31 | p.setProperty("auroraDiscoveryAuthMode", "basic");
32 | p.setProperty("auroraDiscoveryKeyId", "keyId");
33 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
34 | p.setProperty("discoveryMode", "AWS_API");
35 | p.setProperty("replicaPollInterval", "5");
36 | p.setProperty("tagsPollInterval", "10");
37 | p.setProperty("replicaEndpointTemplate", "%s.rest-of-myhost.name");
38 | p.setProperty("validateConnection", "true");
39 | p.setProperty("awsEndpointOverride", "http://localhost:11342");
40 | return p;
41 | }
42 |
43 | @Test
44 | public void withExclusions() throws JsonProcessingException {
45 | ResourceGroupApiTagDiscovery underTest =
46 | new ResourceGroupApiTagDiscovery(
47 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()));
48 |
49 | stubFor(
50 | post(urlEqualTo("/"))
51 | .willReturn(
52 | aResponse()
53 | .withStatus(200)
54 | .withBody(
55 | this.stringifyResponse(
56 | new ResourceGroupApiResponse(
57 | ImmutableList.of(
58 | new Resource(
59 | "arn1",
60 | ImmutableList.of(
61 | new Tag("k", "v"),
62 | new Tag("Fairlink-Exclude", "true")))),
63 | "")))));
64 |
65 | final Set actual =
66 | underTest.listExcludedInstances(new ExclusionTag("FairlinkConfiguration", "true"));
67 | Assert.assertEquals(ImmutableSet.of("arn1"), actual);
68 | }
69 |
70 | @Test
71 | public void noExclusions() throws JsonProcessingException {
72 | ResourceGroupApiTagDiscovery underTest =
73 | new ResourceGroupApiTagDiscovery(
74 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()));
75 |
76 | stubFor(
77 | post(urlEqualTo("/"))
78 | .willReturn(
79 | aResponse()
80 | .withStatus(200)
81 | .withBody(
82 | this.stringifyResponse(
83 | new ResourceGroupApiResponse(ImmutableList.of(), "")))));
84 |
85 | final Set actual =
86 | underTest.listExcludedInstances(new ExclusionTag("FairlinkConfiguration", "true"));
87 | Assert.assertEquals(ImmutableSet.of(), actual);
88 | }
89 |
90 | @Test
91 | public void exceptionAssumesEmpty() {
92 | ResourceGroupApiTagDiscovery underTest =
93 | new ResourceGroupApiTagDiscovery(
94 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()));
95 |
96 | stubFor(post(urlEqualTo("/")).willReturn(aResponse().withStatus(500)));
97 |
98 | final Set actual =
99 | underTest.listExcludedInstances(new ExclusionTag("FairlinkConfiguration", "true"));
100 | Assert.assertEquals(ImmutableSet.of(), actual);
101 | }
102 |
103 | @Test
104 | public void noInstancesFound() {
105 | ResourceGroupApiTagDiscovery underTest =
106 | new ResourceGroupApiTagDiscovery(
107 | new FairlinkConfiguration(this.baseTestProperties(), ImmutableMap.of()));
108 |
109 | stubFor(post(urlEqualTo("/")).willReturn(aResponse().withStatus(404)));
110 |
111 | final Set actual =
112 | underTest.listExcludedInstances(new ExclusionTag("FairlinkConfiguration", "true"));
113 | Assert.assertEquals(ImmutableSet.of(), actual);
114 | }
115 |
116 | private String stringifyResponse(ResourceGroupApiResponse response)
117 | throws JsonProcessingException {
118 | return this.mapper.writeValueAsString(response);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/discovery/tags/awsapi/ResourceGroupApiTagDiscoveryTestIT.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.discovery.tags.awsapi;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import java.util.stream.Collectors;
5 | import org.junit.Test;
6 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
7 | import software.amazon.awssdk.regions.Region;
8 | import software.amazon.awssdk.services.resourcegroupstaggingapi.ResourceGroupsTaggingApiClient;
9 | import software.amazon.awssdk.services.resourcegroupstaggingapi.model.GetResourcesRequest;
10 |
11 | public class ResourceGroupApiTagDiscoveryTestIT {
12 |
13 | // Here to capture the http wire traffic. Not automatically ran as part of CI
14 | @Test
15 | public void makeAPicall() {
16 | final ResourceGroupsTaggingApiClient client =
17 | ResourceGroupsTaggingApiClient.builder()
18 | .region(Region.EU_WEST_1)
19 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
20 | .build();
21 |
22 | GetResourcesRequest request =
23 | GetResourcesRequest.builder()
24 | .resourceTypeFilters(ImmutableList.of("rds:db"))
25 | .tagFilters(
26 | software.amazon.awssdk.services.resourcegroupstaggingapi.model.TagFilter.builder()
27 | .key("Fairlink-Exclude")
28 | .values("true")
29 | .build())
30 | .build();
31 |
32 | client.getResourcesPaginator(request).stream().collect(Collectors.toSet());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/driver/AuroraReadReplicasDriverEndToEndTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.driver;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.common.base.Function;
5 | import com.google.common.collect.ImmutableMap;
6 | import com.google.common.collect.ImmutableSet;
7 | import com.google.common.io.CharStreams;
8 | import java.net.URISyntaxException;
9 | import org.checkerframework.checker.nullness.qual.Nullable;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.testcontainers.containers.MySQLContainer;
15 | import technology.dice.dicefairlink.StepByStepExecutor;
16 | import technology.dice.dicefairlink.discovery.members.sql.MySqlReplicasFinderTest;
17 | import technology.dice.dicefairlink.iterators.CyclicIterator;
18 | import technology.dice.dicefairlink.iterators.SizedIterator;
19 | import technology.dice.dicefairlink.support.discovery.tags.FixedSetExcludedReplicasFinder;
20 | import technology.dice.dicefairlink.support.iterators.TestCyclicIterator;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.io.InputStreamReader;
25 | import java.sql.Connection;
26 | import java.sql.DriverManager;
27 | import java.sql.ResultSet;
28 | import java.sql.SQLException;
29 | import java.util.ArrayList;
30 | import java.util.Collection;
31 | import java.util.List;
32 | import java.util.Map;
33 | import java.util.Properties;
34 |
35 | public class AuroraReadReplicasDriverEndToEndTest {
36 | @Rule public MySQLContainer master = new MySQLContainer();
37 | @Rule public MySQLContainer slave1 = new MySQLContainer();
38 | @Rule public MySQLContainer slave2 = new MySQLContainer();
39 |
40 | private void runScript(MySQLContainer container, String path) throws IOException, SQLException {
41 | this.runScript(container, path, ImmutableMap.of());
42 | }
43 |
44 | private void runScript(MySQLContainer container, String path, Map replacing)
45 | throws IOException, SQLException {
46 | Properties p = new Properties();
47 | p.setProperty("allowMultiQueries", "true");
48 | p.setProperty("user", container.getUsername());
49 | p.setProperty("password", container.getPassword());
50 | final InputStream resourceAsStream =
51 | MySqlReplicasFinderTest.class.getClassLoader().getResourceAsStream(path);
52 | String schemaSql =
53 | CharStreams.toString(new InputStreamReader(resourceAsStream, Charsets.UTF_8));
54 | for (Map.Entry replace : replacing.entrySet()) {
55 | schemaSql = schemaSql.replace(replace.getKey(), replace.getValue());
56 | }
57 | Connection conn = DriverManager.getConnection(container.getJdbcUrl(), p);
58 |
59 | conn.createStatement().executeUpdate(schemaSql);
60 | conn.close();
61 | }
62 |
63 | @Before
64 | public void before() throws IOException, SQLException {
65 | this.runScript(master, "technology/dice/dicefairlink/discovery/members/sql/mysql/schema.sql");
66 | this.runScript(slave1, "technology/dice/dicefairlink/discovery/members/sql/mysql/schema.sql");
67 | this.runScript(slave2, "technology/dice/dicefairlink/discovery/members/sql/mysql/schema.sql");
68 | this.runScript(
69 | master,
70 | "technology/dice/dicefairlink/discovery/members/sql/mysql/3replicasPorts.sql",
71 | ImmutableMap.of(
72 | "__PORT_SLAVE_1__",
73 | slave1.getFirstMappedPort().toString(),
74 | "__PORT_SLAVE_2__",
75 | slave2.getFirstMappedPort().toString()));
76 | this.runScript(master, "technology/dice/dicefairlink/discovery/members/sql/mysql/masterData.sql");
77 | this.runScript(slave1, "technology/dice/dicefairlink/discovery/members/sql/mysql/slave1Data.sql");
78 | this.runScript(slave2, "technology/dice/dicefairlink/discovery/members/sql/mysql/slave2Data.sql");
79 | }
80 |
81 | private StepByStepExecutor exclusionTagsExecutor;
82 | private StepByStepExecutor memberDiscoveryExecutor;
83 |
84 | private Properties baseTestProperties() {
85 | Properties p = new Properties();
86 | p.setProperty("auroraClusterRegion", "eu-west-1");
87 | p.setProperty("auroraDiscoveryAuthMode", "basic");
88 | p.setProperty("auroraDiscoveryKeyId", "keyId");
89 | p.setProperty("discoveryMode", "SQL_MYSQL");
90 | p.setProperty("auroraDiscoverKeySecret", "keySecret");
91 | p.setProperty("replicaPollInterval", "5");
92 | p.setProperty("tagsPollInterval", "10");
93 | p.setProperty("replicaEndpointTemplate", "%s");
94 | p.setProperty("validateConnection", "true");
95 | p.setProperty("_fairlinkMySQLSchemaOverride", "test");
96 | p.setProperty("user", master.getUsername());
97 | p.setProperty("password", master.getPassword());
98 | return p;
99 | }
100 |
101 | @Before
102 | public void setup() {
103 | this.exclusionTagsExecutor = new StepByStepExecutor(1);
104 | this.memberDiscoveryExecutor = new StepByStepExecutor(1);
105 | }
106 |
107 | @Test
108 | public void readFromTwoReplicas() throws SQLException {
109 | CyclicIterator[] i = new CyclicIterator[1];
110 | AuroraReadReplicasDriver underTest =
111 | new AuroraReadReplicasDriver(
112 | () -> memberDiscoveryExecutor,
113 | () -> exclusionTagsExecutor,
114 | () -> new FixedSetExcludedReplicasFinder(ImmutableSet.of("replica1", "replica3")),
115 | null,
116 | null,
117 | new Function, SizedIterator>() {
118 | @Nullable
119 | @Override
120 | public SizedIterator apply(@Nullable Collection strings) {
121 | final CyclicIterator iterator = TestCyclicIterator.of(strings);
122 | i[0] = iterator;
123 | return iterator;
124 | }
125 | });
126 |
127 | List foundData = readTwice(underTest);
128 | Assert.assertEquals(2, i[0].size());
129 | Assert.assertEquals(4, foundData.size());
130 | Assert.assertTrue(
131 | foundData.stream()
132 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE1"))
133 | .findFirst()
134 | .isPresent());
135 | Assert.assertTrue(
136 | foundData.stream()
137 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
138 | .findFirst()
139 | .isPresent());
140 |
141 | final Connection masterConnection =
142 | DriverManager.getConnection(master.getJdbcUrl(), baseTestProperties());
143 | masterConnection
144 | .createStatement()
145 | .executeUpdate("DELETE from replica_host_status WHERE SESSION_ID='another-sesion2'");
146 | masterConnection.close();
147 |
148 | memberDiscoveryExecutor.step();
149 | foundData = readTwice(underTest);
150 |
151 | Assert.assertEquals(1, i[0].size());
152 | Assert.assertEquals(4, foundData.size());
153 | Assert.assertEquals(
154 | 2, foundData.stream().filter(d -> d.getB().equalsIgnoreCase("SLAVE1")).count());
155 | Assert.assertFalse(
156 | foundData.stream()
157 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
158 | .findFirst()
159 | .isPresent());
160 | }
161 |
162 | @Test
163 | public void refreshBeforeScheduledDiscovery() throws SQLException, URISyntaxException {
164 | CyclicIterator[] i = new CyclicIterator[1];
165 | AuroraReadReplicasDriver underTest =
166 | new AuroraReadReplicasDriver(
167 | () -> memberDiscoveryExecutor,
168 | () -> exclusionTagsExecutor,
169 | () -> new FixedSetExcludedReplicasFinder(ImmutableSet.of("replica1", "replica3")),
170 | null,
171 | null,
172 | new Function, SizedIterator>() {
173 | @Nullable
174 | @Override
175 | public SizedIterator apply(@Nullable Collection strings) {
176 | final CyclicIterator iterator = TestCyclicIterator.of(strings);
177 | i[0] = iterator;
178 | return iterator;
179 | }
180 | });
181 |
182 | List foundData = readTwice(underTest);
183 | Assert.assertEquals(2, i[0].size());
184 | Assert.assertEquals(4, foundData.size());
185 | Assert.assertTrue(
186 | foundData.stream()
187 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE1"))
188 | .findFirst()
189 | .isPresent());
190 | Assert.assertTrue(
191 | foundData.stream()
192 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
193 | .findFirst()
194 | .isPresent());
195 |
196 | final Connection masterConnection =
197 | DriverManager.getConnection(master.getJdbcUrl(), baseTestProperties());
198 | masterConnection
199 | .createStatement()
200 | .executeUpdate("DELETE from replica_host_status WHERE SESSION_ID='another-sesion2'");
201 | masterConnection.close();
202 |
203 | underTest.refreshReplicas(master.getJdbcUrl().replace("mysql", "fairlink:mysql"));
204 | foundData = readTwice(underTest);
205 |
206 | Assert.assertEquals(1, i[0].size());
207 | Assert.assertEquals(4, foundData.size());
208 | Assert.assertEquals(
209 | 2, foundData.stream().filter(d -> d.getB().equalsIgnoreCase("SLAVE1")).count());
210 | Assert.assertFalse(
211 | foundData.stream()
212 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
213 | .findFirst()
214 | .isPresent());
215 | }
216 |
217 | @Test
218 | public void refreshUnexistingEndpoint() throws SQLException, URISyntaxException {
219 | CyclicIterator[] i = new CyclicIterator[1];
220 | AuroraReadReplicasDriver underTest =
221 | new AuroraReadReplicasDriver(
222 | () -> memberDiscoveryExecutor,
223 | () -> exclusionTagsExecutor,
224 | () -> new FixedSetExcludedReplicasFinder(ImmutableSet.of("replica1", "replica3")),
225 | null,
226 | null,
227 | new Function, SizedIterator>() {
228 | @Nullable
229 | @Override
230 | public SizedIterator apply(@Nullable Collection strings) {
231 | final CyclicIterator iterator = TestCyclicIterator.of(strings);
232 | i[0] = iterator;
233 | return iterator;
234 | }
235 | });
236 |
237 | List foundData = readTwice(underTest);
238 | Assert.assertEquals(2, i[0].size());
239 | Assert.assertEquals(4, foundData.size());
240 | Assert.assertTrue(
241 | foundData.stream()
242 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE1"))
243 | .findFirst()
244 | .isPresent());
245 | Assert.assertTrue(
246 | foundData.stream()
247 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
248 | .findFirst()
249 | .isPresent());
250 |
251 | final Connection masterConnection =
252 | DriverManager.getConnection(master.getJdbcUrl(), baseTestProperties());
253 | masterConnection
254 | .createStatement()
255 | .executeUpdate("DELETE from replica_host_status WHERE SESSION_ID='another-sesion2'");
256 | masterConnection.close();
257 |
258 | underTest.refreshReplicas(slave1.getJdbcUrl().replace("mysql", "fairlink:mysql"));
259 | foundData = readTwice(underTest);
260 |
261 | Assert.assertEquals(2, i[0].size());
262 | Assert.assertEquals(4, foundData.size());
263 | Assert.assertTrue(
264 | foundData.stream()
265 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE1"))
266 | .findFirst()
267 | .isPresent());
268 | Assert.assertTrue(
269 | foundData.stream()
270 | .filter(d -> d.getB().equalsIgnoreCase("SLAVE2"))
271 | .findFirst()
272 | .isPresent());
273 | }
274 |
275 | private List readTwice(AuroraReadReplicasDriver underTest) throws SQLException {
276 | List foundData = new ArrayList();
277 | Connection connect =
278 | underTest.connect(
279 | master.getJdbcUrl().replace("mysql", "fairlink:mysql"), this.baseTestProperties());
280 | ResultSet resultSet = connect.createStatement().executeQuery("SELECT * FROM data");
281 | while (resultSet.next()) {
282 | Data data = new Data(resultSet.getString("a"), resultSet.getString("b"));
283 | foundData.add(data);
284 | }
285 | connect.close();
286 | resultSet.close();
287 | connect =
288 | underTest.connect(
289 | master.getJdbcUrl().replace("mysql", "fairlink:mysql"), this.baseTestProperties());
290 | resultSet = connect.createStatement().executeQuery("SELECT * FROM data");
291 | while (resultSet.next()) {
292 | Data data = new Data(resultSet.getString("a"), resultSet.getString("b"));
293 | foundData.add(data);
294 | }
295 |
296 | connect.close();
297 | resultSet.close();
298 |
299 | return foundData;
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/driver/AuroraReadReplicasDriverTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.driver;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | import java.sql.DriverManager;
11 | import java.sql.DriverPropertyInfo;
12 | import java.sql.SQLException;
13 | import java.util.NoSuchElementException;
14 | import java.util.Properties;
15 | import java.util.logging.Logger;
16 | import org.junit.Assert;
17 | import org.junit.BeforeClass;
18 | import org.junit.Test;
19 | import technology.dice.dicefairlink.support.driver.TestDriver;
20 |
21 | public class AuroraReadReplicasDriverTest {
22 | private static final String VALID_JDBC_URL =
23 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc";
24 |
25 | @BeforeClass
26 | public static void setupClass() throws SQLException {
27 | DriverManager.registerDriver(new TestDriver());
28 | }
29 |
30 | @Test
31 | public void driverInterfaceLock() {
32 | final AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
33 | Assert.assertFalse(underTest.jdbcCompliant());
34 | Assert.assertEquals(2, underTest.getMajorVersion());
35 | Assert.assertEquals(1, underTest.getMinorVersion());
36 | Assert.assertArrayEquals(
37 | new DriverPropertyInfo[] {}, underTest.getPropertyInfo(VALID_JDBC_URL, new Properties()));
38 | Assert.assertEquals(Logger.getLogger(""), underTest.getParentLogger());
39 | }
40 |
41 | @Test
42 | public void accepts() throws SQLException {
43 | final AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
44 | Assert.assertTrue(underTest.acceptsURL(VALID_JDBC_URL));
45 | }
46 |
47 | @Test
48 | public void doesNotAccept() throws SQLException {
49 | final AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
50 | Assert.assertFalse(
51 | underTest.acceptsURL(
52 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc"));
53 | }
54 |
55 | @Test(expected = SQLException.class)
56 | public void throwsOnNullAccepts() throws SQLException {
57 | final AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
58 | underTest.acceptsURL(null);
59 | }
60 |
61 | @Test(expected = SQLException.class)
62 | public void throwsOnAcceptsURL_nullString() throws Exception {
63 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
64 | underTest.acceptsURL(null);
65 | }
66 |
67 | @Test
68 | public void canAcceptsURL_emptyString() throws Exception {
69 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
70 | boolean retunedValue = underTest.acceptsURL("");
71 | assertThat(retunedValue).isEqualTo(false);
72 | }
73 |
74 | @Test
75 | public void refuses_vanillaJdbc() throws Exception {
76 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
77 | boolean retunedValue =
78 | underTest.acceptsURL("jdbc:fairlinktestdriver://host:3306/id?useSSL=false");
79 | assertThat(retunedValue).isEqualTo(false);
80 | }
81 |
82 | @Test
83 | public void canAcceptsURL_validString() throws Exception {
84 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
85 | boolean retunedValue = underTest.acceptsURL(VALID_JDBC_URL);
86 | assertThat(retunedValue).isEqualTo(true);
87 | }
88 |
89 | @Test(expected = NullPointerException.class)
90 | public void failToConnectToValidUrl_nullProperties() throws Exception {
91 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
92 | underTest.connect(VALID_JDBC_URL, null); // last call must throw
93 | }
94 |
95 | @Test(expected = IllegalStateException.class)
96 | public void failToConnectToValidUrl_emptyProperties_andNoRegionAvailable() throws Exception {
97 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
98 | underTest.connect(VALID_JDBC_URL, new Properties()); // last call must throw
99 | }
100 |
101 | @Test
102 | public void failedToConnectBadUrl() throws Exception {
103 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
104 | Assert.assertNull(
105 | underTest.connect("jdbc:fairlinktestdriver://host:3306/id?useSSL=false", new Properties()));
106 | }
107 |
108 | @Test
109 | public void failedToConnectIncompatibleDiscoveryMode() throws Exception {
110 | final Properties properties = new Properties();
111 | properties.setProperty("discoveryMode", "SQL_MYSQL");
112 | properties.setProperty("auroraClusterRegion", "eu-west-1");
113 | properties.setProperty("replicaEndpointTemplate", "%s");
114 |
115 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
116 | Assert.assertNull(
117 | underTest.connect(
118 | "jdbc:fairlink:fairlinktestdriver://host:3306/id?useSSL=false", properties));
119 | }
120 |
121 | @Test
122 | public void testWellformedConnectionstringWithMalformedHost() throws Exception {
123 | AuroraReadReplicasDriver underTest = new AuroraReadReplicasDriver();
124 | Assert.assertNull(underTest.connect("jdbc:fairlink:<>|/://host:3306/id?useSSL=false", new Properties()));
125 | }
126 |
127 | @Test
128 | public void staticLoad() throws ClassNotFoundException {
129 | Class.forName(AuroraReadReplicasDriver.class.getCanonicalName());
130 | }
131 |
132 | @Test
133 | public void throwsNoSuchElementException() throws Exception {
134 |
135 | AuroraReadReplicasDriver underTest =
136 | new AuroraReadReplicasDriver(
137 | () -> {throw new NoSuchElementException();},
138 | () -> {throw new NoSuchElementException();},
139 | null,
140 | null,
141 | null,
142 | null
143 | );
144 | Properties properties = new Properties();
145 | properties.setProperty("discoveryMode", "AWS_API");
146 | properties.setProperty("auroraClusterRegion", "eu-west-1");
147 | properties.setProperty("replicaEndpointTemplate", "%s");
148 |
149 | Assert.assertNull(underTest.connect("jdbc:fairlink:fairlinktestdriver://host:3306/id?useSSL=false", properties));
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/driver/Data.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.driver;
2 |
3 | public class Data {
4 | private final String a;
5 | private final String b;
6 |
7 | public Data(String a, String b) {
8 | this.a = a;
9 | this.b = b;
10 | }
11 |
12 | public String getA() {
13 | return a;
14 | }
15 |
16 | public String getB() {
17 | return b;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/driver/FairlinkConnectionStringTest.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.driver;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.net.URISyntaxException;
7 | import java.util.Properties;
8 |
9 | public class FairlinkConnectionStringTest {
10 | @Test
11 | public void valid() throws URISyntaxException {
12 | String connString =
13 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc";
14 | final Properties properties = new Properties();
15 | properties.setProperty("a", "b");
16 | final FairlinkConnectionString underTest = new FairlinkConnectionString(connString, properties);
17 | Assert.assertEquals(connString, underTest.getFairlinkUri());
18 | Assert.assertEquals("fairlinktestdriver", underTest.getDelegateProtocol());
19 | Assert.assertEquals("aa", underTest.getHost());
20 | Assert.assertEquals(
21 | "jdbc:fairlinktestdriver://anotherHost:123/db?param1=123¶m2=true¶m3=abc",
22 | underTest.delegateConnectionString("anotherHost"));
23 | Assert.assertEquals(
24 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
25 | underTest.delegateConnectionString());
26 | Assert.assertEquals(1, underTest.getProperties().size());
27 | Assert.assertEquals("b", underTest.getProperties().getProperty("a"));
28 | }
29 |
30 | @Test
31 | public void validBackwardCompatibility() throws URISyntaxException {
32 | String connString =
33 | "jdbc:auroraro:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc";
34 | final Properties properties = new Properties();
35 | properties.setProperty("a", "b");
36 | final FairlinkConnectionString underTest = new FairlinkConnectionString(connString, properties);
37 | Assert.assertEquals(connString, underTest.getFairlinkUri());
38 | Assert.assertEquals("fairlinktestdriver", underTest.getDelegateProtocol());
39 | Assert.assertEquals("aa", underTest.getHost());
40 | Assert.assertEquals(
41 | "jdbc:fairlinktestdriver://anotherHost:123/db?param1=123¶m2=true¶m3=abc",
42 | underTest.delegateConnectionString("anotherHost"));
43 | Assert.assertEquals(
44 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
45 | underTest.delegateConnectionString());
46 | Assert.assertEquals(1, underTest.getProperties().size());
47 | Assert.assertEquals("b", underTest.getProperties().getProperty("a"));
48 | }
49 |
50 | @Test
51 | public void validHostInDifferentPort() throws URISyntaxException {
52 | String connString =
53 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc";
54 | final Properties properties = new Properties();
55 | properties.setProperty("a", "b");
56 | final FairlinkConnectionString underTest = new FairlinkConnectionString(connString, properties);
57 | Assert.assertEquals(connString, underTest.getFairlinkUri());
58 | Assert.assertEquals("fairlinktestdriver", underTest.getDelegateProtocol());
59 | Assert.assertEquals("aa", underTest.getHost());
60 | Assert.assertEquals(
61 | "jdbc:fairlinktestdriver://anotherHost:999/db?param1=123¶m2=true¶m3=abc",
62 | underTest.delegateConnectionString("anotherHost:999"));
63 | Assert.assertEquals(
64 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc",
65 | underTest.delegateConnectionString());
66 | Assert.assertEquals(1, underTest.getProperties().size());
67 | Assert.assertEquals("b", underTest.getProperties().getProperty("a"));
68 | }
69 |
70 | @Test
71 | public void accepts() {
72 | Assert.assertTrue(
73 | FairlinkConnectionString.accepts(
74 | "jdbc:fairlink:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc"));
75 | }
76 |
77 | @Test
78 | public void acceptsBackwardsCompatibility() {
79 | Assert.assertTrue(
80 | FairlinkConnectionString.accepts(
81 | "jdbc:auroraro:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc"));
82 | }
83 |
84 | @Test
85 | public void doesNotAccept() {
86 | Assert.assertFalse(
87 | FairlinkConnectionString.accepts(
88 | "jdbc:something:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc"));
89 | Assert.assertFalse(
90 | FairlinkConnectionString.accepts(
91 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc"));
92 | }
93 |
94 | @Test(expected = IllegalArgumentException.class)
95 | public void ctorBadConnString() throws URISyntaxException {
96 | new FairlinkConnectionString(
97 | "jdbc:fairlinktestdriver://aa:123/db?param1=123¶m2=true¶m3=abc", new Properties());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/iterators/RandomisedCyclicIteratorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 - present by Dice Technology Ltd.
3 | *
4 | * Please see distribution for license.
5 | */
6 | package technology.dice.dicefairlink.iterators;
7 |
8 | import org.assertj.core.api.Assertions;
9 | import org.junit.Before;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.junit.rules.TestName;
13 |
14 | import java.util.Collections;
15 | import java.util.LinkedList;
16 | import java.util.List;
17 | import java.util.NoSuchElementException;
18 | import java.util.concurrent.ThreadLocalRandom;
19 |
20 | import static org.assertj.core.api.Assertions.assertThat;
21 |
22 | public class RandomisedCyclicIteratorTest {
23 |
24 | private static final int MIN_SIZE = 10;
25 | private static final int MAX_SIZE = 1_000;
26 | private static final String ELEMENT_PREFIX = "TEST_";
27 |
28 | private int numberOfElementsToTest = -1;
29 |
30 | @Rule public TestName testName = new TestName();
31 |
32 | @Before
33 | public void setUp() {
34 | numberOfElementsToTest = ThreadLocalRandom.current().nextInt(MIN_SIZE, MAX_SIZE);
35 | }
36 |
37 | @Test(expected = NoSuchElementException.class)
38 | public void canCallOfFromEmptyList() {
39 | RandomisedCyclicIterator cyclicIterator = RandomisedCyclicIterator.of(Collections.EMPTY_LIST);
40 |
41 | Assertions.assertThat(cyclicIterator).isNotNull();
42 |
43 | assertThat(cyclicIterator.hasNext()).isFalse();
44 | cyclicIterator.next(); // final step throws
45 | }
46 |
47 | @Test(expected = NoSuchElementException.class)
48 | public void canCallOfFromEmptySet() {
49 | RandomisedCyclicIterator cyclicIterator = RandomisedCyclicIterator.of(Collections.EMPTY_SET);
50 |
51 | Assertions.assertThat(cyclicIterator).isNotNull();
52 |
53 | assertThat(cyclicIterator.hasNext()).isFalse();
54 | cyclicIterator.next(); // final step throws
55 | }
56 |
57 | @Test
58 | public void canOperateWithListOfSingleElement() {
59 | final String singleElement = ELEMENT_PREFIX + "_1";
60 |
61 | final List listOfStrings = new LinkedList<>();
62 | listOfStrings.add(singleElement);
63 |
64 | RandomisedCyclicIterator cyclicIterator = RandomisedCyclicIterator.of(listOfStrings);
65 |
66 | assertThat(cyclicIterator).isNotNull();
67 | for (int cycle = 0; cycle < numberOfElementsToTest; cycle++) {
68 | assertThat(cyclicIterator.hasNext()).isTrue();
69 | assertThat(cyclicIterator.next()).isEqualTo(singleElement);
70 | }
71 | }
72 |
73 | @Test
74 | public void canOperateWithListOfMultipleIdenticalElements() {
75 | final String singleElement = ELEMENT_PREFIX + "_1";
76 | final List listOfStrings = new LinkedList<>();
77 |
78 | for (int i = 0; i < numberOfElementsToTest; i++) {
79 | listOfStrings.add(singleElement);
80 | }
81 |
82 | RandomisedCyclicIterator cyclicIterator = RandomisedCyclicIterator.of(listOfStrings);
83 |
84 | assertThat(cyclicIterator).isNotNull();
85 | for (int cycle = 0; cycle < numberOfElementsToTest; cycle++) {
86 | for (int i = 0; i < numberOfElementsToTest; i++) {
87 | assertThat(cyclicIterator.hasNext()).isTrue();
88 | assertThat(cyclicIterator.next()).isEqualTo(singleElement);
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/discovery/members/FailingReplicasFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.discovery.members;
2 |
3 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
4 |
5 | import java.util.Set;
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | public class FailingReplicasFinder extends FixedSetReplicasFinder {
9 | private final AtomicInteger discoveriesUntilFailure;
10 |
11 | public FailingReplicasFinder(String fallbackEndpoint, Set replicas, int failAfter) {
12 | super(fallbackEndpoint, replicas);
13 | this.discoveriesUntilFailure = new AtomicInteger(failAfter);
14 | }
15 |
16 | @Override
17 | public ClusterInfo discoverCluster() {
18 | final int left = this.discoveriesUntilFailure.decrementAndGet();
19 | if (left < 0) {
20 | throw new RuntimeException("Programmed exception set number of executions");
21 | }
22 | return super.discoverCluster();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/discovery/members/FixedMemberFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.discovery.members;
2 |
3 | import technology.dice.dicefairlink.discovery.members.MemberFinder;
4 | import technology.dice.dicefairlink.iterators.SizedIterator;
5 | import technology.dice.dicefairlink.support.iterators.TestCyclicIterator;
6 |
7 | import java.util.Collection;
8 |
9 | public class FixedMemberFinder implements MemberFinder {
10 | private Collection members;
11 |
12 | public FixedMemberFinder(Collection members) {
13 | this.members = members;
14 | }
15 |
16 | public void updateMembers(Collection replicas) {
17 | this.members = replicas;
18 | }
19 |
20 | @Override
21 | public SizedIterator discoverReplicas() {
22 | return TestCyclicIterator.of(this.members);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/discovery/members/FixedSetReplicasFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.discovery.members;
2 |
3 | import technology.dice.dicefairlink.discovery.members.ClusterInfo;
4 | import technology.dice.dicefairlink.discovery.members.MemberFinderMethod;
5 |
6 | import java.util.Set;
7 |
8 | public class FixedSetReplicasFinder implements MemberFinderMethod {
9 | private Set replicas;
10 | private final String fallbackEndpoint;
11 |
12 | public FixedSetReplicasFinder(String fallbackEndpoint, Set replicas) {
13 | this.fallbackEndpoint = fallbackEndpoint;
14 | this.replicas = replicas;
15 | }
16 |
17 | public void updateReplicas(Set replicas) {
18 | this.replicas = replicas;
19 | }
20 |
21 | @Override
22 | public ClusterInfo discoverCluster() {
23 | return new ClusterInfo(this.fallbackEndpoint, this.replicas);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/discovery/tags/FailingExcludedReplicasFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.discovery.tags;
2 |
3 | import technology.dice.dicefairlink.discovery.tags.ExclusionTag;
4 |
5 | import java.util.Collection;
6 | import java.util.Set;
7 | import java.util.concurrent.atomic.AtomicInteger;
8 |
9 | public class FailingExcludedReplicasFinder extends FixedSetExcludedReplicasFinder {
10 | private final AtomicInteger discoveriesUntilFailure;
11 |
12 | public FailingExcludedReplicasFinder(Collection exclusions, int failAfter) {
13 | super(exclusions);
14 | this.discoveriesUntilFailure = new AtomicInteger(failAfter);
15 | }
16 |
17 | @Override
18 | public Set listExcludedInstances(ExclusionTag tag) {
19 | final int left = this.discoveriesUntilFailure.decrementAndGet();
20 | if (left < 0) {
21 | throw new RuntimeException("Programmed exception set number of executions");
22 | }
23 | return super.listExcludedInstances(tag);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/discovery/tags/FixedSetExcludedReplicasFinder.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.discovery.tags;
2 |
3 | import technology.dice.dicefairlink.discovery.tags.ExclusionTag;
4 | import technology.dice.dicefairlink.discovery.tags.TagFilter;
5 |
6 | import java.util.Collection;
7 | import java.util.Set;
8 | import java.util.stream.Collectors;
9 |
10 | public class FixedSetExcludedReplicasFinder implements TagFilter {
11 | private Collection exclusions;
12 |
13 | public FixedSetExcludedReplicasFinder(Collection exclusions) {
14 | this.exclusions = exclusions;
15 | }
16 |
17 | public void updateExclusions(Collection replicas) {
18 | this.exclusions = replicas;
19 | }
20 |
21 | @Override
22 | public Set listExcludedInstances(ExclusionTag tag) {
23 | return this.exclusions.stream().collect(Collectors.toSet());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/driver/TestDriver.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.driver;
2 |
3 | import java.sql.Connection;
4 | import java.sql.Driver;
5 | import java.sql.DriverPropertyInfo;
6 | import java.sql.SQLException;
7 | import java.sql.SQLFeatureNotSupportedException;
8 | import java.util.Properties;
9 | import java.util.logging.Logger;
10 |
11 | public class TestDriver implements Driver {
12 | private static final Logger LOGGER = Logger.getLogger(TestDriver.class.getName());
13 |
14 | @Override
15 | public Connection connect(String url, Properties info) throws SQLException {
16 | return null;
17 | }
18 |
19 | @Override
20 | public boolean acceptsURL(String url) throws SQLException {
21 | return url.contains("fairlinktestdriver");
22 | }
23 |
24 | @Override
25 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
26 | return new DriverPropertyInfo[0];
27 | }
28 |
29 | @Override
30 | public int getMajorVersion() {
31 | return 0;
32 | }
33 |
34 | @Override
35 | public int getMinorVersion() {
36 | return 0;
37 | }
38 |
39 | @Override
40 | public boolean jdbcCompliant() {
41 | return false;
42 | }
43 |
44 | @Override
45 | public Logger getParentLogger() throws SQLFeatureNotSupportedException {
46 | return LOGGER.getParent();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/technology/dice/dicefairlink/support/iterators/TestCyclicIterator.java:
--------------------------------------------------------------------------------
1 | package technology.dice.dicefairlink.support.iterators;
2 |
3 | import technology.dice.dicefairlink.iterators.CyclicIterator;
4 |
5 | import java.util.Collection;
6 |
7 | public class TestCyclicIterator extends CyclicIterator {
8 | private final Collection extends T> collection;
9 |
10 | protected TestCyclicIterator(Collection extends T> collection) {
11 | super(collection);
12 | this.collection = collection;
13 | }
14 |
15 | public static CyclicIterator of(Collection extends T> collection) {
16 | return new TestCyclicIterator<>(collection);
17 | }
18 |
19 | public Collection extends T> getElements() {
20 | return this.collection;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/awsapi/allAvailableDbInstances.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1
6 |
7 | error
8 | slowquery
9 |
10 |
11 |
12 |
13 | param-group
14 | in-sync
15 |
16 |
17 | eu-west-1a
18 |
19 |
20 | keyId
21 |
22 |
23 | monitor-id
24 |
25 | 731
26 | 5.6.10a
27 | username
28 | 2019-07-04T13:18:00.906Z
29 | db.r5.large
30 | false
31 |
32 | 5
33 | available
34 | 30
35 | 1
36 |
37 |
38 | default:aurora-5-6
39 | in-sync
40 |
41 |
42 | my.cluster
43 | rds-ca-2015
44 | 0
45 | db-id-2
46 | 22:25-22:55
47 | false
48 | my-db-cluster-agd-1
49 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-1
50 |
51 |
52 | hostedZoneId
53 | address1
54 | 3306
55 |
56 | aurora
57 | true
58 | false
59 | true
60 | false
61 | arn:aws:iam::123:role/rds-monitoring-role
62 |
63 | false
64 |
65 | vpc-id-2
66 |
67 |
68 | subnet-id
69 | Active
70 |
71 | eu-west-1a
72 |
73 |
74 |
75 | subnet-id-2
76 | Active
77 |
78 | eu-west-1c
79 |
80 |
81 |
82 | subnet-id-3
83 | Active
84 |
85 | eu-west-1b
86 |
87 |
88 |
89 | Complete
90 | Subnets used by Consumer-payment (prod) Aurora cluster
91 |
92 | my.cluster
93 |
94 |
95 |
96 | sg-id
97 | active
98 |
99 |
100 | general-public-license
101 |
102 | tue:13:00-tue:15:00
103 | aurora
104 | false
105 | false
106 |
107 |
108 | 1
109 |
110 | error
111 | slowquery
112 |
113 |
114 |
115 |
116 | param-group
117 | in-sync
118 |
119 |
120 | eu-west-1a
121 |
122 |
123 | keyId
124 |
125 |
126 | monitor-id
127 |
128 | 731
129 | 5.6.10a
130 | username
131 | 2019-07-04T13:18:00.906Z
132 | db.r5.large
133 | false
134 |
135 | 5
136 | available
137 | 30
138 | 1
139 |
140 |
141 | default:aurora-5-6
142 | in-sync
143 |
144 |
145 | my.cluster
146 | rds-ca-2015
147 | 0
148 | db-id-2
149 | 22:25-22:55
150 | false
151 | my-db-cluster-agd-2
152 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-2
153 |
154 |
155 | hostedZoneId
156 | address2
157 | 3306
158 |
159 | aurora
160 | true
161 | false
162 | true
163 | false
164 | arn:aws:iam::123:role/rds-monitoring-role
165 |
166 | false
167 |
168 | vpc-id-2
169 |
170 |
171 | subnet-id
172 | Active
173 |
174 | eu-west-1a
175 |
176 |
177 |
178 | subnet-id-2
179 | Active
180 |
181 | eu-west-1c
182 |
183 |
184 |
185 | subnet-id-3
186 | Active
187 |
188 | eu-west-1b
189 |
190 |
191 |
192 | Complete
193 | Subnets used by Consumer-payment (prod) Aurora cluster
194 |
195 | my.cluster
196 |
197 |
198 |
199 | sg-id
200 | active
201 |
202 |
203 | general-public-license
204 |
205 | tue:13:00-tue:15:00
206 | aurora
207 | false
208 | false
209 |
210 |
211 | 1
212 |
213 | error
214 | slowquery
215 |
216 |
217 |
218 |
219 | param-group
220 | in-sync
221 |
222 |
223 | eu-west-1b
224 |
225 |
226 | keyId
227 |
228 |
229 | piArn
230 |
231 | 731
232 | 5.6.10a
233 | username
234 | 2019-07-04T13:23:31.598Z
235 | db.r5.large
236 | false
237 |
238 | 5
239 | available
240 | 30
241 | 1
242 |
243 |
244 | default:aurora-5-6
245 | in-sync
246 |
247 |
248 | my.cluster
249 | rds-ca-2015
250 | 0
251 | dbId
252 | 22:25-22:55
253 | false
254 | my-db-cluster-agd-3
255 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-3
256 |
257 |
258 | hostedZoneId
259 | address3
260 | 3306
261 |
262 | aurora
263 | true
264 | false
265 | true
266 | false
267 | arn:aws:iam::123:role/rds-monitoring-role
268 |
269 | false
270 |
271 | vpc-id-2
272 |
273 |
274 | subnet-id
275 |
276 | Active
277 |
278 | eu-west-1a
279 |
280 |
281 |
282 | subnet-id-2
283 | Active
284 |
285 | eu-west-1c
286 |
287 |
288 |
289 | subnet-id-3
290 | Active
291 |
292 | eu-west-1b
293 |
294 |
295 |
296 | Complete
297 | Subnets used by Consumer-payment (prod) Aurora cluster
298 |
299 | my.cluster
300 |
301 |
302 |
303 | sg-id
304 | active
305 |
306 |
307 | general-public-license
308 |
309 | wed:11:00-wed:13:00
310 | aurora
311 | false
312 | false
313 |
314 |
315 |
316 |
317 | requestId
318 |
319 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/awsapi/emptyResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | a0ccfa67-61ec-445d-9304-4a17e9787522
7 |
8 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/awsapi/oneReplicaDeletingInstances.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1
6 |
7 | error
8 | slowquery
9 |
10 |
11 |
12 |
13 | param-group
14 | in-sync
15 |
16 |
17 | eu-west-1a
18 |
19 |
20 | keyId
21 |
22 |
23 | monitor-id
24 |
25 | 731
26 | 5.6.10a
27 | username
28 | 2019-07-04T13:18:00.906Z
29 | db.r5.large
30 | false
31 |
32 | 5
33 | available
34 | 30
35 | 1
36 |
37 |
38 | default:aurora-5-6
39 | in-sync
40 |
41 |
42 | my.cluster
43 | rds-ca-2015
44 | 0
45 | db-id-2
46 | 22:25-22:55
47 | false
48 | my-db-cluster-agd-1
49 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-1
50 |
51 |
52 | hostedZoneId
53 | address1
54 | 3306
55 |
56 | aurora
57 | true
58 | false
59 | true
60 | false
61 | arn:aws:iam::123:role/rds-monitoring-role
62 |
63 | false
64 |
65 | vpc-id-2
66 |
67 |
68 | subnet-id
69 | Active
70 |
71 | eu-west-1a
72 |
73 |
74 |
75 | subnet-id-2
76 | Active
77 |
78 | eu-west-1c
79 |
80 |
81 |
82 | subnet-id-3
83 | Active
84 |
85 | eu-west-1b
86 |
87 |
88 |
89 | Complete
90 | Subnets used by Consumer-payment (prod) Aurora cluster
91 |
92 | my.cluster
93 |
94 |
95 |
96 | sg-id
97 | active
98 |
99 |
100 | general-public-license
101 |
102 | tue:13:00-tue:15:00
103 | aurora
104 | false
105 | false
106 |
107 |
108 | 1
109 |
110 | error
111 | slowquery
112 |
113 |
114 |
115 |
116 | param-group
117 | in-sync
118 |
119 |
120 | eu-west-1a
121 |
122 |
123 | keyId
124 |
125 |
126 | monitor-id
127 |
128 | 731
129 | 5.6.10a
130 | username
131 | 2019-07-04T13:18:00.906Z
132 | db.r5.large
133 | false
134 |
135 | 5
136 | available
137 | 30
138 | 1
139 |
140 |
141 | default:aurora-5-6
142 | in-sync
143 |
144 |
145 | my.cluster
146 | rds-ca-2015
147 | 0
148 | db-id-2
149 | 22:25-22:55
150 | false
151 | my-db-cluster-agd-2
152 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-2
153 |
154 |
155 | hostedZoneId
156 | address2
157 | 3306
158 |
159 | aurora
160 | true
161 | false
162 | true
163 | false
164 | arn:aws:iam::123:role/rds-monitoring-role
165 |
166 | false
167 |
168 | vpc-id-2
169 |
170 |
171 | subnet-id
172 | Active
173 |
174 | eu-west-1a
175 |
176 |
177 |
178 | subnet-id-2
179 | Active
180 |
181 | eu-west-1c
182 |
183 |
184 |
185 | subnet-id-3
186 | Active
187 |
188 | eu-west-1b
189 |
190 |
191 |
192 | Complete
193 | Subnets used by Consumer-payment (prod) Aurora cluster
194 |
195 | my.cluster
196 |
197 |
198 |
199 | sg-id
200 | active
201 |
202 |
203 | general-public-license
204 |
205 | tue:13:00-tue:15:00
206 | aurora
207 | false
208 | false
209 |
210 |
211 | 1
212 |
213 | error
214 | slowquery
215 |
216 |
217 |
218 |
219 | param-group
220 | in-sync
221 |
222 |
223 | eu-west-1b
224 |
225 |
226 | keyId
227 |
228 |
229 | piArn
230 |
231 | 731
232 | 5.6.10a
233 | username
234 | 2019-07-04T13:23:31.598Z
235 | db.r5.large
236 | false
237 |
238 | 5
239 | deleting
240 | 30
241 | 1
242 |
243 |
244 | default:aurora-5-6
245 | in-sync
246 |
247 |
248 | my.cluster
249 | rds-ca-2015
250 | 0
251 | dbId
252 | 22:25-22:55
253 | false
254 | my-db-cluster-agd-3
255 | arn:aws:rds:eu-west-1:123:db:my-db-cluster-agd-3
256 |
257 |
258 | hostedZoneId
259 | address3
260 | 3306
261 |
262 | aurora
263 | true
264 | false
265 | true
266 | false
267 | arn:aws:iam::123:role/rds-monitoring-role
268 |
269 | false
270 |
271 | vpc-id-2
272 |
273 |
274 | subnet-id
275 |
276 | Active
277 |
278 | eu-west-1a
279 |
280 |
281 |
282 | subnet-id-2
283 | Active
284 |
285 | eu-west-1c
286 |
287 |
288 |
289 | subnet-id-3
290 | Active
291 |
292 | eu-west-1b
293 |
294 |
295 |
296 | Complete
297 | Subnets used by Consumer-payment (prod) Aurora cluster
298 |
299 | my.cluster
300 |
301 |
302 |
303 | sg-id
304 | active
305 |
306 |
307 | general-public-license
308 |
309 | wed:11:00-wed:13:00
310 | aurora
311 | false
312 | false
313 |
314 |
315 |
316 |
317 | requestId
318 |
319 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/awsapi/withMembers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | 1
8 |
9 | error
10 | slowquery
11 |
12 |
13 |
14 | eu-west-1c
15 | eu-west-1a
16 | eu-west-1b
17 |
18 |
19 | 5.6.10a
20 | masterUsername
21 |
22 |
23 | my-db-cluster-agd-3
24 | in-sync
25 | 1
26 | false
27 |
28 |
29 | my-db-cluster-agd-2
30 | in-sync
31 | 1
32 | false
33 |
34 |
35 | my-db-cluster-agd-1
36 | in-sync
37 | 1
38 | true
39 |
40 |
41 | stopped
42 | false
43 | 3306
44 | 30
45 | my-db-cluster-agd
46 | cluster-resouceId
47 | available
48 | 2019-08-28T17:09:22.630Z
49 | 00:56-01:26
50 | false
51 | cluster-writer-endpoint
52 | global
53 | super-turbo
54 | cluster-reader-endpoint
55 | false
56 | 2019-07-29T01:03:27.962Z
57 | 2019-07-01T18:23:35.376Z
58 | stopped
59 | true
60 |
61 | false
62 | my-db-cluster-agd
63 |
64 |
65 | sg-id
66 | active
67 |
68 |
69 | hostedZoneId
70 | mon:01:57-mon:02:27
71 | paramGroup
72 | false
73 | arn:aws:rds:eu-west-1:accountId:cluster:my-db-cluster-agd
74 |
75 |
76 |
77 |
78 | 748735e7-fe64-48d8-96c6-42fcdf60a421
79 |
80 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/awsapi/withoutMembers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 | 1
7 |
8 |
9 | eu-west-1c
10 | eu-west-1a
11 | eu-west-1b
12 |
13 |
14 | 5.7.12
15 | master
16 |
17 | stopped
18 | false
19 | 3306
20 | 1
21 | sample-cluster
22 | cluster-clusterId
23 | available
24 | 2019-08-28T17:29:21.234Z
25 | 23:19-23:49
26 | false
27 | sample-cluster.cluster-writer-endpoint
28 | provisioned
29 | aurora-mysql
30 | sample-cluster.reader-endpoint
31 | false
32 | 2019-08-28T17:29:21.234Z
33 | 2019-08-28T17:28:46.587Z
34 | stopped
35 | false
36 |
37 | false
38 | dev-consumer
39 |
40 |
41 | securityGroupId
42 | active
43 |
44 |
45 | hostedZoneId
46 | sat:22:13-sat:22:43
47 | default.aurora-mysql5.7
48 | false
49 | arn:aws:rds:eu-west-1:accountId:cluster:sample-cluster
50 |
51 |
52 |
53 |
54 | a0ccfa67-61ec-445d-9304-4a17e9787522
55 |
56 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/2replicas.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO replica_host_status VALUES ('master','MASTER_SESSION_ID');
2 | INSERT INTO replica_host_status VALUES ('replica','another-sesion');
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/3replicasPorts.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO replica_host_status
2 | VALUES ('writer', 'MASTER_SESSION_ID');
3 | INSERT INTO replica_host_status
4 | VALUES ('localhost:__PORT_SLAVE_1__', 'another-sesion1');
5 | INSERT INTO replica_host_status
6 | VALUES ('localhost:__PORT_SLAVE_2__', 'another-sesion2');
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/masterData.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO data VALUES ('abc','def');
2 | INSERT INTO data VALUES ('ghi','MASTER');
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/schema.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE replica_host_status(
2 | SERVER_ID varchar(100)NOT NULL DEFAULT '',
3 | SESSION_ID varchar(100)NOT NULL DEFAULT ''
4 | );
5 |
6 | CREATE TABLE data(
7 | a varchar(100)NOT NULL,
8 | b varchar(100)NOT NULL
9 | );
10 |
11 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/slave1Data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO data VALUES ('abc','def');
2 | INSERT INTO data VALUES ('ghi','SLAVE1');
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/mysql/slave2Data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO data VALUES ('abc','def');
2 | INSERT INTO data VALUES ('ghi','SLAVE2');
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/postgresql/2replicas.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO aurora_cluster_information VALUES ('master','MASTER_SESSION_ID');
2 | INSERT INTO aurora_cluster_information VALUES ('replica','another-sesion');
3 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/postgresql/drop_function.sql:
--------------------------------------------------------------------------------
1 | drop function aurora_replica_status();
2 |
--------------------------------------------------------------------------------
/src/test/resources/technology/dice/dicefairlink/discovery/members/sql/postgresql/schema.sql:
--------------------------------------------------------------------------------
1 |
2 | create table aurora_cluster_information (server_id text, session_id text);
3 |
4 | create or replace function aurora_replica_status() returns setof aurora_cluster_information as $$
5 | select * from aurora_cluster_information;
6 | $$ language SQL;
7 |
--------------------------------------------------------------------------------