KEYCLOAK-9536 DB Allocator Plugin
This commit is contained in:
parent
ccc8e06f9a
commit
2e7f717e50
22 changed files with 1132 additions and 31 deletions
|
@ -56,13 +56,10 @@ Stop MySQl:
|
||||||
Using built-in profiles to run database tests using docker containers
|
Using built-in profiles to run database tests using docker containers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The project provides specific profiles to run database tests using containers. The supported databases and their respective profiles are:
|
The project provides specific profiles to run database tests using containers. Below is a just a sample of implemented profiles. In order to get a full list, please invoke (`mvn help:all-profiles -pl testsuite/integration-arquillian | grep -- db- | grep -v allocator`):
|
||||||
|
|
||||||
* `db-mysql`
|
* `db-mysql`
|
||||||
* `db-postgres`
|
* `db-postgres`
|
||||||
* `db-mariadb`
|
|
||||||
* `db-mssql2017`
|
|
||||||
* `db-oracle11g`
|
|
||||||
|
|
||||||
As an example, to run tests using a MySQL docker container on Undertow auth-server:
|
As an example, to run tests using a MySQL docker container on Undertow auth-server:
|
||||||
|
|
||||||
|
@ -94,3 +91,31 @@ name or tag for the image.
|
||||||
Note that Docker containers may occupy some space even after termination, and
|
Note that Docker containers may occupy some space even after termination, and
|
||||||
especially with databases that might be easily a gigabyte. It is thus
|
especially with databases that might be easily a gigabyte. It is thus
|
||||||
advisable to run `docker system prune` occasionally to reclaim that space.
|
advisable to run `docker system prune` occasionally to reclaim that space.
|
||||||
|
|
||||||
|
|
||||||
|
Using DB Allocator Service
|
||||||
|
-------
|
||||||
|
|
||||||
|
The testsuite can use the DB Allocator Service to allocate and release desired database automatically.
|
||||||
|
Since some of the database properties (such as JDBC URL, Username or Password) need to be used when building the Auth Server,
|
||||||
|
the allocation and deallocation need to happen when building the `integration-arquillian` project (instead of `tests/base` as
|
||||||
|
it happens in other cases).
|
||||||
|
|
||||||
|
In order to use the DB Allocator Service, you must use the `jpa` profile with one of the `db-allocator-*`. Here's a full example to
|
||||||
|
run JPA with Auth Server Wildfly and MSSQL 2016:
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn -f testsuite/integration-arquillian/pom.xml \
|
||||||
|
-Pjpa,auth-server-wildfly,db-allocator-db-mssql2016 \
|
||||||
|
-Ddballocator.uri=<<db-allocator-servlet-url>> \
|
||||||
|
-Ddballocator.user=<<db-allocator-user>> \
|
||||||
|
-Dmaven.test.failure.ignore=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `-Dmaven.test.failure.ignore=true` is not strictly required but highly recommended. After running the tests,
|
||||||
|
the DB Allocator Plugin should release the allocated database.
|
||||||
|
|
||||||
|
Below is a just a sample of implemented profiles. In order to get a full list, please invoke (`mvn help:all-profiles -pl testsuite/integration-arquillian | grep -- db-allocator-db-`):
|
||||||
|
|
||||||
|
* `db-allocator-db-postgres` - dor testing with Postgres 9.6.x
|
||||||
|
* `db-allocator-db-mysql` - dor testing with MySQL 5.7
|
76
testsuite/db-allocator-plugin/pom.xml
Normal file
76
testsuite/db-allocator-plugin/pom.xml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2019 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-testsuite-pom</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>6.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>db-allocator-plugin</artifactId>
|
||||||
|
<packaging>maven-plugin</packaging>
|
||||||
|
<name>DB Allocator Plugin</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.version>3.6.0</maven.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-plugin-api</artifactId>
|
||||||
|
<version>${maven.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||||
|
<artifactId>maven-plugin-annotations</artifactId>
|
||||||
|
<version>${maven.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-core</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-plugin-plugin</artifactId>
|
||||||
|
<version>${maven.version}</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,176 @@
|
||||||
|
package org.keycloak.testsuite.dballocator;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.maven.plugin.AbstractMojo;
|
||||||
|
import org.apache.maven.plugin.MojoFailureException;
|
||||||
|
import org.apache.maven.plugin.logging.Log;
|
||||||
|
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||||
|
import org.apache.maven.plugins.annotations.Mojo;
|
||||||
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
|
import org.apache.maven.project.MavenProject;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.AllocationResult;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.DBAllocatorServiceClient;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.retry.IncrementalBackoffRetryPolicy;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocated a DB from DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Mojo(name = "allocate", defaultPhase = LifecyclePhase.PROCESS_TEST_RESOURCES)
|
||||||
|
public class AllocateDBMojo extends AbstractMojo {
|
||||||
|
|
||||||
|
|
||||||
|
private final Log logger = getLog();
|
||||||
|
|
||||||
|
@Parameter(defaultValue = "${project}", required = true, readonly = true)
|
||||||
|
protected MavenProject project;
|
||||||
|
|
||||||
|
@Parameter(defaultValue = "${reactorProjects}", readonly = true)
|
||||||
|
List<MavenProject> reactorProjects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables printing out a summary after execution.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_PRINT_SUMMARY, defaultValue = "true")
|
||||||
|
private boolean printSummary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the execution of this Mojo.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_SKIP, defaultValue = "false")
|
||||||
|
private boolean skip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of retries for reaching the DB Allocator Service
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_RETRY_TOTAL_RETRIES, defaultValue = "3")
|
||||||
|
private int totalRetries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backoff time for reaching out the DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_RETRY_BACKOFF_SECONDS, defaultValue = "10")
|
||||||
|
private int backoffTimeSeconds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI to the DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_URI)
|
||||||
|
private String dbAllocatorURI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Username used for allocating DBs.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_USER)
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback username used for allocating DBs.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_USER_FALLBACK)
|
||||||
|
private String fallbackUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of the database to be used.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_DATABASE_TYPE)
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expiration in minutes.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_EXPIRATION_MIN, defaultValue = "1440")
|
||||||
|
private int expirationInMinutes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferred DB location.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_LOCATION, defaultValue = "geo_RDU")
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property set as an output of this Mojo for JDBC Driver.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_TO_BE_SET_DRIVER, defaultValue = "keycloak.connectionsJpa.driver")
|
||||||
|
private String propertyDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property set as an output of this Mojo for Database Schema.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_TO_BE_SET_DATABASE, defaultValue = "keycloak.connectionsJpa.database")
|
||||||
|
private String propertyDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property set as an output of this Mojo for DB Username.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_TO_BE_SET_USER, defaultValue = "keycloak.connectionsJpa.user")
|
||||||
|
private String propertyUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property set as an output of this Mojo for DB Password.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_TO_BE_SET_PASSWORD, defaultValue = "keycloak.connectionsJpa.password")
|
||||||
|
private String propertyPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property set as an output of this Mojo for JDBC Connection URI.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_TO_BE_SET_JDBC_URL, defaultValue = "keycloak.connectionsJpa.url")
|
||||||
|
private String propertyURL;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws MojoFailureException {
|
||||||
|
if (skip) {
|
||||||
|
logger.info("Skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
IncrementalBackoffRetryPolicy retryPolicy = new IncrementalBackoffRetryPolicy(totalRetries, backoffTimeSeconds, TimeUnit.SECONDS);
|
||||||
|
DBAllocatorServiceClient client = new DBAllocatorServiceClient(dbAllocatorURI, retryPolicy);
|
||||||
|
|
||||||
|
setFallbackUserIfNecessary();
|
||||||
|
AllocationResult allocate = client.allocate(user, type, expirationInMinutes, TimeUnit.MINUTES, location);
|
||||||
|
|
||||||
|
reactorProjects.forEach((project) -> setPropertiesToProject(project, allocate));
|
||||||
|
|
||||||
|
if (printSummary) {
|
||||||
|
logger.info("Allocated database:");
|
||||||
|
logger.info("-- UUID: " + allocate.getUUID());
|
||||||
|
logger.info("-- Driver: " + allocate.getDriver());
|
||||||
|
logger.info("-- Database: " + allocate.getDatabase());
|
||||||
|
logger.info("-- User: " + allocate.getUser());
|
||||||
|
logger.info("-- Password: " + allocate.getPassword());
|
||||||
|
logger.info("-- URL: " + allocate.getURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (DBAllocatorException e) {
|
||||||
|
String error = e.getMessage();
|
||||||
|
if (e.getErrorResponse() != null) {
|
||||||
|
error = String.format("[%s](%s)", e.getErrorResponse().getStatus(), e.getErrorResponse().readEntity(String.class));
|
||||||
|
}
|
||||||
|
throw new MojoFailureException("An error occurred while communicating with DBAllocator (" + error + ")", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFallbackUserIfNecessary() {
|
||||||
|
if (StringUtils.isBlank(user)) {
|
||||||
|
if (StringUtils.isBlank(fallbackUser)) {
|
||||||
|
throw new IllegalArgumentException("Both " + Constants.PROPERTY_DB_ALLOCATOR_USER + " and " + Constants.PROPERTY_DB_ALLOCATOR_USER_FALLBACK + " are empty");
|
||||||
|
}
|
||||||
|
user = fallbackUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPropertiesToProject(MavenProject project, AllocationResult allocate) {
|
||||||
|
project.getProperties().setProperty(propertyDriver, allocate.getDriver());
|
||||||
|
project.getProperties().setProperty(propertyDatabase, allocate.getDatabase());
|
||||||
|
project.getProperties().setProperty(propertyUser, allocate.getUser());
|
||||||
|
project.getProperties().setProperty(propertyPassword, allocate.getPassword());
|
||||||
|
project.getProperties().setProperty(propertyURL, allocate.getURL());
|
||||||
|
project.getProperties().setProperty(Constants.PROPERTY_ALLOCATED_DB, allocate.getUUID());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.keycloak.testsuite.dballocator;
|
||||||
|
|
||||||
|
public interface Constants {
|
||||||
|
String PROPERTY_ALLOCATED_DB = "dballocator.allocated.uuid";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_URI = "dballocator.uri";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_USER = "dballocator.user";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_USER_FALLBACK = "user.name";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_DATABASE_TYPE = "dballocator.type";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_EXPIRATION_MIN = "dballocator.expirationMin";
|
||||||
|
String PROPERTY_DB_ALLOCATOR_LOCATION = "dballocator.location";
|
||||||
|
String PROPERTY_TO_BE_SET_DRIVER = "dballocator.properties.driver";
|
||||||
|
String PROPERTY_TO_BE_SET_DATABASE = "dballocator.properties.database";
|
||||||
|
String PROPERTY_TO_BE_SET_USER = "dballocator.properties.user";
|
||||||
|
String PROPERTY_TO_BE_SET_PASSWORD = "dballocator.properties.password";
|
||||||
|
String PROPERTY_TO_BE_SET_JDBC_URL = "dballocator.properties.url";
|
||||||
|
String PROPERTY_PRINT_SUMMARY = "dballocator.summary";
|
||||||
|
String PROPERTY_SKIP = "dballocator.skip";
|
||||||
|
String PROPERTY_RETRY_TOTAL_RETRIES = "dballocator.retry.totalRetries";
|
||||||
|
String PROPERTY_RETRY_BACKOFF_SECONDS = "dballocator.retry.backoffSeconds";
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.keycloak.testsuite.dballocator;
|
||||||
|
|
||||||
|
import org.apache.maven.plugin.AbstractMojo;
|
||||||
|
import org.apache.maven.plugin.MojoFailureException;
|
||||||
|
import org.apache.maven.plugin.logging.Log;
|
||||||
|
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||||
|
import org.apache.maven.plugins.annotations.Mojo;
|
||||||
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
|
import org.apache.maven.project.MavenProject;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.AllocationResult;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.DBAllocatorServiceClient;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.retry.IncrementalBackoffRetryPolicy;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.ReleaseResult;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases a DB from DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Mojo(name = "release", defaultPhase = LifecyclePhase.TEST)
|
||||||
|
public class ReleaseDBMojo extends AbstractMojo {
|
||||||
|
|
||||||
|
private final Log logger = getLog();
|
||||||
|
|
||||||
|
@Parameter(defaultValue = "${project}", required = true)
|
||||||
|
protected MavenProject project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables printing out a summary after execution.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_PRINT_SUMMARY, defaultValue = "true")
|
||||||
|
private boolean printSummary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the execution of this Mojo.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_SKIP, defaultValue = "false")
|
||||||
|
private boolean skip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of retries for reaching the DB Allocator Service
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_RETRY_TOTAL_RETRIES, defaultValue = "3")
|
||||||
|
private int totalRetries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backoff time for reaching out the DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_RETRY_BACKOFF_SECONDS, defaultValue = "10")
|
||||||
|
private int backoffTimeSeconds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI to the DB Allocator Service.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_DB_ALLOCATOR_URI)
|
||||||
|
private String dbAllocatorURI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID for releasing the allocated DB.
|
||||||
|
*/
|
||||||
|
@Parameter(property = Constants.PROPERTY_ALLOCATED_DB)
|
||||||
|
private String allocatedUUID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws MojoFailureException {
|
||||||
|
if (skip) {
|
||||||
|
logger.info("Skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
IncrementalBackoffRetryPolicy retryPolicy = new IncrementalBackoffRetryPolicy(totalRetries, backoffTimeSeconds, TimeUnit.SECONDS);
|
||||||
|
DBAllocatorServiceClient client = new DBAllocatorServiceClient(dbAllocatorURI, retryPolicy);
|
||||||
|
|
||||||
|
ReleaseResult release = client.release(AllocationResult.forRelease(allocatedUUID));
|
||||||
|
|
||||||
|
if (printSummary) {
|
||||||
|
logger.info("Released database:");
|
||||||
|
logger.info("-- UUID: " + release.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (DBAllocatorException e) {
|
||||||
|
String error = e.getMessage();
|
||||||
|
if (e.getErrorResponse() != null) {
|
||||||
|
error = String.format("[%s](%s)", e.getErrorResponse().getStatus(), e.getErrorResponse().readEntity(String.class));
|
||||||
|
}
|
||||||
|
throw new MojoFailureException("An error occurred while communicating with DBAllocator (" + error + ")", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorUnavailableException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface BackoffRetryPolicy {
|
||||||
|
Response retryTillHttpOk(Callable<Response> callableSupplier) throws DBAllocatorUnavailableException;
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client;
|
||||||
|
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
|
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.AllocationResult;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.ReleaseResult;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.retry.IncrementalBackoffRetryPolicy;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Invocation;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class DBAllocatorServiceClient {
|
||||||
|
|
||||||
|
private static final int TIMEOUT = 10_000;
|
||||||
|
|
||||||
|
private final Client restClient;
|
||||||
|
private final URI allocatorServletURI;
|
||||||
|
private final BackoffRetryPolicy retryPolicy;
|
||||||
|
|
||||||
|
public DBAllocatorServiceClient(String allocatorServletURI, BackoffRetryPolicy retryPolicy) {
|
||||||
|
Objects.requireNonNull(allocatorServletURI, "DB Allocator URI must not be null");
|
||||||
|
|
||||||
|
this.allocatorServletURI = URI.create(allocatorServletURI);
|
||||||
|
this.retryPolicy = retryPolicy != null ? retryPolicy : new IncrementalBackoffRetryPolicy();
|
||||||
|
this.restClient = new ResteasyClientBuilder().httpEngine(createEngine()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ApacheHttpClient43Engine createEngine() {
|
||||||
|
RequestConfig reqConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(TIMEOUT)
|
||||||
|
.setSocketTimeout(TIMEOUT)
|
||||||
|
.setConnectionRequestTimeout(TIMEOUT)
|
||||||
|
.build();
|
||||||
|
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||||
|
.setDefaultRequestConfig(reqConfig)
|
||||||
|
.setMaxConnTotal(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
|
||||||
|
engine.setFollowRedirects(true);
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllocationResult allocate(String user, String type, int expiration, TimeUnit expirationTimeUnit, String location) throws DBAllocatorException {
|
||||||
|
Objects.requireNonNull(user, "User can not be null");
|
||||||
|
Objects.requireNonNull(type, "DB Type must not be null");
|
||||||
|
|
||||||
|
try {
|
||||||
|
String typeWithLocation = location != null ? type + "&&" + location : type;
|
||||||
|
Invocation.Builder target = restClient
|
||||||
|
.target(allocatorServletURI)
|
||||||
|
.queryParam("operation", "allocate")
|
||||||
|
.queryParam("requestee", user)
|
||||||
|
.queryParam("expression", typeWithLocation)
|
||||||
|
.queryParam("expiry", expirationTimeUnit.toMinutes(expiration))
|
||||||
|
.request();
|
||||||
|
|
||||||
|
Response response = retryPolicy.retryTillHttpOk(() -> target.get());
|
||||||
|
Properties properties = new Properties();
|
||||||
|
String content = response.readEntity(String.class);
|
||||||
|
|
||||||
|
if (content != null) {
|
||||||
|
try(InputStream is = new ByteArrayInputStream(content.getBytes())) {
|
||||||
|
properties.load(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AllocationResult.successful(properties);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DBAllocatorException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReleaseResult release(AllocationResult allocationResult) throws DBAllocatorException {
|
||||||
|
Objects.requireNonNull(allocationResult, "Previous allocation result must not be null");
|
||||||
|
Objects.requireNonNull(allocationResult.getUUID(), "UUID must not be null");
|
||||||
|
|
||||||
|
Invocation.Builder target = restClient
|
||||||
|
.target(allocatorServletURI)
|
||||||
|
.queryParam("operation", "dealloc")
|
||||||
|
.queryParam("uuid", allocationResult.getUUID())
|
||||||
|
.request();
|
||||||
|
|
||||||
|
retryPolicy.retryTillHttpOk(() -> target.get());
|
||||||
|
|
||||||
|
return ReleaseResult.successful(allocationResult.getUUID());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.data;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class AllocationResult {
|
||||||
|
|
||||||
|
private final String uuid;
|
||||||
|
private final String driver;
|
||||||
|
private final String database;
|
||||||
|
private final String user;
|
||||||
|
private final String password;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
private AllocationResult(String uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.driver = null;
|
||||||
|
this.database = null;
|
||||||
|
this.user = null;
|
||||||
|
this.password = null;
|
||||||
|
this.url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllocationResult(String uuid, String driver, String database, String user, String password, String url) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.driver = driver;
|
||||||
|
this.database = database;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AllocationResult forRelease(String uuid) {
|
||||||
|
return new AllocationResult(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AllocationResult successful(Properties properties) {
|
||||||
|
return new AllocationResult(
|
||||||
|
properties.getProperty("uuid"),
|
||||||
|
properties.getProperty("db.jdbc_class"),
|
||||||
|
properties.getProperty("db.name"),
|
||||||
|
properties.getProperty("db.username"),
|
||||||
|
properties.getProperty("db.password"),
|
||||||
|
properties.getProperty("db.jdbc_url"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDriver() {
|
||||||
|
return driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabase() {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getURL() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AllocationResult{" +
|
||||||
|
"uuid='" + uuid + '\'' +
|
||||||
|
", driver='" + driver + '\'' +
|
||||||
|
", database='" + database + '\'' +
|
||||||
|
", user='" + user + '\'' +
|
||||||
|
", password='" + password + '\'' +
|
||||||
|
", url='" + url + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.data;
|
||||||
|
|
||||||
|
public class ReleaseResult {
|
||||||
|
|
||||||
|
private final String uuid;
|
||||||
|
|
||||||
|
private ReleaseResult(String uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReleaseResult successful(String uuid) {
|
||||||
|
return new ReleaseResult(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReleaseResult{" +
|
||||||
|
"uuid='" + uuid + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.exceptions;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
public class DBAllocatorException extends Exception {
|
||||||
|
|
||||||
|
private Response errorResponse;
|
||||||
|
|
||||||
|
public DBAllocatorException(Response errorResponse) {
|
||||||
|
this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DBAllocatorException(Response errorResponse, Throwable throwable) {
|
||||||
|
super(throwable);
|
||||||
|
this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DBAllocatorException(Throwable throwable) {
|
||||||
|
super(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response getErrorResponse() {
|
||||||
|
return errorResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
public class DBAllocatorUnavailableException extends DBAllocatorException {
|
||||||
|
|
||||||
|
public DBAllocatorUnavailableException(Response errorResponse) {
|
||||||
|
super(errorResponse);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.retry;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.dballocator.client.BackoffRetryPolicy;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorUnavailableException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
public class IncrementalBackoffRetryPolicy implements BackoffRetryPolicy {
|
||||||
|
|
||||||
|
public static final int DEFAULT_TOTAL_RETRIES = 3;
|
||||||
|
public static final int DEFAULT_BACKOFF_TIME = 10;
|
||||||
|
public static final TimeUnit DEFAULT_BACKOFF_TIME_UNIT = TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
private final int totalRetries;
|
||||||
|
private final int backoffTime;
|
||||||
|
private final TimeUnit backoffTimeUnit;
|
||||||
|
|
||||||
|
public IncrementalBackoffRetryPolicy() {
|
||||||
|
this(DEFAULT_TOTAL_RETRIES, DEFAULT_BACKOFF_TIME, DEFAULT_BACKOFF_TIME_UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncrementalBackoffRetryPolicy(int totalRetries, int backoffTime, TimeUnit backoffTimeUnit) {
|
||||||
|
this.backoffTime = backoffTime;
|
||||||
|
this.backoffTimeUnit = backoffTimeUnit;
|
||||||
|
this.totalRetries = totalRetries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response retryTillHttpOk(Callable<Response> callableSupplier) throws DBAllocatorUnavailableException {
|
||||||
|
return retryTillHttpOk(callableSupplier, totalRetries, backoffTime, backoffTimeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response retryTillHttpOk(Callable<Response> callableSupplier, int totalRetries, int backoffTime, TimeUnit backoffTimeUnit) throws DBAllocatorUnavailableException {
|
||||||
|
int retryCount = 0;
|
||||||
|
Response response = null;
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
response = callableSupplier.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
response = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null && response.getStatus() == 200) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++retryCount > totalRetries) {
|
||||||
|
throw new DBAllocatorUnavailableException(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
LockSupport.parkNanos(backoffTimeUnit.toNanos(backoffTime * retryCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.data.AllocationResult;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorUnavailableException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.mock.MockResponse;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
public class DBAllocatorServiceClientTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulAllocation() throws Exception {
|
||||||
|
//given
|
||||||
|
String mockURI = "http://localhost:8080/test";
|
||||||
|
|
||||||
|
String testProperties = null;
|
||||||
|
try(InputStream is = DBAllocatorServiceClientTest.class.getResourceAsStream("/db-allocator-response.properties")) {
|
||||||
|
testProperties = IOUtils.toString(is, Charset.defaultCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
Response successfulResponse = new MockResponse(200, testProperties);
|
||||||
|
BackoffRetryPolicy retryPolicyMock = callableSupplier -> successfulResponse;
|
||||||
|
|
||||||
|
DBAllocatorServiceClient client = new DBAllocatorServiceClient(mockURI, retryPolicyMock);
|
||||||
|
|
||||||
|
//when
|
||||||
|
AllocationResult allocationResult = client.allocate("user", "mariadb_galera_101", 1440, TimeUnit.SECONDS, "geo_RDU");
|
||||||
|
|
||||||
|
//then
|
||||||
|
Assert.assertEquals("d328bb0e-3dcc-42da-8ce1-83738a8dfede", allocationResult.getUUID());
|
||||||
|
Assert.assertEquals("org.mariadb.jdbc.Driver", allocationResult.getDriver());
|
||||||
|
Assert.assertEquals("dbname", allocationResult.getDatabase());
|
||||||
|
Assert.assertEquals("username", allocationResult.getUser());
|
||||||
|
Assert.assertEquals("password", allocationResult.getPassword());
|
||||||
|
Assert.assertEquals("jdbc:mariadb://mariadb-101-galera.keycloak.org:3306", allocationResult.getURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailureAllocation() throws Exception {
|
||||||
|
//given
|
||||||
|
String mockURI = "http://localhost:8080/test";
|
||||||
|
|
||||||
|
Response serverErrorResponse = new MockResponse(500, null);
|
||||||
|
BackoffRetryPolicy retryPolicyMock = callableSupplier -> {
|
||||||
|
throw new DBAllocatorUnavailableException(serverErrorResponse);
|
||||||
|
};
|
||||||
|
|
||||||
|
DBAllocatorServiceClient client = new DBAllocatorServiceClient(mockURI, retryPolicyMock);
|
||||||
|
|
||||||
|
//when
|
||||||
|
try {
|
||||||
|
client.allocate("user", "mariadb_galera_101", 1440, TimeUnit.SECONDS, "geo_RDU");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (DBAllocatorException e) {
|
||||||
|
Assert.assertEquals(500, e.getErrorResponse().getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.mock;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.EntityTag;
|
||||||
|
import javax.ws.rs.core.GenericType;
|
||||||
|
import javax.ws.rs.core.Link;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.NewCookie;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MockResponse extends Response {
|
||||||
|
|
||||||
|
private final int status;
|
||||||
|
private final String entity;
|
||||||
|
|
||||||
|
public MockResponse(int status, String entity) {
|
||||||
|
this.status = status;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusType getStatusInfo() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T readEntity(Class<T> aClass) {
|
||||||
|
if (aClass.isAssignableFrom(String.class)) {
|
||||||
|
return (T) entity;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T readEntity(GenericType<T> genericType) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T readEntity(Class<T> aClass, Annotation[] annotations) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T readEntity(GenericType<T> genericType, Annotation[] annotations) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasEntity() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean bufferEntity() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaType getMediaType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale getLanguage() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLength() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAllowedMethods() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, NewCookie> getCookies() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityTag getEntityTag() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDate() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getLastModified() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getLocation() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Link> getLinks() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasLink(String s) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Link getLink(String s) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Link.Builder getLinkBuilder(String s) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultivaluedMap<String, Object> getMetadata() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultivaluedMap<String, String> getStringHeaders() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeaderString(String s) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.keycloak.testsuite.dballocator.client.retry;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.exceptions.DBAllocatorUnavailableException;
|
||||||
|
import org.keycloak.testsuite.dballocator.client.retry.IncrementalBackoffRetryPolicy;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
|
|
||||||
|
public class IncrementalBackoffRetryPolicyTest {
|
||||||
|
|
||||||
|
static class BackoffCounter implements Callable<Response> {
|
||||||
|
|
||||||
|
LongAdder adder = new LongAdder();
|
||||||
|
Response responseToReport;
|
||||||
|
|
||||||
|
public BackoffCounter(Response responseToReport) {
|
||||||
|
this.responseToReport = responseToReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response call() throws Exception {
|
||||||
|
adder.add(1);
|
||||||
|
return responseToReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCounter() {
|
||||||
|
return adder.longValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBackoffLoop() {
|
||||||
|
//given
|
||||||
|
long expectedNumberOfRetries = 2;
|
||||||
|
long expectedNumberOfInvocations = expectedNumberOfRetries + 1;
|
||||||
|
BackoffCounter counter = new BackoffCounter(Response.serverError().build());
|
||||||
|
IncrementalBackoffRetryPolicy backoffRetryPolicy = new IncrementalBackoffRetryPolicy((int) expectedNumberOfRetries, 0, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
//when
|
||||||
|
try {
|
||||||
|
backoffRetryPolicy.retryTillHttpOk(counter);
|
||||||
|
Assert.fail();
|
||||||
|
} catch (DBAllocatorUnavailableException e) {
|
||||||
|
//then
|
||||||
|
Assert.assertEquals(expectedNumberOfInvocations, counter.getCounter().longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoringBackoffWhenGettingSuccessfulResponse() throws Exception {
|
||||||
|
//given
|
||||||
|
BackoffCounter counter = new BackoffCounter(Response.ok().build());
|
||||||
|
IncrementalBackoffRetryPolicy backoffRetryPolicy = new IncrementalBackoffRetryPolicy(3, 0, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
//when
|
||||||
|
Response response = backoffRetryPolicy.retryTillHttpOk(counter);
|
||||||
|
|
||||||
|
//then
|
||||||
|
Assert.assertEquals(1, counter.getCounter().longValue());
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#Generated by DBAllocator
|
||||||
|
#Mon Mar 18 12:49:24 UTC 2019
|
||||||
|
db.password=password
|
||||||
|
hibernate.connection.password=password
|
||||||
|
hibernate41.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||||
|
broken=false
|
||||||
|
db.username=username
|
||||||
|
server_geo=RDU
|
||||||
|
dballoc.db_type=clustered
|
||||||
|
db.name=dbname
|
||||||
|
db.jdbc_url=jdbc\:mariadb\://mariadb-101-galera.keycloak.org\:3306
|
||||||
|
datasource.class.xa=org.mariadb.jdbc.MySQLDataSource
|
||||||
|
server_uid=RDU_mariadb_galera_101
|
||||||
|
hibernate33.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||||
|
hibernate.connection.username=username
|
||||||
|
server_labels=mariadb_galera_101
|
||||||
|
db.jdbc_class=org.mariadb.jdbc.Driver
|
||||||
|
db.schema=dballo01
|
||||||
|
hibernate.connection.driver_class=org.mariadb.jdbc.Driver
|
||||||
|
uuid=d328bb0e-3dcc-42da-8ce1-83738a8dfede
|
||||||
|
db.primary_label=mariadb_galera_101
|
||||||
|
server_label_primary=mariadb_galera_101
|
||||||
|
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||||
|
hibernate.connection.url=jdbc\:mariadb\://mariadb-101-galera-01.keycloak.org\:3306
|
||||||
|
hibernate.connection.schema=dballo01
|
5
testsuite/db-allocator-plugin/src/test/resources/log4j.properties
Executable file
5
testsuite/db-allocator-plugin/src/test/resources/log4j.properties
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
log4j.rootLogger=info, stdout
|
||||||
|
|
||||||
|
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||||
|
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||||
|
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
|
|
@ -717,6 +717,7 @@ The exact steps to configure Docker depend on the operating system.
|
||||||
By default, the test will run against Undertow based embedded Keycloak Server, thus no distribution build is required beforehand.
|
By default, the test will run against Undertow based embedded Keycloak Server, thus no distribution build is required beforehand.
|
||||||
The exact command line arguments depend on the operating system.
|
The exact command line arguments depend on the operating system.
|
||||||
|
|
||||||
|
|
||||||
### General guidelines
|
### General guidelines
|
||||||
|
|
||||||
If docker daemon doesn't run locally, or if you're not running on Linux, you may need
|
If docker daemon doesn't run locally, or if you're not running on Linux, you may need
|
||||||
|
|
|
@ -94,6 +94,8 @@
|
||||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
||||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||||
<keycloak.connectionsJpa.schema>DEFAULT</keycloak.connectionsJpa.schema>
|
<keycloak.connectionsJpa.schema>DEFAULT</keycloak.connectionsJpa.schema>
|
||||||
|
|
||||||
|
<dballocator.skip>true</dballocator.skip>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -244,6 +246,18 @@
|
||||||
<build>
|
<build>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>db-allocator-plugin</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<propertyDatabase>keycloak.connectionsJpa.database</propertyDatabase>
|
||||||
|
<propertyDriver>keycloak.connectionsJpa.driver</propertyDriver>
|
||||||
|
<propertyURL>keycloak.connectionsJpa.url</propertyURL>
|
||||||
|
<propertyUser>keycloak.connectionsJpa.user</propertyUser>
|
||||||
|
<propertyPassword>keycloak.connectionsJpa.password</propertyPassword>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.19.1</version>
|
<version>2.19.1</version>
|
||||||
|
@ -272,6 +286,27 @@
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>db-allocator-plugin</artifactId>
|
||||||
|
<inherited>false</inherited>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>allocate-db</id>
|
||||||
|
<goals>
|
||||||
|
<goal>allocate</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>release-db</id>
|
||||||
|
<goals>
|
||||||
|
<goal>release</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@ -379,6 +414,16 @@
|
||||||
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-allocator-db-mysql</id>
|
||||||
|
<properties>
|
||||||
|
<jdbc.mvn.groupId>mysql</jdbc.mvn.groupId>
|
||||||
|
<jdbc.mvn.artifactId>mysql-connector-java</jdbc.mvn.artifactId>
|
||||||
|
<jdbc.mvn.version>${mysql.version}</jdbc.mvn.version>
|
||||||
|
<dballocator.type>mysql57</dballocator.type>
|
||||||
|
<dballocator.skip>false</dballocator.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>db-postgres</id>
|
<id>db-postgres</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -396,6 +441,16 @@
|
||||||
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-allocator-db-postgres</id>
|
||||||
|
<properties>
|
||||||
|
<jdbc.mvn.groupId>org.postgresql</jdbc.mvn.groupId>
|
||||||
|
<jdbc.mvn.artifactId>postgresql</jdbc.mvn.artifactId>
|
||||||
|
<jdbc.mvn.version>${postgresql.version}</jdbc.mvn.version>
|
||||||
|
<dballocator.type>postgresql96</dballocator.type>
|
||||||
|
<dballocator.skip>false</dballocator.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>db-mariadb</id>
|
<id>db-mariadb</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -413,6 +468,16 @@
|
||||||
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
<docker.database.wait-for-log-regex>(?si)Ready for start up.*ready [^\n]{0,30}connections</docker.database.wait-for-log-regex>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-allocator-db-mariadb</id>
|
||||||
|
<properties>
|
||||||
|
<jdbc.mvn.groupId>org.mariadb.jdbc</jdbc.mvn.groupId>
|
||||||
|
<jdbc.mvn.artifactId>mariadb-java-client</jdbc.mvn.artifactId>
|
||||||
|
<jdbc.mvn.version>${mariadb.version}</jdbc.mvn.version>
|
||||||
|
<dballocator.type>mariadb_galera_101</dballocator.type>
|
||||||
|
<dballocator.skip>false</dballocator.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>db-mssql2017</id>
|
<id>db-mssql2017</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -431,6 +496,16 @@
|
||||||
<jdbc.mvn.version>${mssql.version}</jdbc.mvn.version>
|
<jdbc.mvn.version>${mssql.version}</jdbc.mvn.version>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-allocator-db-mssql2016</id>
|
||||||
|
<properties>
|
||||||
|
<jdbc.mvn.groupId>com.microsoft.sqlserver</jdbc.mvn.groupId>
|
||||||
|
<jdbc.mvn.artifactId>mssql-jdbc</jdbc.mvn.artifactId>
|
||||||
|
<jdbc.mvn.version>${mssql.version}</jdbc.mvn.version>
|
||||||
|
<dballocator.type>mssql2016</dballocator.type>
|
||||||
|
<dballocator.skip>false</dballocator.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>db-oracle11g</id>
|
<id>db-oracle11g</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -450,6 +525,16 @@
|
||||||
<jdbc.mvn.version>12.1.0</jdbc.mvn.version>
|
<jdbc.mvn.version>12.1.0</jdbc.mvn.version>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-allocator-db-oracle11g</id>
|
||||||
|
<properties>
|
||||||
|
<jdbc.mvn.groupId>com.oracle</jdbc.mvn.groupId>
|
||||||
|
<jdbc.mvn.artifactId>ojdbc7</jdbc.mvn.artifactId>
|
||||||
|
<jdbc.mvn.version>12.1.0</jdbc.mvn.version>
|
||||||
|
<dballocator.type>oracle11gR1</dballocator.type>
|
||||||
|
<dballocator.skip>false</dballocator.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -103,6 +103,9 @@
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="configure-server-jpa" depends="update-jpa-schema">
|
<target name="configure-server-jpa" depends="update-jpa-schema">
|
||||||
|
<!-- I'm intentionally leaving this here. This shows up environment variables that are used during the build.
|
||||||
|
If anything goes wrong, this is the first place to look at -->
|
||||||
|
<echoproperties/>
|
||||||
<copy todir="${cli.tmp.dir}">
|
<copy todir="${cli.tmp.dir}">
|
||||||
<resources>
|
<resources>
|
||||||
<file file="${common.resources}/jboss-cli/configure-server-jpa.cli"/>
|
<file file="${common.resources}/jboss-cli/configure-server-jpa.cli"/>
|
||||||
|
|
|
@ -63,6 +63,10 @@
|
||||||
|
|
||||||
<!-- default ant scenario -->
|
<!-- default ant scenario -->
|
||||||
<ant.scenario>scenario-standalone</ant.scenario>
|
<ant.scenario>scenario-standalone</ant.scenario>
|
||||||
|
|
||||||
|
<session.cache.owners>1</session.cache.owners>
|
||||||
|
<offline.session.cache.owners>1</offline.session.cache.owners>
|
||||||
|
<login.failure.cache.owners>1</login.failure.cache.owners>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
@ -216,16 +220,16 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<target>
|
<target>
|
||||||
<ant antfile="${common.resources}/ant/configure.xml" target="${ant.scenario}-generate">
|
<ant antfile="${common.resources}/ant/configure.xml" target="${ant.scenario}-generate" >
|
||||||
<!-- These properties become equivalent to properties defined on the command line. -->
|
<!-- In most of the cases, Ant Plugin picks up properties automatically.
|
||||||
<!-- Without specifying those the default values would be used regardless what is -->
|
However, in some rare cases, it will not detect if a property has been overriden
|
||||||
<!-- defined via -Dproperty=value when executing maven command -->
|
in the command line using "-D" switch (see why here: https://technotes.khitrenovich.com/properties-resolution-maven-implications-antrun-plugin/
|
||||||
<property name="auth.server.worker.io-threads">${auth.server.worker.io-threads}</property>
|
There's also another case, when we have a dynamic property (like "keycloak.connectionsJpa.url")
|
||||||
<property name="auth.server.worker.task-max-threads">${auth.server.worker.task-max-threads}</property>
|
that can change in the runtime. In such cases, we CAN NOT put is as a property (or
|
||||||
<!-- Following properties are cluster specific -->
|
Ant will see outdated values, not the dynamic ones). -->
|
||||||
<property name="session.cache.owners">${session.cache.owners}</property>
|
<property name="session.cache.owners" value="${session.cache.owners}" />
|
||||||
<property name="offline.session.cache.owners">${offline.session.cache.owners}</property>
|
<property name="offline.session.cache.owners" value="${offline.session.cache.owners}" />
|
||||||
<property name="login.failure.cache.owners">${login.failure.cache.owners}</property>
|
<property name="login.failure.cache.owners" value="${login.failure.cache.owners}" />
|
||||||
</ant>
|
</ant>
|
||||||
</target>
|
</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -415,18 +419,7 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<target>
|
<target>
|
||||||
<ant antfile="${common.resources}/ant/configure.xml" target="configure-server-jpa">
|
<ant antfile="${common.resources}/ant/configure.xml" target="configure-server-jpa" />
|
||||||
<!-- These properties become equivalent to properties defined on the command line. -->
|
|
||||||
<!-- Without specifying those the default values would be used regardless what is -->
|
|
||||||
<!-- defined via -Dproperty=value when executing maven command -->
|
|
||||||
<property name="jdbc.driver.tmp.dir">${jdbc.driver.tmp.dir}</property>
|
|
||||||
<property name="jdbc.mvn.artifactId">${jdbc.mvn.artifactId}</property>
|
|
||||||
<property name="jdbc.mvn.version">${jdbc.mvn.version}</property>
|
|
||||||
<property name="keycloak.connectionsJpa.url">${keycloak.connectionsJpa.url}</property>
|
|
||||||
<property name="keycloak.connectionsJpa.user">${keycloak.connectionsJpa.user}</property>
|
|
||||||
<property name="keycloak.connectionsJpa.password">${keycloak.connectionsJpa.password}</property>
|
|
||||||
<property name="keycloak.connectionsJpa.schema">${keycloak.connectionsJpa.schema}</property>
|
|
||||||
</ant>
|
|
||||||
</target>
|
</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
@ -574,11 +567,6 @@
|
||||||
<id>auth-server-cluster</id>
|
<id>auth-server-cluster</id>
|
||||||
<properties>
|
<properties>
|
||||||
<ant.scenario>scenario-cluster</ant.scenario>
|
<ant.scenario>scenario-cluster</ant.scenario>
|
||||||
|
|
||||||
<session.cache.owners>1</session.cache.owners>
|
|
||||||
<offline.session.cache.owners>1</offline.session.cache.owners>
|
|
||||||
<login.failure.cache.owners>1</login.failure.cache.owners>
|
|
||||||
|
|
||||||
<load.metric>simple</load.metric>
|
<load.metric>simple</load.metric>
|
||||||
<!-- The default value 'simple' configures mod-cluster with simple-load-provider.
|
<!-- The default value 'simple' configures mod-cluster with simple-load-provider.
|
||||||
Any other value configures it with dynamic-load-provider using the particular `load.metric`.
|
Any other value configures it with dynamic-load-provider using the particular `load.metric`.
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<modules>
|
<modules>
|
||||||
|
<module>db-allocator-plugin</module>
|
||||||
<module>integration-arquillian</module>
|
<module>integration-arquillian</module>
|
||||||
<module>utils</module>
|
<module>utils</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
Loading…
Reference in a new issue