Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mapping volumes in Dev Service container based databases #31859

Merged
merged 1 commit into from
Mar 21, 2023

Conversation

Sgitario
Copy link
Contributor

This is a very useful feature that allows mapping volumes in the Dev Service containers.

For example: to keep the Postgres data folder in the localsystem and hence have the data persistently.

Fix #30595

@quarkus-bot

This comment has been minimized.

@geoand
Copy link
Contributor

geoand commented Mar 16, 2023

I think this is a great feature!

But I'll defer reviewing to @yrodiere since it involves the Datasource code

Copy link
Member

@yrodiere yrodiere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

The feature looks nice indeed, but there's some danger involved as Hibernate ORM will clear the database on startup by default. So if you mount a volume on your devservice, and don't take care to configure Hibernate ORM appropriately, you will lose data.

Granted, I don't expect any sensible person to mount valuable data there, but still, I think some more documentation is in order:

  1. Clarify in the javadoc of that new configuration property that data contained in mounted volumes may be altered by the application in dev mode (and tests!), and may be completely wiped out.
  2. Maybe add a subsection with an example in the databases-dev-services documentation, explaining in more details the risks (in particular with Hibernate ORM, but probably also Flyway, ...)? For ORM, essentially if you don't use Flyway/Liquibase and don't set quarkus.hibernate-orm.database.generation your devservice schema and data will be wiped out on startup; if you use Flyway/Liquibase or set quarkus.hibernate-orm.database.generation to none or validate you're safe.

@stuartwdouglas
Copy link
Member

The code LGTM, but it would be great to see some docs with real world (e.g. postgres and mysql) examples of how you actually do this. I know you could figure it out by reading the relevant containers docs but a copy and paste example would be way more user friendly imho.

@Sgitario
Copy link
Contributor Author

Thanks for your suggestions! I will be working on this.

@Sgitario
Copy link
Contributor Author

PR updated with more documentation and a couple of examples. I verified each of these examples using the following tests:

public class DevServicesPostgresqlDatasourceWithFileVolumeTestCase {

    @RegisterExtension
    static QuarkusUnitTest test = new QuarkusUnitTest()
            .overrideConfigKey("quarkus.datasource.db-kind", "postgresql")
            .overrideConfigKey("quarkus.datasource.devservices.volumes.\"/home/jcarvaja/sources/tmp/data\"",
                    "/var/lib/postgresql/data");

    @Inject
    DataSource ds;

    // I executed this test to create my database schema and the data, and then I disabled to only execute the following one.
    @Disabled 
    @Test
    public void create() throws Exception {
        try (Connection con = ds.getConnection()) {
            con.createStatement()
                    .execute("CREATE TABLE PERSON (name VARCHAR(50) PRIMARY KEY)");
            con.createStatement()
                    .execute("INSERT INTO PERSON (name) VALUES ('JOHN')");
        }
    }

    // In the second round, this test passed!
    @Test
    public void testDatasource() throws Exception {
        String name = "";
        try (Connection con = ds.getConnection();
                CallableStatement cs = con.prepareCall("SELECT name FROM PERSON");
                ResultSet rs = cs.executeQuery()) {
            if (rs.next()) {
                name = rs.getString(1);
            }
        }
        assertEquals("JOHN", name, "The volume was not properly mounted");
    }
}

And for MySQL:

public class DevServicesMySqlDatasourceWithFileVolumeTestCase {

    @RegisterExtension
    static QuarkusUnitTest test = new QuarkusUnitTest()
            .overrideConfigKey("quarkus.datasource.db-kind", "mysql")
            .overrideConfigKey("quarkus.datasource.devservices.volumes.\"/home/jcarvaja/sources/tmp/mysqldata\"",
                    "/var/lib/mysql");

    @Inject
    DataSource ds;

    // I executed this test to create my database schema and the data, and then I disabled to only execute the following one.
    @Disabled 
    @Test
    public void create() throws Exception {
        try (Connection con = ds.getConnection()) {
            con.createStatement()
                    .execute("CREATE TABLE PERSON (name VARCHAR(50) PRIMARY KEY)");
            con.createStatement()
                    .execute("INSERT INTO PERSON (name) VALUES ('JOHN')");
        }
    }

    // In the second round, this test passed!
    @Test
    public void testDatasource() throws Exception {
        String name = "";
        try (Connection con = ds.getConnection();
                CallableStatement cs = con.prepareCall("SELECT name FROM PERSON");
                ResultSet rs = cs.executeQuery()) {
            if (rs.next()) {
                name = rs.getString(1);
            }
        }
        assertEquals("JOHN", name, "The volume was not properly mounted");
    }
}

Note that to make Postgresql worked, I had to modify the log waiting logic because of one issue in Testcontainers side.

Copy link
Member

@yrodiere yrodiere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! A few suggestions below, mostly about wording.

docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/databases-dev-services.adoc Outdated Show resolved Hide resolved
* When using a file system location, the volume will be created with read-write permission, so the data in your file
* system might be wiped out or altered.
*
* If the provider is not container based (e.g. an H2 or Derby Database) then this has no effect.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add some code to throw an exception if that happens?

Copy link
Contributor Author

@Sgitario Sgitario Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other properties that are container-based and that do not have this exception, so I would not over-complicate the logic here. We can always add it if it's really necessary.


import io.quarkus.test.QuarkusUnitTest;

public class DevServicesPostgresqlDatasourceWithVolumeTestCase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note you could probably take advantage of QuarkusDevModeTest to test that data is persisted upon restart... Though I don't know how to force a restart, except by modifying a file like in io.quarkus.hibernate.orm.HibernateHotReloadTestCase#testImportSqlWithContinuousTesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not mapping the db data, just using a volume where there is a script.
Maybe we could add another test case to verify this use case using QuarkusDevModeTest (actually, I have these tests in another repository because I wanted to be sure about it), but I don't think it's really necessary to be covered here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The container is not restarted on hot reload, so testing by modifying the file does not really prove anything with regards to how container volumes are handled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. We'd need to modify application.properties and change very specific configuration properties to trigger a restart:

boolean restartRequired = false;
if (!restartRequired) {
for (Map.Entry<String, String> entry : cachedProperties.entrySet()) {
if (!Objects.equals(entry.getValue(),
trim(ConfigProvider.getConfig().getOptionalValue(entry.getKey(), String.class).orElse(null)))) {
restartRequired = true;
break;
}
}
}
if (!restartRequired) {
//devservices properties may have been added
for (var name : ConfigProvider.getConfig().getPropertyNames()) {
if (name.startsWith("quarkus.datasource.") && name.contains(".devservices.")
&& !cachedProperties.containsKey(name) && !EXCLUDED_PROPERTIES.contains(name)) {
restartRequired = true;
break;
}
}
}
if (!restartRequired) {
for (RunningDevService database : databases) {
devServicesResultBuildItemBuildProducer.produce(database.toBuildItem());
}
// keep the previous behaviour of producing DevServicesDatasourceResultBuildItem only when the devservices first starts.
return null;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is out of the scope of this pull request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think testing the features we document in a PR ("is handy [...] to preserve database data and reuse it after an application restart") is very much in scope of that PR.
But I'll grant you this opinion isn't widely shared.

@github-actions
Copy link

github-actions bot commented Mar 17, 2023

🙈 The PR is closed and the preview is expired.

@quarkus-bot

This comment has been minimized.

@Sgitario
Copy link
Contributor Author

I think the failures are unrelated to these changes.
Would you mind having another round to review this PR? @yrodiere @stuartwdouglas

Copy link
Member

@yrodiere yrodiere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the feature is ready, and it's nice. Thanks for that.

I also think the tests you ran manually (and posted somewhere in a comment) should be automated and included in this PR, because otherwise they'll never make it into the code base and then we won't notice regressions.

That being said, feel free to merge if you don't want to work on those tests.

This is a very useful feature that allows mapping volumes in the Dev Service containers. 

For example: to keep the Postgres data folder in the localsystem and hence have the data persistently. 

Fix quarkusio#30595

Co-authored-by: Yoann Rodière <[email protected]>
@quarkus-bot quarkus-bot bot added the area/persistence OBSOLETE, DO NOT USE label Mar 21, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Mar 21, 2023

Failing Jobs - Building 00625d0

Status Name Step Failures Logs Raw logs
✔️ JVM Tests - JDK 11
✔️ JVM Tests - JDK 17
JVM Tests - JDK 17 Windows Build Failures Logs Raw logs
✔️ JVM Tests - JDK 19

Full information is available in the Build summary check run.

Failures

⚙️ JVM Tests - JDK 17 Windows #

- Failing: integration-tests/mongodb-panache 

📦 integration-tests/mongodb-panache

io.quarkus.it.mongodb.panache.reactive.ReactiveMongodbPanacheResourceTest.testMoreRepositoryFunctionalities line 351 - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <500>.

@Sgitario
Copy link
Contributor Author

I think the feature is ready, and it's nice. Thanks for that.

I also think the tests you ran manually (and posted somewhere in a comment) should be automated and included in this PR, because otherwise they'll never make it into the code base and then we won't notice regressions.

That being said, feel free to merge if you don't want to work on those tests.

I feel that the example only exposes a very handy use case and yes, ideally it should be covered by tests, but taking into account the complexity, let's merge it as it is and we can add tests if required in the future.

The test failures are unrelated. Merging

@Sgitario Sgitario merged commit 73499a7 into quarkusio:main Mar 21, 2023
@Sgitario Sgitario deleted the 30595 branch March 21, 2023 14:41
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label Mar 21, 2023
@quarkus-bot quarkus-bot bot added this to the 3.0 - main milestone Mar 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Persist devservices database
4 participants