-
Notifications
You must be signed in to change notification settings - Fork 0
/
iprange.go
428 lines (398 loc) · 10.6 KB
/
iprange.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
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
package models
import (
"database/sql"
"encoding/json"
"io"
"net"
"code.google.com/p/go-uuid/uuid"
"github.com/hashicorp/go-multierror"
"github.com/mistifyio/mistify-operator-admin/db"
)
type (
// IPRange describes a segment of IP addresses
IPRange struct {
ID string `json:"id"`
CIDR *net.IPNet `json:"cidr"`
Gateway net.IP `json:"gateway"`
Start net.IP `json:"start"`
End net.IP `json:"end"`
Metadata map[string]string `json:"metadata"`
Network *Network `json:"-"`
Hypervisors []*Hypervisor `json:"-"`
}
// ipRangeData is a middle-man for JSON and database (un)marshalling
ipRangeData struct {
ID string `json:"id"`
CIDR string `json:"cidr"`
Gateway string `json:"gateway"`
Start string `json:"start"`
End string `json:"end"`
Metadata map[string]string `json:"metadata"`
}
)
// id returns the id, required by the relatable interface
func (iprange *IPRange) id() string {
return iprange.ID
}
// pkeyName returns the database primary key name, required by the relatable
// interface
func (iprange *IPRange) pkeyName() string {
return "iprange_id"
}
// importData unmarshals the middle-man structure into an iprange object
func (iprange *IPRange) importData(data *ipRangeData) error {
_, cidr, err := net.ParseCIDR(data.CIDR)
if err != nil {
return err
}
iprange.ID = data.ID
iprange.CIDR = cidr
iprange.Gateway = net.ParseIP(data.Gateway)
iprange.Start = net.ParseIP(data.Start)
iprange.End = net.ParseIP(data.End)
iprange.Metadata = data.Metadata
return nil
}
// exportData marshals the iprange object into the middle-man structure
func (iprange *IPRange) exportData() *ipRangeData {
return &ipRangeData{
ID: iprange.ID,
CIDR: fmtString(iprange.CIDR),
Gateway: fmtString(iprange.Gateway),
Start: fmtString(iprange.Start),
End: fmtString(iprange.End),
Metadata: iprange.Metadata,
}
}
// UnmarshalJSON unmarshals JSON into an iprange object
func (iprange *IPRange) UnmarshalJSON(b []byte) error {
data := &ipRangeData{}
if err := json.Unmarshal(b, data); err != nil {
return err
}
if err := iprange.importData(data); err != nil {
return err
}
return nil
}
// MarshalJSON marshals an iprange object into JSON
func (iprange IPRange) MarshalJSON() ([]byte, error) {
return json.Marshal(iprange.exportData())
}
// Validate ensures the iprange proerties are set correctly
func (iprange *IPRange) Validate() error {
var results *multierror.Error
if iprange.ID == "" {
results = multierror.Append(results, ErrNoID)
}
if uuid.Parse(iprange.ID) == nil {
results = multierror.Append(results, ErrBadID)
}
if iprange.CIDR == nil {
results = multierror.Append(results, ErrNoCIDR)
}
if iprange.Gateway == nil {
results = multierror.Append(results, ErrNoGateway)
}
if iprange.Start == nil {
results = multierror.Append(results, ErrNoStartIP)
}
if iprange.End == nil {
results = multierror.Append(results, ErrNoEndIP)
}
if iprange.Metadata == nil {
results = multierror.Append(results, ErrNilMetadata)
}
return results.ErrorOrNil()
}
// Save persists an iprange to the database
func (iprange *IPRange) Save() error {
if err := iprange.Validate(); err != nil {
return err
}
d, err := db.Connect(nil)
if err != nil {
return err
}
// Writable CTE for an Upsert
// See: http://stackoverflow.com/a/8702291
// And: http://dba.stackexchange.com/a/78535
sql := `
WITH new_values (iprange_id, cidr, gateway, start_ip, end_ip, metadata) as (
VALUES ($1::uuid, $2::cidr, $3::inet, $4::inet, $5::inet, $6::json)
),
upsert as (
UPDATE ipranges i SET
cidr = nv.cidr,
gateway = nv.gateway,
start_ip = nv.start_ip,
end_ip = nv.end_ip,
metadata = nv.metadata
FROM new_values nv
WHERE i.iprange_id = nv.iprange_id
RETURNING i.iprange_id
)
INSERT INTO ipranges
(iprange_id, cidr, gateway, start_ip, end_ip, metadata)
SELECT iprange_id, cidr, gateway, start_ip, end_ip, metadata
FROM new_values nv
WHERE NOT EXISTS (SELECT 1 FROM upsert u WHERE nv.iprange_id = u.iprange_id)
`
data := iprange.exportData()
metadata, err := json.Marshal(data.Metadata)
if err != nil {
return err
}
_, err = d.Exec(sql,
data.ID,
data.CIDR,
data.Gateway,
data.Start,
data.End,
string(metadata),
)
return err
}
// Delete removes an iprange from the database
func (iprange *IPRange) Delete() error {
d, err := db.Connect(nil)
if err != nil {
return err
}
sql := "DELETE FROM ipranges WHERE iprange_id = $1"
_, err = d.Exec(sql, iprange.ID)
return err
}
// Load retrieves an iprange from the database
func (iprange *IPRange) Load() error {
d, err := db.Connect(nil)
if err != nil {
return err
}
sql := `
SELECT iprange_id, cidr, gateway, start_ip, end_ip, metadata
FROM ipranges
WHERE iprange_id = $1
`
rows, err := d.Query(sql, iprange.ID)
if err != nil {
return err
}
defer rows.Close()
rows.Next()
if err := iprange.fromRows(rows); err != nil {
return err
}
return rows.Err()
}
// fromRows unmarshals a database query result row into the iprange object
func (iprange *IPRange) fromRows(rows *sql.Rows) error {
var metadata string
data := &ipRangeData{}
err := rows.Scan(
&data.ID,
&data.CIDR,
&data.Gateway,
&data.Start,
&data.End,
&metadata,
)
if err != nil {
return err
}
if err := json.Unmarshal([]byte(metadata), &data.Metadata); err != nil {
return err
}
return iprange.importData(data)
}
// Decode unmarshals JSON into the flavor object
func (iprange *IPRange) Decode(data io.Reader) error {
if err := json.NewDecoder(data).Decode(iprange); err != nil {
return err
}
if iprange.Metadata == nil {
iprange.Metadata = make(map[string]string)
} else {
for key, value := range iprange.Metadata {
if value == "" {
delete(iprange.Metadata, key)
}
}
}
return nil
}
// LoadHypervisors retrieves the hypervisors associated with the iprange from
// the database
func (iprange *IPRange) LoadHypervisors() error {
hypervisors, err := HypervisorsByIPRange(iprange)
if err != nil {
return err
}
iprange.Hypervisors = hypervisors
return nil
}
// AddHypervisor adds a relation to a hypervisor
func (iprange *IPRange) AddHypervisor(hypervisor *Hypervisor) error {
return AddRelation("hypervisors_ipranges", iprange, hypervisor)
}
// RemoveHypervisor removes a relation from a hypervisor
func (iprange *IPRange) RemoveHypervisor(hypervisor *Hypervisor) error {
return RemoveRelation("hypervisors_ipranges", iprange, hypervisor)
}
// SetHypervisors creates and ensures the only relations the iprange has with
// hypervisors
func (iprange *IPRange) SetHypervisors(hypervisors []*Hypervisor) error {
if len(hypervisors) == 0 {
return ClearRelations("hypervisors_ipranges", iprange)
}
relatables := make([]relatable, len(hypervisors))
for i, hypervisor := range hypervisors {
relatables[i] = relatable(hypervisor)
}
if err := SetRelations("hypervisors_ipranges", iprange, relatables); err != nil {
return err
}
return iprange.LoadHypervisors()
}
// LoadNetwork retrieves the network related with the iprange from the
// database
func (iprange *IPRange) LoadNetwork() error {
networks, err := NetworksByIPRange(iprange)
if err != nil {
return err
}
if len(networks) > 0 {
iprange.Network = networks[0]
} else {
iprange.Network = nil
}
return nil
}
// SetNetwork sets the related network
func (iprange *IPRange) SetNetwork(network *Network) error {
// Only one can be set at a time
relatables := make([]relatable, 1)
relatables[0] = relatable(network)
return SetRelations("iprange_networks", iprange, relatables)
}
// RemoveNetwork clears the network relation
func (iprange *IPRange) RemoveNetwork(network *Network) error {
return RemoveRelation("iprange_networks", iprange, network)
}
// NewID generates a new uuid ID
func (iprange *IPRange) NewID() string {
iprange.ID = uuid.New()
return iprange.ID
}
// NewIPRange creates and initializes a new iprange object
func NewIPRange() *IPRange {
iprange := &IPRange{
ID: uuid.New(),
Metadata: make(map[string]string),
}
return iprange
}
// FetchIPRange retrieves an iprange object from the database by ID
func FetchIPRange(id string) (*IPRange, error) {
iprange := &IPRange{
ID: id,
}
if err := iprange.Load(); err != nil {
return nil, err
}
return iprange, nil
}
// ListIPRanges retrieves an array of all iprange objects from the database
func ListIPRanges() ([]*IPRange, error) {
d, err := db.Connect(nil)
if err != nil {
return nil, err
}
sql := `
SELECT iprange_id, cidr, gateway, start_ip, end_ip, metadata
FROM ipranges
ORDER BY iprange_id
`
rows, err := d.Query(sql)
if err != nil {
return nil, err
}
ipranges := make([]*IPRange, 0, 1)
for rows.Next() {
iprange := &IPRange{}
if err := iprange.fromRows(rows); err != nil {
return nil, err
}
ipranges = append(ipranges, iprange)
}
if err = rows.Err(); err != nil {
return nil, err
}
return ipranges, nil
}
// IPRangesByHypervisor retrieves an array of iprange objects associated with a
// hypervisor from the database
func IPRangesByHypervisor(hypervisor *Hypervisor) ([]*IPRange, error) {
d, err := db.Connect(nil)
if err != nil {
return nil, err
}
sql := `
SELECT i.iprange_id, i.cidr, i.gateway, i.start_ip, i.end_ip, i.metadata
FROM ipranges i
JOIN hypervisors_ipranges hi ON i.iprange_id = hi.iprange_id
WHERE hi.hypervisor_id = $1
ORDER BY i.iprange_id asc
`
rows, err := d.Query(sql, hypervisor.ID)
if err != nil {
return nil, err
}
ipranges, err := iprangesFromRows(rows)
if err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return ipranges, nil
}
// IPRangesByNetwork retrieves an array of iprange objects associated with a
// network from the database
func IPRangesByNetwork(network *Network) ([]*IPRange, error) {
d, err := db.Connect(nil)
if err != nil {
return nil, err
}
sql := `
SELECT i.iprange_id, i.cidr, i.gateway, i.start_ip, i.end_ip, i.metadata
FROM ipranges i
JOIN iprange_networks i_n ON i.iprange_id = i_n.iprange_id
WHERE i_n.network_id = $1
ORDER BY i.iprange_id asc
`
rows, err := d.Query(sql, network.ID)
if err != nil {
return nil, err
}
ipranges, err := iprangesFromRows(rows)
if err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return ipranges, nil
}
// iprangesFromRows unmarshals multiple query rows into an array of ipranges
func iprangesFromRows(rows *sql.Rows) ([]*IPRange, error) {
ipranges := make([]*IPRange, 0, 1)
for rows.Next() {
iprange := &IPRange{}
if err := iprange.fromRows(rows); err != nil {
return nil, err
}
ipranges = append(ipranges, iprange)
}
return ipranges, nil
}