github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/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 = 60000
    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  		// Check if any of the reserved ports are in use
   187  		for _, port := range ask.ReservedPorts {
   188  			// Guard against invalid port
   189  			if port.Value < 0 || port.Value >= maxValidPort {
   190  				err = fmt.Errorf("invalid port %d (out of range)", port.Value)
   191  				return
   192  			}
   193  
   194  			// Check if in use
   195  			used := idx.UsedPorts[ipStr]
   196  			if used != nil && used.Check(uint(port.Value)) {
   197  				err = fmt.Errorf("reserved port collision")
   198  				return
   199  			}
   200  		}
   201  
   202  		// Create the offer
   203  		offer := &NetworkResource{
   204  			Device:        n.Device,
   205  			IP:            ipStr,
   206  			ReservedPorts: ask.ReservedPorts,
   207  			DynamicPorts:  ask.DynamicPorts,
   208  		}
   209  
   210  		// Check if we need to generate any ports
   211  		for i := 0; i < len(ask.DynamicPorts); i++ {
   212  			attempts := 0
   213  		PICK:
   214  			attempts++
   215  			if attempts > maxRandPortAttempts {
   216  				err = fmt.Errorf("dynamic port selection failed")
   217  				return
   218  			}
   219  
   220  			randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   221  			used := idx.UsedPorts[ipStr]
   222  			if used != nil && used.Check(uint(randPort)) {
   223  				goto PICK
   224  			}
   225  
   226  			for _, ports := range [][]Port{offer.ReservedPorts, offer.DynamicPorts} {
   227  				if isPortReserved(ports, randPort) {
   228  					goto PICK
   229  				}
   230  			}
   231  			offer.DynamicPorts[i].Value = randPort
   232  		}
   233  
   234  		// Stop, we have an offer!
   235  		out = offer
   236  		err = nil
   237  		return true
   238  	})
   239  	return
   240  }
   241  
   242  // IntContains scans an integer slice for a value
   243  func isPortReserved(haystack []Port, needle int) bool {
   244  	for _, item := range haystack {
   245  		if item.Value == needle {
   246  			return true
   247  		}
   248  	}
   249  	return false
   250  }