github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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 NodeNetworks []*NodeNetworkResource // List of available node networks 38 AvailAddresses map[string][]NodeNetworkAddress // Map of host network aliases to list of addresses 39 AvailBandwidth map[string]int // Bandwidth by device 40 UsedPorts map[string]Bitmap // Ports by IP 41 UsedBandwidth map[string]int // Bandwidth by device 42 } 43 44 // NewNetworkIndex is used to construct a new network index 45 func NewNetworkIndex() *NetworkIndex { 46 return &NetworkIndex{ 47 AvailAddresses: make(map[string][]NodeNetworkAddress), 48 AvailBandwidth: make(map[string]int), 49 UsedPorts: make(map[string]Bitmap), 50 UsedBandwidth: make(map[string]int), 51 } 52 } 53 54 func (idx *NetworkIndex) getUsedPortsFor(ip string) Bitmap { 55 used := idx.UsedPorts[ip] 56 if used == nil { 57 // Try to get a bitmap from the pool, else create 58 raw := bitmapPool.Get() 59 if raw != nil { 60 used = raw.(Bitmap) 61 used.Clear() 62 } else { 63 used, _ = NewBitmap(maxValidPort) 64 } 65 idx.UsedPorts[ip] = used 66 } 67 return used 68 } 69 70 // Release is called when the network index is no longer needed 71 // to attempt to re-use some of the memory it has allocated 72 func (idx *NetworkIndex) Release() { 73 for _, b := range idx.UsedPorts { 74 bitmapPool.Put(b) 75 } 76 } 77 78 // Overcommitted checks if the network is overcommitted 79 func (idx *NetworkIndex) Overcommitted() bool { 80 // TODO remove since bandwidth is deprecated 81 /*for device, used := range idx.UsedBandwidth { 82 avail := idx.AvailBandwidth[device] 83 if used > avail { 84 return true 85 } 86 }*/ 87 return false 88 } 89 90 // SetNode is used to setup the available network resources. Returns 91 // true if there is a collision 92 func (idx *NetworkIndex) SetNode(node *Node) (collide bool) { 93 94 // COMPAT(0.11): Remove in 0.11 95 // Grab the network resources, handling both new and old 96 var networks []*NetworkResource 97 if node.NodeResources != nil && len(node.NodeResources.Networks) != 0 { 98 networks = node.NodeResources.Networks 99 } else if node.Resources != nil { 100 networks = node.Resources.Networks 101 } 102 103 var nodeNetworks []*NodeNetworkResource 104 if node.NodeResources != nil && len(node.NodeResources.NodeNetworks) != 0 { 105 nodeNetworks = node.NodeResources.NodeNetworks 106 } 107 108 // Add the available CIDR blocks 109 for _, n := range networks { 110 if n.Device != "" { 111 idx.AvailNetworks = append(idx.AvailNetworks, n) 112 idx.AvailBandwidth[n.Device] = n.MBits 113 } 114 } 115 116 // TODO: upgrade path? 117 // is it possible to get duplicates here? 118 for _, n := range nodeNetworks { 119 for _, a := range n.Addresses { 120 idx.AvailAddresses[a.Alias] = append(idx.AvailAddresses[a.Alias], a) 121 if idx.AddReservedPortsForIP(a.ReservedPorts, a.Address) { 122 collide = true 123 } 124 } 125 } 126 127 // COMPAT(0.11): Remove in 0.11 128 // Handle reserving ports, handling both new and old 129 if node.ReservedResources != nil && node.ReservedResources.Networks.ReservedHostPorts != "" { 130 collide = idx.AddReservedPortRange(node.ReservedResources.Networks.ReservedHostPorts) 131 } else if node.Reserved != nil { 132 for _, n := range node.Reserved.Networks { 133 if idx.AddReserved(n) { 134 collide = true 135 } 136 } 137 } 138 139 return 140 } 141 142 // AddAllocs is used to add the used network resources. Returns 143 // true if there is a collision 144 func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) { 145 for _, alloc := range allocs { 146 // Do not consider the resource impact of terminal allocations 147 if alloc.TerminalStatus() { 148 continue 149 } 150 151 if alloc.AllocatedResources != nil { 152 // Only look at AllocatedPorts if populated, otherwise use pre 0.12 logic 153 // COMPAT(1.0): Remove when network resources struct is removed. 154 if len(alloc.AllocatedResources.Shared.Ports) > 0 { 155 if idx.AddReservedPorts(alloc.AllocatedResources.Shared.Ports) { 156 collide = true 157 } 158 } else { 159 // Add network resources that are at the task group level 160 if len(alloc.AllocatedResources.Shared.Networks) > 0 { 161 for _, network := range alloc.AllocatedResources.Shared.Networks { 162 if idx.AddReserved(network) { 163 collide = true 164 } 165 } 166 } 167 168 for _, task := range alloc.AllocatedResources.Tasks { 169 if len(task.Networks) == 0 { 170 continue 171 } 172 n := task.Networks[0] 173 if idx.AddReserved(n) { 174 collide = true 175 } 176 } 177 } 178 } else { 179 // COMPAT(0.11): Remove in 0.11 180 for _, task := range alloc.TaskResources { 181 if len(task.Networks) == 0 { 182 continue 183 } 184 n := task.Networks[0] 185 if idx.AddReserved(n) { 186 collide = true 187 } 188 } 189 } 190 } 191 return 192 } 193 194 // AddReserved is used to add a reserved network usage, returns true 195 // if there is a port collision 196 func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) { 197 // Add the port usage 198 used := idx.getUsedPortsFor(n.IP) 199 200 for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} { 201 for _, port := range ports { 202 // Guard against invalid port 203 if port.Value < 0 || port.Value >= maxValidPort { 204 return true 205 } 206 if used.Check(uint(port.Value)) { 207 collide = true 208 } else { 209 used.Set(uint(port.Value)) 210 } 211 } 212 } 213 214 // Add the bandwidth 215 idx.UsedBandwidth[n.Device] += n.MBits 216 return 217 } 218 219 func (idx *NetworkIndex) AddReservedPorts(ports AllocatedPorts) (collide bool) { 220 for _, port := range ports { 221 used := idx.getUsedPortsFor(port.HostIP) 222 if port.Value < 0 || port.Value >= maxValidPort { 223 return true 224 } 225 if used.Check(uint(port.Value)) { 226 collide = true 227 } else { 228 used.Set(uint(port.Value)) 229 } 230 } 231 232 return 233 } 234 235 // AddReservedPortRange marks the ports given as reserved on all network 236 // interfaces. The port format is comma delimited, with spans given as n1-n2 237 // (80,100-200,205) 238 func (idx *NetworkIndex) AddReservedPortRange(ports string) (collide bool) { 239 // Convert the ports into a slice of ints 240 resPorts, err := ParsePortRanges(ports) 241 if err != nil { 242 return 243 } 244 245 // Ensure we create a bitmap for each available network 246 for _, n := range idx.AvailNetworks { 247 idx.getUsedPortsFor(n.IP) 248 } 249 250 for _, used := range idx.UsedPorts { 251 for _, port := range resPorts { 252 // Guard against invalid port 253 if port >= maxValidPort { 254 return true 255 } 256 if used.Check(uint(port)) { 257 collide = true 258 } else { 259 used.Set(uint(port)) 260 } 261 } 262 } 263 264 return 265 } 266 267 // AddReservedPortsForIP 268 func (idx *NetworkIndex) AddReservedPortsForIP(ports string, ip string) (collide bool) { 269 // Convert the ports into a slice of ints 270 resPorts, err := ParsePortRanges(ports) 271 if err != nil { 272 return 273 } 274 275 used := idx.getUsedPortsFor(ip) 276 for _, port := range resPorts { 277 // Guard against invalid port 278 if port >= maxValidPort { 279 return true 280 } 281 if used.Check(uint(port)) { 282 collide = true 283 } else { 284 used.Set(uint(port)) 285 } 286 } 287 288 return 289 } 290 291 // yieldIP is used to iteratively invoke the callback with 292 // an available IP 293 func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) { 294 inc := func(ip net.IP) { 295 for j := len(ip) - 1; j >= 0; j-- { 296 ip[j]++ 297 if ip[j] > 0 { 298 break 299 } 300 } 301 } 302 303 for _, n := range idx.AvailNetworks { 304 ip, ipnet, err := net.ParseCIDR(n.CIDR) 305 if err != nil { 306 continue 307 } 308 for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { 309 if cb(n, ip) { 310 return 311 } 312 } 313 } 314 } 315 316 func (idx *NetworkIndex) AssignPorts(ask *NetworkResource) (AllocatedPorts, error) { 317 var offer AllocatedPorts 318 319 // index of host network name to slice of reserved ports, used during dynamic port assignment 320 reservedIdx := map[string][]Port{} 321 322 for _, port := range ask.ReservedPorts { 323 reservedIdx[port.HostNetwork] = append(reservedIdx[port.HostNetwork], port) 324 325 // allocPort is set in the inner for loop if a port mapping can be created 326 // if allocPort is still nil after the loop, the port wasn't available for reservation 327 var allocPort *AllocatedPortMapping 328 var addrErr error 329 for _, addr := range idx.AvailAddresses[port.HostNetwork] { 330 used := idx.getUsedPortsFor(addr.Address) 331 // Guard against invalid port 332 if port.Value < 0 || port.Value >= maxValidPort { 333 return nil, fmt.Errorf("invalid port %d (out of range)", port.Value) 334 } 335 336 // Check if in use 337 if used != nil && used.Check(uint(port.Value)) { 338 return nil, fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value) 339 } 340 341 allocPort = &AllocatedPortMapping{ 342 Label: port.Label, 343 Value: port.Value, 344 To: port.To, 345 HostIP: addr.Address, 346 } 347 break 348 } 349 350 if allocPort == nil { 351 if addrErr != nil { 352 return nil, addrErr 353 } 354 355 return nil, fmt.Errorf("no addresses available for %q network", port.HostNetwork) 356 } 357 358 offer = append(offer, *allocPort) 359 } 360 361 for _, port := range ask.DynamicPorts { 362 var allocPort *AllocatedPortMapping 363 var addrErr error 364 for _, addr := range idx.AvailAddresses[port.HostNetwork] { 365 used := idx.getUsedPortsFor(addr.Address) 366 // Try to stochastically pick the dynamic ports as it is faster and 367 // lower memory usage. 368 var dynPorts []int 369 // TODO: its more efficient to find multiple dynamic ports at once 370 dynPorts, addrErr = getDynamicPortsStochastic(used, reservedIdx[port.HostNetwork], 1) 371 if addrErr != nil { 372 // Fall back to the precise method if the random sampling failed. 373 dynPorts, addrErr = getDynamicPortsPrecise(used, reservedIdx[port.HostNetwork], 1) 374 if addrErr != nil { 375 continue 376 } 377 } 378 379 allocPort = &AllocatedPortMapping{ 380 Label: port.Label, 381 Value: dynPorts[0], 382 To: port.To, 383 HostIP: addr.Address, 384 } 385 if allocPort.To == -1 { 386 allocPort.To = allocPort.Value 387 } 388 break 389 } 390 391 if allocPort == nil { 392 if addrErr != nil { 393 return nil, addrErr 394 } 395 396 return nil, fmt.Errorf("no addresses available for %q network", port.HostNetwork) 397 } 398 offer = append(offer, *allocPort) 399 } 400 401 return offer, nil 402 } 403 404 // AssignNetwork is used to assign network resources given an ask. 405 // If the ask cannot be satisfied, returns nil 406 func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) { 407 err = fmt.Errorf("no networks available") 408 idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) { 409 // Convert the IP to a string 410 ipStr := ip.String() 411 412 // Check if we would exceed the bandwidth cap 413 availBandwidth := idx.AvailBandwidth[n.Device] 414 usedBandwidth := idx.UsedBandwidth[n.Device] 415 if usedBandwidth+ask.MBits > availBandwidth { 416 err = fmt.Errorf("bandwidth exceeded") 417 return 418 } 419 420 used := idx.UsedPorts[ipStr] 421 422 // Check if any of the reserved ports are in use 423 for _, port := range ask.ReservedPorts { 424 // Guard against invalid port 425 if port.Value < 0 || port.Value >= maxValidPort { 426 err = fmt.Errorf("invalid port %d (out of range)", port.Value) 427 return 428 } 429 430 // Check if in use 431 if used != nil && used.Check(uint(port.Value)) { 432 err = fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value) 433 return 434 } 435 } 436 437 // Create the offer 438 offer := &NetworkResource{ 439 Mode: ask.Mode, 440 Device: n.Device, 441 IP: ipStr, 442 MBits: ask.MBits, 443 DNS: ask.DNS, 444 ReservedPorts: ask.ReservedPorts, 445 DynamicPorts: ask.DynamicPorts, 446 } 447 448 // Try to stochastically pick the dynamic ports as it is faster and 449 // lower memory usage. 450 var dynPorts []int 451 var dynErr error 452 dynPorts, dynErr = getDynamicPortsStochastic(used, ask.ReservedPorts, len(ask.DynamicPorts)) 453 if dynErr == nil { 454 goto BUILD_OFFER 455 } 456 457 // Fall back to the precise method if the random sampling failed. 458 dynPorts, dynErr = getDynamicPortsPrecise(used, ask.ReservedPorts, len(ask.DynamicPorts)) 459 if dynErr != nil { 460 err = dynErr 461 return 462 } 463 464 BUILD_OFFER: 465 for i, port := range dynPorts { 466 offer.DynamicPorts[i].Value = port 467 468 // This syntax allows you to set the mapped to port to the same port 469 // allocated by the scheduler on the host. 470 if offer.DynamicPorts[i].To == -1 { 471 offer.DynamicPorts[i].To = port 472 } 473 } 474 475 // Stop, we have an offer! 476 out = offer 477 err = nil 478 return true 479 }) 480 return 481 } 482 483 // getDynamicPortsPrecise takes the nodes used port bitmap which may be nil if 484 // no ports have been allocated yet, the network ask and returns a set of unused 485 // ports to fulfil the ask's DynamicPorts or an error if it failed. An error 486 // means the ask can not be satisfied as the method does a precise search. 487 func getDynamicPortsPrecise(nodeUsed Bitmap, reserved []Port, numDyn int) ([]int, error) { 488 // Create a copy of the used ports and apply the new reserves 489 var usedSet Bitmap 490 var err error 491 if nodeUsed != nil { 492 usedSet, err = nodeUsed.Copy() 493 if err != nil { 494 return nil, err 495 } 496 } else { 497 usedSet, err = NewBitmap(maxValidPort) 498 if err != nil { 499 return nil, err 500 } 501 } 502 503 for _, port := range reserved { 504 usedSet.Set(uint(port.Value)) 505 } 506 507 // Get the indexes of the unset 508 availablePorts := usedSet.IndexesInRange(false, MinDynamicPort, MaxDynamicPort) 509 510 // Randomize the amount we need 511 if len(availablePorts) < numDyn { 512 return nil, fmt.Errorf("dynamic port selection failed") 513 } 514 515 numAvailable := len(availablePorts) 516 for i := 0; i < numDyn; i++ { 517 j := rand.Intn(numAvailable) 518 availablePorts[i], availablePorts[j] = availablePorts[j], availablePorts[i] 519 } 520 521 return availablePorts[:numDyn], nil 522 } 523 524 // getDynamicPortsStochastic takes the nodes used port bitmap which may be nil if 525 // no ports have been allocated yet, the network ask and returns a set of unused 526 // ports to fulfil the ask's DynamicPorts or an error if it failed. An error 527 // does not mean the ask can not be satisfied as the method has a fixed amount 528 // of random probes and if these fail, the search is aborted. 529 func getDynamicPortsStochastic(nodeUsed Bitmap, reservedPorts []Port, count int) ([]int, error) { 530 var reserved, dynamic []int 531 for _, port := range reservedPorts { 532 reserved = append(reserved, port.Value) 533 } 534 535 for i := 0; i < count; i++ { 536 attempts := 0 537 PICK: 538 attempts++ 539 if attempts > maxRandPortAttempts { 540 return nil, fmt.Errorf("stochastic dynamic port selection failed") 541 } 542 543 randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort) 544 if nodeUsed != nil && nodeUsed.Check(uint(randPort)) { 545 goto PICK 546 } 547 548 for _, ports := range [][]int{reserved, dynamic} { 549 if isPortReserved(ports, randPort) { 550 goto PICK 551 } 552 } 553 dynamic = append(dynamic, randPort) 554 } 555 556 return dynamic, nil 557 } 558 559 // IntContains scans an integer slice for a value 560 func isPortReserved(haystack []int, needle int) bool { 561 for _, item := range haystack { 562 if item == needle { 563 return true 564 } 565 } 566 return false 567 } 568 569 // COMPAT(1.0) remove when NetworkResource is no longer used for materialized client view of ports 570 func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts, node *NodeResources) *NetworkResource { 571 out := ask.Copy() 572 573 for i, port := range ask.DynamicPorts { 574 if p, ok := ports.Get(port.Label); ok { 575 out.DynamicPorts[i].Value = p.Value 576 out.DynamicPorts[i].To = p.To 577 } 578 } 579 if len(node.NodeNetworks) > 0 { 580 for _, nw := range node.NodeNetworks { 581 if nw.Mode == "host" { 582 out.IP = nw.Addresses[0].Address 583 break 584 } 585 } 586 } else { 587 for _, nw := range node.Networks { 588 if nw.Mode == "host" { 589 out.IP = nw.IP 590 } 591 } 592 } 593 return out 594 } 595 596 type ClientHostNetworkConfig struct { 597 Name string `hcl:",key"` 598 CIDR string `hcl:"cidr"` 599 Interface string `hcl:"interface"` 600 ReservedPorts string `hcl:"reserved_ports"` 601 }