blob: 54386698d178e00aeaf1aa9b14e47b43f4ff3cff [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2012-2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package gomaasapi
5
6import (
7 "encoding/json"
8 "fmt"
9 "io"
10 "net"
11 "net/http"
12 "net/url"
13 "regexp"
14 "sort"
15 "strings"
16)
17
18func getSubnetsEndpoint(version string) string {
19 return fmt.Sprintf("/api/%s/subnets/", version)
20}
21
22// CreateSubnet is used to receive new subnets via the MAAS API
23type CreateSubnet struct {
24 DNSServers []string `json:"dns_servers"`
25 Name string `json:"name"`
26 Space string `json:"space"`
27 GatewayIP string `json:"gateway_ip"`
28 CIDR string `json:"cidr"`
29
30 // VLAN this subnet belongs to. Currently ignored.
31 // TODO: Defaults to the default VLAN
32 // for the provided fabric or defaults to the default VLAN
33 // in the default fabric.
34 VLAN *uint `json:"vlan"`
35
36 // Fabric for the subnet. Currently ignored.
37 // TODO: Defaults to the fabric the provided
38 // VLAN belongs to or defaults to the default fabric.
39 Fabric *uint `json:"fabric"`
40
41 // VID of the VLAN this subnet belongs to. Currently ignored.
42 // TODO: Only used when vlan
43 // is not provided. Picks the VLAN with this VID in the provided
44 // fabric or the default fabric if one is not given.
45 VID *uint `json:"vid"`
46
47 // This is used for updates (PUT) and is ignored by create (POST)
48 ID uint `json:"id"`
49}
50
51// TestSubnet is the MAAS API subnet representation
52type TestSubnet struct {
53 DNSServers []string `json:"dns_servers"`
54 Name string `json:"name"`
55 Space string `json:"space"`
56 VLAN TestVLAN `json:"vlan"`
57 GatewayIP string `json:"gateway_ip"`
58 CIDR string `json:"cidr"`
59
60 ResourceURI string `json:"resource_uri"`
61 ID uint `json:"id"`
62 InUseIPAddresses []IP `json:"-"`
63 FixedAddressRanges []AddressRange `json:"-"`
64}
65
66// AddFixedAddressRange adds an AddressRange to the list of fixed address ranges
67// that subnet stores.
68func (server *TestServer) AddFixedAddressRange(subnetID uint, ar AddressRange) {
69 subnet := server.subnets[subnetID]
70 ar.startUint = IPFromString(ar.Start).UInt64()
71 ar.endUint = IPFromString(ar.End).UInt64()
72 subnet.FixedAddressRanges = append(subnet.FixedAddressRanges, ar)
73 server.subnets[subnetID] = subnet
74}
75
76// subnetsHandler handles requests for '/api/<version>/subnets/'.
77func subnetsHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
78 var err error
79 values, err := url.ParseQuery(r.URL.RawQuery)
80 checkError(err)
81 op := values.Get("op")
82 includeRangesString := strings.ToLower(values.Get("include_ranges"))
83 subnetsURLRE := regexp.MustCompile(`/subnets/(.+?)/`)
84 subnetsURLMatch := subnetsURLRE.FindStringSubmatch(r.URL.Path)
85 subnetsURL := getSubnetsEndpoint(server.version)
86
87 var ID uint
88 var gotID bool
89 if subnetsURLMatch != nil {
90 ID, err = NameOrIDToID(subnetsURLMatch[1], server.subnetNameToID, 1, uint(len(server.subnets)))
91
92 if err != nil {
93 http.NotFoundHandler().ServeHTTP(w, r)
94 return
95 }
96
97 gotID = true
98 }
99
100 var includeRanges bool
101 switch includeRangesString {
102 case "true", "yes", "1":
103 includeRanges = true
104 }
105
106 switch r.Method {
107 case "GET":
108 w.Header().Set("Content-Type", "application/vnd.api+json")
109 if len(server.subnets) == 0 {
110 // Until a subnet is registered, behave as if the endpoint
111 // does not exist. This way we can simulate older MAAS
112 // servers that do not support subnets.
113 http.NotFoundHandler().ServeHTTP(w, r)
114 return
115 }
116
117 if r.URL.Path == subnetsURL {
118 var subnets []TestSubnet
119 for i := uint(1); i < server.nextSubnet; i++ {
120 s, ok := server.subnets[i]
121 if ok {
122 subnets = append(subnets, s)
123 }
124 }
125 PrettyJsonWriter(subnets, w)
126 } else if gotID == false {
127 w.WriteHeader(http.StatusBadRequest)
128 } else {
129 switch op {
130 case "unreserved_ip_ranges":
131 PrettyJsonWriter(server.subnetUnreservedIPRanges(server.subnets[ID]), w)
132 case "reserved_ip_ranges":
133 PrettyJsonWriter(server.subnetReservedIPRanges(server.subnets[ID]), w)
134 case "statistics":
135 PrettyJsonWriter(server.subnetStatistics(server.subnets[ID], includeRanges), w)
136 default:
137 PrettyJsonWriter(server.subnets[ID], w)
138 }
139 }
140 checkError(err)
141 case "POST":
142 server.NewSubnet(r.Body)
143 case "PUT":
144 server.UpdateSubnet(r.Body)
145 case "DELETE":
146 delete(server.subnets, ID)
147 w.WriteHeader(http.StatusOK)
148 default:
149 w.WriteHeader(http.StatusBadRequest)
150 }
151}
152
153type addressList []IP
154
155func (a addressList) Len() int { return len(a) }
156func (a addressList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
157func (a addressList) Less(i, j int) bool { return a[i].UInt64() < a[j].UInt64() }
158
159// AddressRange is used to generate reserved IP address range lists
160type AddressRange struct {
161 Start string `json:"start"`
162 startUint uint64
163 End string `json:"end"`
164 endUint uint64
165 Purpose []string `json:"purpose,omitempty"`
166 NumAddresses uint `json:"num_addresses"`
167}
168
169// AddressRangeList is a list of AddressRange
170type AddressRangeList struct {
171 ar []AddressRange
172}
173
174// Append appends a new AddressRange to an AddressRangeList
175func (ranges *AddressRangeList) Append(startIP, endIP IP) {
176 var i AddressRange
177 i.Start, i.End = startIP.String(), endIP.String()
178 i.startUint, i.endUint = startIP.UInt64(), endIP.UInt64()
179 i.NumAddresses = uint(1 + endIP.UInt64() - startIP.UInt64())
180 i.Purpose = startIP.Purpose
181 ranges.ar = append(ranges.ar, i)
182}
183
184func appendRangesToIPList(subnet TestSubnet, ipAddresses *[]IP) {
185 for _, r := range subnet.FixedAddressRanges {
186 for v := r.startUint; v <= r.endUint; v++ {
187 ip := IPFromInt64(v)
188 ip.Purpose = r.Purpose
189 *ipAddresses = append(*ipAddresses, ip)
190 }
191 }
192}
193
194func (server *TestServer) subnetUnreservedIPRanges(subnet TestSubnet) []AddressRange {
195 // Make a sorted copy of subnet.InUseIPAddresses
196 ipAddresses := make([]IP, len(subnet.InUseIPAddresses))
197 copy(ipAddresses, subnet.InUseIPAddresses)
198 appendRangesToIPList(subnet, &ipAddresses)
199 sort.Sort(addressList(ipAddresses))
200
201 // We need the first and last address in the subnet
202 var ranges AddressRangeList
203 var startIP, endIP, lastUsableIP IP
204
205 _, ipNet, err := net.ParseCIDR(subnet.CIDR)
206 checkError(err)
207 startIP = IPFromNetIP(ipNet.IP)
208 // Start with the lowest usable address in the range, which is 1 above
209 // what net.ParseCIDR will give back.
210 startIP.SetUInt64(startIP.UInt64() + 1)
211
212 ones, bits := ipNet.Mask.Size()
213 set := ^((^uint64(0)) << uint(bits-ones))
214
215 // The last usable address is one below the broadcast address, which is
216 // what you get by bitwise ORing 'set' with any IP address in the subnet.
217 lastUsableIP.SetUInt64((startIP.UInt64() | set) - 1)
218
219 for _, endIP = range ipAddresses {
220 end := endIP.UInt64()
221
222 if endIP.UInt64() == startIP.UInt64() {
223 if endIP.UInt64() != lastUsableIP.UInt64() {
224 startIP.SetUInt64(end + 1)
225 }
226 continue
227 }
228
229 if end == lastUsableIP.UInt64() {
230 continue
231 }
232
233 ranges.Append(startIP, IPFromInt64(end-1))
234 startIP.SetUInt64(end + 1)
235 }
236
237 if startIP.UInt64() != lastUsableIP.UInt64() {
238 ranges.Append(startIP, lastUsableIP)
239 }
240
241 return ranges.ar
242}
243
244func (server *TestServer) subnetReservedIPRanges(subnet TestSubnet) []AddressRange {
245 var ranges AddressRangeList
246 var startIP, thisIP IP
247
248 // Make a sorted copy of subnet.InUseIPAddresses
249 ipAddresses := make([]IP, len(subnet.InUseIPAddresses))
250 copy(ipAddresses, subnet.InUseIPAddresses)
251 appendRangesToIPList(subnet, &ipAddresses)
252 sort.Sort(addressList(ipAddresses))
253 if len(ipAddresses) == 0 {
254 ar := ranges.ar
255 if ar == nil {
256 ar = []AddressRange{}
257 }
258 return ar
259 }
260
261 startIP = ipAddresses[0]
262 lastIP := ipAddresses[0]
263 for _, thisIP = range ipAddresses {
264 var purposeMissmatch bool
265 for i, p := range thisIP.Purpose {
266 if startIP.Purpose[i] != p {
267 purposeMissmatch = true
268 }
269 }
270 if (thisIP.UInt64() != lastIP.UInt64() && thisIP.UInt64() != lastIP.UInt64()+1) || purposeMissmatch {
271 ranges.Append(startIP, lastIP)
272 startIP = thisIP
273 }
274 lastIP = thisIP
275 }
276
277 if len(ranges.ar) == 0 || ranges.ar[len(ranges.ar)-1].endUint != lastIP.UInt64() {
278 ranges.Append(startIP, lastIP)
279 }
280
281 return ranges.ar
282}
283
284// SubnetStats holds statistics about a subnet
285type SubnetStats struct {
286 NumAvailable uint `json:"num_available"`
287 LargestAvailable uint `json:"largest_available"`
288 NumUnavailable uint `json:"num_unavailable"`
289 TotalAddresses uint `json:"total_addresses"`
290 Usage float32 `json:"usage"`
291 UsageString string `json:"usage_string"`
292 Ranges []AddressRange `json:"ranges"`
293}
294
295func (server *TestServer) subnetStatistics(subnet TestSubnet, includeRanges bool) SubnetStats {
296 var stats SubnetStats
297 _, ipNet, err := net.ParseCIDR(subnet.CIDR)
298 checkError(err)
299
300 ones, bits := ipNet.Mask.Size()
301 stats.TotalAddresses = (1 << uint(bits-ones)) - 2
302 stats.NumUnavailable = uint(len(subnet.InUseIPAddresses))
303 stats.NumAvailable = stats.TotalAddresses - stats.NumUnavailable
304 stats.Usage = float32(stats.NumUnavailable) / float32(stats.TotalAddresses)
305 stats.UsageString = fmt.Sprintf("%0.1f%%", stats.Usage*100)
306
307 // Calculate stats.LargestAvailable - the largest contiguous block of IP addresses available
308 reserved := server.subnetUnreservedIPRanges(subnet)
309 for _, addressRange := range reserved {
310 if addressRange.NumAddresses > stats.LargestAvailable {
311 stats.LargestAvailable = addressRange.NumAddresses
312 }
313 }
314
315 if includeRanges {
316 stats.Ranges = reserved
317 }
318
319 return stats
320}
321
322func decodePostedSubnet(subnetJSON io.Reader) CreateSubnet {
323 var postedSubnet CreateSubnet
324 decoder := json.NewDecoder(subnetJSON)
325 err := decoder.Decode(&postedSubnet)
326 checkError(err)
327 if postedSubnet.DNSServers == nil {
328 postedSubnet.DNSServers = []string{}
329 }
330 return postedSubnet
331}
332
333// UpdateSubnet creates a subnet in the test server
334func (server *TestServer) UpdateSubnet(subnetJSON io.Reader) TestSubnet {
335 postedSubnet := decodePostedSubnet(subnetJSON)
336 updatedSubnet := subnetFromCreateSubnet(postedSubnet)
337 server.subnets[updatedSubnet.ID] = updatedSubnet
338 return updatedSubnet
339}
340
341// NewSubnet creates a subnet in the test server
342func (server *TestServer) NewSubnet(subnetJSON io.Reader) *TestSubnet {
343 postedSubnet := decodePostedSubnet(subnetJSON)
344 newSubnet := subnetFromCreateSubnet(postedSubnet)
345 newSubnet.ID = server.nextSubnet
346 server.subnets[server.nextSubnet] = newSubnet
347 server.subnetNameToID[newSubnet.Name] = newSubnet.ID
348
349 server.nextSubnet++
350 return &newSubnet
351}
352
353// NodeNetworkInterface represents a network interface attached to a node
354type NodeNetworkInterface struct {
355 Name string `json:"name"`
356 Links []NetworkLink `json:"links"`
357}
358
359// Node represents a node
360type Node struct {
361 SystemID string `json:"system_id"`
362 Interfaces []NodeNetworkInterface `json:"interface_set"`
363}
364
365// NetworkLink represents a MAAS network link
366type NetworkLink struct {
367 ID uint `json:"id"`
368 Mode string `json:"mode"`
369 Subnet *TestSubnet `json:"subnet"`
370}
371
372// SetNodeNetworkLink records that the given node + interface are in subnet
373func (server *TestServer) SetNodeNetworkLink(SystemID string, nodeNetworkInterface NodeNetworkInterface) {
374 for i, ni := range server.nodeMetadata[SystemID].Interfaces {
375 if ni.Name == nodeNetworkInterface.Name {
376 server.nodeMetadata[SystemID].Interfaces[i] = nodeNetworkInterface
377 return
378 }
379 }
380 n := server.nodeMetadata[SystemID]
381 n.Interfaces = append(n.Interfaces, nodeNetworkInterface)
382 server.nodeMetadata[SystemID] = n
383}
384
385// subnetFromCreateSubnet creates a subnet in the test server
386func subnetFromCreateSubnet(postedSubnet CreateSubnet) TestSubnet {
387 var newSubnet TestSubnet
388 newSubnet.DNSServers = postedSubnet.DNSServers
389 newSubnet.Name = postedSubnet.Name
390 newSubnet.Space = postedSubnet.Space
391 //TODO: newSubnet.VLAN = server.postedSubnetVLAN
392 newSubnet.GatewayIP = postedSubnet.GatewayIP
393 newSubnet.CIDR = postedSubnet.CIDR
394 newSubnet.ID = postedSubnet.ID
395 return newSubnet
396}