Browse code

Fix volume plugin refecounting on daemon restart

Ensures all known volumes (known b/c they are persisted to disk) have
their volume drivers refcounted properly.

In testing this, I found an issue with `--live-restore` (required since
currently the provided volume plugin doesn't keep state on restart)
where restorted plugins did not have a plugin client loaded causing a
panic when trying to use the plugin.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit 6ef1060cd0acb847e06db890abb335faa837a9e2)
Signed-off-by: Victor Vieux <vieux@docker.com>

Brian Goff authored on 2016/12/02 07:17:07
Showing 7 changed files
... ...
@@ -794,6 +794,12 @@ func (daemon *Daemon) Shutdown() error {
794 794
 		})
795 795
 	}
796 796
 
797
+	if daemon.volumes != nil {
798
+		if err := daemon.volumes.Shutdown(); err != nil {
799
+			logrus.Errorf("Error shutting down volume store: %v", err)
800
+		}
801
+	}
802
+
797 803
 	if daemon.layerStore != nil {
798 804
 		if err := daemon.layerStore.Cleanup(); err != nil {
799 805
 			logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
... ...
@@ -275,6 +275,27 @@ func (s *DockerDaemonSuite) TestGraphdriverPlugin(c *check.C) {
275 275
 	c.Assert(err, checker.IsNil, check.Commentf(out))
276 276
 }
277 277
 
278
+func (s *DockerDaemonSuite) TestPluginVolumeRemoveOnRestart(c *check.C) {
279
+	testRequires(c, DaemonIsLinux, Network, IsAmd64)
280
+
281
+	s.d.Start("--live-restore=true")
282
+
283
+	out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName)
284
+	c.Assert(err, checker.IsNil, check.Commentf(out))
285
+	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
286
+
287
+	out, err = s.d.Cmd("volume", "create", "--driver", pName, "test")
288
+	c.Assert(err, checker.IsNil, check.Commentf(out))
289
+
290
+	s.d.Restart("--live-restore=true")
291
+
292
+	out, err = s.d.Cmd("plugin", "disable", pName)
293
+	c.Assert(err, checker.IsNil, check.Commentf(out))
294
+	out, err = s.d.Cmd("plugin", "rm", pName)
295
+	c.Assert(err, checker.NotNil, check.Commentf(out))
296
+	c.Assert(out, checker.Contains, "in use")
297
+}
298
+
278 299
 func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
279 300
 	mounts, err := mount.GetMounts()
280 301
 	if err != nil {
... ...
@@ -1343,7 +1343,7 @@ func (s *DockerDaemonSuite) TestDaemonWithWrongkey(c *check.C) {
1343 1343
 	content, _ := ioutil.ReadFile(s.d.logFile.Name())
1344 1344
 
1345 1345
 	if !strings.Contains(string(content), "Public Key ID does not match") {
1346
-		c.Fatal("Missing KeyID message from daemon logs")
1346
+		c.Fatalf("Missing KeyID message from daemon logs: %s", string(content))
1347 1347
 	}
1348 1348
 }
1349 1349
 
1350 1350
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package store
1
+
2
+import (
3
+	"encoding/json"
4
+
5
+	"github.com/boltdb/bolt"
6
+	"github.com/pkg/errors"
7
+)
8
+
9
+var volumeBucketName = []byte("volumes")
10
+
11
+type dbEntry struct {
12
+	Key   []byte
13
+	Value []byte
14
+}
15
+
16
+type volumeMetadata struct {
17
+	Name    string
18
+	Driver  string
19
+	Labels  map[string]string
20
+	Options map[string]string
21
+}
22
+
23
+func (s *VolumeStore) setMeta(name string, meta volumeMetadata) error {
24
+	return s.db.Update(func(tx *bolt.Tx) error {
25
+		return setMeta(tx, name, meta)
26
+	})
27
+}
28
+
29
+func setMeta(tx *bolt.Tx, name string, meta volumeMetadata) error {
30
+	metaJSON, err := json.Marshal(meta)
31
+	if err != nil {
32
+		return err
33
+	}
34
+	b := tx.Bucket(volumeBucketName)
35
+	return errors.Wrap(b.Put([]byte(name), metaJSON), "error setting volume metadata")
36
+}
37
+
38
+func (s *VolumeStore) getMeta(name string) (volumeMetadata, error) {
39
+	var meta volumeMetadata
40
+	err := s.db.View(func(tx *bolt.Tx) error {
41
+		return getMeta(tx, name, &meta)
42
+	})
43
+	return meta, err
44
+}
45
+
46
+func getMeta(tx *bolt.Tx, name string, meta *volumeMetadata) error {
47
+	b := tx.Bucket(volumeBucketName)
48
+	val := b.Get([]byte(name))
49
+	if string(val) == "" {
50
+		return nil
51
+	}
52
+	if err := json.Unmarshal(val, meta); err != nil {
53
+		return errors.Wrap(err, "error unmarshaling volume metadata")
54
+	}
55
+	return nil
56
+}
57
+
58
+func (s *VolumeStore) removeMeta(name string) error {
59
+	return s.db.Update(func(tx *bolt.Tx) error {
60
+		return removeMeta(tx, name)
61
+	})
62
+}
63
+
64
+func removeMeta(tx *bolt.Tx, name string) error {
65
+	b := tx.Bucket(volumeBucketName)
66
+	return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata")
67
+}
68
+
69
+func listEntries(tx *bolt.Tx) []*dbEntry {
70
+	var entries []*dbEntry
71
+	b := tx.Bucket(volumeBucketName)
72
+	b.ForEach(func(k, v []byte) error {
73
+		entries = append(entries, &dbEntry{k, v})
74
+		return nil
75
+	})
76
+	return entries
77
+}
0 78
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+package store
1
+
2
+import (
3
+	"encoding/json"
4
+	"sync"
5
+
6
+	"github.com/Sirupsen/logrus"
7
+	"github.com/boltdb/bolt"
8
+	"github.com/docker/docker/volume"
9
+	"github.com/docker/docker/volume/drivers"
10
+)
11
+
12
+// restore is called when a new volume store is created.
13
+// It's primary purpose is to ensure that all drivers' refcounts are set based
14
+// on known volumes after a restart.
15
+// This only attempts to track volumes that are actually stored in the on-disk db.
16
+// It does not probe the available drivers to find anything that may have been added
17
+// out of band.
18
+func (s *VolumeStore) restore() {
19
+	var entries []*dbEntry
20
+	s.db.View(func(tx *bolt.Tx) error {
21
+		entries = listEntries(tx)
22
+		return nil
23
+	})
24
+
25
+	chRemove := make(chan []byte, len(entries))
26
+	var wg sync.WaitGroup
27
+	for _, entry := range entries {
28
+		wg.Add(1)
29
+		// this is potentially a very slow operation, so do it in a goroutine
30
+		go func(entry *dbEntry) {
31
+			defer wg.Done()
32
+			var meta volumeMetadata
33
+			if len(entry.Value) != 0 {
34
+				if err := json.Unmarshal(entry.Value, &meta); err != nil {
35
+					logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(entry.Key), err)
36
+					// don't return here, we can try with `getVolume` below
37
+				}
38
+			}
39
+
40
+			var v volume.Volume
41
+			var err error
42
+			if meta.Driver != "" {
43
+				v, err = lookupVolume(meta.Driver, string(entry.Key))
44
+				if err != nil && err != errNoSuchVolume {
45
+					logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", string(entry.Key)).Warn("Error restoring volume")
46
+					return
47
+				}
48
+				if v == nil {
49
+					// doesn't exist in the driver, remove it from the db
50
+					chRemove <- entry.Key
51
+					return
52
+				}
53
+			} else {
54
+				v, err = s.getVolume(string(entry.Key))
55
+				if err != nil {
56
+					if err == errNoSuchVolume {
57
+						chRemove <- entry.Key
58
+					}
59
+					return
60
+				}
61
+
62
+				meta.Driver = v.DriverName()
63
+				if err := s.setMeta(v.Name(), meta); err != nil {
64
+					logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", v.Name()).Warn("Error updating volume metadata on restore")
65
+				}
66
+			}
67
+
68
+			// increment driver refcount
69
+			volumedrivers.CreateDriver(meta.Driver)
70
+
71
+			// cache the volume
72
+			s.globalLock.Lock()
73
+			s.options[v.Name()] = meta.Options
74
+			s.labels[v.Name()] = meta.Labels
75
+			s.names[v.Name()] = v
76
+			s.globalLock.Unlock()
77
+		}(entry)
78
+	}
79
+
80
+	wg.Wait()
81
+	close(chRemove)
82
+	s.db.Update(func(tx *bolt.Tx) error {
83
+		for k := range chRemove {
84
+			if err := removeMeta(tx, string(k)); err != nil {
85
+				logrus.Warnf("Error removing stale entry from volume db: %v", err)
86
+			}
87
+		}
88
+		return nil
89
+	})
90
+}
... ...
@@ -1,8 +1,6 @@
1 1
 package store
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"encoding/json"
6 4
 	"net"
7 5
 	"os"
8 6
 	"path/filepath"
... ...
@@ -19,16 +17,9 @@ import (
19 19
 )
20 20
 
21 21
 const (
22
-	volumeDataDir    = "volumes"
23
-	volumeBucketName = "volumes"
22
+	volumeDataDir = "volumes"
24 23
 )
25 24
 
26
-type volumeMetadata struct {
27
-	Name    string
28
-	Labels  map[string]string
29
-	Options map[string]string
30
-}
31
-
32 25
 type volumeWrapper struct {
33 26
 	volume.Volume
34 27
 	labels  map[string]string
... ...
@@ -89,16 +80,17 @@ func New(rootPath string) (*VolumeStore, error) {
89 89
 
90 90
 		// initialize volumes bucket
91 91
 		if err := vs.db.Update(func(tx *bolt.Tx) error {
92
-			if _, err := tx.CreateBucketIfNotExists([]byte(volumeBucketName)); err != nil {
92
+			if _, err := tx.CreateBucketIfNotExists(volumeBucketName); err != nil {
93 93
 				return errors.Wrap(err, "error while setting up volume store metadata database")
94 94
 			}
95
-
96 95
 			return nil
97 96
 		}); err != nil {
98 97
 			return nil, err
99 98
 		}
100 99
 	}
101 100
 
101
+	vs.restore()
102
+
102 103
 	return vs, nil
103 104
 }
104 105
 
... ...
@@ -131,6 +123,15 @@ func (s *VolumeStore) getRefs(name string) []string {
131 131
 // the internal data is out of sync with volumes driver plugins.
132 132
 func (s *VolumeStore) Purge(name string) {
133 133
 	s.globalLock.Lock()
134
+	v, exists := s.names[name]
135
+	if exists {
136
+		if _, err := volumedrivers.RemoveDriver(v.DriverName()); err != nil {
137
+			logrus.Error("Error dereferencing volume driver: %v", err)
138
+		}
139
+	}
140
+	if err := s.removeMeta(name); err != nil {
141
+		logrus.Errorf("Error removing volume metadata for volume %q: %v", name, err)
142
+	}
134 143
 	delete(s.names, name)
135 144
 	delete(s.refs, name)
136 145
 	delete(s.labels, name)
... ...
@@ -322,24 +323,11 @@ func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, err
322 322
 // volumeExists returns if the volume is still present in the driver.
323 323
 // An error is returned if there was an issue communicating with the driver.
324 324
 func volumeExists(v volume.Volume) (bool, error) {
325
-	vd, err := volumedrivers.GetDriver(v.DriverName())
325
+	exists, err := lookupVolume(v.DriverName(), v.Name())
326 326
 	if err != nil {
327
-		return false, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", v.Name(), v.DriverName())
328
-	}
329
-	exists, err := vd.Get(v.Name())
330
-	if err != nil {
331
-		err = errors.Cause(err)
332
-		if _, ok := err.(net.Error); ok {
333
-			return false, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", v.Name(), v.DriverName())
334
-		}
335
-
336
-		// At this point, the error could be anything from the driver, such as "no such volume"
337
-		// Let's not check an error here, and instead check if the driver returned a volume
338
-	}
339
-	if exists == nil {
340
-		return false, nil
327
+		return false, err
341 328
 	}
342
-	return true, nil
329
+	return exists != nil, nil
343 330
 }
344 331
 
345 332
 // create asks the given driver to create a volume with the name/opts.
... ...
@@ -395,27 +383,16 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
395 395
 	s.options[name] = opts
396 396
 	s.globalLock.Unlock()
397 397
 
398
-	if s.db != nil {
399
-		metadata := &volumeMetadata{
400
-			Name:    name,
401
-			Labels:  labels,
402
-			Options: opts,
403
-		}
404
-
405
-		volData, err := json.Marshal(metadata)
406
-		if err != nil {
407
-			return nil, err
408
-		}
409
-
410
-		if err := s.db.Update(func(tx *bolt.Tx) error {
411
-			b := tx.Bucket([]byte(volumeBucketName))
412
-			err := b.Put([]byte(name), volData)
413
-			return err
414
-		}); err != nil {
415
-			return nil, errors.Wrap(err, "error while persisting volume metadata")
416
-		}
398
+	metadata := volumeMetadata{
399
+		Name:    name,
400
+		Driver:  vd.Name(),
401
+		Labels:  labels,
402
+		Options: opts,
417 403
 	}
418 404
 
405
+	if err := s.setMeta(name, metadata); err != nil {
406
+		return nil, err
407
+	}
419 408
 	return volumeWrapper{v, labels, vd.Scope(), opts}, nil
420 409
 }
421 410
 
... ...
@@ -462,48 +439,41 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
462 462
 // if the driver is unknown it probes all drivers until it finds the first volume with that name.
463 463
 // it is expected that callers of this function hold any necessary locks
464 464
 func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
465
-	labels := map[string]string{}
466
-	options := map[string]string{}
467
-
468
-	if s.db != nil {
469
-		// get meta
470
-		if err := s.db.Update(func(tx *bolt.Tx) error {
471
-			b := tx.Bucket([]byte(volumeBucketName))
472
-			data := b.Get([]byte(name))
473
-
474
-			if string(data) == "" {
475
-				return nil
476
-			}
477
-
478
-			var meta volumeMetadata
479
-			buf := bytes.NewBuffer(data)
465
+	var meta volumeMetadata
466
+	meta, err := s.getMeta(name)
467
+	if err != nil {
468
+		return nil, err
469
+	}
480 470
 
481
-			if err := json.NewDecoder(buf).Decode(&meta); err != nil {
482
-				return err
471
+	driverName := meta.Driver
472
+	if driverName == "" {
473
+		s.globalLock.RLock()
474
+		v, exists := s.names[name]
475
+		s.globalLock.RUnlock()
476
+		if exists {
477
+			meta.Driver = v.DriverName()
478
+			if err := s.setMeta(name, meta); err != nil {
479
+				return nil, err
483 480
 			}
484
-			labels = meta.Labels
485
-			options = meta.Options
486
-
487
-			return nil
488
-		}); err != nil {
489
-			return nil, err
490 481
 		}
491 482
 	}
492 483
 
493
-	logrus.Debugf("Getting volume reference for name: %s", name)
494
-	s.globalLock.RLock()
495
-	v, exists := s.names[name]
496
-	s.globalLock.RUnlock()
497
-	if exists {
498
-		vd, err := volumedrivers.GetDriver(v.DriverName())
484
+	if meta.Driver != "" {
485
+		vol, err := lookupVolume(meta.Driver, name)
499 486
 		if err != nil {
500 487
 			return nil, err
501 488
 		}
502
-		vol, err := vd.Get(name)
503
-		if err != nil {
504
-			return nil, err
489
+		if vol == nil {
490
+			s.Purge(name)
491
+			return nil, errNoSuchVolume
505 492
 		}
506
-		return volumeWrapper{vol, labels, vd.Scope(), options}, nil
493
+
494
+		var scope string
495
+		vd, err := volumedrivers.GetDriver(meta.Driver)
496
+		if err == nil {
497
+			scope = vd.Scope()
498
+		}
499
+		return volumeWrapper{vol, meta.Labels, scope, meta.Options}, nil
507 500
 	}
508 501
 
509 502
 	logrus.Debugf("Probing all drivers for volume with name: %s", name)
... ...
@@ -514,15 +484,42 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
514 514
 
515 515
 	for _, d := range drivers {
516 516
 		v, err := d.Get(name)
517
-		if err != nil {
517
+		if err != nil || v == nil {
518 518
 			continue
519 519
 		}
520
-
521
-		return volumeWrapper{v, labels, d.Scope(), options}, nil
520
+		meta.Driver = v.DriverName()
521
+		if err := s.setMeta(name, meta); err != nil {
522
+			return nil, err
523
+		}
524
+		return volumeWrapper{v, meta.Labels, d.Scope(), meta.Options}, nil
522 525
 	}
523 526
 	return nil, errNoSuchVolume
524 527
 }
525 528
 
529
+// lookupVolume gets the specified volume from the specified driver.
530
+// This will only return errors related to communications with the driver.
531
+// If the driver returns an error that is not communication related the
532
+//   error is logged but not returned.
533
+// If the volume is not found it will return `nil, nil``
534
+func lookupVolume(driverName, volumeName string) (volume.Volume, error) {
535
+	vd, err := volumedrivers.GetDriver(driverName)
536
+	if err != nil {
537
+		return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName)
538
+	}
539
+	v, err := vd.Get(volumeName)
540
+	if err != nil {
541
+		err = errors.Cause(err)
542
+		if _, ok := err.(net.Error); ok {
543
+			return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", v.Name(), v.DriverName())
544
+		}
545
+
546
+		// At this point, the error could be anything from the driver, such as "no such volume"
547
+		// Let's not check an error here, and instead check if the driver returned a volume
548
+		logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Warnf("Error while looking up volume")
549
+	}
550
+	return v, nil
551
+}
552
+
526 553
 // Remove removes the requested volume. A volume is not removed if it has any refs
527 554
 func (s *VolumeStore) Remove(v volume.Volume) error {
528 555
 	name := normaliseVolumeName(v.Name())
... ...
@@ -534,7 +531,7 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
534 534
 		return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: refs}
535 535
 	}
536 536
 
537
-	vd, err := volumedrivers.RemoveDriver(v.DriverName())
537
+	vd, err := volumedrivers.GetDriver(v.DriverName())
538 538
 	if err != nil {
539 539
 		return &OpErr{Err: err, Name: vd.Name(), Op: "remove"}
540 540
 	}
... ...
@@ -635,3 +632,9 @@ func unwrapVolume(v volume.Volume) volume.Volume {
635 635
 
636 636
 	return v
637 637
 }
638
+
639
+// Shutdown releases all resources used by the volume store
640
+// It does not make any changes to volumes, drivers, etc.
641
+func (s *VolumeStore) Shutdown() error {
642
+	return s.db.Close()
643
+}
... ...
@@ -2,6 +2,8 @@ package store
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"io/ioutil"
6
+	"os"
5 7
 	"strings"
6 8
 	"testing"
7 9
 
... ...
@@ -16,7 +18,13 @@ func TestCreate(t *testing.T) {
16 16
 
17 17
 	volumedrivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
18 18
 	defer volumedrivers.Unregister("fake")
19
-	s, err := New("")
19
+	dir, err := ioutil.TempDir("", "test-create")
20
+	if err != nil {
21
+		t.Fatal(err)
22
+	}
23
+	defer os.RemoveAll(dir)
24
+
25
+	s, err := New(dir)
20 26
 	if err != nil {
21 27
 		t.Fatal(err)
22 28
 	}
... ...
@@ -47,7 +55,12 @@ func TestRemove(t *testing.T) {
47 47
 	volumedrivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
48 48
 	defer volumedrivers.Unregister("fake")
49 49
 	defer volumedrivers.Unregister("noop")
50
-	s, err := New("")
50
+	dir, err := ioutil.TempDir("", "test-remove")
51
+	if err != nil {
52
+		t.Fatal(err)
53
+	}
54
+	defer os.RemoveAll(dir)
55
+	s, err := New(dir)
51 56
 	if err != nil {
52 57
 		t.Fatal(err)
53 58
 	}
... ...
@@ -80,8 +93,13 @@ func TestList(t *testing.T) {
80 80
 	volumedrivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
81 81
 	defer volumedrivers.Unregister("fake")
82 82
 	defer volumedrivers.Unregister("fake2")
83
+	dir, err := ioutil.TempDir("", "test-list")
84
+	if err != nil {
85
+		t.Fatal(err)
86
+	}
87
+	defer os.RemoveAll(dir)
83 88
 
84
-	s, err := New("")
89
+	s, err := New(dir)
85 90
 	if err != nil {
86 91
 		t.Fatal(err)
87 92
 	}
... ...
@@ -99,9 +117,12 @@ func TestList(t *testing.T) {
99 99
 	if len(ls) != 2 {
100 100
 		t.Fatalf("expected 2 volumes, got: %d", len(ls))
101 101
 	}
102
+	if err := s.Shutdown(); err != nil {
103
+		t.Fatal(err)
104
+	}
102 105
 
103 106
 	// and again with a new store
104
-	s, err = New("")
107
+	s, err = New(dir)
105 108
 	if err != nil {
106 109
 		t.Fatal(err)
107 110
 	}
... ...
@@ -119,7 +140,11 @@ func TestFilterByDriver(t *testing.T) {
119 119
 	volumedrivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
120 120
 	defer volumedrivers.Unregister("fake")
121 121
 	defer volumedrivers.Unregister("noop")
122
-	s, err := New("")
122
+	dir, err := ioutil.TempDir("", "test-filter-driver")
123
+	if err != nil {
124
+		t.Fatal(err)
125
+	}
126
+	s, err := New(dir)
123 127
 	if err != nil {
124 128
 		t.Fatal(err)
125 129
 	}
... ...
@@ -146,8 +171,12 @@ func TestFilterByDriver(t *testing.T) {
146 146
 func TestFilterByUsed(t *testing.T) {
147 147
 	volumedrivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
148 148
 	volumedrivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
149
+	dir, err := ioutil.TempDir("", "test-filter-used")
150
+	if err != nil {
151
+		t.Fatal(err)
152
+	}
149 153
 
150
-	s, err := New("")
154
+	s, err := New(dir)
151 155
 	if err != nil {
152 156
 		t.Fatal(err)
153 157
 	}
... ...
@@ -183,8 +212,12 @@ func TestFilterByUsed(t *testing.T) {
183 183
 
184 184
 func TestDerefMultipleOfSameRef(t *testing.T) {
185 185
 	volumedrivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
186
+	dir, err := ioutil.TempDir("", "test-same-deref")
187
+	if err != nil {
188
+		t.Fatal(err)
189
+	}
186 190
 
187
-	s, err := New("")
191
+	s, err := New(dir)
188 192
 	if err != nil {
189 193
 		t.Fatal(err)
190 194
 	}