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  }