diff --git a/integration/volume/volume_test.go b/integration/volume/volume_test.go index 38ce5782e1..20dcfef9a6 100644 --- a/integration/volume/volume_test.go +++ b/integration/volume/volume_test.go @@ -34,7 +34,6 @@ func TestVolumesCreateAndList(t *testing.T) { Driver: "local", Scope: "local", Name: name, - Options: map[string]string{}, Mountpoint: fmt.Sprintf("%s/volumes/%s/_data", testEnv.DaemonInfo.DockerRootDir, name), } assert.Equal(t, vol, expected) @@ -95,7 +94,6 @@ func TestVolumesInspect(t *testing.T) { Driver: "local", Scope: "local", Name: name, - Options: map[string]string{}, Mountpoint: fmt.Sprintf("%s/volumes/%s/_data", testEnv.DaemonInfo.DockerRootDir, name), } assert.Equal(t, vol, expected) diff --git a/volume/store/db.go b/volume/store/db.go index fe6dbae9af..5a280ca2dc 100644 --- a/volume/store/db.go +++ b/volume/store/db.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/boltdb/bolt" + "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -28,7 +29,10 @@ func setMeta(tx *bolt.Tx, name string, meta volumeMetadata) error { if err != nil { return err } - b := tx.Bucket(volumeBucketName) + b, err := tx.CreateBucketIfNotExists(volumeBucketName) + if err != nil { + return errors.Wrap(err, "error creating volume bucket") + } return errors.Wrap(b.Put([]byte(name), metaJSON), "error setting volume metadata") } @@ -42,8 +46,11 @@ func (s *VolumeStore) getMeta(name string) (volumeMetadata, error) { func getMeta(tx *bolt.Tx, name string, meta *volumeMetadata) error { b := tx.Bucket(volumeBucketName) + if b == nil { + return errdefs.NotFound(errors.New("volume bucket does not exist")) + } val := b.Get([]byte(name)) - if string(val) == "" { + if len(val) == 0 { return nil } if err := json.Unmarshal(val, meta); err != nil { diff --git a/volume/store/db_test.go b/volume/store/db_test.go new file mode 100644 index 0000000000..9c45cd13ba --- /dev/null +++ b/volume/store/db_test.go @@ -0,0 +1,51 @@ +package store + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/boltdb/bolt" + "github.com/stretchr/testify/require" +) + +func TestSetGetMeta(t *testing.T) { + t.Parallel() + + dir, err := ioutil.TempDir("", "test-set-get") + require.NoError(t, err) + defer os.RemoveAll(dir) + + db, err := bolt.Open(filepath.Join(dir, "db"), 0600, &bolt.Options{Timeout: 1 * time.Second}) + require.NoError(t, err) + + store := &VolumeStore{db: db} + + _, err = store.getMeta("test") + require.Error(t, err) + + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket(volumeBucketName) + return err + }) + require.NoError(t, err) + + meta, err := store.getMeta("test") + require.NoError(t, err) + require.Equal(t, volumeMetadata{}, meta) + + testMeta := volumeMetadata{ + Name: "test", + Driver: "fake", + Labels: map[string]string{"a": "1", "b": "2"}, + Options: map[string]string{"foo": "bar"}, + } + err = store.setMeta("test", testMeta) + require.NoError(t, err) + + meta, err = store.getMeta("test") + require.NoError(t, err) + require.Equal(t, testMeta, meta) +} diff --git a/volume/store/restore_test.go b/volume/store/restore_test.go new file mode 100644 index 0000000000..83efd36b63 --- /dev/null +++ b/volume/store/restore_test.go @@ -0,0 +1,55 @@ +package store + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/docker/docker/volume" + volumedrivers "github.com/docker/docker/volume/drivers" + volumetestutils "github.com/docker/docker/volume/testutils" + "github.com/stretchr/testify/require" +) + +func TestRestore(t *testing.T) { + t.Parallel() + + dir, err := ioutil.TempDir("", "test-restore") + require.NoError(t, err) + defer os.RemoveAll(dir) + + driverName := "test-restore" + volumedrivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) + defer volumedrivers.Unregister("test-restore") + + s, err := New(dir) + require.NoError(t, err) + defer s.Shutdown() + + _, err = s.Create("test1", driverName, nil, nil) + require.NoError(t, err) + + testLabels := map[string]string{"a": "1"} + testOpts := map[string]string{"foo": "bar"} + _, err = s.Create("test2", driverName, testOpts, testLabels) + require.NoError(t, err) + + s.Shutdown() + + s, err = New(dir) + require.NoError(t, err) + + v, err := s.Get("test1") + require.NoError(t, err) + + dv := v.(volume.DetailedVolume) + var nilMap map[string]string + require.Equal(t, nilMap, dv.Options()) + require.Equal(t, nilMap, dv.Labels()) + + v, err = s.Get("test2") + require.NoError(t, err) + dv = v.(volume.DetailedVolume) + require.Equal(t, testOpts, dv.Options()) + require.Equal(t, testLabels, dv.Labels()) +} diff --git a/volume/store/store.go b/volume/store/store.go index 643096a781..70dd8b2d89 100644 --- a/volume/store/store.go +++ b/volume/store/store.go @@ -29,7 +29,10 @@ type volumeWrapper struct { } func (v volumeWrapper) Options() map[string]string { - options := map[string]string{} + if v.options == nil { + return nil + } + options := make(map[string]string, len(v.options)) for key, value := range v.options { options[key] = value } @@ -37,7 +40,15 @@ func (v volumeWrapper) Options() map[string]string { } func (v volumeWrapper) Labels() map[string]string { - return v.labels + if v.labels == nil { + return nil + } + + labels := make(map[string]string, len(v.labels)) + for key, value := range v.labels { + labels[key] = value + } + return labels } func (v volumeWrapper) Scope() string { diff --git a/volume/store/store_test.go b/volume/store/store_test.go index 54b7d7cfce..e53c728dd0 100644 --- a/volume/store/store_test.go +++ b/volume/store/store_test.go @@ -12,6 +12,8 @@ import ( "github.com/docker/docker/volume" "github.com/docker/docker/volume/drivers" volumetestutils "github.com/docker/docker/volume/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCreate(t *testing.T) { @@ -291,6 +293,7 @@ func TestDefererencePluginOnCreateError(t *testing.T) { pg := volumetestutils.NewFakePluginGetter(p) volumedrivers.RegisterPluginGetter(pg) + defer volumedrivers.RegisterPluginGetter(nil) dir, err := ioutil.TempDir("", "test-plugin-deref-err") if err != nil { @@ -320,3 +323,103 @@ func TestDefererencePluginOnCreateError(t *testing.T) { t.Fatalf("expected 1 plugin reference, got: %d", refs) } } + +func TestRefDerefRemove(t *testing.T) { + t.Parallel() + + driverName := "test-ref-deref-remove" + s, cleanup := setupTest(t, driverName) + defer cleanup(t) + + v, err := s.CreateWithRef("test", driverName, "test-ref", nil, nil) + require.NoError(t, err) + + err = s.Remove(v) + require.Error(t, err) + require.Equal(t, errVolumeInUse, err.(*OpErr).Err) + + s.Dereference(v, "test-ref") + err = s.Remove(v) + require.NoError(t, err) +} + +func TestGet(t *testing.T) { + t.Parallel() + + driverName := "test-get" + s, cleanup := setupTest(t, driverName) + defer cleanup(t) + + _, err := s.Get("not-exist") + require.Error(t, err) + require.Equal(t, errNoSuchVolume, err.(*OpErr).Err) + + v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"}) + require.NoError(t, err) + + v2, err := s.Get("test") + require.NoError(t, err) + require.Equal(t, v1, v2) + + dv := v2.(volume.DetailedVolume) + require.Equal(t, "1", dv.Labels()["a"]) + + err = s.Remove(v1) + require.NoError(t, err) +} + +func TestGetWithRef(t *testing.T) { + t.Parallel() + + driverName := "test-get-with-ref" + s, cleanup := setupTest(t, driverName) + defer cleanup(t) + + _, err := s.GetWithRef("not-exist", driverName, "test-ref") + require.Error(t, err) + + v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"}) + require.NoError(t, err) + + v2, err := s.GetWithRef("test", driverName, "test-ref") + require.NoError(t, err) + require.Equal(t, v1, v2) + + err = s.Remove(v2) + require.Error(t, err) + require.Equal(t, errVolumeInUse, err.(*OpErr).Err) + + s.Dereference(v2, "test-ref") + err = s.Remove(v2) + require.NoError(t, err) +} + +func setupTest(t *testing.T, name string) (*VolumeStore, func(*testing.T)) { + t.Helper() + s, cleanup := newTestStore(t) + + volumedrivers.Register(volumetestutils.NewFakeDriver(name), name) + return s, func(t *testing.T) { + cleanup(t) + volumedrivers.Unregister(name) + } +} + +func newTestStore(t *testing.T) (*VolumeStore, func(*testing.T)) { + t.Helper() + + dir, err := ioutil.TempDir("", "store-root") + require.NoError(t, err) + + cleanup := func(t *testing.T) { + err := os.RemoveAll(dir) + assert.NoError(t, err) + } + + s, err := New(dir) + assert.NoError(t, err) + return s, func(t *testing.T) { + s.Shutdown() + cleanup(t) + } +}