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 }