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