Browse code

Merge pull request #20526 from tiborvass/1.10.2-cherrypicks

1.10.2 cherrypicks

Tibor Vass authored on 2016/02/20 12:45:26
Showing 18 changed files
... ...
@@ -5,6 +5,35 @@ information on the list of deprecated flags and APIs please have a look at
5 5
 https://docs.docker.com/misc/deprecated/ where target removal dates can also
6 6
 be found.
7 7
 
8
+## 1.10.2 (2016-02-22)
9
+
10
+### Runtime
11
+
12
+- Prevent systemd from deleting containers' cgroups when its configuration is reloaded [#20518](https://github.com/docker/docker/pull/20518)
13
+- Fix SELinux issues by disregarding `--read-only` when mounting `/dev/mqueue` [#20333](https://github.com/docker/docker/pull/20333)
14
+- Fix chown permissions used during `docker cp` when userns is used [#20446](https://github.com/docker/docker/pull/20446)
15
+- Fix configuration loading issue with all booleans defaulting to `true` [#20471](https://github.com/docker/docker/pull/20471)
16
+- Fix occasional panic with `docker logs -f` [#20522](https://github.com/docker/docker/pull/20522)
17
+
18
+### Distribution
19
+
20
+- Keep layer reference if deletion failed to avoid a badly inconsistent state [#20513](https://github.com/docker/docker/pull/20513)
21
+- Handle gracefully a corner case when canceling migration [#20372](https://github.com/docker/docker/pull/20372)
22
+- Fix docker import on compressed data [#20367](https://github.com/docker/docker/pull/20367)
23
+- Fix tar-split files corruption during migration that later cause docker push and docker save to fail [#20458](https://github.com/docker/docker/pull/20458)
24
+
25
+### Networking
26
+
27
+- Fix daemon crash if embedded DNS is sent garbage [#20510](https://github.com/docker/docker/pull/20510)
28
+
29
+### Volumes
30
+
31
+- Fix issue with multiple volume references with same name [#20381](https://github.com/docker/docker/pull/20381)
32
+
33
+### Security
34
+
35
+- Fix potential cache corruption and delegation conflict issues [#20523](https://github.com/docker/docker/pull/20523)
36
+
8 37
 ## 1.10.1 (2016-02-11)
9 38
 
10 39
 ### Runtime
... ...
@@ -248,13 +248,13 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
248 248
 		return ErrRootFSReadOnly
249 249
 	}
250 250
 
251
+	uid, gid := daemon.GetRemappedUIDGID()
251 252
 	options := &archive.TarOptions{
253
+		NoOverwriteDirNonDir: noOverwriteDirNonDir,
252 254
 		ChownOpts: &archive.TarChownOptions{
253
-			UID: 0, GID: 0, // TODO: use config.User? Remap to userns root?
255
+			UID: uid, GID: gid, // TODO: should all ownership be set to root (either real or remapped)?
254 256
 		},
255
-		NoOverwriteDirNonDir: noOverwriteDirNonDir,
256 257
 	}
257
-
258 258
 	if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
259 259
 		return err
260 260
 	}
... ...
@@ -151,14 +151,20 @@ func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (strin
151 151
 }
152 152
 
153 153
 // ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
154
-func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) {
154
+func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) error {
155 155
 	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
156 156
 	newConfig, err := getConflictFreeConfiguration(configFile, flags)
157 157
 	if err != nil {
158
-		logrus.Error(err)
159
-	} else {
160
-		reload(newConfig)
158
+		return err
161 159
 	}
160
+	reload(newConfig)
161
+	return nil
162
+}
163
+
164
+// boolValue is an interface that boolean value flags implement
165
+// to tell the command line how to make -name equivalent to -name=true.
166
+type boolValue interface {
167
+	IsBoolFlag() bool
162 168
 }
163 169
 
164 170
 // MergeDaemonConfigurations reads a configuration file,
... ...
@@ -203,6 +209,36 @@ func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Conf
203 203
 			return nil, err
204 204
 		}
205 205
 
206
+		// Override flag values to make sure the values set in the config file with nullable values, like `false`,
207
+		// are not overriden by default truthy values from the flags that were not explicitly set.
208
+		// See https://github.com/docker/docker/issues/20289 for an example.
209
+		//
210
+		// TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
211
+		namedOptions := make(map[string]interface{})
212
+		for key, value := range configSet {
213
+			f := flags.Lookup("-" + key)
214
+			if f == nil { // ignore named flags that don't match
215
+				namedOptions[key] = value
216
+				continue
217
+			}
218
+
219
+			if _, ok := f.Value.(boolValue); ok {
220
+				f.Value.Set(fmt.Sprintf("%v", value))
221
+			}
222
+		}
223
+		if len(namedOptions) > 0 {
224
+			// set also default for mergeVal flags that are boolValue at the same time.
225
+			flags.VisitAll(func(f *flag.Flag) {
226
+				if opt, named := f.Value.(opts.NamedOption); named {
227
+					v, set := namedOptions[opt.Name()]
228
+					_, boolean := f.Value.(boolValue)
229
+					if set && boolean {
230
+						f.Value.Set(fmt.Sprintf("%v", v))
231
+					}
232
+				}
233
+			})
234
+		}
235
+
206 236
 		config.valuesSet = configSet
207 237
 	}
208 238
 
... ...
@@ -242,14 +278,16 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS
242 242
 
243 243
 	// 2. Discard values that implement NamedOption.
244 244
 	// Their configuration name differs from their flag name, like `labels` and `label`.
245
-	unknownNamedConflicts := func(f *flag.Flag) {
246
-		if namedOption, ok := f.Value.(opts.NamedOption); ok {
247
-			if _, valid := unknownKeys[namedOption.Name()]; valid {
248
-				delete(unknownKeys, namedOption.Name())
245
+	if len(unknownKeys) > 0 {
246
+		unknownNamedConflicts := func(f *flag.Flag) {
247
+			if namedOption, ok := f.Value.(opts.NamedOption); ok {
248
+				if _, valid := unknownKeys[namedOption.Name()]; valid {
249
+					delete(unknownKeys, namedOption.Name())
250
+				}
249 251
 			}
250 252
 		}
253
+		flags.VisitAll(unknownNamedConflicts)
251 254
 	}
252
-	flags.VisitAll(unknownNamedConflicts)
253 255
 
254 256
 	if len(unknownKeys) > 0 {
255 257
 		var unknown []string
... ...
@@ -103,7 +103,7 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
103 103
 	if container.Readonlyfs {
104 104
 		for i := range container.Mounts {
105 105
 			switch container.Mounts[i].Destination {
106
-			case "/proc", "/dev", "/dev/pts":
106
+			case "/proc", "/dev", "/dev/pts", "/dev/mqueue":
107 107
 				continue
108 108
 			}
109 109
 			container.Mounts[i].Flags |= syscall.MS_RDONLY
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/dockerversion"
12 12
 	"github.com/docker/docker/image"
13 13
 	"github.com/docker/docker/layer"
14
+	"github.com/docker/docker/pkg/archive"
14 15
 	"github.com/docker/docker/pkg/httputils"
15 16
 	"github.com/docker/docker/pkg/progress"
16 17
 	"github.com/docker/docker/pkg/streamformatter"
... ...
@@ -24,13 +25,13 @@ import (
24 24
 // the repo and tag arguments, respectively.
25 25
 func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string, inConfig io.ReadCloser, outStream io.Writer, config *container.Config) error {
26 26
 	var (
27
-		sf      = streamformatter.NewJSONStreamFormatter()
28
-		archive io.ReadCloser
29
-		resp    *http.Response
27
+		sf   = streamformatter.NewJSONStreamFormatter()
28
+		rc   io.ReadCloser
29
+		resp *http.Response
30 30
 	)
31 31
 
32 32
 	if src == "-" {
33
-		archive = inConfig
33
+		rc = inConfig
34 34
 	} else {
35 35
 		inConfig.Close()
36 36
 		u, err := url.Parse(src)
... ...
@@ -48,15 +49,20 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string
48 48
 			return err
49 49
 		}
50 50
 		progressOutput := sf.NewProgressOutput(outStream, true)
51
-		archive = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing")
51
+		rc = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing")
52 52
 	}
53 53
 
54
-	defer archive.Close()
54
+	defer rc.Close()
55 55
 	if len(msg) == 0 {
56 56
 		msg = "Imported from " + src
57 57
 	}
58
+
59
+	inflatedLayerData, err := archive.DecompressStream(rc)
60
+	if err != nil {
61
+		return err
62
+	}
58 63
 	// TODO: support windows baselayer?
59
-	l, err := daemon.layerStore.Register(archive, "")
64
+	l, err := daemon.layerStore.Register(inflatedLayerData, "")
60 65
 	if err != nil {
61 66
 		return err
62 67
 	}
... ...
@@ -291,3 +291,80 @@ func TestLoadDaemonConfigWithMapOptions(t *testing.T) {
291 291
 		t.Fatalf("expected log tag `test`, got %s", tag)
292 292
 	}
293 293
 }
294
+
295
+func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) {
296
+	c := &daemon.Config{}
297
+	common := &cli.CommonFlags{}
298
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
299
+	flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "")
300
+
301
+	f, err := ioutil.TempFile("", "docker-config-")
302
+	if err != nil {
303
+		t.Fatal(err)
304
+	}
305
+
306
+	if err := flags.ParseFlags([]string{}, false); err != nil {
307
+		t.Fatal(err)
308
+	}
309
+
310
+	configFile := f.Name()
311
+	f.Write([]byte(`{
312
+		"userland-proxy": false
313
+}`))
314
+	f.Close()
315
+
316
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
317
+	if err != nil {
318
+		t.Fatal(err)
319
+	}
320
+	if loadedConfig == nil {
321
+		t.Fatal("expected configuration, got nil")
322
+	}
323
+
324
+	if loadedConfig.EnableUserlandProxy {
325
+		t.Fatal("expected userland proxy to be disabled, got enabled")
326
+	}
327
+
328
+	// make sure reloading doesn't generate configuration
329
+	// conflicts after normalizing boolean values.
330
+	err = daemon.ReloadConfiguration(configFile, flags, func(reloadedConfig *daemon.Config) {
331
+		if reloadedConfig.EnableUserlandProxy {
332
+			t.Fatal("expected userland proxy to be disabled, got enabled")
333
+		}
334
+	})
335
+	if err != nil {
336
+		t.Fatal(err)
337
+	}
338
+}
339
+
340
+func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) {
341
+	c := &daemon.Config{}
342
+	common := &cli.CommonFlags{}
343
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
344
+	flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "")
345
+
346
+	f, err := ioutil.TempFile("", "docker-config-")
347
+	if err != nil {
348
+		t.Fatal(err)
349
+	}
350
+
351
+	if err := flags.ParseFlags([]string{}, false); err != nil {
352
+		t.Fatal(err)
353
+	}
354
+
355
+	configFile := f.Name()
356
+	f.Write([]byte(`{}`))
357
+	f.Close()
358
+
359
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
360
+	if err != nil {
361
+		t.Fatal(err)
362
+	}
363
+	if loadedConfig == nil {
364
+		t.Fatal("expected configuration, got nil")
365
+	}
366
+
367
+	if !loadedConfig.EnableUserlandProxy {
368
+		t.Fatal("expected userland proxy to be enabled, got disabled")
369
+	}
370
+}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"os/signal"
9 9
 	"syscall"
10 10
 
11
+	"github.com/Sirupsen/logrus"
11 12
 	apiserver "github.com/docker/docker/api/server"
12 13
 	"github.com/docker/docker/daemon"
13 14
 	"github.com/docker/docker/pkg/mflag"
... ...
@@ -59,7 +60,9 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(
59 59
 	signal.Notify(c, syscall.SIGHUP)
60 60
 	go func() {
61 61
 		for range c {
62
-			daemon.ReloadConfiguration(configFile, flags, reload)
62
+			if err := daemon.ReloadConfiguration(configFile, flags, reload); err != nil {
63
+				logrus.Error(err)
64
+			}
63 65
 		}
64 66
 	}()
65 67
 }
... ...
@@ -50,7 +50,9 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(
50 50
 			logrus.Debugf("Config reload - waiting signal at %s", ev)
51 51
 			for {
52 52
 				syscall.WaitForSingleObject(h, syscall.INFINITE)
53
-				daemon.ReloadConfiguration(configFile, flags, reload)
53
+				if err := daemon.ReloadConfiguration(configFile, flags, reload); err != nil {
54
+					logrus.Error(err)
55
+				}
54 56
 			}
55 57
 		}
56 58
 	}()
57 59
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+// +build !windows
1
+
2
+package main
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"path/filepath"
8
+
9
+	"github.com/docker/docker/pkg/integration/checker"
10
+	"github.com/docker/docker/pkg/system"
11
+	"github.com/go-check/check"
12
+)
13
+
14
+// Check ownership is root, both in non-userns and userns enabled modes
15
+func (s *DockerSuite) TestCpCheckDestOwnership(c *check.C) {
16
+	testRequires(c, DaemonIsLinux, SameHostDaemon)
17
+	tmpVolDir := getTestDir(c, "test-cp-tmpvol")
18
+	containerID := makeTestContainer(c,
19
+		testContainerOptions{volumes: []string{fmt.Sprintf("%s:/tmpvol", tmpVolDir)}})
20
+
21
+	tmpDir := getTestDir(c, "test-cp-to-check-ownership")
22
+	defer os.RemoveAll(tmpDir)
23
+
24
+	makeTestContentInDir(c, tmpDir)
25
+
26
+	srcPath := cpPath(tmpDir, "file1")
27
+	dstPath := containerCpPath(containerID, "/tmpvol", "file1")
28
+
29
+	err := runDockerCp(c, srcPath, dstPath)
30
+	c.Assert(err, checker.IsNil)
31
+
32
+	stat, err := system.Stat(filepath.Join(tmpVolDir, "file1"))
33
+	c.Assert(err, checker.IsNil)
34
+	uid, gid, err := getRootUIDGID()
35
+	c.Assert(err, checker.IsNil)
36
+	c.Assert(stat.UID(), checker.Equals, uint32(uid), check.Commentf("Copied file not owned by container root UID"))
37
+	c.Assert(stat.GID(), checker.Equals, uint32(gid), check.Commentf("Copied file not owned by container root GID"))
38
+}
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"bufio"
5
+	"compress/gzip"
5 6
 	"io/ioutil"
6 7
 	"os"
7 8
 	"os/exec"
... ...
@@ -59,6 +60,31 @@ func (s *DockerSuite) TestImportFile(c *check.C) {
59 59
 	c.Assert(out, checker.Equals, "", check.Commentf("command output should've been nothing."))
60 60
 }
61 61
 
62
+func (s *DockerSuite) TestImportGzipped(c *check.C) {
63
+	testRequires(c, DaemonIsLinux)
64
+	dockerCmd(c, "run", "--name", "test-import", "busybox", "true")
65
+
66
+	temporaryFile, err := ioutil.TempFile("", "exportImportTest")
67
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create temporary file"))
68
+	defer os.Remove(temporaryFile.Name())
69
+
70
+	runCmd := exec.Command(dockerBinary, "export", "test-import")
71
+	w := gzip.NewWriter(temporaryFile)
72
+	runCmd.Stdout = w
73
+
74
+	_, err = runCommand(runCmd)
75
+	c.Assert(err, checker.IsNil, check.Commentf("failed to export a container"))
76
+	err = w.Close()
77
+	c.Assert(err, checker.IsNil, check.Commentf("failed to close gzip writer"))
78
+	temporaryFile.Close()
79
+	out, _ := dockerCmd(c, "import", temporaryFile.Name())
80
+	c.Assert(out, checker.Count, "\n", 1, check.Commentf("display is expected 1 '\\n' but didn't"))
81
+	image := strings.TrimSpace(out)
82
+
83
+	out, _ = dockerCmd(c, "run", "--rm", image, "true")
84
+	c.Assert(out, checker.Equals, "", check.Commentf("command output should've been nothing."))
85
+}
86
+
62 87
 func (s *DockerSuite) TestImportFileWithMessage(c *check.C) {
63 88
 	testRequires(c, DaemonIsLinux)
64 89
 	dockerCmd(c, "run", "--name", "test-import", "busybox", "true")
... ...
@@ -1720,3 +1720,20 @@ func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string)
1720 1720
 	args = append(args, defaultSleepCommand...)
1721 1721
 	return dockerCmd(c, args...)
1722 1722
 }
1723
+
1724
+func getRootUIDGID() (int, int, error) {
1725
+	uidgid := strings.Split(filepath.Base(dockerBasePath), ".")
1726
+	if len(uidgid) == 1 {
1727
+		//user namespace remapping is not turned on; return 0
1728
+		return 0, 0, nil
1729
+	}
1730
+	uid, err := strconv.Atoi(uidgid[0])
1731
+	if err != nil {
1732
+		return 0, 0, err
1733
+	}
1734
+	gid, err := strconv.Atoi(uidgid[1])
1735
+	if err != nil {
1736
+		return 0, 0, err
1737
+	}
1738
+	return uid, gid, nil
1739
+}
... ...
@@ -498,18 +498,21 @@ func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) {
498 498
 
499 499
 	if err := ls.driver.Remove(m.mountID); err != nil {
500 500
 		logrus.Errorf("Error removing mounted layer %s: %s", m.name, err)
501
+		m.retakeReference(l)
501 502
 		return nil, err
502 503
 	}
503 504
 
504 505
 	if m.initID != "" {
505 506
 		if err := ls.driver.Remove(m.initID); err != nil {
506 507
 			logrus.Errorf("Error removing init layer %s: %s", m.name, err)
508
+			m.retakeReference(l)
507 509
 			return nil, err
508 510
 		}
509 511
 	}
510 512
 
511 513
 	if err := ls.store.RemoveMount(m.name); err != nil {
512 514
 		logrus.Errorf("Error removing mount metadata: %s: %s", m.name, err)
515
+		m.retakeReference(l)
513 516
 		return nil, err
514 517
 	}
515 518
 
... ...
@@ -127,6 +127,7 @@ func (ls *layerStore) checksumForGraphIDNoTarsplit(id, parent, newTarDataPath st
127 127
 	}
128 128
 	defer f.Close()
129 129
 	mfz := gzip.NewWriter(f)
130
+	defer mfz.Close()
130 131
 	metaPacker := storage.NewJSONPacker(mfz)
131 132
 
132 133
 	packerCounter := &packSizeCounter{metaPacker, &size}
... ...
@@ -96,6 +96,13 @@ func (ml *mountedLayer) deleteReference(ref RWLayer) error {
96 96
 	return nil
97 97
 }
98 98
 
99
+func (ml *mountedLayer) retakeReference(r RWLayer) {
100
+	if ref, ok := r.(*referencedRWLayer); ok {
101
+		ref.activityCount = 0
102
+		ml.references[ref] = ref
103
+	}
104
+}
105
+
99 106
 type referencedRWLayer struct {
100 107
 	*mountedLayer
101 108
 
... ...
@@ -27,10 +27,10 @@ container is unpaused, and then run
27 27
 
28 28
 # OPTIONS
29 29
 **-d**, **--detach**=*true*|*false*
30
-    Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
30
+   Detached mode: run command in the background. The default is *false*.
31 31
 
32 32
 **--detach-keys**=""
33
-  Define the key sequence which detaches the container.
33
+  Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
34 34
 
35 35
 **--help**
36 36
   Print usage statement
... ...
@@ -160,7 +160,12 @@ func calculateLayerChecksum(graphDir, id string, ls checksumCalculator) error {
160 160
 		return err
161 161
 	}
162 162
 
163
-	if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil {
163
+	tmpFile := filepath.Join(graphDir, id, migrationDiffIDFileName+".tmp")
164
+	if err := ioutil.WriteFile(tmpFile, []byte(diffID), 0600); err != nil {
165
+		return err
166
+	}
167
+
168
+	if err := os.Rename(tmpFile, filepath.Join(graphDir, id, migrationDiffIDFileName)); err != nil {
164 169
 		return err
165 170
 	}
166 171
 
... ...
@@ -423,7 +428,11 @@ func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metad
423 423
 		history = parentImg.History
424 424
 	}
425 425
 
426
-	diffID, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName))
426
+	diffIDData, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName))
427
+	if err != nil {
428
+		return err
429
+	}
430
+	diffID, err := digest.ParseDigest(string(diffIDData))
427 431
 	if err != nil {
428 432
 		return err
429 433
 	}
... ...
@@ -291,16 +291,14 @@ func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
291 291
 
292 292
 	s.globalLock.Lock()
293 293
 	defer s.globalLock.Unlock()
294
-	refs, exists := s.refs[v.Name()]
295
-	if !exists {
296
-		return
297
-	}
294
+	var refs []string
298 295
 
299
-	for i, r := range refs {
300
-		if r == ref {
301
-			s.refs[v.Name()] = append(s.refs[v.Name()][:i], s.refs[v.Name()][i+1:]...)
296
+	for _, r := range s.refs[v.Name()] {
297
+		if r != ref {
298
+			refs = append(refs, r)
302 299
 		}
303 300
 	}
301
+	s.refs[v.Name()] = refs
304 302
 }
305 303
 
306 304
 // Refs gets the current list of refs for the given volume
... ...
@@ -157,3 +157,22 @@ func TestFilterByUsed(t *testing.T) {
157 157
 		t.Fatalf("expected used volume fake1, got %s", used[0].Name())
158 158
 	}
159 159
 }
160
+
161
+func TestDerefMultipleOfSameRef(t *testing.T) {
162
+	volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
163
+
164
+	s := New()
165
+	v, err := s.CreateWithRef("fake1", "fake", "volReference", nil)
166
+	if err != nil {
167
+		t.Fatal(err)
168
+	}
169
+
170
+	if _, err := s.GetWithRef("fake1", "fake", "volReference"); err != nil {
171
+		t.Fatal(err)
172
+	}
173
+
174
+	s.Dereference(v, "volReference")
175
+	if err := s.Remove(v); err != nil {
176
+		t.Fatal(err)
177
+	}
178
+}