github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/allocator/cnmallocator/portallocator.go (about) 1 package cnmallocator 2 3 import ( 4 "fmt" 5 6 "github.com/docker/libnetwork/idm" 7 "github.com/docker/swarmkit/api" 8 ) 9 10 const ( 11 // Start of the dynamic port range from which node ports will 12 // be allocated when the user did not specify a port. 13 dynamicPortStart = 30000 14 15 // End of the dynamic port range from which node ports will be 16 // allocated when the user did not specify a port. 17 dynamicPortEnd = 32767 18 19 // The start of master port range which will hold all the 20 // allocation state of ports allocated so far regardless of 21 // whether it was user defined or not. 22 masterPortStart = 1 23 24 // The end of master port range which will hold all the 25 // allocation state of ports allocated so far regardless of 26 // whether it was user defined or not. 27 masterPortEnd = 65535 28 ) 29 30 type portAllocator struct { 31 // portspace definition per protocol 32 portSpaces map[api.PortConfig_Protocol]*portSpace 33 } 34 35 type portSpace struct { 36 protocol api.PortConfig_Protocol 37 masterPortSpace *idm.Idm 38 dynamicPortSpace *idm.Idm 39 } 40 41 type allocatedPorts map[api.PortConfig]map[uint32]*api.PortConfig 42 43 // addState add the state of an allocated port to the collection. 44 // `allocatedPorts` is a map of portKey:publishedPort:portState. 45 // In case the value of the portKey is missing, the map 46 // publishedPort:portState is created automatically 47 func (ps allocatedPorts) addState(p *api.PortConfig) { 48 portKey := getPortConfigKey(p) 49 if _, ok := ps[portKey]; !ok { 50 ps[portKey] = make(map[uint32]*api.PortConfig) 51 } 52 ps[portKey][p.PublishedPort] = p 53 } 54 55 // delState delete the state of an allocated port from the collection. 56 // `allocatedPorts` is a map of portKey:publishedPort:portState. 57 // 58 // If publishedPort is non-zero, then it is user defined. We will try to 59 // remove the portState from `allocatedPorts` directly and return 60 // the portState (or nil if no portState exists) 61 // 62 // If publishedPort is zero, then it is dynamically allocated. We will try 63 // to remove the portState from `allocatedPorts`, as long as there is 64 // a portState associated with a non-zero publishedPort. 65 // Note multiple dynamically allocated ports might exists. In this case, 66 // we will remove only at a time so both allocated ports are tracked. 67 // 68 // Note because of the potential co-existence of user-defined and dynamically 69 // allocated ports, delState has to be called for user-defined port first. 70 // dynamically allocated ports should be removed later. 71 func (ps allocatedPorts) delState(p *api.PortConfig) *api.PortConfig { 72 portKey := getPortConfigKey(p) 73 74 portStateMap, ok := ps[portKey] 75 76 // If name, port, protocol values don't match then we 77 // are not allocated. 78 if !ok { 79 return nil 80 } 81 82 if p.PublishedPort != 0 { 83 // If SwarmPort was user defined but the port state 84 // SwarmPort doesn't match we are not allocated. 85 v := portStateMap[p.PublishedPort] 86 87 // Delete state from allocatedPorts 88 delete(portStateMap, p.PublishedPort) 89 90 return v 91 } 92 93 // If PublishedPort == 0 and we don't have non-zero port 94 // then we are not allocated 95 for publishedPort, v := range portStateMap { 96 if publishedPort != 0 { 97 // Delete state from allocatedPorts 98 delete(portStateMap, publishedPort) 99 return v 100 } 101 } 102 103 return nil 104 } 105 106 func newPortAllocator() (*portAllocator, error) { 107 portSpaces := make(map[api.PortConfig_Protocol]*portSpace) 108 for _, protocol := range []api.PortConfig_Protocol{api.ProtocolTCP, api.ProtocolUDP, api.ProtocolSCTP} { 109 ps, err := newPortSpace(protocol) 110 if err != nil { 111 return nil, err 112 } 113 114 portSpaces[protocol] = ps 115 } 116 117 return &portAllocator{portSpaces: portSpaces}, nil 118 } 119 120 func newPortSpace(protocol api.PortConfig_Protocol) (*portSpace, error) { 121 masterName := fmt.Sprintf("%s-master-ports", protocol) 122 dynamicName := fmt.Sprintf("%s-dynamic-ports", protocol) 123 124 master, err := idm.New(nil, masterName, masterPortStart, masterPortEnd) 125 if err != nil { 126 return nil, err 127 } 128 129 dynamic, err := idm.New(nil, dynamicName, dynamicPortStart, dynamicPortEnd) 130 if err != nil { 131 return nil, err 132 } 133 134 return &portSpace{ 135 protocol: protocol, 136 masterPortSpace: master, 137 dynamicPortSpace: dynamic, 138 }, nil 139 } 140 141 // getPortConfigKey returns a map key for doing set operations with 142 // ports. The key consists of name, protocol and target port which 143 // uniquely identifies a port within a single Endpoint. 144 func getPortConfigKey(p *api.PortConfig) api.PortConfig { 145 return api.PortConfig{ 146 Name: p.Name, 147 Protocol: p.Protocol, 148 TargetPort: p.TargetPort, 149 } 150 } 151 152 func reconcilePortConfigs(s *api.Service) []*api.PortConfig { 153 // If runtime state hasn't been created or if port config has 154 // changed from port state return the port config from Spec. 155 if s.Endpoint == nil || len(s.Spec.Endpoint.Ports) != len(s.Endpoint.Ports) { 156 return s.Spec.Endpoint.Ports 157 } 158 159 portStates := allocatedPorts{} 160 for _, portState := range s.Endpoint.Ports { 161 if portState.PublishMode == api.PublishModeIngress { 162 portStates.addState(portState) 163 } 164 } 165 166 var portConfigs []*api.PortConfig 167 168 // Process the portConfig with portConfig.PublishMode != api.PublishModeIngress 169 // and PublishedPort != 0 (high priority) 170 for _, portConfig := range s.Spec.Endpoint.Ports { 171 if portConfig.PublishMode != api.PublishModeIngress { 172 // If the PublishMode is not Ingress simply pick up the port config. 173 portConfigs = append(portConfigs, portConfig) 174 } else if portConfig.PublishedPort != 0 { 175 // Otherwise we only process PublishedPort != 0 in this round 176 177 // Remove record from portState 178 portStates.delState(portConfig) 179 180 // For PublishedPort != 0 prefer the portConfig 181 portConfigs = append(portConfigs, portConfig) 182 } 183 } 184 185 // Iterate portConfigs with PublishedPort == 0 (low priority) 186 for _, portConfig := range s.Spec.Endpoint.Ports { 187 // Ignore ports which are not PublishModeIngress (already processed) 188 // And we only process PublishedPort == 0 in this round 189 // So the following: 190 // `portConfig.PublishMode == api.PublishModeIngress && portConfig.PublishedPort == 0` 191 if portConfig.PublishMode == api.PublishModeIngress && portConfig.PublishedPort == 0 { 192 // If the portConfig is exactly the same as portState 193 // except if SwarmPort is not user-define then prefer 194 // portState to ensure sticky allocation of the same 195 // port that was allocated before. 196 197 // Remove record from portState 198 if portState := portStates.delState(portConfig); portState != nil { 199 portConfigs = append(portConfigs, portState) 200 continue 201 } 202 203 // For all other cases prefer the portConfig 204 portConfigs = append(portConfigs, portConfig) 205 } 206 } 207 208 return portConfigs 209 } 210 211 func (pa *portAllocator) serviceAllocatePorts(s *api.Service) (err error) { 212 if s.Spec.Endpoint == nil { 213 return nil 214 } 215 216 // We might have previous allocations which we want to stick 217 // to if possible. So instead of strictly going by port 218 // configs in the Spec reconcile the list of port configs from 219 // both the Spec and runtime state. 220 portConfigs := reconcilePortConfigs(s) 221 222 // Port configuration might have changed. Cleanup all old allocations first. 223 pa.serviceDeallocatePorts(s) 224 225 defer func() { 226 if err != nil { 227 // Free all the ports allocated so far which 228 // should be present in s.Endpoints.ExposedPorts 229 pa.serviceDeallocatePorts(s) 230 } 231 }() 232 233 for _, portConfig := range portConfigs { 234 // Make a copy of port config to create runtime state 235 portState := portConfig.Copy() 236 237 // Do an actual allocation only if the PublishMode is Ingress 238 if portConfig.PublishMode == api.PublishModeIngress { 239 if err = pa.portSpaces[portState.Protocol].allocate(portState); err != nil { 240 return 241 } 242 } 243 244 if s.Endpoint == nil { 245 s.Endpoint = &api.Endpoint{} 246 } 247 248 s.Endpoint.Ports = append(s.Endpoint.Ports, portState) 249 } 250 251 return nil 252 } 253 254 func (pa *portAllocator) serviceDeallocatePorts(s *api.Service) { 255 if s.Endpoint == nil { 256 return 257 } 258 259 for _, portState := range s.Endpoint.Ports { 260 // Do an actual free only if the PublishMode is 261 // Ingress 262 if portState.PublishMode != api.PublishModeIngress { 263 continue 264 } 265 266 pa.portSpaces[portState.Protocol].free(portState) 267 } 268 269 s.Endpoint.Ports = nil 270 } 271 272 func (pa *portAllocator) hostPublishPortsNeedUpdate(s *api.Service) bool { 273 if s.Endpoint == nil && s.Spec.Endpoint == nil { 274 return false 275 } 276 277 portStates := allocatedPorts{} 278 if s.Endpoint != nil { 279 for _, portState := range s.Endpoint.Ports { 280 if portState.PublishMode == api.PublishModeHost { 281 portStates.addState(portState) 282 } 283 } 284 } 285 286 if s.Spec.Endpoint != nil { 287 for _, portConfig := range s.Spec.Endpoint.Ports { 288 if portConfig.PublishMode == api.PublishModeHost && 289 portConfig.PublishedPort != 0 { 290 if portStates.delState(portConfig) == nil { 291 return true 292 } 293 } 294 } 295 } 296 297 return false 298 } 299 300 func (pa *portAllocator) isPortsAllocated(s *api.Service) bool { 301 return pa.isPortsAllocatedOnInit(s, false) 302 } 303 304 func (pa *portAllocator) isPortsAllocatedOnInit(s *api.Service, onInit bool) bool { 305 // If service has no user-defined endpoint and allocated endpoint, 306 // we assume it is allocated and return true. 307 if s.Endpoint == nil && s.Spec.Endpoint == nil { 308 return true 309 } 310 311 // If service has allocated endpoint while has no user-defined endpoint, 312 // we assume allocated endpoints are redundant, and they need deallocated. 313 // If service has no allocated endpoint while has user-defined endpoint, 314 // we assume it is not allocated. 315 if (s.Endpoint != nil && s.Spec.Endpoint == nil) || 316 (s.Endpoint == nil && s.Spec.Endpoint != nil) { 317 return false 318 } 319 320 // If we don't have same number of port states as port configs 321 // we assume it is not allocated. 322 if len(s.Spec.Endpoint.Ports) != len(s.Endpoint.Ports) { 323 return false 324 } 325 326 portStates := allocatedPorts{} 327 hostTargetPorts := map[uint32]struct{}{} 328 for _, portState := range s.Endpoint.Ports { 329 switch portState.PublishMode { 330 case api.PublishModeIngress: 331 portStates.addState(portState) 332 case api.PublishModeHost: 333 // build a map of host mode ports we've seen. if in the spec we get 334 // a host port that's not in the service, then we need to do 335 // allocation. if we get the same target port but something else 336 // has changed, then HostPublishPortsNeedUpdate will cover that 337 // case. see docker/swarmkit#2376 338 hostTargetPorts[portState.TargetPort] = struct{}{} 339 } 340 } 341 342 // Iterate portConfigs with PublishedPort != 0 (high priority) 343 for _, portConfig := range s.Spec.Endpoint.Ports { 344 // Ignore ports which are not PublishModeIngress 345 if portConfig.PublishMode != api.PublishModeIngress { 346 continue 347 } 348 if portConfig.PublishedPort != 0 && portStates.delState(portConfig) == nil { 349 return false 350 } 351 } 352 353 // Iterate portConfigs with PublishedPort == 0 (low priority) 354 for _, portConfig := range s.Spec.Endpoint.Ports { 355 // Ignore ports which are not PublishModeIngress 356 switch portConfig.PublishMode { 357 case api.PublishModeIngress: 358 if portConfig.PublishedPort == 0 && portStates.delState(portConfig) == nil { 359 return false 360 } 361 362 // If SwarmPort was not defined by user and the func 363 // is called during allocator initialization state then 364 // we are not allocated. 365 if portConfig.PublishedPort == 0 && onInit { 366 return false 367 } 368 case api.PublishModeHost: 369 // check if the target port is already in the port config. if it 370 // isn't, then it's our problem. 371 if _, ok := hostTargetPorts[portConfig.TargetPort]; !ok { 372 return false 373 } 374 // NOTE(dperny) there could be a further case where we check if 375 // there are host ports in the config that aren't in the spec, but 376 // that's only possible if there's a mismatch in the number of 377 // ports, which is handled by a length check earlier in the code 378 } 379 } 380 381 return true 382 } 383 384 func (ps *portSpace) allocate(p *api.PortConfig) (err error) { 385 if p.PublishedPort != 0 { 386 // If it falls in the dynamic port range check out 387 // from dynamic port space first. 388 if p.PublishedPort >= dynamicPortStart && p.PublishedPort <= dynamicPortEnd { 389 if err = ps.dynamicPortSpace.GetSpecificID(uint64(p.PublishedPort)); err != nil { 390 return err 391 } 392 393 defer func() { 394 if err != nil { 395 ps.dynamicPortSpace.Release(uint64(p.PublishedPort)) 396 } 397 }() 398 } 399 400 return ps.masterPortSpace.GetSpecificID(uint64(p.PublishedPort)) 401 } 402 403 // Check out an arbitrary port from dynamic port space. 404 swarmPort, err := ps.dynamicPortSpace.GetID(true) 405 if err != nil { 406 return 407 } 408 defer func() { 409 if err != nil { 410 ps.dynamicPortSpace.Release(swarmPort) 411 } 412 }() 413 414 // Make sure we allocate the same port from the master space. 415 if err = ps.masterPortSpace.GetSpecificID(swarmPort); err != nil { 416 return 417 } 418 419 p.PublishedPort = uint32(swarmPort) 420 return nil 421 } 422 423 func (ps *portSpace) free(p *api.PortConfig) { 424 if p.PublishedPort >= dynamicPortStart && p.PublishedPort <= dynamicPortEnd { 425 ps.dynamicPortSpace.Release(uint64(p.PublishedPort)) 426 } 427 428 ps.masterPortSpace.Release(uint64(p.PublishedPort)) 429 }