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