-
Notifications
You must be signed in to change notification settings - Fork 3
/
hypervisor.go
730 lines (619 loc) · 16.1 KB
/
hypervisor.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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
package lochness
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/mistifyio/lochness/pkg/kv"
"github.com/pborman/uuid"
)
var (
// HypervisorPath is the path in the config store
HypervisorPath = "lochness/hypervisors/"
// id of currently running hypervisor
hypervisorID = ""
)
type (
// Hypervisor is a physical box on which guests run
Hypervisor struct {
context *Context
modifiedIndex uint64
ID string `json:"id"`
Metadata map[string]string `json:"metadata"`
IP net.IP `json:"ip"`
Netmask net.IP `json:"netmask"`
Gateway net.IP `json:"gateway"`
MAC net.HardwareAddr `json:"mac"`
TotalResources Resources `json:"total_resources"`
AvailableResources Resources `json:"available_resources"`
subnets map[string]string
guests []string
alive bool
heart kv.EphemeralKey
// Config is a set of key/values for driving various config options. writes should
// only be done using SetConfig
Config map[string]string
}
// Hypervisors is an alias to a slice of *Hypervisor
Hypervisors []*Hypervisor
// hypervisorJSON is used to ease json marshal/unmarshal
hypervisorJSON struct {
ID string `json:"id"`
Metadata map[string]string `json:"metadata"`
IP net.IP `json:"ip"`
Netmask net.IP `json:"netmask"`
Gateway net.IP `json:"gateway"`
MAC string `json:"mac"`
TotalResources Resources `json:"total_resources"`
AvailableResources Resources `json:"available_resources"`
}
)
// MarshalJSON is a helper for marshalling a Hypervisor
func (h *Hypervisor) MarshalJSON() ([]byte, error) {
data := hypervisorJSON{
ID: h.ID,
Metadata: h.Metadata,
IP: h.IP,
Netmask: h.Netmask,
Gateway: h.Gateway,
MAC: h.MAC.String(),
TotalResources: h.TotalResources,
AvailableResources: h.AvailableResources,
}
return json.Marshal(data)
}
// UnmarshalJSON is a helper for unmarshalling a Hypervisor
func (h *Hypervisor) UnmarshalJSON(input []byte) error {
data := hypervisorJSON{}
if err := json.Unmarshal(input, &data); err != nil {
return err
}
if data.ID != "" {
h.ID = data.ID
}
if data.Metadata != nil {
h.Metadata = data.Metadata
}
if data.IP != nil {
h.IP = data.IP
}
if data.Netmask != nil {
h.Netmask = data.Netmask
}
if data.Gateway != nil {
h.Gateway = data.Gateway
}
if &data.TotalResources != nil {
h.TotalResources = data.TotalResources
}
if &data.AvailableResources != nil {
h.AvailableResources = data.AvailableResources
}
if data.MAC != "" {
a, err := net.ParseMAC(data.MAC)
if err != nil {
return err
}
h.MAC = a
}
if h.Config == nil {
h.Config = make(map[string]string)
}
return nil
}
// blankHypervisor is a helper for creating a blank Hypervisor.
func (c *Context) blankHypervisor(id string) *Hypervisor {
h := &Hypervisor{
context: c,
ID: id,
Metadata: make(map[string]string),
subnets: make(map[string]string),
Config: make(map[string]string),
guests: make([]string, 0, 0),
}
if id == "" {
h.ID = uuid.New()
}
return h
}
// NewHypervisor create a new blank Hypervisor.
func (c *Context) NewHypervisor() *Hypervisor {
return c.blankHypervisor("")
}
// Hypervisor fetches a Hypervisor from the config store.
func (c *Context) Hypervisor(id string) (*Hypervisor, error) {
var err error
id, err = canonicalizeUUID(id)
if err != nil {
return nil, err
}
h := c.blankHypervisor(id)
err = h.Refresh()
if err != nil {
return nil, err
}
return h, nil
}
// key is a helper to generate the config store key.
func (h *Hypervisor) key() string {
return filepath.Join(HypervisorPath, h.ID, "metadata")
}
// Refresh reloads a Hypervisor from the data store.
func (h *Hypervisor) Refresh() error {
prefix := filepath.Join(HypervisorPath, h.ID)
nodes, err := h.context.kv.GetAll(prefix)
if err != nil {
return err
}
// handle metadata
key := filepath.Join(prefix, "metadata")
value, ok := nodes[key]
if !ok {
return errors.New("metadata key is missing")
}
if err := json.Unmarshal(value.Data, &h); err != nil {
return err
}
h.modifiedIndex = value.Index
delete(nodes, key)
// handle heartbeat
key = filepath.Join(prefix, "heartbeat")
_, ok = nodes[key]
if ok {
//if exists, then it's alive
h.alive = true
delete(nodes, key)
}
config := map[string]string{}
guests := []string{}
subnets := map[string]string{}
// TODO(needs tests)
for k, v := range nodes {
elements := strings.Split(k, "/")
base := elements[len(elements)-1]
dir := elements[len(elements)-2]
switch dir {
case "subnets":
subnets[base] = string(v.Data)
case "guests":
guests = append(guests, base)
case "config":
config[base] = string(v.Data)
}
}
h.Config = config
h.guests = guests
h.subnets = subnets
return nil
}
// TODO: figure out safe amount of memory to report and how to limit it
func memory() (uint64, error) {
f, err := os.Open("/proc/meminfo")
if err != nil {
return 0, err
}
scanner := bufio.NewScanner(f)
mem := 0
for scanner.Scan() {
if !strings.HasPrefix(scanner.Text(), "MemTotal:") {
continue
}
vals := strings.Split(scanner.Text(), " ")
mem, err = strconv.Atoi(vals[len(vals)-2])
if err != nil {
return 0, err
}
}
return uint64(mem) * 80 / 100 / 1024, scanner.Err()
}
// TODO: parameterize this
func disk(path string) (uint64, error) {
stat := &syscall.Statfs_t{}
err := syscall.Statfs(path, stat)
return uint64(stat.Bsize) * stat.Bavail * 80 / 100 / 1024 / 1024, err
}
// cpu gets number of CPU's.
func cpu() (uint32, error) {
f, err := os.Open("/proc/cpuinfo")
if err != nil {
return 0, err
}
scanner := bufio.NewScanner(f)
count := 0
for scanner.Scan() {
if strings.HasPrefix(scanner.Text(), "processor") {
count++
}
}
return uint32(count - 1), scanner.Err()
}
// canonicalizeUUID is a helper to ensure UUID's are in a single form and case
func canonicalizeUUID(id string) (string, error) {
i := uuid.Parse(id)
if i == nil {
return "", fmt.Errorf("invalid UUID: %s", id)
}
return strings.ToLower(i.String()), nil
}
// SetHypervisorID sets the id of the current hypervisor.
// It should be used by all daemons that are ran on a hypervisor and are expected to interact with the data stores directly.
// Passing in a blank string will fall back to first checking the environment variable "HYPERVISOR_ID" and then using the hostname.
// ID must be a valid UUID.
// ID will be lowercased.
func SetHypervisorID(id string) (string, error) {
// the if statement approach is clunky and probably needs refining
var err error
if id == "" {
id = os.Getenv("HYPERVISOR_ID")
}
if id == "" {
id, err = os.Hostname()
if err != nil {
return "", err
}
}
if id == "" {
return "", errors.New("unable to discover an id to set")
}
id, err = canonicalizeUUID(id)
if err != nil {
return "", err
}
// purposefully set here rather than above, in case, for some reason,
// caller knows there is a previous, usable value
hypervisorID = id
return hypervisorID, nil
}
// GetHypervisorID gets the hypervisor id as set with SetHypervisorID.
// It does not make an attempt to discover the id if not set.
func GetHypervisorID() string {
return hypervisorID
}
// VerifyOnHV verifies that it is being ran on hypervisor with same hostname as id.
func (h *Hypervisor) VerifyOnHV() error {
if GetHypervisorID() != h.ID {
return errors.New("Hypervisor ID does not match hostname/environment")
}
return nil
}
// calcGuestsUsage calculates total resource usage of managed guests.
// Note that CPU "usage" is intentionally ignored as cores are not directly allocated to guests.
func (h *Hypervisor) calcGuestsUsage() (Resources, error) {
usage := Resources{}
err := h.ForEachGuest(func(guest *Guest) error {
// cache?
flavor, err := h.context.Flavor(guest.FlavorID)
if err != nil {
return err
}
usage.Memory += flavor.Memory
usage.Disk += flavor.Disk
return nil
})
if err != nil {
return Resources{}, err
}
return usage, nil
}
// UpdateResources syncs Hypervisor resource usage to the data store.
// It should only be ran on the actual hypervisor.
func (h *Hypervisor) UpdateResources() error {
if err := h.VerifyOnHV(); err != nil {
return err
}
m, err := memory()
if err != nil {
return err
}
guestDiskDir, ok := h.Config["guestDiskDir"]
if !ok {
guestDiskDir = "/mistify/guests"
}
d, err := disk(guestDiskDir)
if err != nil {
return err
}
c, err := cpu()
if err != nil {
return err
}
h.TotalResources = Resources{Memory: m, Disk: d, CPU: c}
usage, err := h.calcGuestsUsage()
if err != nil {
return err
}
h.AvailableResources = Resources{
Memory: h.TotalResources.Memory - usage.Memory,
Disk: h.TotalResources.Disk - usage.Disk,
CPU: h.TotalResources.CPU - usage.CPU,
}
return h.Save()
}
// Validate ensures a Hypervisor has reasonable data.
// It currently does nothing.
func (h *Hypervisor) Validate() error {
// TODO: do validation stuff...
if h.ID == "" {
return errors.New("no id")
}
if uuid.Parse(h.ID) == nil {
return errors.New("invalid id")
}
return nil
}
// Save persists a FWGroup.
// It will call Validate.
func (h *Hypervisor) Save() error {
if err := h.Validate(); err != nil {
return err
}
v, err := json.Marshal(h)
if err != nil {
return err
}
index, err := h.context.kv.Update(h.key(), kv.Value{Data: v, Index: h.modifiedIndex})
if err != nil {
return err
}
h.modifiedIndex = index
return nil
}
// the many side of many:one relationships is done with nested keys
func (h *Hypervisor) subnetKey(s *Subnet) string {
var key string
if s != nil {
key = s.ID
}
return filepath.Join(HypervisorPath, h.ID, "subnets", key)
}
// AddSubnet adds a subnet to a Hypervisor.
func (h *Hypervisor) AddSubnet(s *Subnet, bridge string) error {
// Make sure the hypervisor exists
if h.modifiedIndex == 0 {
if err := h.Refresh(); err != nil {
return err
}
}
// Make sure the subnet exists
if s.modifiedIndex == 0 {
if err := s.Refresh(); err != nil {
return err
}
}
err := h.context.kv.Set(filepath.Join(h.subnetKey(s)), bridge)
if err == nil {
h.subnets[s.ID] = bridge
}
return err
}
// RemoveSubnet removes a subnet from a Hypervisor.
func (h *Hypervisor) RemoveSubnet(s *Subnet) error {
if err := h.context.kv.Delete(filepath.Join(h.subnetKey(s)), false); err != nil {
return err
}
delete(h.subnets, s.ID)
return nil
}
// Subnets returns the subnet/bridge mappings for a Hypervisor.
func (h *Hypervisor) Subnets() map[string]string {
return h.subnets
}
// heartbeatKey is a helper for generating a key for config store.
func (h *Hypervisor) heartbeatKey() string {
return filepath.Join(HypervisorPath, h.ID, "heartbeat")
}
// Heartbeat announces the availability of a hypervisor.
// In general, this is useful for service announcement/discovery.
// Should be ran from the hypervisor, or something monitoring it.
func (h *Hypervisor) Heartbeat(ttl time.Duration) error {
if err := h.VerifyOnHV(); err != nil {
return err
}
if h.heart == nil {
ekey, err := h.context.kv.EphemeralKey(h.heartbeatKey(), ttl)
if err != nil {
return err
}
h.heart = ekey
}
if err := h.heart.Set(time.Now().String()); err != nil {
return err
}
h.alive = true
return nil
}
// IsAlive returns true if the heartbeat is present.
func (h *Hypervisor) IsAlive() bool {
return h.alive
}
// guestKey for generating a key for config store.
func (h *Hypervisor) guestKey(g *Guest) string {
var key string
if g != nil {
key = g.ID
}
return filepath.Join(HypervisorPath, h.ID, "guests", key)
}
// AddGuest adds a Guest to the Hypervisor.
// It reserves an IPaddress for the Guest.
// It also updates the Guest.
func (h *Hypervisor) AddGuest(g *Guest) error {
// make sure we have subnet guest wants. we should have this figured out
// when we selected this hypervisor, so this is sort of silly to do again
// we need to rethink how we do this
n, err := h.context.Network(g.NetworkID)
if err != nil {
return err
}
var s *Subnet
var bridge string
LOOP:
for _, k := range n.Subnets() {
for id, br := range h.subnets {
if id == k {
subnet, err := h.context.Subnet(id)
if err != nil {
return err
}
avail := subnet.AvailableAddresses()
if len(avail) > 0 {
s = subnet
bridge = br
break LOOP
}
}
}
}
if s == nil {
return errors.New("no suitable subnet found")
}
ip, err := s.ReserveAddress(g.ID)
if err != nil {
return err
}
// an instance where transactions would be cool...
g.HypervisorID = h.ID
g.IP = ip
g.SubnetID = s.ID
g.Bridge = bridge
err = h.context.kv.Set(filepath.Join(h.guestKey(g)), g.ID)
if err != nil {
return err
}
err = g.Save()
if err != nil {
return err
}
h.guests = append(h.guests, g.ID)
return nil
}
// RemoveGuest removes a guest from the hypervisor.
// Also releases the IP.
func (h *Hypervisor) RemoveGuest(g *Guest) error {
if g.HypervisorID != h.ID {
return errors.New("guest does not belong to hypervisor")
}
subnet, err := h.context.Subnet(g.SubnetID)
if err != nil {
return err
}
if err := subnet.ReleaseAddress(g.IP); err != nil {
return err
}
if err := h.context.kv.Delete(filepath.Join(h.guestKey(g)), false); err != nil {
return err
}
g.HypervisorID = ""
g.IP = nil
g.SubnetID = ""
g.Bridge = ""
if err := g.Save(); err != nil {
return err
}
newGuests := make([]string, 0, len(h.guests)-1)
for i := 0; i < len(h.guests); i++ {
if h.guests[i] != g.ID {
newGuests = append(newGuests, h.guests[i])
}
}
h.guests = newGuests
return nil
}
// Guests returns a slice of GuestIDs assigned to the Hypervisor.
func (h *Hypervisor) Guests() []string {
return h.guests
}
// ForEachGuest will run f on each Guest.
// It will stop iteration if f returns an error.
func (h *Hypervisor) ForEachGuest(f func(*Guest) error) error {
for _, id := range h.guests {
guest, err := h.context.Guest(id)
if err != nil {
return err
}
if err := f(guest); err != nil {
return err
}
}
return nil
}
// FirstHypervisor will return the first hypervisor for which the function returns true.
func (c *Context) FirstHypervisor(f func(*Hypervisor) bool) (*Hypervisor, error) {
keys, err := c.kv.Keys(HypervisorPath)
if err != nil {
return nil, err
}
for _, k := range keys {
h, err := c.Hypervisor(filepath.Base(k))
if err != nil {
return nil, err
}
if f(h) {
return h, nil
}
}
return nil, nil
}
// ForEachHypervisor will run f on each Hypervisor.
// It will stop iteration if f returns an error.
func (c *Context) ForEachHypervisor(f func(*Hypervisor) error) error {
// should we condense this to a single kv call?
// We would need to rework how we "load" hypervisor a bit
keys, err := c.kv.Keys(HypervisorPath)
if err != nil {
return err
}
for _, k := range keys {
h, err := c.Hypervisor(filepath.Base(k))
if err != nil {
return err
}
if err := f(h); err != nil {
return err
}
}
return nil
}
// SetConfig sets a single Hypervisor Config value.
// Set value to "" to unset.
func (h *Hypervisor) SetConfig(key, value string) error {
if key == "" {
return errors.New("empty config key")
}
if value != "" {
if err := h.context.kv.Set(filepath.Join(HypervisorPath, h.ID, "config", key), value); err != nil {
return err
}
h.Config[key] = value
} else {
err := h.context.kv.Delete(filepath.Join(HypervisorPath, h.ID, "config", key), false)
if err != nil && !h.context.kv.IsKeyNotFound(err) {
return err
}
delete(h.Config, key)
}
return nil
}
// Destroy removes a hypervisor.
// The Hypervisor must not have any guests.
func (h *Hypervisor) Destroy() error {
if len(h.guests) != 0 {
// XXX: should use an error var?
return errors.New("not empty")
}
if h.modifiedIndex == 0 {
// it has not been saved?
return errors.New("not persisted")
}
if err := h.context.kv.Remove(h.key(), h.modifiedIndex); err != nil {
return err
}
return h.context.kv.Delete(filepath.Join(HypervisorPath, h.ID), true)
}