github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/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 = 60000 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 // Check if any of the reserved ports are in use 187 for _, port := range ask.ReservedPorts { 188 // Guard against invalid port 189 if port.Value < 0 || port.Value >= maxValidPort { 190 err = fmt.Errorf("invalid port %d (out of range)", port.Value) 191 return 192 } 193 194 // Check if in use 195 used := idx.UsedPorts[ipStr] 196 if used != nil && used.Check(uint(port.Value)) { 197 err = fmt.Errorf("reserved port collision") 198 return 199 } 200 } 201 202 // Create the offer 203 offer := &NetworkResource{ 204 Device: n.Device, 205 IP: ipStr, 206 ReservedPorts: ask.ReservedPorts, 207 DynamicPorts: ask.DynamicPorts, 208 } 209 210 // Check if we need to generate any ports 211 for i := 0; i < len(ask.DynamicPorts); i++ { 212 attempts := 0 213 PICK: 214 attempts++ 215 if attempts > maxRandPortAttempts { 216 err = fmt.Errorf("dynamic port selection failed") 217 return 218 } 219 220 randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort) 221 used := idx.UsedPorts[ipStr] 222 if used != nil && used.Check(uint(randPort)) { 223 goto PICK 224 } 225 226 for _, ports := range [][]Port{offer.ReservedPorts, offer.DynamicPorts} { 227 if isPortReserved(ports, randPort) { 228 goto PICK 229 } 230 } 231 offer.DynamicPorts[i].Value = randPort 232 } 233 234 // Stop, we have an offer! 235 out = offer 236 err = nil 237 return true 238 }) 239 return 240 } 241 242 // IntContains scans an integer slice for a value 243 func isPortReserved(haystack []Port, needle int) bool { 244 for _, item := range haystack { 245 if item.Value == needle { 246 return true 247 } 248 } 249 return false 250 }