<module>participant</module>
</modules>
- <!-- Fix transitive dependencies' vulnerabilities -->
<dependencyManagement>
<dependencies>
+ <!-- Fix transitive dependencies' vulnerabilities -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<type>pom</type>
<scope>import</scope>
</dependency>
+ <!-- Dependencies specific to clamp repo -->
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers-bom</artifactId>
+ <version>1.21.3</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
</project>
<artifactId>spring-boot-test-autoconfigure</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>
+
</dependencies>
<build>
referencedColumnNames: participantId
onUpdate: RESTRICT
onDelete: RESTRICT
+
+ - changeSet:
+ id: 1400-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1400
referencedColumnNames: participantId
onUpdate: RESTRICT
onDelete: RESTRICT
+
+ - changeSet:
+ id: 1500-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1500
- column:
name: stage
type: SMALLINT
+
+ - changeSet:
+ id: 1600-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1600
columns:
- column:
name: identificationId
+
+ - changeSet:
+ id: 1700-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1700
- addNotNullConstraint:
tableName: AutomationComposition
columnName: name
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: name
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: name
- changeSet:
id: 1701-4
- addNotNullConstraint:
tableName: AutomationComposition
columnName: version
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: version
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: version
- changeSet:
id: 1701-5
- addNotNullConstraint:
tableName: AutomationComposition
columnName: deployState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: deployState
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: deployState
- changeSet:
id: 1701-6
- addNotNullConstraint:
tableName: AutomationComposition
columnName: lockState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: lockState
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: lockState
- changeSet:
id: 1701-7
- addNotNullConstraint:
tableName: AutomationComposition
columnName: subState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: subState
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: subState
- changeSet:
id: 1701-8
- addNotNullConstraint:
tableName: AutomationComposition
columnName: lastMsg
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: lastMsg
- changeSet:
id: 1701-9
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: definition_name
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: definition_name
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: definition_name
- changeSet:
id: 1701-10
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: definition_version
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: definition_version
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: definition_version
- changeSet:
id: 1701-11
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: deployState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: deployState
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: deployState
- changeSet:
id: 1701-12
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: lockState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: lockState
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: lockState
- changeSet:
id: 1701-13
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: subState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: subState
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: subState
- changeSet:
id: 1701-14
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: outProperties
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: outProperties
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: outProperties
- changeSet:
id: 1701-15
- addNotNullConstraint:
tableName: AutomationCompositionElement
columnName: properties
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionElement
+ columnName: properties
+ - dropDefaultValue:
+ tableName: AutomationCompositionElement
+ columnName: properties
- changeSet:
id: 1701-16
referencedColumnNames: compositionId
onUpdate: RESTRICT
onDelete: RESTRICT
+ rollback:
+ - dropForeignKeyConstraint:
+ baseTableName: AutomationComposition
+ constraintName: ac_composition_fk
- changeSet:
id: 1701-17
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: name
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: name
+ - dropDefaultValue:
+ tableName: AutomationCompositionDefinition
+ columnName: name
- changeSet:
id: 1701-18
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: version
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: version
+ - dropDefaultValue:
+ tableName: AutomationCompositionDefinition
+ columnName: version
- changeSet:
id: 1701-19
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: state
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: state
+ - dropDefaultValue:
+ tableName: AutomationCompositionDefinition
+ columnName: state
- changeSet:
id: 1701-20
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: serviceTemplate
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: serviceTemplate
+ - dropDefaultValue:
+ tableName: AutomationCompositionDefinition
+ columnName: serviceTemplate
- changeSet:
id: 1701-21
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: lastMsg
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: lastMsg
- changeSet:
id: 1701-22
- addNotNullConstraint:
tableName: NodeTemplateState
columnName: nodeTemplate_name
+ rollback:
+ - dropNotNullConstraint:
+ tableName: NodeTemplateState
+ columnName: nodeTemplate_name
+ - dropDefaultValue:
+ tableName: NodeTemplateState
+ columnName: nodeTemplate_name
- changeSet:
id: 1701-23
- addNotNullConstraint:
tableName: NodeTemplateState
columnName: nodeTemplate_version
+ rollback:
+ - dropNotNullConstraint:
+ tableName: NodeTemplateState
+ columnName: nodeTemplate_version
+ - dropDefaultValue:
+ tableName: NodeTemplateState
+ columnName: nodeTemplate_version
- changeSet:
id: 1701-24
- addNotNullConstraint:
tableName: NodeTemplateState
columnName: outProperties
+ rollback:
+ - dropNotNullConstraint:
+ tableName: NodeTemplateState
+ columnName: outProperties
+ - dropDefaultValue:
+ tableName: NodeTemplateState
+ columnName: outProperties
- changeSet:
id: 1701-25
- addNotNullConstraint:
tableName: NodeTemplateState
columnName: state
+ rollback:
+ - dropNotNullConstraint:
+ tableName: NodeTemplateState
+ columnName: state
+ - dropDefaultValue:
+ tableName: NodeTemplateState
+ columnName: state
- changeSet:
id: 1701-26
columns:
- column:
name: identificationId
+ rollback:
+ - dropIndex:
+ indexName: mb_identificationId_index
+ tableName: Message
- changeSet:
id: 1701-27
- addNotNullConstraint:
tableName: ParticipantReplica
columnName: lastMsg
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantReplica
+ columnName: lastMsg
- changeSet:
id: 1701-28
- addNotNullConstraint:
tableName: ParticipantReplica
columnName: participantId
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantReplica
+ columnName: participantId
+ - dropDefaultValue:
+ tableName: ParticipantReplica
+ columnName: participantId
- changeSet:
id: 1701-29
- addNotNullConstraint:
tableName: ParticipantReplica
columnName: participantState
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantReplica
+ columnName: participantState
+ - dropDefaultValue:
+ tableName: ParticipantReplica
+ columnName: participantState
- changeSet:
id: 1701-30
- addNotNullConstraint:
tableName: ParticipantSupportedAcElements
columnName: participantId
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantSupportedAcElements
+ columnName: participantId
+ - dropDefaultValue:
+ tableName: ParticipantSupportedAcElements
+ columnName: participantId
- changeSet:
id: 1701-31
- addNotNullConstraint:
tableName: ParticipantSupportedAcElements
columnName: typeName
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantSupportedAcElements
+ columnName: typeName
+ - dropDefaultValue:
+ tableName: ParticipantSupportedAcElements
+ columnName: typeName
- changeSet:
id: 1701-32
- addNotNullConstraint:
tableName: ParticipantSupportedAcElements
columnName: typeVersion
+ rollback:
+ - dropNotNullConstraint:
+ tableName: ParticipantSupportedAcElements
+ columnName: typeVersion
+ - dropDefaultValue:
+ tableName: ParticipantSupportedAcElements
+ columnName: typeVersion
+
+ - changeSet:
+ id: 1701-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1701
tableName: AutomationComposition
columnName: phase
newDataType: INT
+ rollback:
+ - modifyDataType:
+ tableName: AutomationComposition
+ columnName: phase
+ newDataType: SMALLINT
- changeSet:
author: policy
tableName: AutomationCompositionElement
columnName: stage
newDataType: INT
+ rollback:
+ - modifyDataType:
+ tableName: AutomationCompositionElement
+ columnName: stage
+ newDataType: SMALLINT
- changeSet:
author: policy
- dropDefaultValue:
tableName: ParticipantReplica
columnName: participantId
+ rollback:
+ - addDefaultValue:
+ tableName: ParticipantReplica
+ columnName: participantId
+ defaultValue: ''
- changeSet:
author: policy
- dropColumn:
tableName: AutomationComposition
columnName: restarting
+ rollback:
+ - addColumn:
+ tableName: AutomationComposition
+ columns:
+ - column:
+ name: restarting
+ type: BOOLEAN
- changeSet:
author: policy
- dropColumn:
tableName: AutomationCompositionDefinition
columnName: restarting
+ rollback:
+ - addColumn:
+ tableName: AutomationCompositionDefinition
+ columns:
+ - column:
+ name: restarting
+ type: BOOLEAN
- changeSet:
author: policy
- dropColumn:
tableName: Participant
columnName: participantState
+ rollback:
+ - addColumn:
+ tableName: Participant
+ columns:
+ - column:
+ name: participantState
+ type: SMALLINT
- changeSet:
author: policy
- dropColumn:
tableName: Participant
columnName: lastMsg
+ rollback:
+ - addColumn:
+ tableName: Participant
+ columns:
+ - column:
+ name: lastMsg
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+
+ - changeSet:
+ id: 1702-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1702
- dropColumn:
tableName: AutomationCompositionElement
columnName: restarting
+ rollback:
+ - addColumn:
+ tableName: AutomationCompositionElement
+ columns:
+ - column:
+ name: restarting
+ type: BOOLEAN
- changeSet:
author: policy
- dropColumn:
tableName: NodeTemplateState
columnName: restarting
+ rollback:
+ - addColumn:
+ tableName: NodeTemplateState
+ columns:
+ - column:
+ name: restarting
+ type: BOOLEAN
- changeSet:
author: policy
- addNotNullConstraint:
tableName: AutomationCompositionDefinition
columnName: stateChangeResult
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationCompositionDefinition
+ columnName: stateChangeResult
+ - dropDefaultValue:
+ tableName: AutomationCompositionDefinition
+ columnName: stateChangeResult
- changeSet:
id: 1800-5
- addNotNullConstraint:
tableName: AutomationComposition
columnName: stateChangeResult
+ rollback:
+ - dropNotNullConstraint:
+ tableName: AutomationComposition
+ columnName: stateChangeResult
+ - dropDefaultValue:
+ tableName: AutomationComposition
+ columnName: stateChangeResult
+
+ - changeSet:
+ id: 1800-tag
+ author: policy
+ changes:
+ - tagDatabase:
+ tag: 1800
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.acm.runtime.liquibase;
+
+import java.io.PrintStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.stream.Stream;
+import liquibase.Liquibase;
+import liquibase.database.DatabaseFactory;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.diff.DiffResult;
+import liquibase.diff.compare.CompareControl;
+import liquibase.diff.output.report.DiffToReport;
+import liquibase.resource.ClassLoaderResourceAccessor;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+// This test class verifies that rollbacks for each Liquibase release tag works correctly.
+@Testcontainers
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class LiquibaseRollbackTest {
+
+ @Container
+ private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
+
+ private Liquibase liquibase;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ liquibase = initLiquibase();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ liquibase.dropAll();
+ liquibase.close();
+ }
+
+ /**
+ * This test will apply all changesets up to the latest change, and roll them all back.
+ * This simple test detects many issues such as missing rollback instructions in changesets.
+ */
+ @Test
+ void testUpdateAndRollback() {
+ Assertions.assertDoesNotThrow(() -> liquibase.updateTestingRollback(null));
+ }
+
+ /**
+ * This test will apply changesets up to a specific tag, roll back to a previous tag,
+ * and then re-apply the changesets to ensure that the rollback was compatible with forward changes.
+ */
+ @ParameterizedTest
+ @MethodSource("rollbackTagProvider")
+ void testUpdateAndRollbackForTags(final String previousTag, final String targetTag) {
+ // Run all changesets up to the target release tag
+ Assertions.assertDoesNotThrow(() -> liquibase.update(targetTag, ""));
+ // Roll back to the previous release
+ Assertions.assertDoesNotThrow(() -> liquibase.rollback(previousTag, ""));
+ // Apply forward changes again to ensure that the rollback was compatible with forwards changes
+ Assertions.assertDoesNotThrow(() -> liquibase.update(targetTag, ""));
+ }
+
+ /**
+ * This test compares the database schema before and after a rollback to ensure they are identical.
+ * It works by creating two separate schemas in the database:
+ * - one to apply changes up to a certain target tag (the original pre-upgraded schema)
+ * - another to apply changes up to a later tag, and roll back to the target tag (the post-rollback schema)
+ * The two schemas are then compared for equality. The schemas should be identical if the rollbacks are correct.
+ */
+ @ParameterizedTest
+ @MethodSource("rollbackTagProvider")
+ void testSchemaEqualityAfterRollback(final String rollbackToTag, final String rollbackFromTag) throws Exception {
+ // Disable column order checking to avoid false positives when columns are dropped and re-added.
+ System.setProperty("liquibase.diffColumnOrder", "false");
+ // Create two schemas
+ try (Connection conn = initConnection(); Statement stmt = conn.createStatement()) {
+ stmt.execute("CREATE SCHEMA pre_upgrade_schema");
+ stmt.execute("CREATE SCHEMA post_rollback_schema");
+ }
+ try (Liquibase liquibaseBefore = initLiquibaseForSchema("pre_upgrade_schema");
+ Liquibase liquibaseAfter = initLiquibaseForSchema("post_rollback_schema")) {
+ // Apply pre-upgrade schema to pre_upgrade_schema
+ liquibaseBefore.update(rollbackToTag, "");
+
+ // Apply upgrade and rollback to post_rollback_schema
+ liquibaseAfter.update(rollbackFromTag, "");
+ liquibaseAfter.rollback(rollbackToTag, "");
+
+ // Compare the schemas and report any differences
+ DiffResult diffResult = liquibaseBefore.diff(liquibaseBefore.getDatabase(), liquibaseAfter.getDatabase(),
+ CompareControl.STANDARD);
+ if (!diffResult.areEqual()) {
+ DiffToReport diffReport = new DiffToReport(diffResult, new PrintStream(System.out));
+ diffReport.print();
+ }
+ // Fail test if schemas are different
+ Assertions.assertTrue(diffResult.areEqual());
+
+ } finally {
+ try (Connection conn = initConnection(); Statement stmt = conn.createStatement()) {
+ stmt.execute("DROP SCHEMA IF EXISTS pre_upgrade_schema CASCADE");
+ stmt.execute("DROP SCHEMA IF EXISTS post_rollback_schema CASCADE");
+ }
+ }
+ }
+
+ private static Stream<Arguments> rollbackTagProvider() {
+ return Stream.of(
+ Arguments.of("1400", "1500"),
+ Arguments.of("1500", "1600"),
+ Arguments.of("1600", "1700"),
+ Arguments.of("1700", "1701"),
+ Arguments.of("1701", "1702"),
+ Arguments.of("1702", "1800")
+ );
+ }
+
+ private Liquibase initLiquibase() throws Exception {
+ return initLiquibaseForSchema(null);
+ }
+
+ private Liquibase initLiquibaseForSchema(String schema) throws Exception {
+ var connection = initConnection();
+ var database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+ database.setDefaultSchemaName(schema);
+ return new Liquibase("db/changelog/db.changelog-master.yaml", new ClassLoaderResourceAccessor(), database);
+ }
+
+ private Connection initConnection() throws SQLException {
+ return DriverManager.getConnection(
+ postgres.getJdbcUrl(),
+ postgres.getUsername(),
+ postgres.getPassword());
+ }
+
+}