Skip to content

Commit 70032f9

Browse files
committed
Fix issues of multiple published ports mapping to the same target port
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>
1 parent 74b49ee commit 70032f9

File tree

2 files changed

+143
-36
lines changed

2 files changed

+143
-36
lines changed

manager/allocator/networkallocator/portallocator.go

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,51 @@ type portSpace struct {
3838
dynamicPortSpace *idm.Idm
3939
}
4040

41+
type allocatedPorts map[api.PortConfig]map[uint32]*api.PortConfig
42+
43+
func (ps allocatedPorts) addState(p *api.PortConfig) {
44+
portKey := getPortConfigKey(p)
45+
if _, ok := ps[portKey]; !ok {
46+
ps[portKey] = make(map[uint32]*api.PortConfig)
47+
}
48+
ps[portKey][p.PublishedPort] = p
49+
}
50+
51+
func (ps allocatedPorts) delState(p *api.PortConfig) *api.PortConfig {
52+
portKey := getPortConfigKey(p)
53+
54+
portStateMap, ok := ps[portKey]
55+
56+
// If name, port, protocol values don't match then we
57+
// are not allocated.
58+
if !ok {
59+
return nil
60+
}
61+
62+
if p.PublishedPort != 0 {
63+
// If SwarmPort was user defined but the port state
64+
// SwarmPort doesn't match we are not allocated.
65+
v := portStateMap[p.PublishedPort]
66+
67+
// Delete state from allocatedPorts
68+
delete(portStateMap, p.PublishedPort)
69+
70+
return v
71+
}
72+
73+
// If PublishedPort == 0 and we don't have non-zero port
74+
// then we are not allocated
75+
for publishedPort, v := range portStateMap {
76+
if publishedPort != 0 {
77+
// Delete state from allocatedPorts
78+
delete(portStateMap, publishedPort)
79+
return v
80+
}
81+
}
82+
83+
return nil
84+
}
85+
4186
func newPortAllocator() (*portAllocator, error) {
4287
portSpaces := make(map[api.PortConfig_Protocol]*portSpace)
4388
for _, protocol := range []api.PortConfig_Protocol{api.ProtocolTCP, api.ProtocolUDP} {
@@ -91,40 +136,54 @@ func reconcilePortConfigs(s *api.Service) []*api.PortConfig {
91136
return s.Spec.Endpoint.Ports
92137
}
93138

94-
allocatedPorts := make(map[api.PortConfig]*api.PortConfig)
139+
portStates := allocatedPorts{}
95140
for _, portState := range s.Endpoint.Ports {
96-
if portState.PublishMode != api.PublishModeIngress {
97-
continue
141+
if portState.PublishMode == api.PublishModeIngress {
142+
portStates.addState(portState)
98143
}
99-
100-
allocatedPorts[getPortConfigKey(portState)] = portState
101144
}
102145

103146
var portConfigs []*api.PortConfig
147+
148+
// Process the portConfig with portConfig.PublishMode != api.PublishModeIngress
149+
// and PublishedPort != 0 (high priority)
104150
for _, portConfig := range s.Spec.Endpoint.Ports {
105151
// If the PublishMode is not Ingress simply pick up
106152
// the port config.
107153
if portConfig.PublishMode != api.PublishModeIngress {
108154
portConfigs = append(portConfigs, portConfig)
109155
continue
110156
}
157+
if portConfig.PublishedPort != 0 {
158+
// Remove record from portState
159+
portStates.delState(portConfig)
111160

112-
portState, ok := allocatedPorts[getPortConfigKey(portConfig)]
161+
// For PublishedPort != 0 prefer the portConfig
162+
portConfigs = append(portConfigs, portConfig)
163+
}
164+
}
165+
166+
// Iterate portConfigs with PublishedPort == 0 (low priority)
167+
for _, portConfig := range s.Spec.Endpoint.Ports {
168+
// Ignore ports which are not PublishModeIngress (already processed)
169+
if portConfig.PublishMode != api.PublishModeIngress {
170+
continue
171+
}
113172

114173
// If the portConfig is exactly the same as portState
115174
// except if SwarmPort is not user-define then prefer
116175
// portState to ensure sticky allocation of the same
117176
// port that was allocated before.
118-
if ok && portConfig.Name == portState.Name &&
119-
portConfig.TargetPort == portState.TargetPort &&
120-
portConfig.Protocol == portState.Protocol &&
121-
portConfig.PublishedPort == 0 {
122-
portConfigs = append(portConfigs, portState)
123-
continue
124-
}
177+
if portConfig.PublishedPort == 0 {
178+
// Remove record from portState
179+
if portState := portStates.delState(portConfig); portState != nil {
180+
portConfigs = append(portConfigs, portState)
181+
continue
182+
}
125183

126-
// For all other cases prefer the portConfig
127-
portConfigs = append(portConfigs, portConfig)
184+
// For all other cases prefer the portConfig
185+
portConfigs = append(portConfigs, portConfig)
186+
}
128187
}
129188

130189
return portConfigs
@@ -213,40 +272,31 @@ func (pa *portAllocator) isPortsAllocated(s *api.Service) bool {
213272
return false
214273
}
215274

216-
allocatedPorts := make(map[api.PortConfig]*api.PortConfig)
275+
portStates := allocatedPorts{}
217276
for _, portState := range s.Endpoint.Ports {
218-
if portState.PublishMode != api.PublishModeIngress {
219-
continue
277+
if portState.PublishMode == api.PublishModeIngress {
278+
portStates.addState(portState)
220279
}
221-
222-
allocatedPorts[getPortConfigKey(portState)] = portState
223280
}
224281

282+
// Iterate portConfigs with PublishedPort != 0 (high priority)
225283
for _, portConfig := range s.Spec.Endpoint.Ports {
226284
// Ignore ports which are not PublishModeIngress
227285
if portConfig.PublishMode != api.PublishModeIngress {
228286
continue
229287
}
230-
231-
portState, ok := allocatedPorts[getPortConfigKey(portConfig)]
232-
233-
// If name, port, protocol values don't match then we
234-
// are not allocated.
235-
if !ok {
288+
if portConfig.PublishedPort != 0 && portStates.delState(portConfig) == nil {
236289
return false
237290
}
291+
}
238292

239-
// If SwarmPort was user defined but the port state
240-
// SwarmPort doesn't match we are not allocated.
241-
if portConfig.PublishedPort != portState.PublishedPort &&
242-
portConfig.PublishedPort != 0 {
243-
return false
293+
// Iterate portConfigs with PublishedPort == 0 (low priority)
294+
for _, portConfig := range s.Spec.Endpoint.Ports {
295+
// Ignore ports which are not PublishModeIngress
296+
if portConfig.PublishMode != api.PublishModeIngress {
297+
continue
244298
}
245-
246-
// If SwarmPort was not defined by user and port state
247-
// is not initialized with a valid SwarmPort value then
248-
// we are not allocated.
249-
if portConfig.PublishedPort == 0 && portState.PublishedPort == 0 {
299+
if portConfig.PublishedPort == 0 && portStates.delState(portConfig) == nil {
250300
return false
251301
}
252302
}

manager/allocator/networkallocator/portallocator_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,63 @@ func TestIsPortsAllocated(t *testing.T) {
550550
},
551551
expect: true,
552552
},
553+
{
554+
// Endpoint and Spec.Endpoint have multiple PublishedPort
555+
// See docker/docker#29730
556+
input: &api.Service{
557+
Spec: api.ServiceSpec{
558+
Endpoint: &api.EndpointSpec{
559+
Ports: []*api.PortConfig{
560+
{
561+
Protocol: api.ProtocolTCP,
562+
TargetPort: 80,
563+
PublishedPort: 5000,
564+
},
565+
{
566+
Protocol: api.ProtocolTCP,
567+
TargetPort: 80,
568+
PublishedPort: 5001,
569+
},
570+
{
571+
Protocol: api.ProtocolTCP,
572+
TargetPort: 80,
573+
PublishedPort: 0,
574+
},
575+
{
576+
Protocol: api.ProtocolTCP,
577+
TargetPort: 80,
578+
PublishedPort: 0,
579+
},
580+
},
581+
},
582+
},
583+
Endpoint: &api.Endpoint{
584+
Ports: []*api.PortConfig{
585+
{
586+
Protocol: api.ProtocolTCP,
587+
TargetPort: 80,
588+
PublishedPort: 5000,
589+
},
590+
{
591+
Protocol: api.ProtocolTCP,
592+
TargetPort: 80,
593+
PublishedPort: 5001,
594+
},
595+
{
596+
Protocol: api.ProtocolTCP,
597+
TargetPort: 80,
598+
PublishedPort: 30000,
599+
},
600+
{
601+
Protocol: api.ProtocolTCP,
602+
TargetPort: 80,
603+
PublishedPort: 30001,
604+
},
605+
},
606+
},
607+
},
608+
expect: true,
609+
},
553610
}
554611

555612
for _, singleTest := range testCases {

0 commit comments

Comments
 (0)