github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/structs/network.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "math/rand" 6 "net" 7 ) 8 9 const ( 10 // MinDynamicPort is the smallest dynamic port generated 11 MinDynamicPort = 20000 12 13 // MaxDynamicPort is the largest dynamic port generated 14 MaxDynamicPort = 60000 15 16 // maxRandPortAttempts is the maximum number of attempt 17 // to assign a random port 18 maxRandPortAttempts = 20 19 ) 20 21 // NetworkIndex is used to index the available network resources 22 // and the used network resources on a machine given allocations 23 type NetworkIndex struct { 24 AvailNetworks []*NetworkResource // List of available networks 25 AvailBandwidth map[string]int // Bandwidth by device 26 UsedPorts map[string]map[int]struct{} // Ports by IP 27 UsedBandwidth map[string]int // Bandwidth by device 28 } 29 30 // NewNetworkIndex is used to construct a new network index 31 func NewNetworkIndex() *NetworkIndex { 32 return &NetworkIndex{ 33 AvailBandwidth: make(map[string]int), 34 UsedPorts: make(map[string]map[int]struct{}), 35 UsedBandwidth: make(map[string]int), 36 } 37 } 38 39 // Overcommitted checks if the network is overcommitted 40 func (idx *NetworkIndex) Overcommitted() bool { 41 for device, used := range idx.UsedBandwidth { 42 avail := idx.AvailBandwidth[device] 43 if used > avail { 44 return true 45 } 46 } 47 return false 48 } 49 50 // SetNode is used to setup the available network resources. Returns 51 // true if there is a collision 52 func (idx *NetworkIndex) SetNode(node *Node) (collide bool) { 53 // Add the available CIDR blocks 54 for _, n := range node.Resources.Networks { 55 if n.Device != "" { 56 idx.AvailNetworks = append(idx.AvailNetworks, n) 57 idx.AvailBandwidth[n.Device] = n.MBits 58 } 59 } 60 61 // Add the reserved resources 62 if r := node.Reserved; r != nil { 63 for _, n := range r.Networks { 64 if idx.AddReserved(n) { 65 collide = true 66 } 67 } 68 } 69 return 70 } 71 72 // AddAllocs is used to add the used network resources. Returns 73 // true if there is a collision 74 func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) { 75 for _, alloc := range allocs { 76 for _, task := range alloc.TaskResources { 77 if len(task.Networks) == 0 { 78 continue 79 } 80 n := task.Networks[0] 81 if idx.AddReserved(n) { 82 collide = true 83 } 84 } 85 } 86 return 87 } 88 89 // AddReserved is used to add a reserved network usage, returns true 90 // if there is a port collision 91 func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) { 92 // Add the port usage 93 used := idx.UsedPorts[n.IP] 94 if used == nil { 95 used = make(map[int]struct{}) 96 idx.UsedPorts[n.IP] = used 97 } 98 99 for _, ports := range [][]Port{n.ReservedPorts, n.DynamicPorts} { 100 for _, port := range ports { 101 if _, ok := used[port.Value]; ok { 102 collide = true 103 } else { 104 used[port.Value] = struct{}{} 105 } 106 } 107 } 108 109 // Add the bandwidth 110 idx.UsedBandwidth[n.Device] += n.MBits 111 return 112 } 113 114 // yieldIP is used to iteratively invoke the callback with 115 // an available IP 116 func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) { 117 inc := func(ip net.IP) { 118 for j := len(ip) - 1; j >= 0; j-- { 119 ip[j]++ 120 if ip[j] > 0 { 121 break 122 } 123 } 124 } 125 126 for _, n := range idx.AvailNetworks { 127 ip, ipnet, err := net.ParseCIDR(n.CIDR) 128 if err != nil { 129 continue 130 } 131 for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { 132 if cb(n, ip) { 133 return 134 } 135 } 136 } 137 } 138 139 // AssignNetwork is used to assign network resources given an ask. 140 // If the ask cannot be satisfied, returns nil 141 func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) { 142 err = fmt.Errorf("no networks available") 143 idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) { 144 // Convert the IP to a string 145 ipStr := ip.String() 146 147 // Check if we would exceed the bandwidth cap 148 availBandwidth := idx.AvailBandwidth[n.Device] 149 usedBandwidth := idx.UsedBandwidth[n.Device] 150 if usedBandwidth+ask.MBits > availBandwidth { 151 err = fmt.Errorf("bandwidth exceeded") 152 return 153 } 154 155 // Check if any of the reserved ports are in use 156 for _, port := range ask.ReservedPorts { 157 if _, ok := idx.UsedPorts[ipStr][port.Value]; ok { 158 err = fmt.Errorf("reserved port collision") 159 return 160 } 161 } 162 163 // Create the offer 164 offer := &NetworkResource{ 165 Device: n.Device, 166 IP: ipStr, 167 ReservedPorts: ask.ReservedPorts, 168 DynamicPorts: ask.DynamicPorts, 169 } 170 171 // Check if we need to generate any ports 172 for i := 0; i < len(ask.DynamicPorts); i++ { 173 attempts := 0 174 PICK: 175 attempts++ 176 if attempts > maxRandPortAttempts { 177 err = fmt.Errorf("dynamic port selection failed") 178 return 179 } 180 181 randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort) 182 if _, ok := idx.UsedPorts[ipStr][randPort]; ok { 183 goto PICK 184 } 185 186 for _, ports := range [][]Port{offer.ReservedPorts, offer.DynamicPorts} { 187 if isPortReserved(ports, randPort) { 188 goto PICK 189 } 190 } 191 offer.DynamicPorts[i].Value = randPort 192 } 193 194 // Stop, we have an offer! 195 out = offer 196 err = nil 197 return true 198 }) 199 return 200 } 201 202 // IntContains scans an integer slice for a value 203 func isPortReserved(haystack []Port, needle int) bool { 204 for _, item := range haystack { 205 if item.Value == needle { 206 return true 207 } 208 } 209 return false 210 }