From e4e13b855ec6629f28cc4c621b181a012ef7c8a2 Mon Sep 17 00:00:00 2001 From: Yavor16 Date: Mon, 29 Jun 2026 11:37:50 +0300 Subject: [PATCH 1/2] Add migration of backup descriptors --- .../database/migration/DatabaseMigration.java | 85 ++++++++++++------- .../type/BytesDatabaseTypeSetter.java | 20 +++++ .../type/DatabaseTypeSetterFactory.java | 4 +- .../type/TimestampDatabaseTypeSetter.java | 22 +++++ .../type/BytesDatabaseTypeSetterTest.java | 72 ++++++++++++++++ .../type/DatabaseTypeSetterFactoryTest.java | 12 +++ .../type/TimestampDatabaseTypeSetterTest.java | 62 ++++++++++++++ 7 files changed, 245 insertions(+), 32 deletions(-) create mode 100644 multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java create mode 100644 multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetter.java create mode 100644 multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetterTest.java create mode 100644 multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetterTest.java diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java index 59d98da2cb..b49f490f0c 100644 --- a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.List; import javax.sql.DataSource; import org.apache.logging.log4j.LogManager; @@ -20,52 +21,74 @@ public class DatabaseMigration { private static final Logger LOGGER = (Logger) LogManager.getLogger(DatabaseMigration.class); private static final String DATABASE_TARGET_SERVICE_KEY = "DATABASE_TARGET_SERVICE_KEY"; + private static final String TARGET_DATABASE_SERVICE_NAME = "deploy-service-database"; + + private static final List SEQUENCES_TO_MIGRATE = List.of("configuration_entry_sequence", + "configuration_subscription_sequence", + "backup_descriptor_sequence"); + + private static final List TABLES_TO_MIGRATE = List.of("configuration_registry", + "configuration_subscription", + "backup_descriptor"); public static void main(String[] args) { configureLogger(); - DatabaseServiceKey databaseServiceKey = getServiceKeyFromEnvironment(); - DataSourceEnvironmentExtractor environmentExtractor = new DataSourceEnvironmentExtractor(); - DataSource targetDataSource = environmentExtractor.extractDataSource("deploy-service-database"); - DataSource sourceDataSource = environmentExtractor.extractDataSource(databaseServiceKey); - - DatabaseSequenceMigrationExecutor sequenceMigrationExecutor = ImmutableDatabaseSequenceMigrationExecutor.builder() - .sourceDataSource( - sourceDataSource) - .targetDataSource( - targetDataSource) - .build(); - - DatabaseTableMigrationExecutor tableMigrationExecutor = ImmutableDatabaseTableMigrationExecutor.builder() - .sourceDataSource(sourceDataSource) - .targetDataSource(targetDataSource) - .build(); - sequenceMigrationExecutor.executeMigration("configuration_entry_sequence"); - sequenceMigrationExecutor.executeMigration("configuration_subscription_sequence"); - tableMigrationExecutor.executeMigration("configuration_registry"); - tableMigrationExecutor.executeMigration("configuration_subscription"); + + DataSource sourceDataSource = extractSourceDataSource(); + DataSource targetDataSource = extractTargetDataSource(); + + migrateSequences(sourceDataSource, targetDataSource); + migrateTables(sourceDataSource, targetDataSource); LOGGER.info("Database migration completed."); } + private static DataSource extractSourceDataSource() { + DatabaseServiceKey serviceKey = getServiceKeyFromEnvironment(); + return new DataSourceEnvironmentExtractor().extractDataSource(serviceKey); + } + + private static DataSource extractTargetDataSource() { + return new DataSourceEnvironmentExtractor().extractDataSource(TARGET_DATABASE_SERVICE_NAME); + } + + private static void migrateSequences(DataSource sourceDataSource, DataSource targetDataSource) { + DatabaseSequenceMigrationExecutor executor = ImmutableDatabaseSequenceMigrationExecutor.builder() + .sourceDataSource(sourceDataSource) + .targetDataSource(targetDataSource) + .build(); + SEQUENCES_TO_MIGRATE.forEach(executor::executeMigration); + } + + private static void migrateTables(DataSource sourceDataSource, DataSource targetDataSource) { + DatabaseTableMigrationExecutor executor = ImmutableDatabaseTableMigrationExecutor.builder() + .sourceDataSource(sourceDataSource) + .targetDataSource(targetDataSource) + .build(); + TABLES_TO_MIGRATE.forEach(executor::executeMigration); + } + private static DatabaseServiceKey getServiceKeyFromEnvironment() { String databaseTargetServiceKey = System.getenv(DATABASE_TARGET_SERVICE_KEY); + LOGGER.error("TTEEEE" + JsonUtil.convertJsonToMap(databaseTargetServiceKey)); return new DatabaseServiceKey(JsonUtil.convertJsonToMap(databaseTargetServiceKey)); } private static void configureLogger() { ClassLoader classLoader = DatabaseMigration.class.getClassLoader(); - if (classLoader != null) { - try (InputStream inputStream = classLoader.getResourceAsStream("console-logger.properties"); - LoggerContext loggerContext = new LoggerContext("DatabaseMigration")) { - if (inputStream != null) { - ConfigurationSource configSource = new ConfigurationSource(inputStream); - loggerContext.setConfigLocation(configSource.getURI()); - } - } catch (IOException e) { - // Using System.out.println() instead of LOGGER.warn(), because logging is likely not configured due to the exception. - System.out.println("An error occurred while trying to configure logging: " + e.getMessage()); - e.printStackTrace(); + if (classLoader == null) { + return; + } + try (InputStream inputStream = classLoader.getResourceAsStream("console-logger.properties"); + LoggerContext loggerContext = new LoggerContext("DatabaseMigration")) { + if (inputStream != null) { + ConfigurationSource configSource = new ConfigurationSource(inputStream); + loggerContext.setConfigLocation(configSource.getURI()); } + } catch (IOException e) { + // Using System.out.println() instead of LOGGER.warn(), because logging is likely not configured due to the exception. + System.out.println("An error occurred while trying to configure logging: " + e.getMessage()); + e.printStackTrace(); } } diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java new file mode 100644 index 0000000000..48007f6456 --- /dev/null +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java @@ -0,0 +1,20 @@ +package org.cloudfoundry.multiapps.controller.database.migration.executor.type; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +public class BytesDatabaseTypeSetter implements DatabaseTypeSetter { + + @Override + public List getSupportedTypes() { + return Arrays.asList("bytea"); + } + + @Override + public void setType(int columnIndex, PreparedStatement insertStatement, Object value) throws SQLException { + insertStatement.setBytes(columnIndex, (byte[]) value); + } + +} diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactory.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactory.java index f7c0a3bd7b..3f3a82365e 100644 --- a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactory.java +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactory.java @@ -9,7 +9,9 @@ public class DatabaseTypeSetterFactory { private static final List DEFAULT_REGISTERED_TYPE_SETTERS = Arrays.asList(new StringDatabaseTypeSetter(), new BooleanDatabaseTypeSetter(), - new LongDatabaseTypeSetter()); + new LongDatabaseTypeSetter(), + new BytesDatabaseTypeSetter(), + new TimestampDatabaseTypeSetter()); private final List registeredTypeSetters; public DatabaseTypeSetterFactory() { diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetter.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetter.java new file mode 100644 index 0000000000..ca4ffde7c4 --- /dev/null +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetter.java @@ -0,0 +1,22 @@ +package org.cloudfoundry.multiapps.controller.database.migration.executor.type; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; + +public class TimestampDatabaseTypeSetter implements DatabaseTypeSetter { + + private static final String TIMESTAMP = "timestamp"; + + @Override + public List getSupportedTypes() { + return List.of(TIMESTAMP); + } + + @Override + public void setType(int columnIndex, PreparedStatement insertStatement, Object value) throws SQLException { + insertStatement.setTimestamp(columnIndex, (Timestamp) value); + } + +} diff --git a/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetterTest.java b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetterTest.java new file mode 100644 index 0000000000..73f2a37729 --- /dev/null +++ b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetterTest.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.multiapps.controller.database.migration.executor.type; + +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class BytesDatabaseTypeSetterTest { + + private static final int COLUMN_INDEX = 1; + + @Mock + private PreparedStatement preparedStatement; + + private BytesDatabaseTypeSetter setter; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + setter = new BytesDatabaseTypeSetter(); + } + + @Test + void testGetSupportedTypesContainsBytea() { + List supportedTypes = setter.getSupportedTypes(); + + Assertions.assertEquals(List.of("bytea"), supportedTypes); + } + + @Test + void testSetTypeDelegatesToSetBytes() throws SQLException { + byte[] value = "descriptor-payload".getBytes(StandardCharsets.UTF_8); + + setter.setType(COLUMN_INDEX, preparedStatement, value); + + Mockito.verify(preparedStatement) + .setBytes(COLUMN_INDEX, value); + } + + @Test + void testSetTypeWithNullValue() throws SQLException { + setter.setType(COLUMN_INDEX, preparedStatement, null); + + Mockito.verify(preparedStatement) + .setBytes(COLUMN_INDEX, null); + } + + @Test + void testSetTypeWithEmptyByteArray() throws SQLException { + byte[] value = new byte[0]; + + setter.setType(COLUMN_INDEX, preparedStatement, value); + + Mockito.verify(preparedStatement) + .setBytes(COLUMN_INDEX, value); + } + + @Test + void testSetTypeWithNonByteArrayValueThrowsClassCastException() { + Assertions.assertThrows(ClassCastException.class, + () -> setter.setType(COLUMN_INDEX, preparedStatement, "not-a-byte-array")); + } + +} diff --git a/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactoryTest.java b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactoryTest.java index 2d957563fb..ec1050e7a7 100644 --- a/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactoryTest.java +++ b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/DatabaseTypeSetterFactoryTest.java @@ -11,6 +11,8 @@ class DatabaseTypeSetterFactoryTest { private static final String BOOL_TYPE = "bool"; private static final String LONG_TYPE = "int8"; private static final String STRING_TYPE = "varchar"; + private static final String BYTES_TYPE = "bytea"; + private static final String TIMESTAMP_TYPE = "timestamp"; @Test void testGetWithNullStringParameter() { @@ -67,6 +69,16 @@ void testGetWithDefaultRegisteredTypeSettersWhenMatchingDefaultTypeSetters() { resultDatabaseTypeSetter = databaseTypeSetterFactory.get(STRING_TYPE); Assertions.assertTrue(resultDatabaseTypeSetter.getSupportedTypes() .contains(STRING_TYPE)); + + resultDatabaseTypeSetter = databaseTypeSetterFactory.get(BYTES_TYPE); + Assertions.assertTrue(resultDatabaseTypeSetter.getSupportedTypes() + .contains(BYTES_TYPE)); + Assertions.assertInstanceOf(BytesDatabaseTypeSetter.class, resultDatabaseTypeSetter); + + resultDatabaseTypeSetter = databaseTypeSetterFactory.get(TIMESTAMP_TYPE); + Assertions.assertTrue(resultDatabaseTypeSetter.getSupportedTypes() + .contains(TIMESTAMP_TYPE)); + Assertions.assertInstanceOf(TimestampDatabaseTypeSetter.class, resultDatabaseTypeSetter); } } diff --git a/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetterTest.java b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetterTest.java new file mode 100644 index 0000000000..33f1eed6f5 --- /dev/null +++ b/multiapps-controller-database-migration/src/test/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/TimestampDatabaseTypeSetterTest.java @@ -0,0 +1,62 @@ +package org.cloudfoundry.multiapps.controller.database.migration.executor.type; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class TimestampDatabaseTypeSetterTest { + + private static final int COLUMN_INDEX = 1; + + @Mock + private PreparedStatement preparedStatement; + + private TimestampDatabaseTypeSetter setter; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + setter = new TimestampDatabaseTypeSetter(); + } + + @Test + void testGetSupportedTypesContainsTimestamp() { + List supportedTypes = setter.getSupportedTypes(); + + Assertions.assertEquals(List.of("timestamp"), supportedTypes); + } + + @Test + void testSetTypeDelegatesToSetTimestamp() throws SQLException { + Timestamp value = Timestamp.valueOf("2026-06-26 13:16:22.060"); + + setter.setType(COLUMN_INDEX, preparedStatement, value); + + Mockito.verify(preparedStatement) + .setTimestamp(COLUMN_INDEX, value); + } + + @Test + void testSetTypeWithNullValue() throws SQLException { + setter.setType(COLUMN_INDEX, preparedStatement, null); + + Mockito.verify(preparedStatement) + .setTimestamp(COLUMN_INDEX, null); + } + + @Test + void testSetTypeWithNonTimestampValueThrowsClassCastException() { + Assertions.assertThrows(ClassCastException.class, + () -> setter.setType(COLUMN_INDEX, preparedStatement, "2026-06-26")); + } + +} From 885d0ad97606222223abc11d12f01a7af991eebc Mon Sep 17 00:00:00 2001 From: Yavor16 Date: Mon, 29 Jun 2026 16:34:35 +0300 Subject: [PATCH 2/2] Fix comments from Kris --- .../controller/database/migration/DatabaseMigration.java | 1 - .../migration/executor/type/BytesDatabaseTypeSetter.java | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java index b49f490f0c..b94079a532 100644 --- a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/DatabaseMigration.java @@ -70,7 +70,6 @@ private static void migrateTables(DataSource sourceDataSource, DataSource target private static DatabaseServiceKey getServiceKeyFromEnvironment() { String databaseTargetServiceKey = System.getenv(DATABASE_TARGET_SERVICE_KEY); - LOGGER.error("TTEEEE" + JsonUtil.convertJsonToMap(databaseTargetServiceKey)); return new DatabaseServiceKey(JsonUtil.convertJsonToMap(databaseTargetServiceKey)); } diff --git a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java index 48007f6456..b3e261b9e2 100644 --- a/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java +++ b/multiapps-controller-database-migration/src/main/java/org/cloudfoundry/multiapps/controller/database/migration/executor/type/BytesDatabaseTypeSetter.java @@ -2,14 +2,15 @@ import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Arrays; import java.util.List; public class BytesDatabaseTypeSetter implements DatabaseTypeSetter { + private static final String BYTEA = "bytea"; + @Override public List getSupportedTypes() { - return Arrays.asList("bytea"); + return List.of(BYTEA); } @Override