github.com/uchennaokeke444/nomad@v0.11.8/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  	AvailBandwidth map[string]int     // Bandwidth by device
    38  	UsedPorts      map[string]Bitmap  // Ports by IP
    39  	UsedBandwidth  map[string]int     // Bandwidth by device
    40  }
    41  
    42  // NewNetworkIndex is used to construct a new network index
    43  func NewNetworkIndex() *NetworkIndex {
    44  	return &NetworkIndex{
    45  		AvailBandwidth: make(map[string]int),
    46  		UsedPorts:      make(map[string]Bitmap),
    47  		UsedBandwidth:  make(map[string]int),
    48  	}
    49  }
    50  
    51  // Release is called when the network index is no longer needed
    52  // to attempt to re-use some of the memory it has allocated
    53  func (idx *NetworkIndex) Release() {
    54  	for _, b := range idx.UsedPorts {
    55  		bitmapPool.Put(b)
    56  	}
    57  }
    58  
    59  // Overcommitted checks if the network is overcommitted
    60  func (idx *NetworkIndex) Overcommitted() bool {
    61  	for device, used := range idx.UsedBandwidth {
    62  		avail := idx.AvailBandwidth[device]
    63  		if used > avail {
    64  			return true
    65  		}
    66  	}
    67  	return false
    68  }
    69  
    70  // SetNode is used to setup the available network resources. Returns
    71  // true if there is a collision
    72  func (idx *NetworkIndex) SetNode(node *Node) (collide bool) {
    73  
    74  	// COMPAT(0.11): Remove in 0.11
    75  	// Grab the network resources, handling both new and old
    76  	var networks []*NetworkResource
    77  	if node.NodeResources != nil && len(node.NodeResources.Networks) != 0 {
    78  		networks = node.NodeResources.Networks
    79  	} else if node.Resources != nil {
    80  		networks = node.Resources.Networks
    81  	}
    82  
    83  	// Add the available CIDR blocks
    84  	for _, n := range networks {
    85  		if n.Device != "" {
    86  			idx.AvailNetworks = append(idx.AvailNetworks, n)
    87  			idx.AvailBandwidth[n.Device] = n.MBits
    88  		}
    89  	}
    90  
    91  	// COMPAT(0.11): Remove in 0.11
    92  	// Handle reserving ports, handling both new and old
    93  	if node.ReservedResources != nil && node.ReservedResources.Networks.ReservedHostPorts != "" {
    94  		collide = idx.AddReservedPortRange(node.ReservedResources.Networks.ReservedHostPorts)
    95  	} else if node.Reserved != nil {
    96  		for _, n := range node.Reserved.Networks {
    97  			if idx.AddReserved(n) {
    98  				collide = true
    99  			}
   100  		}
   101  	}
   102  
   103  	return
   104  }
   105  
   106  // AddAllocs is used to add the used network resources. Returns
   107  // true if there is a collision
   108  func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
   109  	for _, alloc := range allocs {
   110  		// Do not consider the resource impact of terminal allocations
   111  		if alloc.TerminalStatus() {
   112  			continue
   113  		}
   114  
   115  		if alloc.AllocatedResources != nil {
   116  			// Add network resources that are at the task group level
   117  			if len(alloc.AllocatedResources.Shared.Networks) > 0 {
   118  				for _, network := range alloc.AllocatedResources.Shared.Networks {
   119  					if idx.AddReserved(network) {
   120  						collide = true
   121  					}
   122  				}
   123  			}
   124  
   125  			for _, task := range alloc.AllocatedResources.Tasks {
   126  				if len(task.Networks) == 0 {
   127  					continue
   128  				}
   129  				n := task.Networks[0]
   130  				if idx.AddReserved(n) {
   131  					collide = true
   132  				}
   133  			}
   134  		} else {
   135  			// COMPAT(0.11): Remove in 0.11
   136  			for _, task := range alloc.TaskResources {
   137  				if len(task.Networks) == 0 {
   138  					continue
   139  				}
   140  				n := task.Networks[0]
   141  				if idx.AddReserved(n) {
   142  					collide = true
   143  				}
   144  			}
   145  		}
   146  	}
   147  	return
   148  }
   149  
   150  // AddReserved is used to add a reserved network usage, returns true
   151  // if there is a port collision
   152  func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
   153  	// Add the port usage
   154  	used := idx.UsedPorts[n.IP]
   155  	if used == nil {
   156  		// Try to get a bitmap from the pool, else create
   157  		raw := bitmapPool.Get()
   158  		if raw != nil {
   159  			used = raw.(Bitmap)
   160  			used.Clear()
   161  		} else {
   162  			used, _ = NewBitmap(maxValidPort)
   163  		}
   164  		idx.UsedPorts[n.IP] = used
   165  	}
   166  
   167  	for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} {
   168  		for _, port := range ports {
   169  			// Guard against invalid port
   170  			if port.Value < 0 || port.Value >= maxValidPort {
   171  				return true
   172  			}
   173  			if used.Check(uint(port.Value)) {
   174  				collide = true
   175  			} else {
   176  				used.Set(uint(port.Value))
   177  			}
   178  		}
   179  	}
   180  
   181  	// Add the bandwidth
   182  	idx.UsedBandwidth[n.Device] += n.MBits
   183  	return
   184  }
   185  
   186  // AddReservedPortRange marks the ports given as reserved on all network
   187  // interfaces. The port format is comma delimited, with spans given as n1-n2
   188  // (80,100-200,205)
   189  func (idx *NetworkIndex) AddReservedPortRange(ports string) (collide bool) {
   190  	// Convert the ports into a slice of ints
   191  	resPorts, err := ParsePortRanges(ports)
   192  	if err != nil {
   193  		return
   194  	}
   195  
   196  	// Ensure we create a bitmap for each available network
   197  	for _, n := range idx.AvailNetworks {
   198  		used := idx.UsedPorts[n.IP]
   199  		if used == nil {
   200  			// Try to get a bitmap from the pool, else create
   201  			raw := bitmapPool.Get()
   202  			if raw != nil {
   203  				used = raw.(Bitmap)
   204  				used.Clear()
   205  			} else {
   206  				used, _ = NewBitmap(maxValidPort)
   207  			}
   208  			idx.UsedPorts[n.IP] = used
   209  		}
   210  	}
   211  
   212  	for _, used := range idx.UsedPorts {
   213  		for _, port := range resPorts {
   214  			// Guard against invalid port
   215  			if port < 0 || port >= maxValidPort {
   216  				return true
   217  			}
   218  			if used.Check(uint(port)) {
   219  				collide = true
   220  			} else {
   221  				used.Set(uint(port))
   222  			}
   223  		}
   224  	}
   225  
   226  	return
   227  }
   228  
   229  // yieldIP is used to iteratively invoke the callback with
   230  // an available IP
   231  func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
   232  	inc := func(ip net.IP) {
   233  		for j := len(ip) - 1; j >= 0; j-- {
   234  			ip[j]++
   235  			if ip[j] > 0 {
   236  				break
   237  			}
   238  		}
   239  	}
   240  
   241  	for _, n := range idx.AvailNetworks {
   242  		ip, ipnet, err := net.ParseCIDR(n.CIDR)
   243  		if err != nil {
   244  			continue
   245  		}
   246  		for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
   247  			if cb(n, ip) {
   248  				return
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  // AssignNetwork is used to assign network resources given an ask.
   255  // If the ask cannot be satisfied, returns nil
   256  func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
   257  	err = fmt.Errorf("no networks available")
   258  	idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
   259  		// Convert the IP to a string
   260  		ipStr := ip.String()
   261  
   262  		// Check if we would exceed the bandwidth cap
   263  		availBandwidth := idx.AvailBandwidth[n.Device]
   264  		usedBandwidth := idx.UsedBandwidth[n.Device]
   265  		if usedBandwidth+ask.MBits > availBandwidth {
   266  			err = fmt.Errorf("bandwidth exceeded")
   267  			return
   268  		}
   269  
   270  		used := idx.UsedPorts[ipStr]
   271  
   272  		// Check if any of the reserved ports are in use
   273  		for _, port := range ask.ReservedPorts {
   274  			// Guard against invalid port
   275  			if port.Value < 0 || port.Value >= maxValidPort {
   276  				err = fmt.Errorf("invalid port %d (out of range)", port.Value)
   277  				return
   278  			}
   279  
   280  			// Check if in use
   281  			if used != nil && used.Check(uint(port.Value)) {
   282  				err = fmt.Errorf("reserved port collision")
   283  				return
   284  			}
   285  		}
   286  
   287  		// Create the offer
   288  		offer := &NetworkResource{
   289  			Mode:          ask.Mode,
   290  			Device:        n.Device,
   291  			IP:            ipStr,
   292  			MBits:         ask.MBits,
   293  			ReservedPorts: ask.ReservedPorts,
   294  			DynamicPorts:  ask.DynamicPorts,
   295  		}
   296  
   297  		// Try to stochastically pick the dynamic ports as it is faster and
   298  		// lower memory usage.
   299  		var dynPorts []int
   300  		var dynErr error
   301  		dynPorts, dynErr = getDynamicPortsStochastic(used, ask)
   302  		if dynErr == nil {
   303  			goto BUILD_OFFER
   304  		}
   305  
   306  		// Fall back to the precise method if the random sampling failed.
   307  		dynPorts, dynErr = getDynamicPortsPrecise(used, ask)
   308  		if dynErr != nil {
   309  			err = dynErr
   310  			return
   311  		}
   312  
   313  	BUILD_OFFER:
   314  		for i, port := range dynPorts {
   315  			offer.DynamicPorts[i].Value = port
   316  
   317  			// This syntax allows you to set the mapped to port to the same port
   318  			// allocated by the scheduler on the host.
   319  			if offer.DynamicPorts[i].To == -1 {
   320  				offer.DynamicPorts[i].To = port
   321  			}
   322  		}
   323  
   324  		// Stop, we have an offer!
   325  		out = offer
   326  		err = nil
   327  		return true
   328  	})
   329  	return
   330  }
   331  
   332  // getDynamicPortsPrecise takes the nodes used port bitmap which may be nil if
   333  // no ports have been allocated yet, the network ask and returns a set of unused
   334  // ports to fulfil the ask's DynamicPorts or an error if it failed. An error
   335  // means the ask can not be satisfied as the method does a precise search.
   336  func getDynamicPortsPrecise(nodeUsed Bitmap, ask *NetworkResource) ([]int, error) {
   337  	// Create a copy of the used ports and apply the new reserves
   338  	var usedSet Bitmap
   339  	var err error
   340  	if nodeUsed != nil {
   341  		usedSet, err = nodeUsed.Copy()
   342  		if err != nil {
   343  			return nil, err
   344  		}
   345  	} else {
   346  		usedSet, err = NewBitmap(maxValidPort)
   347  		if err != nil {
   348  			return nil, err
   349  		}
   350  	}
   351  
   352  	for _, port := range ask.ReservedPorts {
   353  		usedSet.Set(uint(port.Value))
   354  	}
   355  
   356  	// Get the indexes of the unset
   357  	availablePorts := usedSet.IndexesInRange(false, MinDynamicPort, MaxDynamicPort)
   358  
   359  	// Randomize the amount we need
   360  	numDyn := len(ask.DynamicPorts)
   361  	if len(availablePorts) < numDyn {
   362  		return nil, fmt.Errorf("dynamic port selection failed")
   363  	}
   364  
   365  	numAvailable := len(availablePorts)
   366  	for i := 0; i < numDyn; i++ {
   367  		j := rand.Intn(numAvailable)
   368  		availablePorts[i], availablePorts[j] = availablePorts[j], availablePorts[i]
   369  	}
   370  
   371  	return availablePorts[:numDyn], nil
   372  }
   373  
   374  // getDynamicPortsStochastic takes the nodes used port bitmap which may be nil if
   375  // no ports have been allocated yet, the network ask and returns a set of unused
   376  // ports to fulfil the ask's DynamicPorts or an error if it failed. An error
   377  // does not mean the ask can not be satisfied as the method has a fixed amount
   378  // of random probes and if these fail, the search is aborted.
   379  func getDynamicPortsStochastic(nodeUsed Bitmap, ask *NetworkResource) ([]int, error) {
   380  	var reserved, dynamic []int
   381  	for _, port := range ask.ReservedPorts {
   382  		reserved = append(reserved, port.Value)
   383  	}
   384  
   385  	for i := 0; i < len(ask.DynamicPorts); i++ {
   386  		attempts := 0
   387  	PICK:
   388  		attempts++
   389  		if attempts > maxRandPortAttempts {
   390  			return nil, fmt.Errorf("stochastic dynamic port selection failed")
   391  		}
   392  
   393  		randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   394  		if nodeUsed != nil && nodeUsed.Check(uint(randPort)) {
   395  			goto PICK
   396  		}
   397  
   398  		for _, ports := range [][]int{reserved, dynamic} {
   399  			if isPortReserved(ports, randPort) {
   400  				goto PICK
   401  			}
   402  		}
   403  		dynamic = append(dynamic, randPort)
   404  	}
   405  
   406  	return dynamic, nil
   407  }
   408  
   409  // IntContains scans an integer slice for a value
   410  func isPortReserved(haystack []int, needle int) bool {
   411  	for _, item := range haystack {
   412  		if item == needle {
   413  			return true
   414  		}
   415  	}
   416  	return false
   417  }