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