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.
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.
Wondering; if
getPortConfigKey()is intended to return the unique identifier for a port; shouldPublishedPortbe just added to whatgetPortConfigKey()returns, instead of adding amaphere?
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.
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.
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. |
| // the port config. | ||
| if portConfig.PublishMode != api.PublishModeIngress { | ||
| portConfigs = append(portConfigs, portConfig) | ||
| continue |
There was a problem hiding this comment.
This continue doesn't look right.
There was a problem hiding this comment.
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.
Maybe this loop should iterate over portConfigs so it doesn't need to check the PublishMode.
There was a problem hiding this comment.
@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.
Same here, iterating over portConfigs could simplify the loop.
c786e14 to
6b7c5df
Compare
| if !ok { | ||
| return nil | ||
| } | ||
| // Dlete state from allocatedPorts |
|
|
||
| if !ok { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
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.
| // then we are not allocated | ||
| for publishedPort, v := range portStateMap { | ||
| if publishedPort != 0 { | ||
| // Dlete state from allocatedPorts |
| } | ||
|
|
||
| // 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.
Comment should start with getPortConfigKey
| } | ||
|
|
||
| allocatedPorts[getPortConfigKey(portState)] = portState | ||
| portStates.addState(portState) |
There was a problem hiding this comment.
if portState.PublishMode == api.PublishModeIngress {
portStates.addState(portState)
}| } | ||
|
|
||
| allocatedPorts[getPortConfigKey(portState)] = portState | ||
| portStates.addState(portState) |
There was a problem hiding this comment.
if portState.PublishMode == api.PublishModeIngress {
portStates.addState(portState)
}| return nil | ||
| } | ||
| // Dlete state from allocatedPorts | ||
| delete(ps[portKey], p.PublishedPort) |
There was a problem hiding this comment.
delete(portStateMap, p.PublishedPort)
| for publishedPort, v := range portStateMap { | ||
| if publishedPort != 0 { | ||
| // Dlete state from allocatedPorts | ||
| delete(ps[portKey], publishedPort) |
There was a problem hiding this comment.
delete(portStateMap, p.PublishedPort)
|
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. |
| portConfigs = append(portConfigs, portConfig) | ||
| continue | ||
| } | ||
| if portConfig.PublishedPort != 0 { |
There was a problem hiding this comment.
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.
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.
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.
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.
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 <yong.tang.github@outlook.com>
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 <yong.tang.github@outlook.com>
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,
getPortConfigKeyis used for both allocated ports and configured (may or may not be allocated) ports. However,getPortConfigKeywill not take into consideration thePublishedPortfield, which actually could be different for different allocated ports.This fix saves a map of
portKey:portNum:portState, instead of currently usedportKey:portStateso 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 yong.tang.github@outlook.com
/cc @vdemeester @aaronlehmann @thaJeztah @stevvooe @mavenugo @icecrime @cpuguy83