-
Notifications
You must be signed in to change notification settings - Fork 141
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
[DRAFT] add extension traits to run container without explicitly creating a c… #503
[DRAFT] add extension traits to run container without explicitly creating a c… #503
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @thomaseizinger I created this in progress pull request to validated my understanding. Could you please take a look and let me know if what I am doing makes sense here?
inner: Arc<Client>, | ||
static CLI_DOCKER: OnceLock<Client> = OnceLock::new(); | ||
fn docker_client() -> &'static Client { | ||
CLI_DOCKER.get_or_init(|| Client::new::<env::Os>()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we have global client which is used implicitly by Docker
implementation.
testcontainers/src/clients/cli.rs
Outdated
@@ -464,6 +392,59 @@ impl Drop for Client { | |||
} | |||
} | |||
|
|||
pub trait RunViaCli<I: Image> { | |||
fn run(self) -> Container<I>; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ extension trait to use CLI as docker backend.
testcontainers/src/clients/cli.rs
Outdated
} | ||
|
||
impl<I: Image> RunViaCli<I> for RunnableImage<I> { | ||
fn run(self) -> Container<I> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run
function moved from Http
to RunnableImage
it can be directly called on RunnableImage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this function up in the file such that the diff is minimal? That would help reviewing a lot :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be better now. 👍
testcontainers/src/clients/http.rs
Outdated
} | ||
} | ||
env::Command::Keep => {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something we need to talk I believe. I recently learned that static variables don't actually call Drop
so we might have to move this logic to WatchDog
or find another way to create global static variable.
Arc could be useful but I am not sure how to create and Arc that behaves like static variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah a static
variable will only be dropped at the end of a program.
Rust executes each test in its own thread so we could look into using a thread-local variable for this. That would clean up a docker client at the end of each thread, i.e. at the end of each test and each test would gets its own docker client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now the tests are updated there are 3 failing tests and all of them about related to "networks" which were suppose to be cleaned up but it's not working due to the issue I mentioned.
failures:
clients::cli::tests::should_create_network_if_image_needs_it_and_drop_it_in_the_end
clients::cli::tests::should_not_delete_network_if_command_is_keep
clients::http::tests::http_should_create_network_if_image_needs_it_and_drop_it_in_the_end
I see 3 options here;
-
The method you mentioned - (I am not very familiar with rust in that regard so if you let me know how this could be achieved I can take a look)
-
We can integrated with ryuk which would handle all kind of clean-up scenarios also align with other testcontainers projects. (I am not %100 sure but this would mean that some test cases can't be run in parallel) This approach is similar to watchdog feature we have in this repo but ryuk is client agnostic.
-
For this specific use case, we can keep track of number of containers which uses the given network and remove at when the last container is dropped.
// assume this is global variable
let networks = Mutex<Map<String,i32>> // network_name -> number_of_containers_using_the_network
// inside start function
insert or increase the number of reference to given network key in global map
// inside impl drop for container
decrease the number of reference to given network. If number of reference is 0 after this operation remove the network as well.
I personally prefer option two so we can align with other implementations and also I like the ryuk approach in terms of design but I would like to hear what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially, what we need to achieve is that a client is only alive for as long as any of its containers are alive. Thus I'd suggest the following:
- Store a
Mutex<Option<Weak<Client>>>
in the static variable - When you try to get a reference to the client:
- Grab a lock to the mutex
- If the value is
None
:- Make a new client
- Wrap it in an
Arc
- Make a copy
- Downgrade the copy to a
Weak
- Store the
Weak
inside theOption
- Release the lock
- Return the
Arc
you still have around
- If the value is
Some
:- Attempt to upgrade to an
Arc
- If successful, return
- If failed, do the same thing as for the above
None
case
- Attempt to upgrade to an
I believe this should achieve the following:
- As long is there is at least 1
Container
around, we can grab a reference to it and make more containers. - If all containers are dropped, the last strong reference to the
Client
is also dropped which meansDrop
is being called onClient
I see integration with ryuk
as a separate feature.
Will review next week! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great start overall! I'd like to see some tests updated so we get an idea what the API feels like.
testcontainers/src/clients/cli.rs
Outdated
} | ||
|
||
impl<I: Image> RunViaCli<I> for RunnableImage<I> { | ||
fn run(self) -> Container<I> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this function up in the file such that the diff is minimal? That would help reviewing a lot :)
testcontainers/src/clients/http.rs
Outdated
} | ||
} | ||
env::Command::Keep => {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah a static
variable will only be dropped at the end of a program.
Rust executes each test in its own thread so we could look into using a thread-local variable for this. That would clean up a docker client at the end of each thread, i.e. at the end of each test and each test would gets its own docker client.
b534af7
to
f6d456f
Compare
…ap with RunnableImage::from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late review, I don't get to spend much time with this repository.
Great progress though, I left some comments :)
} | ||
} | ||
} | ||
|
||
impl Docker for Cli { | ||
impl Docker for &Client { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need the Docker
trait now? By virtue of the RunViaCli
trait, the implementation is no longer pluggable so I believe we could just remove it now.
@@ -276,7 +269,7 @@ impl Drop for Client { | |||
} | |||
|
|||
#[async_trait] | |||
impl DockerAsync for Http { | |||
impl DockerAsync for &Client { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I think we can remove this trait and just say impl Client
here.
let generic = GenericImage::new("simple_web_server", "latest").with_wait_for(msg.clone()); | ||
|
||
let node = docker.run(generic); | ||
let node = generic.start(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I like about this new API is that we can chain it, can we rewrite the tests to do that?
let image = GenericImage::new("hello-world", "latest"); | ||
let image = RunnableImage::from(image).with_container_name("hello_container"); | ||
let container = docker.run(image).await; | ||
let container = RunnableImage::from(image) | ||
.with_container_name("hello_container") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we figure out a way to make this more ergonomic? Ideally, I'd like users to never need to construct RunnableImage
but only Image
s. with_container_name
is defined in a trait, right? Does it already work to just write:
GenericImage::new().with_container_name().start().await;
This is an in progress work regarding to #386 . I just wanted to share the current prototype I have to validate my understanding.