-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,71 @@ type portSpace struct { | |
dynamicPortSpace *idm.Idm | ||
} | ||
|
||
type allocatedPorts map[api.PortConfig]map[uint32]*api.PortConfig | ||
|
||
// addState add the state of an allocated port to the collection. | ||
// `allocatedPorts` is a map of portKey:publishedPort:portState. | ||
// In case the value of the portKey is missing, the map | ||
// publishedPort:portState is created automatically | ||
func (ps allocatedPorts) addState(p *api.PortConfig) { | ||
portKey := getPortConfigKey(p) | ||
if _, ok := ps[portKey]; !ok { | ||
ps[portKey] = make(map[uint32]*api.PortConfig) | ||
} | ||
ps[portKey][p.PublishedPort] = p | ||
} | ||
|
||
// delState delete the state of an allocated port from the collection. | ||
// `allocatedPorts` is a map of portKey:publishedPort:portState. | ||
// | ||
// If publishedPort is non-zero, then it is user defined. We will try to | ||
// remove the portState from `allocatedPorts` directly and return | ||
// the portState (or nil if no portState exists) | ||
// | ||
// If publishedPort is zero, then it is dynamically allocated. We will try | ||
// to remove the portState from `allocatedPorts`, as long as there is | ||
// a portState associated with a non-zero publishedPort. | ||
// Note multiple dynamically allocated ports might exists. In this case, | ||
// we will remove only at a time so both allocated ports are tracked. | ||
// | ||
// Note becasue of the potential co-existence of user-defined and dynamically | ||
// allocated ports, delState has to be called for user-defined port first. | ||
// dynamically allocated ports should be removed later. | ||
func (ps allocatedPorts) delState(p *api.PortConfig) *api.PortConfig { | ||
portKey := getPortConfigKey(p) | ||
|
||
portStateMap, ok := ps[portKey] | ||
|
||
// If name, port, protocol values don't match then we | ||
// are not allocated. | ||
if !ok { | ||
return nil | ||
} | ||
|
||
if p.PublishedPort != 0 { | ||
// If SwarmPort was user defined but the port state | ||
// SwarmPort doesn't match we are not allocated. | ||
v := portStateMap[p.PublishedPort] | ||
|
||
// Delete state from allocatedPorts | ||
delete(portStateMap, p.PublishedPort) | ||
|
||
return v | ||
} | ||
|
||
// If PublishedPort == 0 and we don't have non-zero port | ||
// then we are not allocated | ||
for publishedPort, v := range portStateMap { | ||
if publishedPort != 0 { | ||
// Delete state from allocatedPorts | ||
delete(portStateMap, publishedPort) | ||
return v | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func newPortAllocator() (*portAllocator, error) { | ||
portSpaces := make(map[api.PortConfig_Protocol]*portSpace) | ||
for _, protocol := range []api.PortConfig_Protocol{api.ProtocolTCP, api.ProtocolUDP} { | ||
|
@@ -91,40 +156,53 @@ func reconcilePortConfigs(s *api.Service) []*api.PortConfig { | |
return s.Spec.Endpoint.Ports | ||
} | ||
|
||
allocatedPorts := make(map[api.PortConfig]*api.PortConfig) | ||
portStates := allocatedPorts{} | ||
for _, portState := range s.Endpoint.Ports { | ||
if portState.PublishMode != api.PublishModeIngress { | ||
continue | ||
if portState.PublishMode == api.PublishModeIngress { | ||
portStates.addState(portState) | ||
} | ||
|
||
allocatedPorts[getPortConfigKey(portState)] = portState | ||
} | ||
|
||
var portConfigs []*api.PortConfig | ||
|
||
// Process the portConfig with portConfig.PublishMode != api.PublishModeIngress | ||
// and PublishedPort != 0 (high priority) | ||
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// If the PublishMode is not Ingress simply pick up | ||
// the port config. | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
// If the PublishMode is not Ingress simply pick up the port config. | ||
portConfigs = append(portConfigs, portConfig) | ||
continue | ||
} | ||
} else if portConfig.PublishedPort != 0 { | ||
// Otherwise we only process PublishedPort != 0 in this round | ||
|
||
portState, ok := allocatedPorts[getPortConfigKey(portConfig)] | ||
|
||
// If the portConfig is exactly the same as portState | ||
// except if SwarmPort is not user-define then prefer | ||
// portState to ensure sticky allocation of the same | ||
// port that was allocated before. | ||
if ok && portConfig.Name == portState.Name && | ||
portConfig.TargetPort == portState.TargetPort && | ||
portConfig.Protocol == portState.Protocol && | ||
portConfig.PublishedPort == 0 { | ||
portConfigs = append(portConfigs, portState) | ||
continue | ||
// Remove record from portState | ||
portStates.delState(portConfig) | ||
|
||
// For PublishedPort != 0 prefer the portConfig | ||
portConfigs = append(portConfigs, portConfig) | ||
} | ||
} | ||
|
||
// Iterate portConfigs with PublishedPort == 0 (low priority) | ||
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// Ignore ports which are not PublishModeIngress (already processed) | ||
// And we only process PublishedPort == 0 in this round | ||
// So the following: | ||
// `portConfig.PublishMode == api.PublishModeIngress && portConfig.PublishedPort == 0` | ||
if portConfig.PublishMode == api.PublishModeIngress && portConfig.PublishedPort == 0 { | ||
// If the portConfig is exactly the same as portState | ||
// except if SwarmPort is not user-define then prefer | ||
// portState to ensure sticky allocation of the same | ||
// port that was allocated before. | ||
|
||
// Remove record from portState | ||
if portState := portStates.delState(portConfig); portState != nil { | ||
portConfigs = append(portConfigs, portState) | ||
continue | ||
} | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. Same here; an I apologize for picking nits over this kind of trivial thing. Normally I don't care very much about something like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
} | ||
|
||
return portConfigs | ||
|
@@ -213,40 +291,31 @@ func (pa *portAllocator) isPortsAllocated(s *api.Service) bool { | |
return false | ||
} | ||
|
||
allocatedPorts := make(map[api.PortConfig]*api.PortConfig) | ||
portStates := allocatedPorts{} | ||
for _, portState := range s.Endpoint.Ports { | ||
if portState.PublishMode != api.PublishModeIngress { | ||
continue | ||
if portState.PublishMode == api.PublishModeIngress { | ||
portStates.addState(portState) | ||
} | ||
|
||
allocatedPorts[getPortConfigKey(portState)] = portState | ||
} | ||
|
||
// Iterate portConfigs with PublishedPort != 0 (high priority) | ||
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// Ignore ports which are not PublishModeIngress | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
continue | ||
} | ||
|
||
portState, ok := allocatedPorts[getPortConfigKey(portConfig)] | ||
|
||
// If name, port, protocol values don't match then we | ||
// are not allocated. | ||
if !ok { | ||
if portConfig.PublishedPort != 0 && portStates.delState(portConfig) == nil { | ||
return false | ||
} | ||
} | ||
|
||
// If SwarmPort was user defined but the port state | ||
// SwarmPort doesn't match we are not allocated. | ||
if portConfig.PublishedPort != portState.PublishedPort && | ||
portConfig.PublishedPort != 0 { | ||
return false | ||
// Iterate portConfigs with PublishedPort == 0 (low priority) | ||
for _, portConfig := range s.Spec.Endpoint.Ports { | ||
// Ignore ports which are not PublishModeIngress | ||
if portConfig.PublishMode != api.PublishModeIngress { | ||
continue | ||
} | ||
|
||
// If SwarmPort was not defined by user and port state | ||
// is not initialized with a valid SwarmPort value then | ||
// we are not allocated. | ||
if portConfig.PublishedPort == 0 && portState.PublishedPort == 0 { | ||
if portConfig.PublishedPort == 0 && portStates.delState(portConfig) == nil { | ||
return false | ||
} | ||
} | ||
|
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.