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

refactor(core): Standardize configuration and readiness steps in container lifecycle #527

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

santi
Copy link
Collaborator

@santi santi commented Apr 4, 2024

Aims for this PR:

  • Include _configure() and _wait_until_ready() hooks in base DockerContainer image to be used by all container implementations in need of a configuration and readiness step.
  • Standardize method naming for getting a connection string from a container, where a popular client library expects a standardized URL format for connecting to the service provided by the container (E.g. a database URL for DB containers)
  • Remove DbContainer class, as the two points above will render it redundant.

@santi
Copy link
Collaborator Author

santi commented Apr 4, 2024

Referencing #459 for context

@alexanderankin
Copy link
Member

would this have the potential to break existing code that depends on conventions from v3 and v4?

alexanderankin added a commit that referenced this pull request Apr 16, 2024
…ault wait timeout for `wait_for_logs` (#525)

Removes usage of `sqlalchemy`, as part of the work described in
#526.

- Adds default timeout to the `wait_for_logs` waiting strategy, the same
timeout used by default in the `wait_container_is_ready` strategy.
- Changes wait strategy for `mysql` container to wait for logs
indicating that the DB engine is ready to accept connections (MySQL
performs a restart as part of its startup procedure, so the logs will
always appear twice.
- Add More tests for different `mysql` and `mariadb` versions to ensure
consistency in wait strategy.
- Remove x86 emulation for ARM devices for MariaDB, as it MariaDB images
support ARM architectures already.
- Change wait strategy for `oracle-free`, as the images produce a
consistent `DATABASE IS READY TO USE!` log message on startup.


Next steps will be to remove `sqlalchemy` as a bundled dependency
entirely, but I have not included it in this PR as I consider it a
bigger change than just changing wait strategies as an internal
implementation detail. I plan to do this as part of a bigger rework
where i remove the `DbContainer` class and standardize configuration
hooks and wait strategies across containers (not just DB containers, all
containers in need of a configuration and readiness step). See
#527 for
WIP.

---------

Co-authored-by: David Ankin <[email protected]>
Returns a connection URL following the RFC-1738 format.
Compatible with database clients such as SQLAlchemy and other popular database client libraries.

Example: postgres+psycopg2://myuser:mypassword@localhost:5432/mytestdb
Copy link
Contributor

@jankatins jankatins Apr 26, 2024

Choose a reason for hiding this comment

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

It would be nice to document that setting driver to None or "" will remove the driver from the URL (psycopg v3 does need a url without dialect and any regular http URL also needs that :-))

Or at least add an example without driver.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would actually split this into a generic "create_connection_url" (scheme instead of dialect and driver, path instead of dbname) and one call (create_db_api_connection_url?) which takes db api inputs (like the current one) and calls create_connection_url with the corresponding arguments.

@@ -53,6 +54,43 @@ def inside_container() -> bool:
return os.path.exists("/.dockerenv")


def create_connection_string(

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

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

create_connection_url -> e.g. you couldn't create a kafka one with this, couldn't you?

@@ -53,6 +54,43 @@ def inside_container() -> bool:
return os.path.exists("/.dockerenv")


def create_connection_string(
dialect: str,
Copy link
Contributor

Choose a reason for hiding this comment

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

If this would be DB API only, I would say "dialect" is fine, but if this should also be used to generate http URLs, I would call this base_scheme.

... client = BlobServiceClient.from_connection_string(
... connection_string,
... azurite_container.get_connection_string(),
Copy link
Contributor

Choose a reason for hiding this comment

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

For ArangoDbContainer is get_conenction_url`, here it's '*_string'. Should we unify that?

@wait_container_is_ready(OSError)
def _connect(self) -> None:
def _wait_until_ready(self) -> None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((self.get_container_host_ip(), int(self.get_exposed_port(next(iter(self.ports))))))
Copy link
Contributor

Choose a reason for hiding this comment

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

Might it make sense to also pull this into utils as a wait_for_port? At least I had the need in my code to basically write something similar:

        @wait_container_is_ready(NoSuchPortExposed)
        def _wait_for_port() -> None:
            zks.get_service_port("schemaregistry", 8085)

        _wait_for_port()


return response.json()

def get_influxdb_version(self) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this function be kept? It's seems it has value outside the normal startup procedure?

],
)
def test_influxdbcontainer_get_influxdb_version(
image: str, influxdb_container_class: Type[InfluxDbContainer], expected_version: str
):
with influxdb_container_class(image) as influxdb_container:
assert influxdb_container.get_influxdb_version().startswith(expected_version)
version = get(f"{influxdb_container.get_url()}/health").json()["version"]
assert version == expected_version
Copy link
Contributor

Choose a reason for hiding this comment

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

This test implies that influxdb_container.get_influxdb_version() is API?

@@ -43,13 +54,29 @@ def __init__(
self.dbname = dbname
self.dialect = dialect

@wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS)
def _wait_until_ready(self):
import sqlalchemy
Copy link
Contributor

Choose a reason for hiding this comment

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

This will import sqla and fail the tests if it is not available. Which is something we just removed form PG and kafka. Is there no way to check for a port with nc or so?

microsoft/mssql-docker#133 has some interesting suggestion for a healthcheck

Copy link
Contributor

Choose a reason for hiding this comment

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

See fefb9d0 where it is already removed?


def _wait_until_ready(self) -> None:
pass

def start(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def start(self):
def start(self) -> Self:

Not sure what version we target (so either whats in python or via an backport), but I saw that this PR removes a lot of overwritten start() methods which just do this to get the proper type passed out, so not doing this here will mean that typing will regress.

@alexanderankin
Copy link
Member

if i were to try to merge this, what should i keep in mind @santi ? Anything not already reflected here? I did a first pass and seems like i will have to revisit this several times in order to make sense of this fully...

also, like i asked above, is this breaking at all?

@santi
Copy link
Collaborator Author

santi commented May 20, 2024

@alexanderankin Hey, sorry for the delay on this one. This is just a WIP draft that is not ready for a merge quite yet, and has probably drifted a bit from the main branch. I intend on getting this one up-to-date this week, and do a nice write-up on the implications and document the intention behind the changes / API standardisation it introduces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants