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

Add support for Docker networks. #372

Merged
merged 13 commits into from
Jun 25, 2017
Merged

Add support for Docker networks. #372

merged 13 commits into from
Jun 25, 2017

Conversation

bsideup
Copy link
Member

@bsideup bsideup commented Jun 17, 2017

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 :)

@bsideup bsideup added this to the 1.3.1 milestone Jun 17, 2017
@bsideup bsideup requested review from rnorth and kiview June 17, 2017 18:39
@martin-g
Copy link

It would be nice if some user documentation is added.

The signature of the following two is not very intuitive:

 public Network.JUnitRule network = newNetwork().as(Network.JUnitRule.class);
 Network network = newNetwork().as(Network.AutoCreated.class);

@rnorth
Copy link
Member

rnorth commented Jun 21, 2017

I'd second @martin-g's comment here; the as approach is clever but I worry that it's not intuitive. I think anybody using this would have to be copying-and-pasting from an example. I'm not sure that somebody using just method signatures in-IDE or Javadocs would be able to work out that they have to do this.

Please can we re-think? Maybe something simpler (more coupled to JUnit) for the initial release, then go with a universal as(JUnit4Rule.class) pattern for both networks and containers in v2 (or similar)...

Copy link
Member

@rnorth rnorth left a 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()) {
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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

Copy link
Member Author

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

@bsideup bsideup modified the milestones: 1.4.0, 1.3.1 Jun 23, 2017
Copy link
Contributor

@asafm asafm left a 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;
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 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.

Copy link
Member Author

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

Copy link
Member Author

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

Copy link
Contributor

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?

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 @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.

Copy link
Member Author

@bsideup bsideup Jun 24, 2017

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);
Copy link
Contributor

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.

Copy link
Member Author

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)

Copy link
Contributor

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.

Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

@asafm we don't allow users to set names of containers (and now networks) because it might lead to the names conflict (especially with parallel execution).

As @kiview said: if you reuse things, reuse the networks also

Copy link
Contributor

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.

Copy link
Member

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();
Copy link
Member

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;
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 @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());
Copy link
Member

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);
Copy link
Member

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;
Copy link
Member

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.

Copy link
Member Author

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) {
Copy link
Contributor

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>) () -> {
Copy link
Contributor

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?

Copy link
Member Author

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;
Copy link
Contributor

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.

@bsideup bsideup changed the title Add support for the Docker networks. Add support for Docker networks. Jun 24, 2017
@bsideup
Copy link
Member Author

bsideup commented Jun 24, 2017

  • Simplified the code
  • Removed getters from Network interface (can be accessed with NetworkImpl if needed)
  • Removed isCreated
  • Less Lombok magic

@rnorth rnorth merged commit 3e513be into master Jun 25, 2017
@rnorth rnorth deleted the networks branch June 25, 2017 16:24
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.

5 participants