-
Notifications
You must be signed in to change notification settings - Fork 7
Closes issue #19. Support multiple Docker annotations with networks. #105
base: master
Are you sure you want to change the base?
Closes issue #19. Support multiple Docker annotations with networks. #105
Conversation
d4bd9ea
to
9054d60
Compare
9054d60
to
854ceb5
Compare
import static java.util.Collections.emptySet; | ||
import static java.util.Collections.unmodifiableCollection; | ||
|
||
class ContainerInfo { |
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 fixing my primitive obsession on the container ID :) 👍
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.
:)
String msg = String.format("Multiple networks found with the same name: %s and %s", | ||
name1, | ||
name2); | ||
throw new IllegalStateException(msg); |
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 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 ?
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.
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 :)
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.
@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 ?
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.
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 { |
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 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 ?
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.
yes, agreed :)
I'll get working on those
|
||
containerInfo.getNetworkIds().forEach(networkId -> { | ||
dockerClient.disconnectFromNetwork(containerId, networkId); | ||
dockerClient.maybeRemoveNetwork(networkId); |
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 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 networkId
s. 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
?
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.
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; |
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.
Awesome refactoring ! Makes the code way simpler :) 👍
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! 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); |
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 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 ?
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.
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
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.
Awesome ! Good catch !
@@ -20,6 +21,7 @@ | |||
@Target({ElementType.TYPE}) | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@ExtendWith(DockerExtension.class) | |||
@Repeatable(value = Dockers.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.
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)?
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.
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.
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 ! 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) |
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.
Could we avoid using null
? An empty array would be fine for me (declared in constants ?)
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.
👍
} | ||
|
||
private static ExtensionContext.Store getStore(ExtensionContext context) { | ||
return context.getStore(ExtensionContext.Namespace.GLOBAL); |
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.
Awesome ! Good catch !
@@ -20,6 +21,7 @@ | |||
@Target({ElementType.TYPE}) | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@ExtendWith(DockerExtension.class) | |||
@Repeatable(value = Dockers.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.
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); |
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.
@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() { |
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.
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 ?
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.
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! :)
@hanleyt hi there :) |
No description provided.