github.com/quite/nomad@v0.8.6/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  	// Add the available CIDR blocks
    74  	for _, n := range node.Resources.Networks {
    75  		if n.Device != "" {
    76  			idx.AvailNetworks = append(idx.AvailNetworks, n)
    77  			idx.AvailBandwidth[n.Device] = n.MBits
    78  		}
    79  	}
    80  
    81  	// Add the reserved resources
    82  	if r := node.Reserved; r != nil {
    83  		for _, n := range r.Networks {
    84  			if idx.AddReserved(n) {
    85  				collide = true
    86  			}
    87  		}
    88  	}
    89  	return
    90  }
    91  
    92  // AddAllocs is used to add the used network resources. Returns
    93  // true if there is a collision
    94  func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
    95  	for _, alloc := range allocs {
    96  		for _, task := range alloc.TaskResources {
    97  			if len(task.Networks) == 0 {
    98  				continue
    99  			}
   100  			n := task.Networks[0]
   101  			if idx.AddReserved(n) {
   102  				collide = true
   103  			}
   104  		}
   105  	}
   106  	return
   107  }
   108  
   109  // AddReserved is used to add a reserved network usage, returns true
   110  // if there is a port collision
   111  func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
   112  	// Add the port usage
   113  	used := idx.UsedPorts[n.IP]
   114  	if used == nil {
   115  		// Try to get a bitmap from the pool, else create
   116  		raw := bitmapPool.Get()
   117  		if raw != nil {
   118  			used = raw.(Bitmap)
   119  			used.Clear()
   120  		} else {
   121  			used, _ = NewBitmap(maxValidPort)
   122  		}
   123  		idx.UsedPorts[n.IP] = used
   124  	}
   125  
   126  	for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} {
   127  		for _, port := range ports {
   128  			// Guard against invalid port
   129  			if port.Value < 0 || port.Value >= maxValidPort {
   130  				return true
   131  			}
   132  			if used.Check(uint(port.Value)) {
   133  				collide = true
   134  			} else {
   135  				used.Set(uint(port.Value))
   136  			}
   137  		}
   138  	}
   139  
   140  	// Add the bandwidth
   141  	idx.UsedBandwidth[n.Device] += n.MBits
   142  	return
   143  }
   144  
   145  // yieldIP is used to iteratively invoke the callback with
   146  // an available IP
   147  func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
   148  	inc := func(ip net.IP) {
   149  		for j := len(ip) - 1; j >= 0; j-- {
   150  			ip[j]++
   151  			if ip[j] > 0 {
   152  				break
   153  			}
   154  		}
   155  	}
   156  
   157  	for _, n := range idx.AvailNetworks {
   158  		ip, ipnet, err := net.ParseCIDR(n.CIDR)
   159  		if err != nil {
   160  			continue
   161  		}
   162  		for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
   163  			if cb(n, ip) {
   164  				return
   165  			}
   166  		}
   167  	}
   168  }
   169  
   170  // AssignNetwork is used to assign network resources given an ask.
   171  // If the ask cannot be satisfied, returns nil
   172  func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
   173  	err = fmt.Errorf("no networks available")
   174  	idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
   175  		// Convert the IP to a string
   176  		ipStr := ip.String()
   177  
   178  		// Check if we would exceed the bandwidth cap
   179  		availBandwidth := idx.AvailBandwidth[n.Device]
   180  		usedBandwidth := idx.UsedBandwidth[n.Device]
   181  		if usedBandwidth+ask.MBits > availBandwidth {
   182  			err = fmt.Errorf("bandwidth exceeded")
   183  			return
   184  		}
   185  
   186  		used := idx.UsedPorts[ipStr]
   187  
   188  		// Check if any of the reserved ports are in use
   189  		for _, port := range ask.ReservedPorts {
   190  			// Guard against invalid port
   191  			if port.Value < 0 || port.Value >= maxValidPort {
   192  				err = fmt.Errorf("invalid port %d (out of range)", port.Value)
   193  				return
   194  			}
   195  
   196  			// Check if in use
   197  			if used != nil && used.Check(uint(port.Value)) {
   198  				err = fmt.Errorf("reserved port collision")
   199  				return
   200  			}
   201  		}
   202  
   203  		// Create the offer
   204  		offer := &NetworkResource{
   205  			Device:        n.Device,
   206  			IP:            ipStr,
   207  			MBits:         ask.MBits,
   208  			ReservedPorts: ask.ReservedPorts,
   209  			DynamicPorts:  ask.DynamicPorts,
   210  		}
   211  
   212  		// Try to stochastically pick the dynamic ports as it is faster and
   213  		// lower memory usage.
   214  		var dynPorts []int
   215  		var dynErr error
   216  		dynPorts, dynErr = getDynamicPortsStochastic(used, ask)
   217  		if dynErr == nil {
   218  			goto BUILD_OFFER
   219  		}
   220  
   221  		// Fall back to the precise method if the random sampling failed.
   222  		dynPorts, dynErr = getDynamicPortsPrecise(used, ask)
   223  		if dynErr != nil {
   224  			err = dynErr
   225  			return
   226  		}
   227  
   228  	BUILD_OFFER:
   229  		for i, port := range dynPorts {
   230  			offer.DynamicPorts[i].Value = port
   231  		}
   232  
   233  		// Stop, we have an offer!
   234  		out = offer
   235  		err = nil
   236  		return true
   237  	})
   238  	return
   239  }
   240  
   241  // getDynamicPortsPrecise takes the nodes used port bitmap which may be nil if
   242  // no ports have been allocated yet, the network ask and returns a set of unused
   243  // ports to fullfil the ask's DynamicPorts or an error if it failed. An error
   244  // means the ask can not be satisfied as the method does a precise search.
   245  func getDynamicPortsPrecise(nodeUsed Bitmap, ask *NetworkResource) ([]int, error) {
   246  	// Create a copy of the used ports and apply the new reserves
   247  	var usedSet Bitmap
   248  	var err error
   249  	if nodeUsed != nil {
   250  		usedSet, err = nodeUsed.Copy()
   251  		if err != nil {
   252  			return nil, err
   253  		}
   254  	} else {
   255  		usedSet, err = NewBitmap(maxValidPort)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  	}
   260  
   261  	for _, port := range ask.ReservedPorts {
   262  		usedSet.Set(uint(port.Value))
   263  	}
   264  
   265  	// Get the indexes of the unset
   266  	availablePorts := usedSet.IndexesInRange(false, MinDynamicPort, MaxDynamicPort)
   267  
   268  	// Randomize the amount we need
   269  	numDyn := len(ask.DynamicPorts)
   270  	if len(availablePorts) < numDyn {
   271  		return nil, fmt.Errorf("dynamic port selection failed")
   272  	}
   273  
   274  	numAvailable := len(availablePorts)
   275  	for i := 0; i < numDyn; i++ {
   276  		j := rand.Intn(numAvailable)
   277  		availablePorts[i], availablePorts[j] = availablePorts[j], availablePorts[i]
   278  	}
   279  
   280  	return availablePorts[:numDyn], nil
   281  }
   282  
   283  // getDynamicPortsStochastic takes the nodes used port bitmap which may be nil if
   284  // no ports have been allocated yet, the network ask and returns a set of unused
   285  // ports to fullfil the ask's DynamicPorts or an error if it failed. An error
   286  // does not mean the ask can not be satisfied as the method has a fixed amount
   287  // of random probes and if these fail, the search is aborted.
   288  func getDynamicPortsStochastic(nodeUsed Bitmap, ask *NetworkResource) ([]int, error) {
   289  	var reserved, dynamic []int
   290  	for _, port := range ask.ReservedPorts {
   291  		reserved = append(reserved, port.Value)
   292  	}
   293  
   294  	for i := 0; i < len(ask.DynamicPorts); i++ {
   295  		attempts := 0
   296  	PICK:
   297  		attempts++
   298  		if attempts > maxRandPortAttempts {
   299  			return nil, fmt.Errorf("stochastic dynamic port selection failed")
   300  		}
   301  
   302  		randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   303  		if nodeUsed != nil && nodeUsed.Check(uint(randPort)) {
   304  			goto PICK
   305  		}
   306  
   307  		for _, ports := range [][]int{reserved, dynamic} {
   308  			if isPortReserved(ports, randPort) {
   309  				goto PICK
   310  			}
   311  		}
   312  		dynamic = append(dynamic, randPort)
   313  	}
   314  
   315  	return dynamic, nil
   316  }
   317  
   318  // IntContains scans an integer slice for a value
   319  func isPortReserved(haystack []int, needle int) bool {
   320  	for _, item := range haystack {
   321  		if item == needle {
   322  			return true
   323  		}
   324  	}
   325  	return false
   326  }