github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/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  	for _, port := range n.ReservedPorts {
    99  		if _, ok := used[port]; ok {
   100  			collide = true
   101  		} else {
   102  			used[port] = struct{}{}
   103  		}
   104  	}
   105  
   106  	// Add the bandwidth
   107  	idx.UsedBandwidth[n.Device] += n.MBits
   108  	return
   109  }
   110  
   111  // yieldIP is used to iteratively invoke the callback with
   112  // an available IP
   113  func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
   114  	inc := func(ip net.IP) {
   115  		for j := len(ip) - 1; j >= 0; j-- {
   116  			ip[j]++
   117  			if ip[j] > 0 {
   118  				break
   119  			}
   120  		}
   121  	}
   122  
   123  	for _, n := range idx.AvailNetworks {
   124  		ip, ipnet, err := net.ParseCIDR(n.CIDR)
   125  		if err != nil {
   126  			continue
   127  		}
   128  		for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
   129  			if cb(n, ip) {
   130  				return
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  // AssignNetwork is used to assign network resources given an ask.
   137  // If the ask cannot be satisfied, returns nil
   138  func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
   139  	err = fmt.Errorf("no networks available")
   140  	idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
   141  		// Convert the IP to a string
   142  		ipStr := ip.String()
   143  
   144  		// Check if we would exceed the bandwidth cap
   145  		availBandwidth := idx.AvailBandwidth[n.Device]
   146  		usedBandwidth := idx.UsedBandwidth[n.Device]
   147  		if usedBandwidth+ask.MBits > availBandwidth {
   148  			err = fmt.Errorf("bandwidth exceeded")
   149  			return
   150  		}
   151  
   152  		// Check if any of the reserved ports are in use
   153  		for _, port := range ask.ReservedPorts {
   154  			if _, ok := idx.UsedPorts[ipStr][port]; ok {
   155  				err = fmt.Errorf("reserved port collision")
   156  				return
   157  			}
   158  		}
   159  
   160  		// Create the offer
   161  		offer := &NetworkResource{
   162  			Device:        n.Device,
   163  			IP:            ipStr,
   164  			ReservedPorts: ask.ReservedPorts,
   165  			DynamicPorts:  ask.DynamicPorts,
   166  		}
   167  
   168  		// Check if we need to generate any ports
   169  		for i := 0; i < len(ask.DynamicPorts); i++ {
   170  			attempts := 0
   171  		PICK:
   172  			attempts++
   173  			if attempts > maxRandPortAttempts {
   174  				err = fmt.Errorf("dynamic port selection failed")
   175  				return
   176  			}
   177  
   178  			randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
   179  			if _, ok := idx.UsedPorts[ipStr][randPort]; ok {
   180  				goto PICK
   181  			}
   182  			if IntContains(offer.ReservedPorts, randPort) {
   183  				goto PICK
   184  			}
   185  			offer.ReservedPorts = append(offer.ReservedPorts, randPort)
   186  		}
   187  
   188  		// Stop, we have an offer!
   189  		out = offer
   190  		err = nil
   191  		return true
   192  	})
   193  	return
   194  }
   195  
   196  // IntContains scans an integer slice for a value
   197  func IntContains(haystack []int, needle int) bool {
   198  	for _, item := range haystack {
   199  		if item == needle {
   200  			return true
   201  		}
   202  	}
   203  	return false
   204  }