github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/structs/network.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"net"
     7  )
     8  
     9  const (
    10  	// MinDynamicPort is the smallest dynamic port generated
    11  	MinDynamicPort = 20000
    12  
    13  	// MaxDynamicPort is the largest dynamic port generated
    14  	MaxDynamicPort = 60000
    15  
    16  	// maxRandPortAttempts is the maximum number of attempt
    17  	// to assign a random port
    18  	maxRandPortAttempts = 20
    19  )
    20  
    21  // NetworkIndex is used to index the available network resources
    22  // and the used network resources on a machine given allocations
    23  type NetworkIndex struct {
    24  	AvailNetworks  []*NetworkResource          // List of available networks
    25  	AvailBandwidth map[string]int              // Bandwidth by device
    26  	UsedPorts      map[string]map[int]struct{} // Ports by IP
    27  	UsedBandwidth  map[string]int              // Bandwidth by device
    28  }
    29  
    30  // NewNetworkIndex is used to construct a new network index
    31  func NewNetworkIndex() *NetworkIndex {
    32  	return &NetworkIndex{
    33  		AvailBandwidth: make(map[string]int),
    34  		UsedPorts:      make(map[string]map[int]struct{}),
    35  		UsedBandwidth:  make(map[string]int),
    36  	}
    37  }
    38  
    39  // Overcommitted checks if the network is overcommitted
    40  func (idx *NetworkIndex) Overcommitted() bool {
    41  	for device, used := range idx.UsedBandwidth {
    42  		avail := idx.AvailBandwidth[device]
    43  		if used > avail {
    44  			return true
    45  		}
    46  	}
    47  	return false
    48  }
    49  
    50  // SetNode is used to setup the available network resources. Returns
    51  // true if there is a collision
    52  func (idx *NetworkIndex) SetNode(node *Node) (collide bool) {
    53  	// Add the available CIDR blocks
    54  	for _, n := range node.Resources.Networks {
    55  		if n.Device != "" {
    56  			idx.AvailNetworks = append(idx.AvailNetworks, n)
    57  			idx.AvailBandwidth[n.Device] = n.MBits
    58  		}
    59  	}
    60  
    61  	// Add the reserved resources
    62  	if r := node.Reserved; r != nil {
    63  		for _, n := range r.Networks {
    64  			if idx.AddReserved(n) {
    65  				collide = true
    66  			}
    67  		}
    68  	}
    69  	return
    70  }
    71  
    72  // AddAllocs is used to add the used network resources. Returns
    73  // true if there is a collision
    74  func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
    75  	for _, alloc := range allocs {
    76  		for _, task := range alloc.TaskResources {
    77  			if len(task.Networks) == 0 {
    78  				continue
    79  			}
    80  			n := task.Networks[0]
    81  			if idx.AddReserved(n) {
    82  				collide = true
    83  			}
    84  		}
    85  	}
    86  	return
    87  }
    88  
    89  // AddReserved is used to add a reserved network usage, returns true
    90  // if there is a port collision
    91  func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
    92  	// Add the port usage
    93  	used := idx.UsedPorts[n.IP]
    94  	if used == nil {
    95  		used = make(map[int]struct{})
    96  		idx.UsedPorts[n.IP] = used
    97  	}
    98  
    99  	for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} {
   100  		for _, port := range ports {
   101  			if _, ok := used[port.Value]; ok {
   102  				collide = true
   103  			} else {
   104  				used[port.Value] = struct{}{}
   105  			}
   106  		}
   107  	}
   108  
   109  	// Add the bandwidth
   110  	idx.UsedBandwidth[n.Device] += n.MBits
   111  	return
   112  }
   113  
   114  // yieldIP is used to iteratively invoke the callback with
   115  // an available IP
   116  func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
   117  	inc := func(ip net.IP) {
   118  		for j := len(ip) - 1; j >= 0; j-- {
   119  			ip[j]++
   120  			if ip[j] > 0 {
   121  				break
   122  			}
   123  		}
   124  	}
   125  
   126  	for _, n := range idx.AvailNetworks {
   127  		ip, ipnet, err := net.ParseCIDR(n.CIDR)
   128  		if err != nil {
   129  			continue
   130  		}
   131  		for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
   132  			if cb(n, ip) {
   133  				return
   134  			}
   135  		}
   136  	}
   137  }
   138  
   139  // AssignNetwork is used to assign network resources given an ask.
   140  // If the ask cannot be satisfied, returns nil
   141  func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
   142  	err = fmt.Errorf("no networks available")
   143  	idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
   144  		// Convert the IP to a string
   145  		ipStr := ip.String()
   146  
   147  		// Check if we would exceed the bandwidth cap
   148  		availBandwidth := idx.AvailBandwidth[n.Device]
   149  		usedBandwidth := idx.UsedBandwidth[n.Device]
   150  		if usedBandwidth+ask.MBits > availBandwidth {
   151  			err = fmt.Errorf("bandwidth exceeded")
   152  			return
   153  		}
   154  
   155  		// Check if any of the reserved ports are in use
   156  		for _, port := range ask.ReservedPorts {
   157  			if _, ok := idx.UsedPorts[ipStr][port.Value]; ok {
   158  				err = fmt.Errorf("reserved port collision")
   159  				return
   160  			}
   161  		}
   162  
   163  		// Create the offer
   164  		offer := &NetworkResource{
   165  			Device:        n.Device,
   166  			IP:            ipStr,
   167  			ReservedPorts: ask.ReservedPorts,
   168  			DynamicPorts:  ask.DynamicPorts,
   169  		}
   170  
   171  		// Check if we need to generate any ports
   172  		for i := 0; i < len(ask.DynamicPorts); i++ {
   173  			attempts := 0
   174  		PICK:
   175  			attempts++
   176  			if attempts > maxRandPortAttempts {
   177  				err = fmt.Errorf("dynamic port selection failed")
   178  				return
   179  			}
   180  
   181  			randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   182  			if _, ok := idx.UsedPorts[ipStr][randPort]; ok {
   183  				goto PICK
   184  			}
   185  
   186  			for _, ports := range [][]Port{offer.ReservedPorts, offer.DynamicPorts} {
   187  				if isPortReserved(ports, randPort) {
   188  					goto PICK
   189  				}
   190  			}
   191  			offer.DynamicPorts[i].Value = randPort
   192  		}
   193  
   194  		// Stop, we have an offer!
   195  		out = offer
   196  		err = nil
   197  		return true
   198  	})
   199  	return
   200  }
   201  
   202  // IntContains scans an integer slice for a value
   203  func isPortReserved(haystack []Port, needle int) bool {
   204  	for _, item := range haystack {
   205  		if item.Value == needle {
   206  			return true
   207  		}
   208  	}
   209  	return false
   210  }