forked from hashicorp/otto
/
layered.go
795 lines (681 loc) · 18.9 KB
/
layered.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
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
package vagrant
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"time"
"github.com/boltdb/bolt"
"github.com/hashicorp/otto/context"
"github.com/hashicorp/terraform/dag"
)
// Layered is a Vagrant environment that is created using a series of
// "layers". Otto manages these layers and this library automatically prunes
// unused layers. This library will also do the multi-process locking
// necessary to prevent races.
//
// To update a layer (change it), you should create a Layer with a new ID.
// IDs should be considered immutable for all time. This is to prevent breaking
// other environments. Once a layer is safely no longer in use by anybody
// for a sufficient period of time, Otto will automatically prune it.
//
// Layered itself doesn't manage the final Vagrant environment. This should
// be done outside of this using functions like Dev. Accounting should be done
// to avoid layers being pruned with `AddLeaf`, `RemoveLeaf`. If these
// aren't called layers underneath may be pruned which can corrupt leaves.
type Layered struct {
// Layers are layers that are important for this run. This must include
// all the Vagrantfiles for all the potential layers since we might need
// to run all of them.
Layers []*Layer
// DataDir is the directory where Layered can write data to.
DataDir string
}
// Layer is a single layer of the Layered Vagrant environment.
type Layer struct {
// ID is a unique ID for the layer. See the note in Layered about
// generating a new ID for every change/iteration in the Vagrantfile.
ID string
// Vagrantfile is the path to the Vagrantfile to bring up for this
// layer. The Vagrantfile should handle all provisioning. This
// Vagrantfile will be copied to another directory, so any paths
// in it should be relative to the Vagrantfile.
Vagrantfile string
}
// Graph will return the full graph that is currently encoded.
func (l *Layered) Graph() (*dag.AcyclicGraph, error) {
db, err := l.db()
if err != nil {
return nil, err
}
defer db.Close()
return l.graph(db)
}
// Build will build all the layers that are defined in this Layered
// struct. It will automatically output to the UI as needed.
//
// This will automatically acquire a process-lock to ensure that no duplicate
// layers are ever built. The process lock usually assumes that Otto is
// being run by the same user.
func (l *Layered) Build(ctx *context.Shared) error {
// Grab the DB and initialize all the layers. This just inserts a
// pending layer if it doesn't exist, as well as sets up the edges.
db, err := l.db()
if err != nil {
return err
}
vs, err := l.init(db)
db.Close()
if err != nil {
return err
}
// Go through each layer and build it. This will be a no-op if the
// layer is already built.
for i, v := range vs {
var last *layerVertex
if i > 0 {
last = vs[i-1]
}
if err := l.buildLayer(v, last, ctx); err != nil {
return err
}
}
return nil
}
// Prune will destroy all layers that haven't been used in a certain
// amount of time.
//
// TODO: "certain amount of time" for now we just prune any orphans
func (l *Layered) Prune(ctx *context.Shared) (int, error) {
db, err := l.db()
if err != nil {
return 0, err
}
defer db.Close()
graph, err := l.graph(db)
if err != nil {
return 0, err
}
log.Printf("[DEBUG] vagrant: layer graph: \n%s", graph.String())
// Get all the bad roots. These are anything without something depending
// on it except for the main "root"
roots := make([]dag.Vertex, 0)
for _, v := range graph.Vertices() {
if v == "root" {
continue
}
if graph.UpEdges(v).Len() == 0 {
roots = append(roots, v)
}
}
if len(roots) == 0 {
return 0, nil
}
// Go through the remaining roots, these are the environments
// that must be destroyed.
count := 0
for _, root := range roots {
err := graph.DepthFirstWalk([]dag.Vertex{root},
func(v dag.Vertex, depth int) error {
if err := l.pruneLayer(db, v.(*layerVertex), ctx); err != nil {
return err
}
count++
return nil
})
if err != nil {
return count, err
}
}
return count, nil
}
// ConfigureEnv configures the Vagrant instance with the proper environment
// variables to be able to execute things.
//
// Once the env is used, SetEnvStatus should be used to modify the env
// status around it. This is critical to make sure layers don't get pruned.
func (l *Layered) ConfigureEnv(v *Vagrant) error {
// Get the final layer
layer := l.Layers[len(l.Layers)-1]
// Get the path for the final layer and add it to the environment
path := filepath.Join(l.layerPath(layer), "Vagrantfile")
if v.Env == nil {
v.Env = make(map[string]string)
}
v.Env[layerID] = layer.ID
v.Env[layerPathEnv] = path
return nil
}
// SetEnv configures the status of an env, persisting that its ready or
// deleted which controls whether layers get pruned or not.
//
// The Vagrant pointer given must already be configured using ConfigureEnv.
func (l *Layered) SetEnv(v *Vagrant, state envState) error {
// Update the DB with our environment
db, err := l.db()
if err != nil {
return err
}
defer db.Close()
return db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltEnvsBucket)
key := []byte(v.DataDir)
// If the state is deleted then call it good
if state == envStateDeleted {
return bucket.Delete(key)
}
// Otherwise we're inserting
layerId, ok := v.Env[layerID]
if !ok {
return fmt.Errorf("Vagrant environment not configured with layer ID.")
}
return bucket.Put(key, []byte(layerId))
})
}
// RemoveEnv will remove the environment from the tracked layers.
func (l *Layered) RemoveEnv(v *Vagrant) error {
db, err := l.db()
if err != nil {
return err
}
defer db.Close()
return db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltEnvsBucket)
key := []byte(v.DataDir)
return bucket.Delete(key)
})
}
// Pending returns a list of layers that are pending creation.
// Note that between calling this and calling something like Build(),
// this state may be different.
func (l *Layered) Pending() ([]string, error) {
// Grab the DB and initialize all the layers. This just inserts a
// pending layer if it doesn't exist, as well as sets up the edges.
db, err := l.db()
if err != nil {
return nil, err
}
vs, err := l.init(db)
db.Close()
if err != nil {
return nil, err
}
result := make([]string, 0, len(vs))
for _, v := range vs {
if v.State != layerStateReady {
result = append(result, v.Layer.ID)
}
}
return result, nil
}
func (l *Layered) buildLayer(v *layerVertex, lastV *layerVertex, ctx *context.Shared) error {
log.Printf("[DEBUG] vagrant: building layer: %s", v.Layer.ID)
layer := v.Layer
path := v.Path
// Build the Vagrant instance. We use this a bit later
vagrant := &Vagrant{
Dir: path,
DataDir: filepath.Join(path, ".vagrant"),
Ui: ctx.Ui,
}
if lastV != nil {
vagrant.Env = map[string]string{
layerPathEnv: filepath.Join(lastV.Path, "Vagrantfile"),
}
}
// Layer isn't ready, so grab the lock on the layer and build it
// TODO: multi-process lock
// Once we have the lock, we check shortly in the DB if it is already
// ready. If it is ready, we yield the lock and we're done!
db, err := l.db()
if err != nil {
return err
}
layerV, err := l.readLayer(db, layer)
if err != nil {
db.Close()
return err
}
if layerV.State == layerStateReady {
log.Printf("[DEBUG] vagrant: layer already ready, will verify: %s", v.Layer.ID)
// Touch the layer so that it is recently used, even if its not
// actually ready.
err = l.updateLayer(db, layer, func(v *layerVertex) {
v.Touch()
})
if err != nil {
db.Close()
return err
}
// Verify the layer is actually ready! If it isn't, then
// we have to recreate it.
ctx.Ui.Header(fmt.Sprintf("Verifying created layer: %s", layer.ID))
ok, err := l.verifyLayer(vagrant)
if ok || err != nil {
// It is ready or there is an error.
db.Close()
return err
}
// Layer is invalid! Delete it from the DB and then recreate it
err = l.updateLayer(db, layer, func(v *layerVertex) {
v.State = layerStatePending
})
if err != nil {
db.Close()
return err
}
// Continue!
}
db.Close()
// Tell the user things are happening
ctx.Ui.Header(fmt.Sprintf("Creating layer: %s", layer.ID))
// Prepare the build directory
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
// Copy the Vagrantfile into the destination path
src, err := os.Open(layer.Vagrantfile)
if err != nil {
return err
}
dst, err := os.Create(filepath.Join(path, "Vagrantfile"))
if err == nil {
_, err = io.Copy(dst, src)
}
src.Close()
dst.Close()
if err != nil {
return err
}
// Destroy and recreate the machine
if err := vagrant.ExecuteSilent("destroy", "-f"); err != nil {
return err
}
if err := vagrant.Execute("up"); err != nil {
return err
}
if err := vagrant.Execute("halt"); err != nil {
return err
}
// Update the layer state that it is "ready"
db, err = l.db()
if err != nil {
return err
}
defer db.Close()
return l.updateLayer(db, layer, func(v *layerVertex) {
v.State = layerStateReady
v.Touch()
})
}
func (l *Layered) pruneLayer(db *bolt.DB, v *layerVertex, ctx *context.Shared) error {
log.Printf("[DEBUG] vagrant: pruning layer: %s", v.Layer.ID)
layer := v.Layer
path := v.Path
// First check if the layer even exists
exists, err := l.checkLayer(db, layer)
if err != nil {
return err
}
if !exists {
log.Printf("[DEBUG] vagrant: layer doesn't exist already: %s", v.Layer.ID)
return l.deleteLayer(db, layer, path)
}
ctx.Ui.Header(fmt.Sprintf(
"Deleting layer '%s'...", layer.ID))
// First, note that the layer is no longer ready
err = l.updateLayer(db, layer, func(v *layerVertex) {
v.State = layerStatePending
})
if err != nil {
return err
}
// Check the path. If the path doesn't exist, then it is already destroyed.
// If the path does exist, then we do an actual vagrant destroy
_, err = os.Stat(path)
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
vagrant := &Vagrant{
Dir: path,
DataDir: filepath.Join(path, ".vagrant"),
Ui: ctx.Ui,
}
if err := vagrant.Execute("destroy", "-f"); err != nil {
return err
}
}
// Delete the layer
return l.deleteLayer(db, layer, path)
}
func (l *Layered) layerPath(layer *Layer) string {
return filepath.Join(l.DataDir, "layers", layer.ID)
}
// db returns the database handle, and sets up the DB if it has never been created.
func (l *Layered) db() (*bolt.DB, error) {
// Make the directory to store our DB
if err := os.MkdirAll(l.DataDir, 0755); err != nil {
return nil, err
}
// Create/Open the DB
db, err := bolt.Open(filepath.Join(l.DataDir, "vagrant-layered.db"), 0644, nil)
if err != nil {
return nil, err
}
// Create the buckets
err = db.Update(func(tx *bolt.Tx) error {
for _, b := range boltBuckets {
if _, err := tx.CreateBucketIfNotExists(b); err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
// Check the data version
var version byte
err = db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltVagrantBucket)
data := bucket.Get([]byte("version"))
if data == nil || len(data) == 0 {
version = boltDataVersion
return bucket.Put([]byte("version"), []byte{boltDataVersion})
}
version = data[0]
return nil
})
if err != nil {
return nil, err
}
if version > boltDataVersion {
return nil, fmt.Errorf(
"Vagrant layer data version is higher than this version of Otto knows how\n"+
"to handle! This version of Otto can read up to version %d,\n"+
"but version %d data file found.\n\n"+
"This means that a newer version of Otto touched this data,\n"+
"or the data was corrupted in some other way.",
boltDataVersion, version)
}
return db, nil
}
// verifyLayer verifies that a layer is valid/ready.
func (l *Layered) verifyLayer(v *Vagrant) (bool, error) {
// The callback for checking the state
var ok bool
cb := func(o *Output) {
if o.Type == "state" && len(o.Data) > 0 {
ok = o.Data[0] != "not_created"
}
}
// Save the old callbacks
oldCb := v.Callbacks
defer func() { v.Callbacks = oldCb }()
// Register a callback for the state
v.Callbacks = map[string]OutputCallback{"state": cb}
// Check it
err := v.ExecuteSilent("status")
return ok, err
}
// init initializes the database for this layer setup.
func (l *Layered) init(db *bolt.DB) ([]*layerVertex, error) {
layerVertices := make([]*layerVertex, len(l.Layers))
for i, layer := range l.Layers {
var parent *Layer
if i > 0 {
parent = l.Layers[i-1]
}
layerVertex, err := l.initLayer(db, layer, parent)
if err != nil {
return nil, err
}
layerVertices[i] = layerVertex
if parent != nil {
// We have a prior layer, so setup the edge pointer
err = db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltEdgesBucket)
return bucket.Put(
[]byte(layer.ID),
[]byte(parent.ID))
})
if err != nil {
return nil, err
}
}
}
return layerVertices, nil
}
// initLayer sets up the layer in the database
func (l *Layered) initLayer(db *bolt.DB, layer *Layer, parent *Layer) (*layerVertex, error) {
var parentID string
if parent != nil {
parentID = parent.ID
}
var result layerVertex
err := db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltLayersBucket)
key := []byte(layer.ID)
data := bucket.Get(key)
if len(data) > 0 {
var v layerVertex
if err := l.structRead(&v, data); err != nil {
return err
}
if v.Parent == parentID {
result = v
return nil
}
// The parent didn't match, so we just initialize a new
// entry below. This will also force the destruction of the
// old environment.
}
// Vertex doesn't exist. Create it and save it
result = layerVertex{
Layer: layer,
State: layerStatePending,
Parent: parentID,
Path: l.layerPath(layer),
}
data, err := l.structData(&result)
if err != nil {
return err
}
// Write the pending layer
return bucket.Put(key, data)
})
return &result, err
}
func (l *Layered) checkLayer(db *bolt.DB, layer *Layer) (bool, error) {
var result bool
err := db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltLayersBucket)
key := []byte(layer.ID)
data := bucket.Get(key)
result = len(data) > 0
return nil
})
return result, err
}
func (l *Layered) readLayer(db *bolt.DB, layer *Layer) (*layerVertex, error) {
var result layerVertex
err := db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltLayersBucket)
key := []byte(layer.ID)
data := bucket.Get(key)
if len(data) > 0 {
return l.structRead(&result, data)
}
return fmt.Errorf("layer %s not found", layer.ID)
})
return &result, err
}
func (l *Layered) updateLayer(db *bolt.DB, layer *Layer, f func(*layerVertex)) error {
return db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltLayersBucket)
key := []byte(layer.ID)
data := bucket.Get(key)
if len(data) == 0 {
// This should never happen through this struct
panic(fmt.Errorf("layer %s not found", layer.ID))
}
// Read the vertex, call the function to modify it
var v layerVertex
if err := l.structRead(&v, data); err != nil {
return err
}
f(&v)
// Save the resulting layer data
data, err := l.structData(&v)
if err != nil {
return err
}
return bucket.Put(key, data)
})
}
func (l *Layered) deleteLayer(db *bolt.DB, layer *Layer, path string) error {
if err := os.RemoveAll(path); err != nil {
return err
}
return db.Update(func(tx *bolt.Tx) error {
// Delete the layer itself
bucket := tx.Bucket(boltLayersBucket)
key := []byte(layer.ID)
if err := bucket.Delete(key); err != nil {
return err
}
// Delete all the edges
bucket = tx.Bucket(boltEdgesBucket)
if err := bucket.Delete(key); err != nil {
return err
}
// Find any values
return bucket.ForEach(func(k, data []byte) error {
if string(data) == layer.ID {
return bucket.Delete(k)
}
return nil
})
})
}
func (l *Layered) graph(db *bolt.DB) (*dag.AcyclicGraph, error) {
graph := new(dag.AcyclicGraph)
graph.Add("root")
// First, add all the layers
layers := make(map[string]*layerVertex)
err := db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltLayersBucket)
return bucket.ForEach(func(k, data []byte) error {
var v layerVertex
if err := l.structRead(&v, data); err != nil {
return err
}
// Add this layer to the graph
graph.Add(&v)
// Store the mapping for later
layers[v.Layer.ID] = &v
return nil
})
})
if err != nil {
return nil, err
}
// Next, connect the layers
err = db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltEdgesBucket)
return bucket.ForEach(func(k, data []byte) error {
from := layers[string(k)]
to := layers[string(data)]
if from != nil && to != nil {
graph.Connect(dag.BasicEdge(from, to))
}
return nil
})
})
if err != nil {
return nil, err
}
// Finally, add and connect all the envs
err = db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(boltEnvsBucket)
return bucket.ForEach(func(k, data []byte) error {
key := fmt.Sprintf("env-%s", string(k))
graph.Add(key)
// Connect the env to the layer it depends on
to := &layerVertex{Layer: &Layer{ID: string(data)}}
graph.Connect(dag.BasicEdge(key, to))
// Connect the root to the environment that is active
graph.Connect(dag.BasicEdge("root", key))
return nil
})
})
if err != nil {
return nil, err
}
return graph, nil
}
func (l *Layered) structData(d interface{}) ([]byte, error) {
// Let's just output it in human-readable format to make it easy
// for debugging. Disk space won't matter that much for this data.
return json.MarshalIndent(d, "", "\t")
}
func (l *Layered) structRead(d interface{}, raw []byte) error {
dec := json.NewDecoder(bytes.NewReader(raw))
return dec.Decode(d)
}
var (
boltVagrantBucket = []byte("vagrant")
boltLayersBucket = []byte("layers")
boltEdgesBucket = []byte("edges")
boltEnvsBucket = []byte("envs")
boltBuckets = [][]byte{
boltVagrantBucket,
boltLayersBucket,
boltEdgesBucket,
boltEnvsBucket,
}
)
var (
boltDataVersion byte = 1
)
const (
// layerPathEnv is the path to the previous layer
layerPathEnv = "OTTO_VAGRANT_LAYER_PATH"
// layerID is the ID of the previous layer
layerID = "OTTO_VAGRANT_LAYER_ID"
)
// layerVertex is the type of vertex in the graph that is used to track
// layer usage throughout Otto.
type layerVertex struct {
Layer *Layer `json:"layer"`
State layerState `json:"state"`
Parent string `json:"parent"`
Path string `json:"path"`
LastUsed time.Time `json:"last_used"`
}
func (v *layerVertex) Hashcode() interface{} {
return fmt.Sprintf("layer-%s", v.Layer.ID)
}
func (v *layerVertex) Name() string {
return v.Layer.ID
}
// Touch is used to update the last used time
func (v *layerVertex) Touch() {
v.LastUsed = time.Now().UTC()
}
type layerState byte
const (
layerStateInvalid layerState = iota
layerStatePending
layerStateReady
)
type envState byte
const (
envStateInvalid envState = iota
envStateDeleted
envStateReady
)