-
Notifications
You must be signed in to change notification settings - Fork 142
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
Add udp and sctp support to port mappings #246
Conversation
Maybe a better solution for the PortMapping would look like this:
Do we really need the lookup of the mapped host port for an internal port? |
Yes we need that otherwise we don't know what ports the OS assigned to us if we start the containers with Also, have you tried running the tests locally with |
I ok, now this makes sense. I could imagine two interface to get the mapped host port. Either something like: enum Port {
Tcp(u16),
Udp(u16),
Sctp (u16)
}
....
pub fn get_host_port(&self, internal_port: Port) -> Option<u16> {
...
} or pub fn get_host_port_tcp(&self, internal_port: u16) -> Option<u16> {
...
}
pub fn get_host_port_udp(&self, internal_port: u16) -> Option<u16> {
...
} Edit the single functions just need to resturn |
Thanks for your proposal! I think it will be valuable if the default case (a TCP port mapping) is easily accessible. As such, what do you think about an API along the lines of: enum Protocol {
Tcp,
Udp,
Sctp
}
pub fn map_to_host_port(&self, internal_port: u16) -> Option<(u16, Protocol)> {
...
} A caller could then simply discard the protocol if they don't care about it (or if they know that it is always a specific one) like this: let (port, _) = container.map_to_host_port(8080).unwrap(); This actually got me thinking, do we ever care about which protocol this port was mapped on? I feel like in the context of testcontainers, we actually have an expectation in that regard. The postgres container for example will always expose a TCP port mapping on port 5432. The information of What I am trying to say is: Can we maybe just leave the mapping API as is and simply search for the mapped port in the |
That would be nice, but unfortunately it's possible to bind different protocols to the same port. Something as Another possible solution could be to use the type system. For the TCP case one could just call the functions (e.g. let port = container.get_host_port(UdpPort(8080)).unwrap() |
That is interesting! Thanks for sharing! I think we might be able to achieve a nice API with some trait magic: Imagine fn get_host_port<P>(&self, internal_port: P) -> u16 where P: Into<(u16, Protocol)> {
let (port, protocol) = internal_port.into();
// ...
} We can then implement the following traits: impl Into<(u16, Protocol)> for &str {
fn into(self) -> (u16, Protocol) {
// parse self as '{port}/{udp,tcp,sctp}'
// panic in parsing code if necessary, we are only within tests so panicking is fine
// return tuple of port and protocol
}
}
impl Into<(u16, Protocol)> for u16 {
// default to TcpProtocol for implementations of port
} The user can then call the function in various ways: container.get_host_port(8080);
container.get_host_port("8080/tcp");
container.get_host_port("8080/udp"); Note that I changed the |
Hi, the current solution should be fully backward compatible. I think it still needs some refactoring and some test, but then it should be ready to go. Would like to here your thoughts. |
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.
Thanks for working on this!
Please note that parts of the codebase changed quite a bit with the recent work on an async interface and the shiplift integration. Can you rebase this PR onto latest dev
please?
I've also adapted the parsing code for the ports a bit so it is hopefully easier to adapt to extract the protocol.
I think there are two things we need to still adapt here:
- Users need to be able to specify the protocol when resolving the port. Currently the trait bound is only
Into<u16>
. - We should also adapt the API of
RunArgs
such that users can configure arbitrary port mappings there.
Let me know how the rebase goes. If it is too cumbersome, it might be easier to just start again from current dev! Sorry for the inconvenience.
src/core/container.rs
Outdated
pub fn get_host_port(&self, internal_port: u16) -> Option<u16> { | ||
pub fn get_host_port<T>(&self, internal_port: T) -> Option<u16> | ||
where | ||
T: Into<u16> + Copy, |
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.
If this is only Into<u16>
, how are we going to map a non-TCP port? As far as I understand, the user needs to able to specify the protocol here.
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.
We might need to create our own trait here due to coherence rules.
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.
The Into<u16>
is misleading here, it only is necessary for the debug print, I think it would be better to switch to {:?}
there. The next line is the important one, because it let us forward the call to the type specific implementation of map_to_host_port
. You can try it in this Playground example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=100daefbe4f085548bfc3c620f522b82
I should even change the return value into Option<T>
, so that the user couldn't accidentally mix up different port types - missed that here, because it was a bit late as a have done the commit.
I think the same idea should work for the configuration part.
4b2644d
to
9581810
Compare
Now, only additional documentation and some test are missing. |
I have tried it with my original use case and it seems to work as expected. But, I think the |
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.
Thank you!
I think we still need to iron out a few details before this can merge :)
.unwrap_or_else(|| { | ||
panic!( | ||
"container {} does not expose port {}", | ||
"container {:?} does not expose port {:?}", |
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.
Why do we need to print self.id
as Debug now?
"sctp" => { | ||
mappings.sctp.insert(Sctp(internal), Sctp(external)); | ||
} | ||
_ => panic!("Not a valid port mapping."), |
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.
_ => panic!("Not a valid port mapping."), | |
p => panic!("Unknown protocol '{}'", p), |
@@ -128,13 +133,17 @@ impl<'d, I> Container<'d, I> { | |||
/// This method panics if the given port is not mapped. | |||
/// Testcontainers is designed to be used in tests only. If a certain port is not mapped, the container | |||
/// is unlikely to be useful. | |||
pub fn get_host_port(&self, internal_port: u16) -> u16 { | |||
pub fn get_host_port<T>(&self, internal_port: T) -> T |
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.
How does one go about calling this API? From what I can see, everything that implements MapToHostPort
is actually private and can't be used by users outside of the library.
To make sure this doesn't happen, can you add an example in examples/
(dir doesn't exist yet). Rust examples are compiled as part of the tests so that will be a good way of ensuring people can actually use the API :)
.into_iter() | ||
.filter_map(|(internal, external)| { | ||
// internal is '8332/tcp', split off the protocol ... | ||
let internal = internal.split('/').next()?; | ||
// internal is ')8332/tcp', split off the protocol ... |
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.
A typo got into this comment :)
// internal is ')8332/tcp', split off the protocol ... | |
// internal is '8332/tcp', split off the protocol ... |
Closing due to inactivity and outdated state. |
Resolves #234