Add Context Refresh Hikari sample

Closes gh-39
This commit is contained in:
Olga MaciaszekSharma
2023-10-05 13:32:13 +02:00
committed by Sébastien Deleuze
parent f01a91d613
commit f9d6380b59
22 changed files with 385 additions and 13 deletions

View File

@@ -37,6 +37,11 @@ h|test
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-cr-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-cr-app-test]
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-test]
|context-refresh-hikari
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-hikari-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-hikari-app-test]
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-hikari-cr-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-hikari-cr-app-test]
|
|context-refresh-http
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-http-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-http-app-test]
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-http-cr-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/context-refresh-http-cr-app-test]

View File

@@ -12,6 +12,9 @@ groups:
- name: context-refresh
app_test: true
test: true
- name: context-refresh-hikari
app_test: true
test: false
- name: context-refresh-http
app_test: true
test: true

View File

@@ -0,0 +1,33 @@
plugins {
id "java"
id "org.springframework.boot"
id "org.springframework.cr.smoke-test"
}
// Not using Spring Cloud bom due to existing issues with dependency management
ext {
set('springCloudStarterVersion', "4.1.0-SNAPSHOT")
}
dependencies {
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.crac:crac:$cracVersion")
implementation(project(":cr-listener"))
implementation("org.springframework.cloud:spring-cloud-starter:${springCloudStarterVersion}")
runtimeOnly("org.postgresql:postgresql")
appTestImplementation(project(":cr-smoke-test-support"))
appTestImplementation(project(":cloud:context-refresh-hikari"))
}
crSmokeTest {
webApplication = true
}
// `@ApplicationTest`s need to only be executed during the `checkpointRestoreAppTest and not `appTest`
// (we're checking for a value that we want to have been modified in between these two phases).
appTest.enabled = false

View File

@@ -0,0 +1,3 @@
spring.datasource.url: jdbc:postgresql://${DB1_HOST:localhost}:${DB1_PORT_5432:5432}/database1
spring.datasource.username=user1
spring.datasource.password=passwd1

View File

@@ -0,0 +1,26 @@
version: '2'
services:
db1:
image: 'postgres:16.0-alpine'
container_name: db1
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_USER=user1
- POSTGRES_PASSWORD=passwd1
- POSTGRES_DB=database1
ports:
- "5432"
db2:
image: 'postgres:16.0-alpine'
container_name: db2
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_USER=user2
- POSTGRES_PASSWORD=passwd2
- POSTGRES_DB=database2
ports:
- "5432"

View File

@@ -0,0 +1 @@
create table if not exists users (id bigserial not null, email varchar(255), name varchar(255), primary key (id));

View File

@@ -0,0 +1,64 @@
package com.example.contextrefreshhikari;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import com.example.contextrefreshhikari.entities.User;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.springframework.cr.smoketest.support.assertj.AssertableOutput;
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings({ "JUnitMalformedDeclaration" })
@ApplicationTest
public class ContextRefreshHikariApplicationTests {
// Bring the config file back to init values before next app start,
// since the app updates the property file after properties set
@AfterAll
static void cleanUp() throws IOException {
editExternalConfigurationProperties();
}
@Test
void connectionMetadata(WebTestClient webClient) {
webClient.get().uri("/metadata").exchange().expectStatus().isOk().expectBody().consumeWith(result -> {
String connectionString = new String(result.getResponseBodyContent());
assertThat(connectionString).startsWith("jdbc:postgresql");
assertThat(connectionString).endsWith("database2");
});
}
@Test
void users(WebTestClient webTestClient, AssertableOutput output) {
// We've connected to a different db after restore, so it should not have any
// users
webTestClient.get().uri("/users/add").exchange().expectStatus().isOk();
webTestClient.get()
.uri("/users")
.header("ContentType", MediaType.APPLICATION_JSON_VALUE)
.exchange()
.expectStatus()
.isOk()
.expectBodyList(User.class)
.hasSize(2);
}
private static void editExternalConfigurationProperties() throws IOException {
Files.writeString(Path.of("./dev.properties"), """
spring.datasource.url: jdbc:postgresql://${DB1_HOST:localhost}:${DB1_PORT_5432:5432}/database1
spring.datasource.username=user1
spring.datasource.password=passwd1
""", Charset.defaultCharset(), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}

View File

@@ -0,0 +1,6 @@
spring:
config:
import: file:././dev.properties
config:
path: ././dev.properties

View File

@@ -0,0 +1,33 @@
package com.example.contextrefreshhikari;
import com.example.contextrefreshhikari.entities.User;
import com.example.contextrefreshhikari.repositories.UserRepository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ContextRefreshHikariApplication {
private static final Log LOG = LogFactory.getLog(ContextRefreshHikariApplication.class);
public static void main(String[] args) {
SpringApplication.run(ContextRefreshHikariApplication.class, args);
}
@Bean
public CommandLineRunner run(UserRepository userRepository) {
return (String[] args) -> {
User user1 = new User("John", "john@domain.com");
User user2 = new User("Julie", "julie@domain.com");
userRepository.save(user1);
userRepository.save(user2);
userRepository.findAll().forEach(LOG::info);
};
}
}

View File

@@ -0,0 +1,52 @@
package com.example.contextrefreshhikari.controllers;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigUpdateController {
private static final Log LOG = LogFactory.getLog(ConfigUpdateController.class);
@Value("${config.path}")
private String path;
@EventListener(ApplicationStartedEvent.class)
public void editExternalConfig() throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Updating file values");
}
editExternalConfigurationProperties("""
spring.datasource.url: jdbc:postgresql://${DB2_HOST:localhost}:${DB2_PORT_5432:5432}/database2
spring.datasource.username=user2
spring.datasource.password=passwd2
""");
}
@GetMapping("/reset")
void reset() throws IOException {
editExternalConfigurationProperties("""
spring.datasource.url: jdbc:postgresql://${DB1_HOST:localhost}:${DB1_PORT_5432:5432}/database1
spring.datasource.username=user1
spring.datasource.password=passwd1
""");
}
private void editExternalConfigurationProperties(String newFileContent) throws IOException {
Files.writeString(Path.of(path), newFileContent, Charset.defaultCharset(), StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
}
}

View File

@@ -0,0 +1,24 @@
package com.example.contextrefreshhikari.controllers;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/metadata")
public class DataSourceMetadataController {
@Autowired
DataSource dataSource;
@GetMapping
public String getConnectionsUrl() throws SQLException {
return dataSource.getConnection().getMetaData().getURL();
}
}

View File

@@ -0,0 +1,36 @@
package com.example.contextrefreshhikari.controllers;
import java.util.List;
import com.example.contextrefreshhikari.entities.User;
import com.example.contextrefreshhikari.repositories.UserRepository;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
List<User> getUsers() {
return userRepository.findAll();
}
@GetMapping("/add")
void addUsers() {
User user1 = new User("Steve", "steve@domain.com");
User user2 = new User("Sarah", "sarah@domain.com");
userRepository.save(user1);
userRepository.save(user2);
}
}

