Skip to content

Commit

Permalink
Add spring-boot-jdbc-postgresql-chaos example
Browse files Browse the repository at this point in the history
  • Loading branch information
eddumelendez committed Sep 27, 2024
1 parent a1277db commit 32f1ffd
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<module>spring-boot-cockroachdb-flyway</module>
<module>spring-boot-cockroachdb-liquibase</module>
<module>spring-boot-elasticsearch</module>
<module>spring-boot-jdbc-postgresql-chaos</module>
<module>spring-boot-jpa</module>
<module>spring-boot-kafka</module>
<!-- <module>spring-boot-kafka-chaos</module>-->
Expand Down
129 changes: 129 additions & 0 deletions spring-boot-jdbc-postgresql-chaos/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0-M3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-jdbc-postgresql-chaos</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-jdbc-postgresql-chaos</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>21</java.version>
<testcontainers.version>1.20.1</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.4</version>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>toxiproxy</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-vavr</artifactId>
<version>0.4.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>2.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<rerunFailingTestsCount>3</rerunFailingTestsCount>
</configuration>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.example.springbootjdbcpostgresqlchaos;

public record Profile(Long id, String name) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.springbootjdbcpostgresqlchaos;

import org.springframework.data.repository.CrudRepository;

public interface ProfileRepository extends CrudRepository<Profile, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.springbootjdbcpostgresqlchaos;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootJdbcPostgresqlChaosApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootJdbcPostgresqlChaosApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS profile(id serial primary key, name varchar(255) not null);

INSERT INTO profile (name) VALUES ('profile-1');
INSERT INTO profile (name) VALUES ('profile-2');
INSERT INTO profile (name) VALUES ('profile-3');
INSERT INTO profile (name) VALUES ('profile-4');
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.example.springbootjdbcpostgresqlchaos;

import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.timelimiter.TimeLimiter;
import io.vavr.control.Try;
import org.assertj.vavr.api.VavrAssertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Container.ExecResult;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.ToxiproxyContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@DataJdbcTest
class SpringBootJdbcPostgresqlChaosConfigCliApplicationTests {

private static final Logger logger = LoggerFactory
.getLogger(SpringBootJdbcPostgresqlChaosConfigCliApplicationTests.class);

private static final Network network = Network.newNetwork();

@Container
private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withNetwork(network)
.withNetworkAliases("postgres");

@Container
private static final ToxiproxyContainer toxiproxy = new ToxiproxyContainer("ghcr.io/shopify/toxiproxy:2.9.0")
.withCopyFileToContainer(MountableFile.forClasspathResource("toxiproxy.json"), "/tmp/toxiproxy.json")
.withCommand("-host=0.0.0.0", "-config=/tmp/toxiproxy.json")
.withNetwork(network);

@DynamicPropertySource
static void sqlserverProperties(DynamicPropertyRegistry registry) throws Exception {
registry.add("spring.datasource.url", () -> "jdbc:postgresql://%s:%d/%s".formatted(toxiproxy.getHost(),
toxiproxy.getMappedPort(8666), postgres.getDatabaseName()));
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.flyway.url", postgres::getJdbcUrl);
registry.add("spring.flyway.user", postgres::getUsername);
registry.add("spring.flyway.password", postgres::getPassword);
}

@Autowired
private ProfileRepository profileRepository;

@Test
void normal() {
assertThat(this.profileRepository.findAll()).hasSize(4);
}

@Test
void withLatency() throws Exception {
execute("./toxiproxy-cli toxic add -t latency --downstream -a latency=1600 -a jitter=100 -n latency_downstream postgresql");

assertThat(this.profileRepository.findAll()).hasSize(4);

execute("./toxiproxy-cli toxic remove -n latency_downstream postgresql");
}

@Test
void withLatencyWithTimeout() throws Exception {
execute("./toxiproxy-cli toxic add -t latency --downstream -a latency=1600 -a jitter=100 -n latency_downstream postgresql");

TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofMillis(50));
Supplier<CompletableFuture<Iterable<Profile>>> completableFutureSupplier = () -> CompletableFuture
.supplyAsync(() -> this.profileRepository.findAll());
Try<Iterable<Profile>> actual = Try.ofCallable(timeLimiter.decorateFutureSupplier(completableFutureSupplier));
VavrAssertions.assertThat(actual).isFailure().failBecauseOf(TimeoutException.class);

execute("./toxiproxy-cli toxic remove -n latency_downstream postgresql");
}

@Test
void withLatencyWithRetries() throws Exception {
var intervalFunction = IntervalFunction.of(Duration.ofMillis(500));
var retryConfig = RetryConfig.custom()
.retryExceptions(TimeoutException.class)
.maxAttempts(2)
.failAfterMaxAttempts(true)
.intervalFunction(intervalFunction)
.build();
var jdbcRetry = Retry.of("jdbc", retryConfig);

var timeLimiter = TimeLimiter.of(Duration.ofMillis(500));

execute("./toxiproxy-cli toxic add -t latency --downstream -a latency=1600 -a jitter=100 -n latency_downstream postgresql");

Supplier<CompletableFuture<Iterable<Profile>>> completableFutureSupplier = () -> CompletableFuture
.supplyAsync(() -> {
logger.info("Executing query");
return this.profileRepository.findAll();
});
Callable<Iterable<Profile>> iterableCallable = timeLimiter.decorateFutureSupplier(completableFutureSupplier);
Callable<Iterable<Profile>> iterableCallable1 = Retry.decorateCallable(jdbcRetry, iterableCallable);
VavrAssertions.assertThat(Try.ofCallable(iterableCallable1)).isFailure().failBecauseOf(TimeoutException.class);

execute("./toxiproxy-cli toxic remove -n latency_downstream postgresql");

assertThat(this.profileRepository.findAll()).hasSize(4);
}

@Test
void withToxiProxyConnectionDown() throws Exception {
execute("./toxiproxy-cli toxic add -t bandwidth --downstream -a rate=0 -n bandwidth_downstream postgresql");
execute("./toxiproxy-cli toxic add -t bandwidth --upstream -a rate=0 -n bandwidth_upstream postgresql");

TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofMillis(50));
Supplier<CompletableFuture<Iterable<Profile>>> completableFutureSupplier = () -> CompletableFuture
.supplyAsync(() -> this.profileRepository.findAll());
Try<Iterable<Profile>> actual = Try.ofCallable(timeLimiter.decorateFutureSupplier(completableFutureSupplier));
VavrAssertions.assertThat(actual).isFailure().failBecauseOf(TimeoutException.class);

execute("./toxiproxy-cli toxic remove -n bandwidth_downstream postgresql");
execute("./toxiproxy-cli toxic remove -n bandwidth_upstream postgresql");

assertThat(this.profileRepository.findAll()).hasSize(4);
}

private static void execute(String command) throws Exception {
ExecResult result = toxiproxy.execInContainer(command.split(" "));
if (result.getExitCode() != 0) {
throw new RuntimeException("Error executing command '%s' \nstderr: %s\nstdout: %s".formatted(command,
result.getStderr(), result.getStdout()));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>

</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"name": "postgresql",
"listen": "0.0.0.0:8666",
"upstream": "postgres:5432",
"enabled": true
}
]

0 comments on commit 32f1ffd

Please sign in to comment.