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