View File

@@ -0,0 +1,50 @@
package com.example.contextrefreshhikari.entities;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}';
}
}

View File

@@ -0,0 +1,12 @@
package com.example.contextrefreshhikari.repositories;
import com.example.contextrefreshhikari.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@@ -0,0 +1,23 @@
spring:
datasource:
hikari:
allow-pool-suspension: true
driver-class-name: org.postgresql.Driver
cloud:
refresh:
never-refreshable:
extra-refreshable:
- javax.sql.DataSource
- com.zaxxer.hikari.HikariDataSource
application:
name: context-refresh-hikari
config:
import: file:./cloud/context-refresh-hikari/dev.properties
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate.id.new_generator_mappings: false
config:
path: ./cloud/context-refresh-hikari/dev.properties

View File

@@ -14,6 +14,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings({ "JUnitMalformedDeclaration", "DataFlowIssue" })
@ApplicationTest
public class ContextRefreshHttpApplicationTests {

View File

@@ -1,4 +1,4 @@
package com.example.contextrefresh;
package com.example.contextrefreshhttp.controllers;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;

View File

@@ -1,4 +1,4 @@
package com.example.contextrefreshhttp;
package com.example.contextrefreshhttp.controllers;
import com.example.contextrefreshhttp.config.SimpleConfigurationProperties;

View File

@@ -14,6 +14,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings({ "JUnitMalformedDeclaration", "DataFlowIssue" })
@ApplicationTest
public class ContextRefreshApplicationTests {
@@ -21,10 +22,7 @@ public class ContextRefreshApplicationTests {
// since the app updates the property file after properties set
@AfterAll
static void cleanUp() throws IOException {
editExternalConfigurationProperties("""
simple.test=testVal
test=propVal
""");
editExternalConfigurationProperties();
}
@Test
@@ -49,9 +47,11 @@ public class ContextRefreshApplicationTests {
.consumeWith(result -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("testValNew"));
}
private static void editExternalConfigurationProperties(String newFileContent) throws IOException {
Files.writeString(Path.of("./dev.properties"), newFileContent, Charset.defaultCharset(),
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
private static void editExternalConfigurationProperties() throws IOException {
Files.writeString(Path.of("./dev.properties"), """
simple.test=testVal
test=propVal
""", Charset.defaultCharset(), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}

View File

@@ -1,4 +1,4 @@
package com.example.contextrefresh;
package com.example.contextrefresh.controllers;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -24,7 +24,7 @@ public class ConfigUpdateController {
private String path;
@EventListener(ApplicationStartedEvent.class)
public void doSomethingAfterStartup() throws IOException {
public void editExternalConfig() throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Updating file values");
}

View File

@@ -1,4 +1,4 @@
package com.example.contextrefreshhttp;
package com.example.contextrefresh.controllers;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;

View File

@@ -1,4 +1,4 @@
package com.example.contextrefresh;
package com.example.contextrefresh.controllers;
import com.example.contextrefresh.config.SimpleConfigurationProperties;