Skip to content
This repository has been archived by the owner on Sep 6, 2024. It is now read-only.

Closes issue #19. Support multiple Docker annotations with networks. #105

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

hanleyt
Copy link

@hanleyt hanleyt commented Jan 26, 2018

No description provided.

@hanleyt hanleyt force-pushed the support-starting-multiple-containers branch 3 times, most recently from d4bd9ea to 9054d60 Compare January 26, 2018 19:02
@hanleyt hanleyt force-pushed the support-starting-multiple-containers branch from 9054d60 to 854ceb5 Compare January 26, 2018 19:19
@hanleyt hanleyt changed the title Support multiple Docker annotations (containers) with networks. Fixes… Closes #19. Support multiple Docker annotations with networks. Jan 26, 2018
@hanleyt hanleyt changed the title Closes #19. Support multiple Docker annotations with networks. Closes issue #19. Support multiple Docker annotations with networks. Jan 26, 2018
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableCollection;

class ContainerInfo {
Copy link
Owner

Choose a reason for hiding this comment

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

Thanks for fixing my primitive obsession on the container ID :) 👍

Copy link
Author

Choose a reason for hiding this comment

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

:)

String msg = String.format("Multiple networks found with the same name: %s and %s",
name1,
name2);
throw new IllegalStateException(msg);
Copy link
Owner

Choose a reason for hiding this comment

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

Is it really something that can happen or is it a docker bug ?
If it's a docker bug, I think we could just get the first network having the right name. What do you think ?

Copy link
Author

Choose a reason for hiding this comment

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

Hmm for some reason i thought i saw this happening during development when the networks weren't being cleaned up correctly so I added this guard.
But testing it again now it seems you're right, that you can't have multiple networks with the same name. So i'll change it back to find first :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

@hanleyt @FaustXVI I think there is a case you can if they don't have the same scope — i.e. one is local (bridge) and the other would be swarm (overaly — created elsewhere on the cluster). That said, I think it's an edge case and shouldn't really happen that much 😓

Copy link
Owner

Choose a reason for hiding this comment

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

@vdemeester well, I think it's the kind of edge case we actually don't want to handle. I think that when you start to have this kind of configuration, you're not really on a development machine (dev or ci). So maybe we should actually crash at the network creation time if the network already exists. What do you think ? or we can avoid the crash by appending a random value to it ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Both would work (i.e. let the thing crash at network creation, or adding a random value to make sure it's unique)

@Docker(image = "faustxvi/simple-two-ports", ports = @Port(exposed = 8082, inner = 8080),
networks = {TEST_NETWORK_1, TEST_NETWORK_2})})
@DisplayName("Multiple docker containers with multiple networks")
class StartMultipleDockerAnnotationsIT {
Copy link
Owner

Choose a reason for hiding this comment

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

Thanks a lot for that tests !

We also have the DefaultDockerClientIT that you way want to add tests to (for the network). It will allow us to detect misusage of the Docker-java framework more easily (already helped us a lot :) )

Another thing we like to do it to add scenarios to the index.feature file. It allows you to document your feature in a more natural way and is used to generate the website (so you won't repeat yourself and the website is always up-to-date).

By the way, it looks to me like you're adding two features : multiple containers and network. I agree that the network doesn't make sens without the multiple container feature. But I think having multiple containers without (shared) networks still makes sense. What do you think ?

Copy link
Author

Choose a reason for hiding this comment

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

yes, agreed :)
I'll get working on those


containerInfo.getNetworkIds().forEach(networkId -> {
dockerClient.disconnectFromNetwork(containerId, networkId);
dockerClient.maybeRemoveNetwork(networkId);
Copy link
Owner

Choose a reason for hiding this comment

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

The goal of the dockerClient interface is to define the interface we'd like to have with docker.

Since we want to clean up behind us, I would argue that we want to disconnect from all networks and (always?) remove them. If so, the Docker extension doesn't need to know the networkIds. The DockerClient can do it for us.

I think that stopAndRemoveContainer is the perfect place to do so. Maybe we should rename it to stopAndCleanContainer ?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, good idea! that makes sense and would be much cleaner


class DockerExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {

private final DockerClientAdapter dockerClient;

private String containerId;
private final DockerLogChecker dockerLogChecker;
Copy link
Owner

Choose a reason for hiding this comment

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

Awesome refactoring ! Makes the code way simpler :) 👍

Copy link
Author

Choose a reason for hiding this comment

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

thanks! it was motivated by checkstyle/pmd saying the class had too many dependencies :)

}

