-
Notifications
You must be signed in to change notification settings - Fork 615
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
Fix issues of multiple published ports mapping to the same target port #1835
Fix issues of multiple published ports mapping to the same target port #1835
Conversation
Current coverage is 54.62% (diff: 94.87%)@@ master #1835 diff @@
==========================================
Files 102 102
Lines 17025 17041 +16
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 9304 9309 +5
- Misses 6585 6593 +8
- Partials 1136 1139 +3
|
allocatedPorts[getPortConfigKey(portState)] = portState | ||
portKey := getPortConfigKey(portState) | ||
if _, ok := allocatedPorts[portKey]; !ok { | ||
allocatedPorts[portKey] = make(map[uint32]*api.PortConfig) |
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.
Wondering; if getPortConfigKey()
is intended to return the unique identifier for a port; should PublishedPort
be just added to what getPortConfigKey()
returns, instead of adding a map
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.
Wondering; if
getPortConfigKey()
is intended to return the unique identifier for a port; shouldPublishedPort
be just added to whatgetPortConfigKey()
returns, instead of adding amap
here?
This is a bit tricky. If PublishedPort
was set to 0 when the service was created (to choose an ingress port dynamically), it will be set to 0 inside the spec and the actual allocated port inside s.Endpoint
. These items must still be matched with each other, which is why getPortConfigKey
intentionally excludes PublishedPort
.
I think this is the right general approach.
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.
Ah, right; so there's the situation -p 80 -p 80
, i.e., publish port 80 on two random ports. Might be worth thinking if that's something we want to support, as it's gonna be tricky in various cases. (thinking out loud 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.
I agree with @thaJeztah, even before considering the -p X/80 -p Y/80
problem, already the -p 80 -p 80
cannot be satisfied by the current logic, if I understand it correctly, because it relays on the assumption of a unique config per target port/protocol. This seem the root cause for both the issues (again IIUIC).
Maybe we should rethink the whole logic with that in mind from the beginning.
@yongtang does your fix also address the -p 80 -p 80
use case ?
This looks correct, but I'm wondering we can do some refactoring to keep this long method from getting more complex. Perhaps
It could have getter and setter methods defined on it abstract out tasks like creating the inner map and checking for a non-zero Just an idea and I'm not 100% sure it's the right way to go, but I imagine it will make this part of the code easier to read. Also, it would be great to add a Docker integration test that tries to publish a port to two ingress ports, making sure it works end-to-end. I'm not sure if any other parts of the code make assumptions that would cause problems here. A test proving that it works would be valuable. |
dda04ab
to
c786e14
Compare
Thanks @aaronlehmann @aboch @thaJeztah for the review. The PR has been updated. Now A PR in docker has also been created to add the integration test: Please take a look and let me know if there are any issues. |
} | ||
|
||
var portConfigs []*api.PortConfig | ||
|
||
// Process the portConfig with portConfig.PublishMode != api.PublishModeIngress | ||
// Iterate portConfigs with PublishModeIngress | ||
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// If the PublishMode is not Ingress simply pick up | ||
// the port config. | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
portConfigs = append(portConfigs, portConfig) | ||
continue |
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.
This continue
doesn't look right.
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 @aaronlehmann. I take a look and realized that the first loop and the second loop could be merged into one loop, as PublishMode != PublishModeIngress
and PublishedPort != 0
does not interference with each other. So now the loops has been reduced from 3 to 2 (continue
has been kept because of this).
(The processing of PublishedPort == 0
still has to be separate).
// Ignore ports which are not PublishModeIngress (already processed) | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
continue | ||
} |
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.
Maybe this loop should iterate over portConfigs
so it doesn't need to check the PublishMode
.
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.
@aaronlehmann Not sure we could iterate over portConfigs
, as portConfigs
until now stores the PublisheMode != PublishModeIngress
, while we need to process the following PublisheMode == PublishModeIngress
?
// Ignore ports which are not PublishModeIngress (already processed) | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
continue | ||
} |
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 here, iterating over portConfigs
could simplify the loop.
c786e14
to
6b7c5df
Compare
if !ok { | ||
return nil | ||
} | ||
// Dlete state from allocatedPorts |
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.
typo
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. Done.
|
||
if !ok { | ||
return nil | ||
} |
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.
This check doesn't seem necessary. If p.PublishedPort
was not found, v
will be nil and delete will do nothing. Feel free to keep this as-is if you prefer it that way; just pointing out a possible simplification.
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 @aaronlehmann Done.
// then we are not allocated | ||
for publishedPort, v := range portStateMap { | ||
if publishedPort != 0 { | ||
// Dlete state from allocatedPorts |
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.
Typo
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.
Done.
@@ -73,7 +121,7 @@ func newPortSpace(protocol api.PortConfig_Protocol) (*portSpace, error) { | |||
}, nil | |||
} | |||
|
|||
// getPortConfigKey returns a map key for doing set operations with | |||
// getPortConfigkey returns a map key for doing set operations with |
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.
Comment should start with getPortConfigKey
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.
Done.
for _, portState := range s.Endpoint.Ports { | ||
if portState.PublishMode != api.PublishModeIngress { | ||
continue | ||
} | ||
|
||
allocatedPorts[getPortConfigKey(portState)] = portState | ||
portStates.addState(portState) |
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 portState.PublishMode == api.PublishModeIngress {
portStates.addState(portState)
}
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.
Done.
for _, portState := range s.Endpoint.Ports { | ||
if portState.PublishMode != api.PublishModeIngress { | ||
continue | ||
} | ||
|
||
allocatedPorts[getPortConfigKey(portState)] = portState | ||
portStates.addState(portState) |
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 portState.PublishMode == api.PublishModeIngress {
portStates.addState(portState)
}
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.
Done.
return nil | ||
} | ||
// Dlete state from allocatedPorts | ||
delete(ps[portKey], p.PublishedPort) |
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.
delete(portStateMap, p.PublishedPort)
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.
Done
for publishedPort, v := range portStateMap { | ||
if publishedPort != 0 { | ||
// Dlete state from allocatedPorts | ||
delete(ps[portKey], publishedPort) |
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.
delete(portStateMap, p.PublishedPort)
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.
Done.
Overall LGTM, just some comments on typos and cosmetics. |
6b7c5df
to
70032f9
Compare
Thanks @aaronlehmann for the review. The comments have been addressed. Please take a look. |
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// If the PublishMode is not Ingress simply pick up | ||
// the port config. | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
portConfigs = append(portConfigs, portConfig) | ||
continue | ||
} | ||
if portConfig.PublishedPort != 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.
Just thought of a minor thing. If we make this an else if
, we can remove the continue
, and I think that makes the code slightly easier to follow. When I first read this it took me a few seconds to realize that it only ran for PublishMode == Ingress
, and I think if this was an else if
it would be more obvious.
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 @aaronlehmann. The PR has been updated.
// For all other cases prefer the portConfig | ||
portConfigs = append(portConfigs, portConfig) | ||
// For all other cases prefer the portConfig | ||
portConfigs = append(portConfigs, portConfig) |
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 here; an if/else
formulation might make the code ever so slightly clearer.
I apologize for picking nits over this kind of trivial thing. Normally I don't care very much about something like else
vs continue
. But this function is relatively complicated, and I'm trying to do all I can to make it intuitive.
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 @aaronlehmann. Also added additional comments to help explain the logic of the function.
LGTM |
70032f9
to
2f92077
Compare
ping @aboch @thaJeztah @LK4D4 for review |
} | ||
ps[portKey][p.PublishedPort] = p | ||
} | ||
|
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.
It maybe only me but I feel it will help if we add a comment here saying what this method does and what it means if it return a nil or a non nil object. So to spare the reader from reading the implementation to understand that.
Thanks @yongtang Code changes look good to me. Up to you if you want to improve the comments now. |
2f92077
to
555f411
Compare
Thanks @aboch the PR has been updated with added comments. |
This fix tries to address the issue raised in moby/moby#29370 where a service with multiple published ports mapping to the same target port (e.g., `--publish 5000:80 --publish 5001:80`) can't be allocated. The reason for the issue is that, `getPortConfigKey` is used for both allocated ports and configured (may or may not be allocated) ports. However, `getPortConfigKey` will not take into consideration the `PublishedPort` field, which actually could be different for different allocated ports. This fix saves a map of `portKey:portNum:portState`, instead of currently used `portKey:portState` so that multiple published ports could be processed. A test case has been added in the unit test. The newly added test case will only work with this PR. Signed-off-by: Yong Tang <[email protected]>
555f411
to
5fe66da
Compare
This commit adds the `allocatedPorts` for cherry-pick. The `allocatedPorts` was added in PR moby#1835, which is not part of the v1.13.1 cherry pick. Signed-off-by: Yong Tang <[email protected]>
This fix tries to address the issue raised in moby/moby#29730 where a service with multiple published ports mapping to the same target port (e.g.,
--publish 5000:80 --publish 5001:80
) can't be allocated.The reason for the issue is that,
getPortConfigKey
is used for both allocated ports and configured (may or may not be allocated) ports. However,getPortConfigKey
will not take into consideration thePublishedPort
field, which actually could be different for different allocated ports.This fix saves a map of
portKey:portNum:portState
, instead of currently usedportKey:portState
so that multiple published ports could be processed.A test case has been added in the unit test. The newly added test case will only work with this PR.
Signed-off-by: Yong Tang [email protected]
/cc @vdemeester @aaronlehmann @thaJeztah @stevvooe @mavenugo @icecrime @cpuguy83