1.10.2 cherrypicks
Tibor Vass authored on 2016/02/20 12:45:26... | ... |
@@ -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 |
+} |