private static ExtensionContext.Store getStore(ExtensionContext context) {
return context.getStore(ExtensionContext.Namespace.GLOBAL);
Copy link
Owner

Choose a reason for hiding this comment

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

I didn't know about the store. Looks ok to me but I don't understand why you prefered this vs having a field in the class ?

Copy link
Author

Choose a reason for hiding this comment

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

From what I understand its the recommended way to store state in extensions, and that extension classes should be stateless. It is essentially just a namespaced, hierarchical map, and having a map as a class field I imagine would perform in the exact same way.
But apparently there are no guarantees about the exension lifecycle, so its better to use the store to store state. Plus it could be used by other extensions if they wanted to access the test state.

https://blog.codefx.org/design/architecture/junit-5-extension-model/#Stateless

Copy link
Owner

Choose a reason for hiding this comment

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

Awesome ! Good catch !

@@ -20,6 +21,7 @@
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(DockerExtension.class)
@Repeatable(value = Dockers.class)
Copy link
Owner

Choose a reason for hiding this comment

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

If reapeted, don't we risk to call the DockerExtension several times ? Would removing the ExtendWith fix the problem (since you added it to the Dockers annotation)?

Copy link
Author

Choose a reason for hiding this comment

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

From the docs it seems that registering an extension a second time is fine, that the second attempt will be ignored.
http://junit.org/junit5/docs/current/user-guide/#extensions-registration-inheritance

By keeping it this way we allow users to still use the single Docker annotation by itself, without always having to wrap it in the Dockers annotation.

Copy link
Owner

Choose a reason for hiding this comment

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

Great ! Let's go for it then :)

@@ -90,7 +89,8 @@ public void ensureImageIsPulled() {
@Test
@DisplayName("start a container without ports")
public void shouldStartContainer() {
String containerId = defaultDockerClient.startContainer(WANTED_IMAGE, emptyMap());
String containerId = defaultDockerClient.startContainer(WANTED_IMAGE, emptyMap(), null)
Copy link
Owner

Choose a reason for hiding this comment

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

Could we avoid using null ? An empty array would be fine for me (declared in constants ?)

Copy link
Author

Choose a reason for hiding this comment

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

👍

}

private static ExtensionContext.Store getStore(ExtensionContext context) {
return context.getStore(ExtensionContext.Namespace.GLOBAL);
Copy link
Owner

Choose a reason for hiding this comment

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

Awesome ! Good catch !

@@ -20,6 +21,7 @@
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(DockerExtension.class)
@Repeatable(value = Dockers.class)
Copy link
Owner

Choose a reason for hiding this comment

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

Great ! Let's go for it then :)

String msg = String.format("Multiple networks found with the same name: %s and %s",
name1,
name2);
throw new IllegalStateException(msg);
Copy link
Owner

Choose a reason for hiding this comment

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

@vdemeester well, I think it's the kind of edge case we actually don't want to handle. I think that when you start to have this kind of configuration, you're not really on a development machine (dev or ci). So maybe we should actually crash at the network creation time if the network already exists. What do you think ? or we can avoid the crash by appending a random value to it ?

this.networkIds = networkIds == null ? emptySet() : unmodifiableCollection(networkIds);
}

String getContainerId() {
Copy link
Owner

Choose a reason for hiding this comment

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

oh, i forgot it last time :) Since this is just a immutable data structure, public fields or fine for me. I don't see any value in getters.
What's your opinion on this ?

Copy link
Author

@hanleyt hanleyt Jan 29, 2018

Choose a reason for hiding this comment

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

Hmm I've always done this out of habit after reading effective java. But on further inspection its fine to access fields directly if the class isn't public, and even more so if the fields are immutable.
So sure, I'll make that change. Thanks! :)

@FaustXVI
Copy link
Owner

@hanleyt hi there :)
How are you ?
Any news on his PR ? That's awesome work ! Almost there ! Do you need any help ?
Regards,

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

Successfully merging this pull request may close these issues.

3 participants