-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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 support for Docker networks. #372
Conversation
It would be nice if some user documentation is added. The signature of the following two is not very intuitive:
|
# Conflicts: # CHANGELOG.md
I'd second @martin-g's comment here; the Please can we re-think? Maybe something simpler (more coupled to JUnit) for the initial release, then go with a universal |
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.
Implementation wise I think this is fine, but the general comment on discoverability of this API is my only worry. Once we've worked out that it's 👍 from me!
@@ -412,7 +418,14 @@ private void applyConfiguration(CreateContainerCmd createCommand) { | |||
.toArray(String[]::new); | |||
createCommand.withExtraHosts(extraHostsArray); | |||
|
|||
if (networkMode != null) { | |||
if (network != null) { | |||
if (!network.isCreated()) { |
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.
Is this likely to happen? If the network hasn't been created, could we infer that the user wants it to be created and trigger creation?
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; | ||
import static org.testcontainers.containers.Network.newNetwork; | ||
|
||
@RunWith(Enclosed.class) |
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.
Neat - I didn't know about this!
CHANGELOG.md
Outdated
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. | |||
- Added pre-flight checks (can be disabled with `checks.disable` configuration property) (#363) | |||
- Removed unused Jersey dependencies (#361) | |||
- Fixed non-POSIX fallback for file attribute reading (#371) | |||
- Added support for the Docker networks (#372) |
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.
Trivial grammar note - the
is unnecessary 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.
Thanks! 👍 One day I will learn how to use them properly :D
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.
Nice addition!
boolean isCreated(); | ||
|
||
default boolean create() { | ||
return getId() != null; |
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.
I would leave out the default implementation- it's too magic: you need to guess that getId is doing the actual create.
Regarding the Boolean - why not fail if can't create? In most chances you can't continue without a network.
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.
default implementation here assumes that there is kinda no need to create a network. If id is known then what else to "create"?
The method returns boolean instead of an exception because it's up to the consumer how to react on that. However, I agree that before
should throw if it returns false
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.
Personally, I prefer the previous approach with .as()
where the contract is clear, but for now we decided to go with an old approach until 2.0
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.
In the impl you placed the logic of creating the network in getId() - that was my point. In the default implementation you assumed getId will be called this create the network if needed. Am I missing something?
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.
I think @asafm point is, that getId()
method here is definitly not a regular getter as one might expect, since it does the actual creation as well. So maybe it's having elegant code leveraging Lombok vs. self-explanatory code at this point.
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.
id
is the only thing needed to use this network. Default implementation assumes "no creation needed" semantic and returns true if id exists. Do not read the implementation to understand the idea of default implementation here.
Example:
class ExistingNetwork implements Network {
String getId() {
return "some-predefined-id";
}
}
// First try to remove by name | ||
dockerClient.removeNetworkCmd(networkName).exec(); | ||
} catch (Exception e) { | ||
LOGGER.trace("Error encountered removing network by name ({}) - it may not have been removed", networkName); |
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.
Two tests running with same network name will fail , at least the second create will. They will not understand why since the failure is hidden under trace level. I would keep this at error level or warn.
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.
network's name is random (see NetworkImpl)
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.
So no way of allowing me to specify a name? I'm Starting to build Testclusters, where I'm reusing containers across runs to shorten test duration. My assumption is same network name across runs. Would love to use the class created here for this.
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 your use case would reuse the containers, it would reuse the network as well, I assume?
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.
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.
I'm reusing across builds, on same machine. Quite similar to docker compose. I can code it with the client my self, but I think it's harmless to leave the opening to set the name to who ever needs it. I'm ok with any direction you take.
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.
Let's think about whether we could allow reuse between runs separately. It's probably quite doable, but for the sake of this PR let's resume later.
|
||
String getName(); | ||
|
||
Boolean getEnableIpv6(); |
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 you really want a Boolean
?
boolean isCreated(); | ||
|
||
default boolean create() { | ||
return getId() != null; |
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.
I think @asafm point is, that getId()
method here is definitly not a regular getter as one might expect, since it does the actual creation as well. So maybe it's having elegant code leveraging Lombok vs. self-explanatory code at this point.
|
||
@Getter(lazy = true) | ||
private final String id = ((Supplier<String>) () -> { | ||
ResourceReaper.instance().registerNetworkForCleanup(getName()); |
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.
I'd prefer to have this in a private method.
// First try to remove by name | ||
dockerClient.removeNetworkCmd(networkName).exec(); | ||
} catch (Exception e) { | ||
LOGGER.trace("Error encountered removing network by name ({}) - it may not have been removed", networkName); |
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 your use case would reuse the containers, it would reuse the network as well, I assume?
|
||
private final String name = UUID.randomUUID().toString(); | ||
|
||
private Boolean enableIpv6; |
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 thing with the Boolean
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.
null here means "the value wasn't provided and withEnableIpv6
should not be called (use a default value from CreateNetworkCmd
)
|
||
@SneakyThrows | ||
@SuppressWarnings("unchecked") | ||
default <T extends Network> T as(Class<T> type) { |
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.
I'm curious to that purpose of this class, which is related to another question I had: why have an interface? Do we plan on users extending and supplying their own Network implementation?
private Set<Consumer<CreateNetworkCmd>> createNetworkCmdModifiers = new LinkedHashSet<>(); | ||
|
||
@Getter(lazy = true) | ||
private final String id = ((Supplier<String>) () -> { |
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.
I don't understand why a variable of type String receives a lambda function, casted to a Suplier of String and then executed? I mean - what's going on?
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.
lazy = true
is the answer
@Override | ||
public boolean isCreated() { | ||
// Lombok with @Getter(lazy = true) will use AtomicReference as a field type for id | ||
return ((AtomicReference<String>) (Object) id).get() != null; |
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.
Just a personal opinion - that's too much magic for such a simple thing as having a field called Id of type String. Maybe it will more clear if for this field, Lombok won't be used.
|
Hi.
One of the highly requested features, especially from users who wanted to run a fleet of containers, i.e. Hazelcast (hey @gAmUssA :D )
Also very helpful for Selenium + container-under-the-test scenario
As you can see, I experimented a bit with DSL, and it seems very usable :)