github.com/hernad/nomad@v1.6.112/nomad/structs/network.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package structs 5 6 import ( 7 "fmt" 8 "math/rand" 9 "net" 10 "sync" 11 12 "golang.org/x/exp/maps" 13 "golang.org/x/exp/slices" 14 ) 15 16 const ( 17 // DefaultMinDynamicPort is the smallest dynamic port generated by 18 // default 19 DefaultMinDynamicPort = 20000 20 21 // DefaultMaxDynamicPort is the largest dynamic port generated by 22 // default 23 DefaultMaxDynamicPort = 32000 24 25 // maxRandPortAttempts is the maximum number of attempt 26 // to assign a random port 27 maxRandPortAttempts = 20 28 29 // MaxValidPort is the max valid port number 30 MaxValidPort = 65536 31 ) 32 33 var ( 34 // bitmapPool is used to pool the bitmaps used for port collision 35 // checking. They are fairly large (8K) so we can re-use them to 36 // avoid GC pressure. Care should be taken to call Clear() on any 37 // bitmap coming from the pool. 38 bitmapPool = new(sync.Pool) 39 ) 40 41 // NetworkIndex is used to index the available network resources 42 // and the used network resources on a machine given allocations 43 // 44 // Fields are exported so they may be JSON serialized for debugging. 45 // Fields are *not* intended to be used directly. 46 type NetworkIndex struct { 47 // TaskNetworks are the node networks available for 48 // task.resources.network asks. 49 TaskNetworks []*NetworkResource 50 51 // GroupNetworks are the node networks available for group.network 52 // asks. 53 GroupNetworks []*NodeNetworkResource 54 55 // HostNetworks indexes addresses by host network alias 56 HostNetworks map[string][]NodeNetworkAddress 57 58 // UsedPorts tracks which ports are used on a per-IP address basis. For 59 // example if a node has `network_interface=lo` and port 22 reserved, 60 // then on a dual stack loopback interface UsedPorts would contain: 61 // { 62 // "127.0.0.1": Bitmap{22}, 63 // "::1": Bitmap{22}, 64 // } 65 UsedPorts map[string]Bitmap 66 67 // Deprecated bandwidth fields 68 AvailBandwidth map[string]int // Bandwidth by device 69 UsedBandwidth map[string]int // Bandwidth by device 70 71 MinDynamicPort int // The smallest dynamic port generated 72 MaxDynamicPort int // The largest dynamic port generated 73 } 74 75 // NewNetworkIndex is used to construct a new network index 76 func NewNetworkIndex() *NetworkIndex { 77 return &NetworkIndex{ 78 HostNetworks: make(map[string][]NodeNetworkAddress), 79 UsedPorts: make(map[string]Bitmap), 80 AvailBandwidth: make(map[string]int), 81 UsedBandwidth: make(map[string]int), 82 MinDynamicPort: DefaultMinDynamicPort, 83 MaxDynamicPort: DefaultMaxDynamicPort, 84 } 85 } 86 87 func (idx *NetworkIndex) getUsedPortsFor(ip string) Bitmap { 88 used := idx.UsedPorts[ip] 89 if used == nil { 90 // Try to get a bitmap from the pool, else create 91 raw := bitmapPool.Get() 92 if raw != nil { 93 used = raw.(Bitmap) 94 used.Clear() 95 } else { 96 used, _ = NewBitmap(MaxValidPort) 97 } 98 idx.UsedPorts[ip] = used 99 } 100 return used 101 } 102 103 func (idx *NetworkIndex) Copy() *NetworkIndex { 104 if idx == nil { 105 return nil 106 } 107 108 c := new(NetworkIndex) 109 *c = *idx 110 111 c.TaskNetworks = copyNetworkResources(idx.TaskNetworks) 112 c.GroupNetworks = copyNodeNetworks(idx.GroupNetworks) 113 c.HostNetworks = copyAvailAddresses(idx.HostNetworks) 114 if idx.AvailBandwidth != nil && len(idx.AvailBandwidth) == 0 { 115 c.AvailBandwidth = make(map[string]int) 116 } else { 117 c.AvailBandwidth = maps.Clone(idx.AvailBandwidth) 118 } 119 if len(idx.UsedPorts) > 0 { 120 c.UsedPorts = make(map[string]Bitmap, len(idx.UsedPorts)) 121 for k, v := range idx.UsedPorts { 122 c.UsedPorts[k], _ = v.Copy() 123 } 124 } 125 if idx.UsedBandwidth != nil && len(idx.UsedBandwidth) == 0 { 126 c.UsedBandwidth = make(map[string]int) 127 } else { 128 c.UsedBandwidth = maps.Clone(idx.UsedBandwidth) 129 } 130 131 return c 132 } 133 134 func copyNetworkResources(resources []*NetworkResource) []*NetworkResource { 135 l := len(resources) 136 if l == 0 { 137 return nil 138 } 139 140 c := make([]*NetworkResource, l) 141 for i, resource := range resources { 142 c[i] = resource.Copy() 143 } 144 return c 145 } 146 147 func copyNodeNetworks(resources []*NodeNetworkResource) []*NodeNetworkResource { 148 l := len(resources) 149 if l == 0 { 150 return nil 151 } 152 153 c := make([]*NodeNetworkResource, l) 154 for i, resource := range resources { 155 c[i] = resource.Copy() 156 } 157 return c 158 } 159 160 func copyAvailAddresses(a map[string][]NodeNetworkAddress) map[string][]NodeNetworkAddress { 161 l := len(a) 162 if l == 0 { 163 return nil 164 } 165 166 c := make(map[string][]NodeNetworkAddress, l) 167 for k, v := range a { 168 if len(v) == 0 { 169 continue 170 } 171 c[k] = make([]NodeNetworkAddress, len(v)) 172 copy(c[k], v) 173 } 174 175 return c 176 } 177 178 // Release is called when the network index is no longer needed 179 // to attempt to re-use some of the memory it has allocated 180 func (idx *NetworkIndex) Release() { 181 for _, b := range idx.UsedPorts { 182 bitmapPool.Put(b) 183 } 184 } 185 186 // Overcommitted checks if the network is overcommitted 187 func (idx *NetworkIndex) Overcommitted() bool { 188 // TODO remove since bandwidth is deprecated 189 /*for device, used := range idx.UsedBandwidth { 190 avail := idx.AvailBandwidth[device] 191 if used > avail { 192 return true 193 } 194 }*/ 195 return false 196 } 197 198 // SetNode is used to initialize a node's network index with available IPs, 199 // reserved ports, and other details from a node's configuration and 200 // fingerprinting. 201 // 202 // SetNode must be idempotent as preemption causes SetNode to be called 203 // multiple times on the same NetworkIndex, only clearing UsedPorts between 204 // calls. 205 // 206 // An error is returned if the Node cannot produce a consistent NetworkIndex 207 // such as if reserved_ports are unparseable. 208 // 209 // Any errors returned by SetNode indicate a bug! The bug may lie in client 210 // code not properly validating its configuration or it may lie in improper 211 // Node object handling by servers. Users should not be able to cause SetNode 212 // to error. Data that cause SetNode to error should be caught upstream such as 213 // a client agent refusing to start with an invalid configuration. 214 func (idx *NetworkIndex) SetNode(node *Node) error { 215 216 // COMPAT(0.11): Deprecated. taskNetworks are only used for 217 // task.resources.network asks which have been deprecated since before 218 // 0.11. 219 // Grab the network resources, handling both new and old Node layouts 220 // from clients. 221 var taskNetworks []*NetworkResource 222 if node.NodeResources != nil && len(node.NodeResources.Networks) != 0 { 223 taskNetworks = node.NodeResources.Networks 224 } else if node.Resources != nil { 225 taskNetworks = node.Resources.Networks 226 } 227 228 // Reserved ports get merged downward. For example given an agent 229 // config: 230 // 231 // client.reserved.reserved_ports = "22" 232 // client.host_network["eth0"] = {reserved_ports = "80,443"} 233 // client.host_network["eth1"] = {reserved_ports = "1-1000"} 234 // 235 // Addresses on taskNetworks reserve port 22 236 // Addresses on eth0 reserve 22,80,443 (note 22 is also reserved!) 237 // Addresses on eth1 reserve 1-1000 238 globalResPorts := []uint{} 239 240 if node.ReservedResources != nil && node.ReservedResources.Networks.ReservedHostPorts != "" { 241 resPorts, err := ParsePortRanges(node.ReservedResources.Networks.ReservedHostPorts) 242 if err != nil { 243 // This is a fatal error that should have been 244 // prevented by client validation. 245 return fmt.Errorf("error parsing reserved_ports: %w", err) 246 } 247 248 globalResPorts = make([]uint, len(resPorts)) 249 for i, p := range resPorts { 250 globalResPorts[i] = uint(p) 251 } 252 } else if node.Reserved != nil { 253 // COMPAT(0.11): Remove after 0.11. Nodes stopped reporting 254 // reserved ports under Node.Reserved.Resources in #4750 / v0.9 255 for _, n := range node.Reserved.Networks { 256 used := idx.getUsedPortsFor(n.IP) 257 for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} { 258 for _, p := range ports { 259 if p.Value > MaxValidPort || p.Value < 0 { 260 // This is a fatal error that 261 // should have been prevented 262 // by validation upstream. 263 return fmt.Errorf("invalid port %d for reserved_ports", p.Value) 264 } 265 266 globalResPorts = append(globalResPorts, uint(p.Value)) 267 used.Set(uint(p.Value)) 268 } 269 } 270 271 // Reserve mbits 272 if n.Device != "" { 273 idx.UsedBandwidth[n.Device] += n.MBits 274 } 275 } 276 } 277 278 // Filter task networks down to those with a device. For example 279 // taskNetworks may contain a "bridge" interface which has no device 280 // set and cannot be used to fulfill asks. 281 for _, n := range taskNetworks { 282 if n.Device != "" { 283 idx.TaskNetworks = append(idx.TaskNetworks, n) 284 idx.AvailBandwidth[n.Device] = n.MBits 285 286 // Reserve ports 287 used := idx.getUsedPortsFor(n.IP) 288 for _, p := range globalResPorts { 289 used.Set(p) 290 } 291 } 292 } 293 294 // nodeNetworks are used for group.network asks. 295 var nodeNetworks []*NodeNetworkResource 296 if node.NodeResources != nil && len(node.NodeResources.NodeNetworks) != 0 { 297 nodeNetworks = node.NodeResources.NodeNetworks 298 } 299 300 for _, n := range nodeNetworks { 301 for _, a := range n.Addresses { 302 // Index host networks by their unique alias for asks 303 // with group.network.port.host_network set. 304 idx.HostNetworks[a.Alias] = append(idx.HostNetworks[a.Alias], a) 305 306 // Mark reserved ports as used without worrying about 307 // collisions. This effectively merges 308 // client.reserved.reserved_ports into each 309 // host_network. 310 used := idx.getUsedPortsFor(a.Address) 311 for _, p := range globalResPorts { 312 used.Set(p) 313 } 314 315 // If ReservedPorts is set on the NodeNetwork, use it 316 // and the global reserved ports. 317 if a.ReservedPorts != "" { 318 rp, err := ParsePortRanges(a.ReservedPorts) 319 if err != nil { 320 // This is a fatal error that should 321 // have been prevented by validation 322 // upstream. 323 return fmt.Errorf("error parsing reserved_ports for network %q: %w", a.Alias, err) 324 } 325 for _, p := range rp { 326 used.Set(uint(p)) 327 } 328 } 329 } 330 } 331 332 // Set dynamic port range (applies to all addresses) 333 if node.NodeResources != nil && node.NodeResources.MinDynamicPort > 0 { 334 idx.MinDynamicPort = node.NodeResources.MinDynamicPort 335 } 336 337 if node.NodeResources != nil && node.NodeResources.MaxDynamicPort > 0 { 338 idx.MaxDynamicPort = node.NodeResources.MaxDynamicPort 339 } 340 341 return nil 342 } 343 344 // AddAllocs is used to add the used network resources. Returns 345 // true if there is a collision 346 // 347 // AddAllocs may be called multiple times for the same NetworkIndex with 348 // UsedPorts cleared between calls (by Release). Therefore AddAllocs must be 349 // determistic and must not manipulate state outside of UsedPorts as that state 350 // would persist between Release calls. 351 func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool, reason string) { 352 for _, alloc := range allocs { 353 // Do not consider the resource impact of terminal allocations 354 if alloc.ClientTerminalStatus() { 355 continue 356 } 357 358 if alloc.AllocatedResources != nil { 359 // Only look at AllocatedPorts if populated, otherwise use pre 0.12 logic 360 // COMPAT(1.0): Remove when network resources struct is removed. 361 if len(alloc.AllocatedResources.Shared.Ports) > 0 { 362 if c, r := idx.AddReservedPorts(alloc.AllocatedResources.Shared.Ports); c { 363 collide = true 364 reason = fmt.Sprintf("collision when reserving port for alloc %s: %v", alloc.ID, r) 365 } 366 } else { 367 // Add network resources that are at the task group level 368 if len(alloc.AllocatedResources.Shared.Networks) > 0 { 369 for _, network := range alloc.AllocatedResources.Shared.Networks { 370 if c, r := idx.AddReserved(network); c { 371 collide = true 372 reason = fmt.Sprintf("collision when reserving port for network %s in alloc %s: %v", network.IP, alloc.ID, r) 373 } 374 } 375 } 376 377 for task, resources := range alloc.AllocatedResources.Tasks { 378 if len(resources.Networks) == 0 { 379 continue 380 } 381 n := resources.Networks[0] 382 if c, r := idx.AddReserved(n); c { 383 collide = true 384 reason = fmt.Sprintf("collision when reserving port for network %s in task %s of alloc %s: %v", n.IP, task, alloc.ID, r) 385 } 386 } 387 } 388 } else { 389 // COMPAT(0.11): Remove in 0.11 390 for task, resources := range alloc.TaskResources { 391 if len(resources.Networks) == 0 { 392 continue 393 } 394 n := resources.Networks[0] 395 if c, r := idx.AddReserved(n); c { 396 collide = true 397 reason = fmt.Sprintf("(deprecated) collision when reserving port for network %s in task %s of alloc %s: %v", n.IP, task, alloc.ID, r) 398 } 399 } 400 } 401 } 402 return 403 } 404 405 // AddReserved is used to add a reserved network usage, returns true 406 // if there is a port collision 407 func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool, reasons []string) { 408 // Add the port usage 409 used := idx.getUsedPortsFor(n.IP) 410 411 for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} { 412 for _, port := range ports { 413 // Guard against invalid port 414 if port.Value < 0 || port.Value >= MaxValidPort { 415 return true, []string{fmt.Sprintf("invalid port %d", port.Value)} 416 } 417 if used.Check(uint(port.Value)) { 418 collide = true 419 reason := fmt.Sprintf("port %d already in use", port.Value) 420 reasons = append(reasons, reason) 421 } else { 422 used.Set(uint(port.Value)) 423 } 424 } 425 } 426 427 // Add the bandwidth 428 idx.UsedBandwidth[n.Device] += n.MBits 429 return 430 } 431 432 func (idx *NetworkIndex) AddReservedPorts(ports AllocatedPorts) (collide bool, reasons []string) { 433 for _, port := range ports { 434 used := idx.getUsedPortsFor(port.HostIP) 435 if port.Value < 0 || port.Value >= MaxValidPort { 436 return true, []string{fmt.Sprintf("invalid port %d", port.Value)} 437 } 438 if used.Check(uint(port.Value)) { 439 collide = true 440 reason := fmt.Sprintf("port %d already in use", port.Value) 441 reasons = append(reasons, reason) 442 } else { 443 used.Set(uint(port.Value)) 444 } 445 } 446 447 return 448 } 449 450 // AddReservedPortsForIP checks whether any reserved ports collide with those 451 // in use for the IP address. 452 func (idx *NetworkIndex) AddReservedPortsForIP(ports []uint64, ip string) (collide bool, reasons []string) { 453 used := idx.getUsedPortsFor(ip) 454 for _, port := range ports { 455 // Guard against invalid port 456 if port >= MaxValidPort { 457 return true, []string{fmt.Sprintf("invalid port %d", port)} 458 } 459 if used.Check(uint(port)) { 460 collide = true 461 reason := fmt.Sprintf("port %d already in use", port) 462 reasons = append(reasons, reason) 463 } else { 464 used.Set(uint(port)) 465 } 466 } 467 468 return 469 } 470 471 // yieldIP is used to iteratively invoke the callback with 472 // an available IP 473 func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, offerIP net.IP) bool) { 474 for _, n := range idx.TaskNetworks { 475 ip, ipnet, err := net.ParseCIDR(n.CIDR) 476 if err != nil { 477 continue 478 } 479 for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); incIP(ip) { 480 if cb(n, ip) { 481 return 482 } 483 } 484 } 485 } 486 487 func incIP(ip net.IP) { 488 // Iterate over IP octects from right to left 489 for j := len(ip) - 1; j >= 0; j-- { 490 491 // Increment octect 492 ip[j]++ 493 494 // If this octect did not wrap around to 0, it's the next IP to 495 // try. If it did wrap (p[j]==0), then the next octect is 496 // incremented. 497 if ip[j] > 0 { 498 break 499 } 500 } 501 } 502 503 // AssignPorts based on an ask from the scheduler processing a group.network 504 // block. Supports multi-interfaces through node configured host_networks. 505 // 506 // AssignTaskNetwork supports the deprecated task.resources.network block. 507 func (idx *NetworkIndex) AssignPorts(ask *NetworkResource) (AllocatedPorts, error) { 508 var offer AllocatedPorts 509 var portsInOffer []int 510 511 // index of host network name to slice of reserved ports, used during dynamic port assignment 512 reservedIdx := map[string][]Port{} 513 514 for _, port := range ask.ReservedPorts { 515 reservedIdx[port.HostNetwork] = append(reservedIdx[port.HostNetwork], port) 516 517 // allocPort is set in the inner for loop if a port mapping can be created 518 // if allocPort is still nil after the loop, the port wasn't available for reservation 519 var allocPort *AllocatedPortMapping 520 var addrErr error 521 for _, addr := range idx.HostNetworks[port.HostNetwork] { 522 used := idx.getUsedPortsFor(addr.Address) 523 // Guard against invalid port 524 if port.Value < 0 || port.Value >= MaxValidPort { 525 return nil, fmt.Errorf("invalid port %d (out of range)", port.Value) 526 } 527 528 // Check if in use 529 if used != nil && used.Check(uint(port.Value)) { 530 return nil, fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value) 531 } 532 533 allocPort = &AllocatedPortMapping{ 534 Label: port.Label, 535 Value: port.Value, 536 To: port.To, 537 HostIP: addr.Address, 538 } 539 break 540 } 541 542 if allocPort == nil { 543 if addrErr != nil { 544 return nil, addrErr 545 } 546 547 return nil, fmt.Errorf("no addresses available for %s network", port.HostNetwork) 548 } 549 550 offer = append(offer, *allocPort) 551 portsInOffer = append(portsInOffer, allocPort.Value) 552 } 553 554 for _, port := range ask.DynamicPorts { 555 var allocPort *AllocatedPortMapping 556 var addrErr error 557 for _, addr := range idx.HostNetworks[port.HostNetwork] { 558 used := idx.getUsedPortsFor(addr.Address) 559 // Try to stochastically pick the dynamic ports as it is faster and 560 // lower memory usage. 561 var dynPorts []int 562 // TODO: its more efficient to find multiple dynamic ports at once 563 dynPorts, addrErr = getDynamicPortsStochastic( 564 used, portsInOffer, idx.MinDynamicPort, idx.MaxDynamicPort, 565 reservedIdx[port.HostNetwork], 1) 566 if addrErr != nil { 567 // Fall back to the precise method if the random sampling failed. 568 dynPorts, addrErr = getDynamicPortsPrecise(used, portsInOffer, 569 idx.MinDynamicPort, idx.MaxDynamicPort, 570 reservedIdx[port.HostNetwork], 1) 571 if addrErr != nil { 572 continue 573 } 574 } 575 576 allocPort = &AllocatedPortMapping{ 577 Label: port.Label, 578 Value: dynPorts[0], 579 To: port.To, 580 HostIP: addr.Address, 581 } 582 if allocPort.To == -1 { 583 allocPort.To = allocPort.Value 584 } 585 break 586 } 587 588 if allocPort == nil { 589 if addrErr != nil { 590 return nil, addrErr 591 } 592 593 return nil, fmt.Errorf("no addresses available for %s network", port.HostNetwork) 594 } 595 offer = append(offer, *allocPort) 596 portsInOffer = append(portsInOffer, allocPort.Value) 597 } 598 599 return offer, nil 600 } 601 602 // AssignTaskNetwork is used to offer network resources given a 603 // task.resources.network ask. If the ask cannot be satisfied, returns nil 604 // 605 // AssignTaskNetwork and task.resources.network are deprecated in favor of 606 // AssignPorts and group.network. AssignTaskNetwork does not support multiple 607 // interfaces and only uses the node's default interface. AssignPorts is the 608 // method that is used for group.network asks. 609 func (idx *NetworkIndex) AssignTaskNetwork(ask *NetworkResource) (out *NetworkResource, err error) { 610 err = fmt.Errorf("no networks available") 611 idx.yieldIP(func(n *NetworkResource, offerIP net.IP) (stop bool) { 612 // Convert the IP to a string 613 offerIPStr := offerIP.String() 614 615 // Check if we would exceed the bandwidth cap 616 availBandwidth := idx.AvailBandwidth[n.Device] 617 usedBandwidth := idx.UsedBandwidth[n.Device] 618 if usedBandwidth+ask.MBits > availBandwidth { 619 err = fmt.Errorf("bandwidth exceeded") 620 return 621 } 622 623 used := idx.UsedPorts[offerIPStr] 624 625 // Check if any of the reserved ports are in use 626 for _, port := range ask.ReservedPorts { 627 // Guard against invalid port 628 if port.Value < 0 || port.Value >= MaxValidPort { 629 err = fmt.Errorf("invalid port %d (out of range)", port.Value) 630 return 631 } 632 633 // Check if in use 634 if used != nil && used.Check(uint(port.Value)) { 635 err = fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value) 636 return 637 } 638 } 639 640 // Create the offer 641 offer := &NetworkResource{ 642 Mode: ask.Mode, 643 Device: n.Device, 644 IP: offerIPStr, 645 MBits: ask.MBits, 646 DNS: ask.DNS, 647 ReservedPorts: ask.ReservedPorts, 648 DynamicPorts: ask.DynamicPorts, 649 } 650 651 // Try to stochastically pick the dynamic ports as it is faster and 652 // lower memory usage. 653 var dynPorts []int 654 var dynErr error 655 dynPorts, dynErr = getDynamicPortsStochastic(used, nil, 656 idx.MinDynamicPort, idx.MaxDynamicPort, ask.ReservedPorts, len(ask.DynamicPorts)) 657 if dynErr == nil { 658 goto BUILD_OFFER 659 } 660 661 // Fall back to the precise method if the random sampling failed. 662 dynPorts, dynErr = getDynamicPortsPrecise(used, nil, 663 idx.MinDynamicPort, idx.MaxDynamicPort, ask.ReservedPorts, len(ask.DynamicPorts)) 664 if dynErr != nil { 665 err = dynErr 666 return 667 } 668 669 BUILD_OFFER: 670 for i, port := range dynPorts { 671 offer.DynamicPorts[i].Value = port 672 673 // This syntax allows you to set the mapped to port to the same port 674 // allocated by the scheduler on the host. 675 if offer.DynamicPorts[i].To == -1 { 676 offer.DynamicPorts[i].To = port 677 } 678 } 679 680 // Stop, we have an offer! 681 out = offer 682 err = nil 683 return true 684 }) 685 return 686 } 687 688 // getDynamicPortsPrecise takes the nodes used port bitmap which may be nil if 689 // no ports have been allocated yet, any ports already offered in the caller, 690 // and the network ask. It returns a set of unused ports to fulfil the ask's 691 // DynamicPorts or an error if it failed. An error means the ask can not be 692 // satisfied as the method does a precise search. 693 func getDynamicPortsPrecise(nodeUsed Bitmap, portsInOffer []int, minDynamicPort, maxDynamicPort int, reserved []Port, numDyn int) ([]int, error) { 694 // Create a copy of the used ports and apply the new reserves 695 var usedSet Bitmap 696 var err error 697 if nodeUsed != nil { 698 usedSet, err = nodeUsed.Copy() 699 if err != nil { 700 return nil, err 701 } 702 } else { 703 usedSet, err = NewBitmap(MaxValidPort) 704 if err != nil { 705 return nil, err 706 } 707 } 708 709 for _, port := range reserved { 710 usedSet.Set(uint(port.Value)) 711 } 712 713 // Get the indexes of the unset ports, less those which have already been 714 // picked as part of this offer 715 availablePorts := usedSet.IndexesInRangeFiltered( 716 false, uint(minDynamicPort), uint(maxDynamicPort), portsInOffer) 717 718 // Randomize the amount we need 719 if len(availablePorts) < numDyn { 720 return nil, fmt.Errorf("dynamic port selection failed") 721 } 722 723 numAvailable := len(availablePorts) 724 for i := 0; i < numDyn; i++ { 725 j := rand.Intn(numAvailable) 726 availablePorts[i], availablePorts[j] = availablePorts[j], availablePorts[i] 727 } 728 729 return availablePorts[:numDyn], nil 730 } 731 732 // getDynamicPortsStochastic takes the nodes used port bitmap which may be nil 733 // if no ports have been allocated yet, any ports already offered in the caller, 734 // and the network ask. It returns a set of unused ports to fulfil the ask's 735 // DynamicPorts or an error if it failed. An error does not mean the ask can not 736 // be satisfied as the method has a fixed amount of random probes and if these 737 // fail, the search is aborted. 738 func getDynamicPortsStochastic(nodeUsed Bitmap, portsInOffer []int, minDynamicPort, maxDynamicPort int, reservedPorts []Port, count int) ([]int, error) { 739 var reserved, dynamic []int 740 for _, port := range reservedPorts { 741 reserved = append(reserved, port.Value) 742 } 743 744 for i := 0; i < count; i++ { 745 attempts := 0 746 PICK: 747 attempts++ 748 if attempts > maxRandPortAttempts { 749 return nil, fmt.Errorf("stochastic dynamic port selection failed") 750 } 751 752 randPort := minDynamicPort 753 if maxDynamicPort-minDynamicPort > 0 { 754 randPort = randPort + rand.Intn(maxDynamicPort-minDynamicPort) 755 } 756 757 if nodeUsed != nil && nodeUsed.Check(uint(randPort)) { 758 goto PICK 759 } 760 761 for _, ports := range [][]int{reserved, dynamic} { 762 if isPortReserved(ports, randPort) { 763 goto PICK 764 } 765 } 766 // the pick conflicted with a previous pick that hasn't been saved to 767 // the index yet 768 if slices.Contains(portsInOffer, randPort) { 769 goto PICK 770 } 771 772 dynamic = append(dynamic, randPort) 773 } 774 775 return dynamic, nil 776 } 777 778 // IntContains scans an integer slice for a value 779 func isPortReserved(haystack []int, needle int) bool { 780 for _, item := range haystack { 781 if item == needle { 782 return true 783 } 784 } 785 return false 786 } 787 788 // AllocatedPortsToNetworkResouce is a COMPAT(1.0) remove when NetworkResource 789 // is no longer used for materialized client view of ports. 790 func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts, node *NodeResources) *NetworkResource { 791 out := ask.Copy() 792 793 for i, port := range ask.DynamicPorts { 794 if p, ok := ports.Get(port.Label); ok { 795 out.DynamicPorts[i].Value = p.Value 796 out.DynamicPorts[i].To = p.To 797 } 798 } 799 if len(node.NodeNetworks) > 0 { 800 for _, nw := range node.NodeNetworks { 801 if nw.Mode == "host" { 802 out.IP = nw.Addresses[0].Address 803 break 804 } 805 } 806 } else { 807 for _, nw := range node.Networks { 808 if nw.Mode == "host" { 809 out.IP = nw.IP 810 } 811 } 812 } 813 return out 814 } 815 816 type ClientHostNetworkConfig struct { 817 Name string `hcl:",key"` 818 CIDR string `hcl:"cidr"` 819 Interface string `hcl:"interface"` 820 ReservedPorts string `hcl:"reserved_ports"` 821 } 822 823 func (p *ClientHostNetworkConfig) Copy() *ClientHostNetworkConfig { 824 if p == nil { 825 return nil 826 } 827 828 c := new(ClientHostNetworkConfig) 829 *c = *p 830 return c 831 }