github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/network.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"net"
     7  	"sync"
     8  )
     9  
    10  const (
    11  	// MinDynamicPort is the smallest dynamic port generated
    12  	MinDynamicPort = 20000
    13  
    14  	// MaxDynamicPort is the largest dynamic port generated
    15  	MaxDynamicPort = 32000
    16  
    17  	// maxRandPortAttempts is the maximum number of attempt
    18  	// to assign a random port
    19  	maxRandPortAttempts = 20
    20  
    21  	// maxValidPort is the max valid port number
    22  	maxValidPort = 65536
    23  )
    24  
    25  var (
    26  	// bitmapPool is used to pool the bitmaps used for port collision
    27  	// checking. They are fairly large (8K) so we can re-use them to
    28  	// avoid GC pressure. Care should be taken to call Clear() on any
    29  	// bitmap coming from the pool.
    30  	bitmapPool = new(sync.Pool)
    31  )
    32  
    33  // NetworkIndex is used to index the available network resources
    34  // and the used network resources on a machine given allocations
    35  type NetworkIndex struct {
    36  	AvailNetworks  []*NetworkResource              // List of available networks
    37  	NodeNetworks   []*NodeNetworkResource          // List of available node networks
    38  	AvailAddresses map[string][]NodeNetworkAddress // Map of host network aliases to list of addresses
    39  	AvailBandwidth map[string]int                  // Bandwidth by device
    40  	UsedPorts      map[string]Bitmap               // Ports by IP
    41  	UsedBandwidth  map[string]int                  // Bandwidth by device
    42  }
    43  
    44  // NewNetworkIndex is used to construct a new network index
    45  func NewNetworkIndex() *NetworkIndex {
    46  	return &NetworkIndex{
    47  		AvailAddresses: make(map[string][]NodeNetworkAddress),
    48  		AvailBandwidth: make(map[string]int),
    49  		UsedPorts:      make(map[string]Bitmap),
    50  		UsedBandwidth:  make(map[string]int),
    51  	}
    52  }
    53  
    54  func (idx *NetworkIndex) getUsedPortsFor(ip string) Bitmap {
    55  	used := idx.UsedPorts[ip]
    56  	if used == nil {
    57  		// Try to get a bitmap from the pool, else create
    58  		raw := bitmapPool.Get()
    59  		if raw != nil {
    60  			used = raw.(Bitmap)
    61  			used.Clear()
    62  		} else {
    63  			used, _ = NewBitmap(maxValidPort)
    64  		}
    65  		idx.UsedPorts[ip] = used
    66  	}
    67  	return used
    68  }
    69  
    70  // Release is called when the network index is no longer needed
    71  // to attempt to re-use some of the memory it has allocated
    72  func (idx *NetworkIndex) Release() {
    73  	for _, b := range idx.UsedPorts {
    74  		bitmapPool.Put(b)
    75  	}
    76  }
    77  
    78  // Overcommitted checks if the network is overcommitted
    79  func (idx *NetworkIndex) Overcommitted() bool {
    80  	// TODO remove since bandwidth is deprecated
    81  	/*for device, used := range idx.UsedBandwidth {
    82  		avail := idx.AvailBandwidth[device]
    83  		if used > avail {
    84  			return true
    85  		}
    86  	}*/
    87  	return false
    88  }
    89  
    90  // SetNode is used to setup the available network resources. Returns
    91  // true if there is a collision
    92  func (idx *NetworkIndex) SetNode(node *Node) (collide bool) {
    93  
    94  	// COMPAT(0.11): Remove in 0.11
    95  	// Grab the network resources, handling both new and old
    96  	var networks []*NetworkResource
    97  	if node.NodeResources != nil && len(node.NodeResources.Networks) != 0 {
    98  		networks = node.NodeResources.Networks
    99  	} else if node.Resources != nil {
   100  		networks = node.Resources.Networks
   101  	}
   102  
   103  	var nodeNetworks []*NodeNetworkResource
   104  	if node.NodeResources != nil && len(node.NodeResources.NodeNetworks) != 0 {
   105  		nodeNetworks = node.NodeResources.NodeNetworks
   106  	}
   107  
   108  	// Add the available CIDR blocks
   109  	for _, n := range networks {
   110  		if n.Device != "" {
   111  			idx.AvailNetworks = append(idx.AvailNetworks, n)
   112  			idx.AvailBandwidth[n.Device] = n.MBits
   113  		}
   114  	}
   115  
   116  	// TODO: upgrade path?
   117  	// is it possible to get duplicates here?
   118  	for _, n := range nodeNetworks {
   119  		for _, a := range n.Addresses {
   120  			idx.AvailAddresses[a.Alias] = append(idx.AvailAddresses[a.Alias], a)
   121  			if idx.AddReservedPortsForIP(a.ReservedPorts, a.Address) {
   122  				collide = true
   123  			}
   124  		}
   125  	}
   126  
   127  	// COMPAT(0.11): Remove in 0.11
   128  	// Handle reserving ports, handling both new and old
   129  	if node.ReservedResources != nil && node.ReservedResources.Networks.ReservedHostPorts != "" {
   130  		collide = idx.AddReservedPortRange(node.ReservedResources.Networks.ReservedHostPorts)
   131  	} else if node.Reserved != nil {
   132  		for _, n := range node.Reserved.Networks {
   133  			if idx.AddReserved(n) {
   134  				collide = true
   135  			}
   136  		}
   137  	}
   138  
   139  	return
   140  }
   141  
   142  // AddAllocs is used to add the used network resources. Returns
   143  // true if there is a collision
   144  func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
   145  	for _, alloc := range allocs {
   146  		// Do not consider the resource impact of terminal allocations
   147  		if alloc.TerminalStatus() {
   148  			continue
   149  		}
   150  
   151  		if alloc.AllocatedResources != nil {
   152  			// Only look at AllocatedPorts if populated, otherwise use pre 0.12 logic
   153  			// COMPAT(1.0): Remove when network resources struct is removed.
   154  			if len(alloc.AllocatedResources.Shared.Ports) > 0 {
   155  				if idx.AddReservedPorts(alloc.AllocatedResources.Shared.Ports) {
   156  					collide = true
   157  				}
   158  			} else {
   159  				// Add network resources that are at the task group level
   160  				if len(alloc.AllocatedResources.Shared.Networks) > 0 {
   161  					for _, network := range alloc.AllocatedResources.Shared.Networks {
   162  						if idx.AddReserved(network) {
   163  							collide = true
   164  						}
   165  					}
   166  				}
   167  
   168  				for _, task := range alloc.AllocatedResources.Tasks {
   169  					if len(task.Networks) == 0 {
   170  						continue
   171  					}
   172  					n := task.Networks[0]
   173  					if idx.AddReserved(n) {
   174  						collide = true
   175  					}
   176  				}
   177  			}
   178  		} else {
   179  			// COMPAT(0.11): Remove in 0.11
   180  			for _, task := range alloc.TaskResources {
   181  				if len(task.Networks) == 0 {
   182  					continue
   183  				}
   184  				n := task.Networks[0]
   185  				if idx.AddReserved(n) {
   186  					collide = true
   187  				}
   188  			}
   189  		}
   190  	}
   191  	return
   192  }
   193  
   194  // AddReserved is used to add a reserved network usage, returns true
   195  // if there is a port collision
   196  func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
   197  	// Add the port usage
   198  	used := idx.getUsedPortsFor(n.IP)
   199  
   200  	for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} {
   201  		for _, port := range ports {
   202  			// Guard against invalid port
   203  			if port.Value < 0 || port.Value >= maxValidPort {
   204  				return true
   205  			}
   206  			if used.Check(uint(port.Value)) {
   207  				collide = true
   208  			} else {
   209  				used.Set(uint(port.Value))
   210  			}
   211  		}
   212  	}
   213  
   214  	// Add the bandwidth
   215  	idx.UsedBandwidth[n.Device] += n.MBits
   216  	return
   217  }
   218  
   219  func (idx *NetworkIndex) AddReservedPorts(ports AllocatedPorts) (collide bool) {
   220  	for _, port := range ports {
   221  		used := idx.getUsedPortsFor(port.HostIP)
   222  		if port.Value < 0 || port.Value >= maxValidPort {
   223  			return true
   224  		}
   225  		if used.Check(uint(port.Value)) {
   226  			collide = true
   227  		} else {
   228  			used.Set(uint(port.Value))
   229  		}
   230  	}
   231  
   232  	return
   233  }
   234  
   235  // AddReservedPortRange marks the ports given as reserved on all network
   236  // interfaces. The port format is comma delimited, with spans given as n1-n2
   237  // (80,100-200,205)
   238  func (idx *NetworkIndex) AddReservedPortRange(ports string) (collide bool) {
   239  	// Convert the ports into a slice of ints
   240  	resPorts, err := ParsePortRanges(ports)
   241  	if err != nil {
   242  		return
   243  	}
   244  
   245  	// Ensure we create a bitmap for each available network
   246  	for _, n := range idx.AvailNetworks {
   247  		idx.getUsedPortsFor(n.IP)
   248  	}
   249  
   250  	for _, used := range idx.UsedPorts {
   251  		for _, port := range resPorts {
   252  			// Guard against invalid port
   253  			if port >= maxValidPort {
   254  				return true
   255  			}
   256  			if used.Check(uint(port)) {
   257  				collide = true
   258  			} else {
   259  				used.Set(uint(port))
   260  			}
   261  		}
   262  	}
   263  
   264  	return
   265  }
   266  
   267  // AddReservedPortsForIP
   268  func (idx *NetworkIndex) AddReservedPortsForIP(ports string, ip string) (collide bool) {
   269  	// Convert the ports into a slice of ints
   270  	resPorts, err := ParsePortRanges(ports)
   271  	if err != nil {
   272  		return
   273  	}
   274  
   275  	used := idx.getUsedPortsFor(ip)
   276  	for _, port := range resPorts {
   277  		// Guard against invalid port
   278  		if port >= maxValidPort {
   279  			return true
   280  		}
   281  		if used.Check(uint(port)) {
   282  			collide = true
   283  		} else {
   284  			used.Set(uint(port))
   285  		}
   286  	}
   287  
   288  	return
   289  }
   290  
   291  // yieldIP is used to iteratively invoke the callback with
   292  // an available IP
   293  func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
   294  	inc := func(ip net.IP) {
   295  		for j := len(ip) - 1; j >= 0; j-- {
   296  			ip[j]++
   297  			if ip[j] > 0 {
   298  				break
   299  			}
   300  		}
   301  	}
   302  
   303  	for _, n := range idx.AvailNetworks {
   304  		ip, ipnet, err := net.ParseCIDR(n.CIDR)
   305  		if err != nil {
   306  			continue
   307  		}
   308  		for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
   309  			if cb(n, ip) {
   310  				return
   311  			}
   312  		}
   313  	}
   314  }
   315  
   316  func (idx *NetworkIndex) AssignPorts(ask *NetworkResource) (AllocatedPorts, error) {
   317  	var offer AllocatedPorts
   318  
   319  	// index of host network name to slice of reserved ports, used during dynamic port assignment
   320  	reservedIdx := map[string][]Port{}
   321  
   322  	for _, port := range ask.ReservedPorts {
   323  		reservedIdx[port.HostNetwork] = append(reservedIdx[port.HostNetwork], port)
   324  
   325  		// allocPort is set in the inner for loop if a port mapping can be created
   326  		// if allocPort is still nil after the loop, the port wasn't available for reservation
   327  		var allocPort *AllocatedPortMapping
   328  		var addrErr error
   329  		for _, addr := range idx.AvailAddresses[port.HostNetwork] {
   330  			used := idx.getUsedPortsFor(addr.Address)
   331  			// Guard against invalid port
   332  			if port.Value < 0 || port.Value >= maxValidPort {
   333  				return nil, fmt.Errorf("invalid port %d (out of range)", port.Value)
   334  			}
   335  
   336  			// Check if in use
   337  			if used != nil && used.Check(uint(port.Value)) {
   338  				return nil, fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value)
   339  			}
   340  
   341  			allocPort = &AllocatedPortMapping{
   342  				Label:  port.Label,
   343  				Value:  port.Value,
   344  				To:     port.To,
   345  				HostIP: addr.Address,
   346  			}
   347  			break
   348  		}
   349  
   350  		if allocPort == nil {
   351  			if addrErr != nil {
   352  				return nil, addrErr
   353  			}
   354  
   355  			return nil, fmt.Errorf("no addresses available for %q network", port.HostNetwork)
   356  		}
   357  
   358  		offer = append(offer, *allocPort)
   359  	}
   360  
   361  	for _, port := range ask.DynamicPorts {
   362  		var allocPort *AllocatedPortMapping
   363  		var addrErr error
   364  		for _, addr := range idx.AvailAddresses[port.HostNetwork] {
   365  			used := idx.getUsedPortsFor(addr.Address)
   366  			// Try to stochastically pick the dynamic ports as it is faster and
   367  			// lower memory usage.
   368  			var dynPorts []int
   369  			// TODO: its more efficient to find multiple dynamic ports at once
   370  			dynPorts, addrErr = getDynamicPortsStochastic(used, reservedIdx[port.HostNetwork], 1)
   371  			if addrErr != nil {
   372  				// Fall back to the precise method if the random sampling failed.
   373  				dynPorts, addrErr = getDynamicPortsPrecise(used, reservedIdx[port.HostNetwork], 1)
   374  				if addrErr != nil {
   375  					continue
   376  				}
   377  			}
   378  
   379  			allocPort = &AllocatedPortMapping{
   380  				Label:  port.Label,
   381  				Value:  dynPorts[0],
   382  				To:     port.To,
   383  				HostIP: addr.Address,
   384  			}
   385  			if allocPort.To == -1 {
   386  				allocPort.To = allocPort.Value
   387  			}
   388  			break
   389  		}
   390  
   391  		if allocPort == nil {
   392  			if addrErr != nil {
   393  				return nil, addrErr
   394  			}
   395  
   396  			return nil, fmt.Errorf("no addresses available for %q network", port.HostNetwork)
   397  		}
   398  		offer = append(offer, *allocPort)
   399  	}
   400  
   401  	return offer, nil
   402  }
   403  
   404  // AssignNetwork is used to assign network resources given an ask.
   405  // If the ask cannot be satisfied, returns nil
   406  func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
   407  	err = fmt.Errorf("no networks available")
   408  	idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
   409  		// Convert the IP to a string
   410  		ipStr := ip.String()
   411  
   412  		// Check if we would exceed the bandwidth cap
   413  		availBandwidth := idx.AvailBandwidth[n.Device]
   414  		usedBandwidth := idx.UsedBandwidth[n.Device]
   415  		if usedBandwidth+ask.MBits > availBandwidth {
   416  			err = fmt.Errorf("bandwidth exceeded")
   417  			return
   418  		}
   419  
   420  		used := idx.UsedPorts[ipStr]
   421  
   422  		// Check if any of the reserved ports are in use
   423  		for _, port := range ask.ReservedPorts {
   424  			// Guard against invalid port
   425  			if port.Value < 0 || port.Value >= maxValidPort {
   426  				err = fmt.Errorf("invalid port %d (out of range)", port.Value)
   427  				return
   428  			}
   429  
   430  			// Check if in use
   431  			if used != nil && used.Check(uint(port.Value)) {
   432  				err = fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value)
   433  				return
   434  			}
   435  		}
   436  
   437  		// Create the offer
   438  		offer := &NetworkResource{
   439  			Mode:          ask.Mode,
   440  			Device:        n.Device,
   441  			IP:            ipStr,
   442  			MBits:         ask.MBits,
   443  			DNS:           ask.DNS,
   444  			ReservedPorts: ask.ReservedPorts,
   445  			DynamicPorts:  ask.DynamicPorts,
   446  		}
   447  
   448  		// Try to stochastically pick the dynamic ports as it is faster and
   449  		// lower memory usage.
   450  		var dynPorts []int
   451  		var dynErr error
   452  		dynPorts, dynErr = getDynamicPortsStochastic(used, ask.ReservedPorts, len(ask.DynamicPorts))
   453  		if dynErr == nil {
   454  			goto BUILD_OFFER
   455  		}
   456  
   457  		// Fall back to the precise method if the random sampling failed.
   458  		dynPorts, dynErr = getDynamicPortsPrecise(used, ask.ReservedPorts, len(ask.DynamicPorts))
   459  		if dynErr != nil {
   460  			err = dynErr
   461  			return
   462  		}
   463  
   464  	BUILD_OFFER:
   465  		for i, port := range dynPorts {
   466  			offer.DynamicPorts[i].Value = port
   467  
   468  			// This syntax allows you to set the mapped to port to the same port
   469  			// allocated by the scheduler on the host.
   470  			if offer.DynamicPorts[i].To == -1 {
   471  				offer.DynamicPorts[i].To = port
   472  			}
   473  		}
   474  
   475  		// Stop, we have an offer!
   476  		out = offer
   477  		err = nil
   478  		return true
   479  	})
   480  	return
   481  }
   482  
   483  // getDynamicPortsPrecise takes the nodes used port bitmap which may be nil if
   484  // no ports have been allocated yet, the network ask and returns a set of unused
   485  // ports to fulfil the ask's DynamicPorts or an error if it failed. An error
   486  // means the ask can not be satisfied as the method does a precise search.
   487  func getDynamicPortsPrecise(nodeUsed Bitmap, reserved []Port, numDyn int) ([]int, error) {
   488  	// Create a copy of the used ports and apply the new reserves
   489  	var usedSet Bitmap
   490  	var err error
   491  	if nodeUsed != nil {
   492  		usedSet, err = nodeUsed.Copy()
   493  		if err != nil {
   494  			return nil, err
   495  		}
   496  	} else {
   497  		usedSet, err = NewBitmap(maxValidPort)
   498  		if err != nil {
   499  			return nil, err
   500  		}
   501  	}
   502  
   503  	for _, port := range reserved {
   504  		usedSet.Set(uint(port.Value))
   505  	}
   506  
   507  	// Get the indexes of the unset
   508  	availablePorts := usedSet.IndexesInRange(false, MinDynamicPort, MaxDynamicPort)
   509  
   510  	// Randomize the amount we need
   511  	if len(availablePorts) < numDyn {
   512  		return nil, fmt.Errorf("dynamic port selection failed")
   513  	}
   514  
   515  	numAvailable := len(availablePorts)
   516  	for i := 0; i < numDyn; i++ {
   517  		j := rand.Intn(numAvailable)
   518  		availablePorts[i], availablePorts[j] = availablePorts[j], availablePorts[i]
   519  	}
   520  
   521  	return availablePorts[:numDyn], nil
   522  }
   523  
   524  // getDynamicPortsStochastic takes the nodes used port bitmap which may be nil if
   525  // no ports have been allocated yet, the network ask and returns a set of unused
   526  // ports to fulfil the ask's DynamicPorts or an error if it failed. An error
   527  // does not mean the ask can not be satisfied as the method has a fixed amount
   528  // of random probes and if these fail, the search is aborted.
   529  func getDynamicPortsStochastic(nodeUsed Bitmap, reservedPorts []Port, count int) ([]int, error) {
   530  	var reserved, dynamic []int
   531  	for _, port := range reservedPorts {
   532  		reserved = append(reserved, port.Value)
   533  	}
   534  
   535  	for i := 0; i < count; i++ {
   536  		attempts := 0
   537  	PICK:
   538  		attempts++
   539  		if attempts > maxRandPortAttempts {
   540  			return nil, fmt.Errorf("stochastic dynamic port selection failed")
   541  		}
   542  
   543  		randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   544  		if nodeUsed != nil && nodeUsed.Check(uint(randPort)) {
   545  			goto PICK
   546  		}
   547  
   548  		for _, ports := range [][]int{reserved, dynamic} {
   549  			if isPortReserved(ports, randPort) {
   550  				goto PICK
   551  			}
   552  		}
   553  		dynamic = append(dynamic, randPort)
   554  	}
   555  
   556  	return dynamic, nil
   557  }
   558  
   559  // IntContains scans an integer slice for a value
   560  func isPortReserved(haystack []int, needle int) bool {
   561  	for _, item := range haystack {
   562  		if item == needle {
   563  			return true
   564  		}
   565  	}
   566  	return false
   567  }
   568  
   569  // COMPAT(1.0) remove when NetworkResource is no longer used for materialized client view of ports
   570  func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts, node *NodeResources) *NetworkResource {
   571  	out := ask.Copy()
   572  
   573  	for i, port := range ask.DynamicPorts {
   574  		if p, ok := ports.Get(port.Label); ok {
   575  			out.DynamicPorts[i].Value = p.Value
   576  			out.DynamicPorts[i].To = p.To
   577  		}
   578  	}
   579  	if len(node.NodeNetworks) > 0 {
   580  		for _, nw := range node.NodeNetworks {
   581  			if nw.Mode == "host" {
   582  				out.IP = nw.Addresses[0].Address
   583  				break
   584  			}
   585  		}
   586  	} else {
   587  		for _, nw := range node.Networks {
   588  			if nw.Mode == "host" {
   589  				out.IP = nw.IP
   590  			}
   591  		}
   592  	}
   593  	return out
   594  }
   595  
   596  type ClientHostNetworkConfig struct {
   597  	Name          string `hcl:",key"`
   598  	CIDR          string `hcl:"cidr"`
   599  	Interface     string `hcl:"interface"`
   600  	ReservedPorts string `hcl:"reserved_ports"`
   601  }