Browse code

restore migrating pre-1.7.0 volumes

This was removed in a clean-up
(060f4ae6179b10aeafa883670826159fdae8204a) but should not have been.
Fixes issues with volumes when upgrading from pre-1.7.0 daemons.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/09/06 22:49:10
Showing 6 changed files
... ...
@@ -160,6 +160,12 @@ func (daemon *Daemon) restore() error {
160 160
 			continue
161 161
 		}
162 162
 
163
+		// verify that all volumes valid and have been migrated from the pre-1.7 layout
164
+		if err := daemon.verifyVolumesInfo(c); err != nil {
165
+			// don't skip the container due to error
166
+			logrus.Errorf("Failed to verify volumes for container '%s': %v", c.ID, err)
167
+		}
168
+
163 169
 		// The LogConfig.Type is empty if the container was created before docker 1.12 with default log driver.
164 170
 		// We should rewrite it to use the daemon defaults.
165 171
 		// Fixes https://github.com/docker/docker/issues/22536
... ...
@@ -165,3 +165,10 @@ func rootFSToAPIType(rootfs *image.RootFS) types.RootFS {
165 165
 func setupDaemonProcess(config *Config) error {
166 166
 	return nil
167 167
 }
168
+
169
+// verifyVolumesInfo is a no-op on solaris.
170
+// This is called during daemon initialization to migrate volumes from pre-1.7.
171
+// Solaris was not supported on pre-1.7 daemons.
172
+func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
173
+	return nil
174
+}
... ...
@@ -5,9 +5,14 @@ package daemon
5 5
 import (
6 6
 	"io/ioutil"
7 7
 	"os"
8
+	"path/filepath"
8 9
 	"testing"
9 10
 
10 11
 	"github.com/docker/docker/container"
12
+	"github.com/docker/docker/volume"
13
+	"github.com/docker/docker/volume/drivers"
14
+	"github.com/docker/docker/volume/local"
15
+	"github.com/docker/docker/volume/store"
11 16
 	containertypes "github.com/docker/engine-api/types/container"
12 17
 )
13 18
 
... ...
@@ -197,3 +202,82 @@ func TestNetworkOptions(t *testing.T) {
197 197
 		t.Fatalf("Expected networkOptions error, got nil")
198 198
 	}
199 199
 }
200
+
201
+func TestMigratePre17Volumes(t *testing.T) {
202
+	rootDir, err := ioutil.TempDir("", "test-daemon-volumes")
203
+	if err != nil {
204
+		t.Fatal(err)
205
+	}
206
+	defer os.RemoveAll(rootDir)
207
+
208
+	volumeRoot := filepath.Join(rootDir, "volumes")
209
+	err = os.MkdirAll(volumeRoot, 0755)
210
+	if err != nil {
211
+		t.Fatal(err)
212
+	}
213
+
214
+	containerRoot := filepath.Join(rootDir, "containers")
215
+	cid := "1234"
216
+	err = os.MkdirAll(filepath.Join(containerRoot, cid), 0755)
217
+
218
+	vid := "5678"
219
+	vfsPath := filepath.Join(rootDir, "vfs", "dir", vid)
220
+	err = os.MkdirAll(vfsPath, 0755)
221
+	if err != nil {
222
+		t.Fatal(err)
223
+	}
224
+
225
+	config := []byte(`
226
+		{
227
+			"ID": "` + cid + `",
228
+			"Volumes": {
229
+				"/foo": "` + vfsPath + `",
230
+				"/bar": "/foo",
231
+				"/quux": "/quux"
232
+			},
233
+			"VolumesRW": {
234
+				"/foo": true,
235
+				"/bar": true,
236
+				"/quux": false
237
+			}
238
+		}
239
+	`)
240
+
241
+	volStore, err := store.New(volumeRoot)
242
+	if err != nil {
243
+		t.Fatal(err)
244
+	}
245
+	drv, err := local.New(volumeRoot, 0, 0)
246
+	if err != nil {
247
+		t.Fatal(err)
248
+	}
249
+	volumedrivers.Register(drv, volume.DefaultDriverName)
250
+
251
+	daemon := &Daemon{root: rootDir, repository: containerRoot, volumes: volStore}
252
+	err = ioutil.WriteFile(filepath.Join(containerRoot, cid, "config.v2.json"), config, 600)
253
+	if err != nil {
254
+		t.Fatal(err)
255
+	}
256
+	c, err := daemon.load(cid)
257
+	if err != nil {
258
+		t.Fatal(err)
259
+	}
260
+	if err := daemon.verifyVolumesInfo(c); err != nil {
261
+		t.Fatal(err)
262
+	}
263
+
264
+	expected := map[string]volume.MountPoint{
265
+		"/foo":  {Destination: "/foo", RW: true, Name: vid},
266
+		"/bar":  {Source: "/foo", Destination: "/bar", RW: true},
267
+		"/quux": {Source: "/quux", Destination: "/quux", RW: false},
268
+	}
269
+	for id, mp := range c.MountPoints {
270
+		x, exists := expected[id]
271
+		if !exists {
272
+			t.Fatal("volume not migrated")
273
+		}
274
+		if mp.Source != x.Source || mp.Destination != x.Destination || mp.RW != x.RW || mp.Name != x.Name {
275
+			t.Fatalf("got unexpected mountpoint, expected: %+v, got: %+v", x, mp)
276
+		}
277
+	}
278
+}
... ...
@@ -434,3 +434,10 @@ func rootFSToAPIType(rootfs *image.RootFS) types.RootFS {
434 434
 func setupDaemonProcess(config *Config) error {
435 435
 	return nil
436 436
 }
437
+
438
+// verifyVolumesInfo is a no-op on windows.
439
+// This is called during daemon initialization to migrate volumes from pre-1.7.
440
+// volumes were not supported on windows pre-1.7
441
+func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
442
+	return nil
443
+}
... ...
@@ -3,12 +3,18 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
+	"encoding/json"
6 7
 	"os"
8
+	"path/filepath"
7 9
 	"sort"
8 10
 	"strconv"
11
+	"strings"
9 12
 
10 13
 	"github.com/docker/docker/container"
11 14
 	"github.com/docker/docker/volume"
15
+	"github.com/docker/docker/volume/drivers"
16
+	"github.com/docker/docker/volume/local"
17
+	"github.com/pkg/errors"
12 18
 )
13 19
 
14 20
 // setupMounts iterates through each of the mount points for a container and
... ...
@@ -84,3 +90,74 @@ func setBindModeIfNull(bind *volume.MountPoint) *volume.MountPoint {
84 84
 	}
85 85
 	return bind
86 86
 }
87
+
88
+// migrateVolume links the contents of a volume created pre Docker 1.7
89
+// into the location expected by the local driver.
90
+// It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
91
+// It preserves the volume json configuration generated pre Docker 1.7 to be able to
92
+// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
93
+func migrateVolume(id, vfs string) error {
94
+	l, err := volumedrivers.GetDriver(volume.DefaultDriverName)
95
+	if err != nil {
96
+		return err
97
+	}
98
+
99
+	newDataPath := l.(*local.Root).DataPath(id)
100
+	fi, err := os.Stat(newDataPath)
101
+	if err != nil && !os.IsNotExist(err) {
102
+		return err
103
+	}
104
+
105
+	if fi != nil && fi.IsDir() {
106
+		return nil
107
+	}
108
+
109
+	return os.Symlink(vfs, newDataPath)
110
+}
111
+
112
+// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
113
+// It reads the container configuration and creates valid mount points for the old volumes.
114
+func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
115
+	// Inspect old structures only when we're upgrading from old versions
116
+	// to versions >= 1.7 and the MountPoints has not been populated with volumes data.
117
+	type volumes struct {
118
+		Volumes   map[string]string
119
+		VolumesRW map[string]bool
120
+	}
121
+	cfgPath, err := container.ConfigPath()
122
+	if err != nil {
123
+		return err
124
+	}
125
+	f, err := os.Open(cfgPath)
126
+	if err != nil {
127
+		return errors.Wrap(err, "could not open container config")
128
+	}
129
+	var cv volumes
130
+	if err := json.NewDecoder(f).Decode(&cv); err != nil {
131
+		return errors.Wrap(err, "could not decode container config")
132
+	}
133
+
134
+	if len(container.MountPoints) == 0 && len(cv.Volumes) > 0 {
135
+		for destination, hostPath := range cv.Volumes {
136
+			vfsPath := filepath.Join(daemon.root, "vfs", "dir")
137
+			rw := cv.VolumesRW != nil && cv.VolumesRW[destination]
138
+
139
+			if strings.HasPrefix(hostPath, vfsPath) {
140
+				id := filepath.Base(hostPath)
141
+				v, err := daemon.volumes.CreateWithRef(id, volume.DefaultDriverName, container.ID, nil, nil)
142
+				if err != nil {
143
+					return err
144
+				}
145
+				if err := migrateVolume(id, hostPath); err != nil {
146
+					return err
147
+				}
148
+				container.AddMountPointWithVolume(destination, v, true)
149
+			} else { // Bind mount
150
+				m := volume.MountPoint{Source: hostPath, Destination: destination, RW: rw}
151
+				container.MountPoints[destination] = &m
152
+			}
153
+		}
154
+		return container.ToDisk()
155
+	}
156
+	return nil
157
+}
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/pkg/integration/checker"
24 24
 	icmd "github.com/docker/docker/pkg/integration/cmd"
25 25
 	"github.com/docker/docker/pkg/mount"
26
+	"github.com/docker/docker/pkg/stringid"
26 27
 	"github.com/docker/go-units"
27 28
 	"github.com/docker/libnetwork/iptables"
28 29
 	"github.com/docker/libtrust"
... ...
@@ -2791,3 +2792,83 @@ func (s *DockerDaemonSuite) TestDaemonRestartSaveContainerExitCode(c *check.C) {
2791 2791
 	c.Assert(err, checker.IsNil)
2792 2792
 	c.Assert(out, checker.Equals, runError)
2793 2793
 }
2794
+
2795
+func (s *DockerDaemonSuite) TestDaemonBackcompatPre17Volumes(c *check.C) {
2796
+	testRequires(c, SameHostDaemon)
2797
+	d := s.d
2798
+	err := d.StartWithBusybox()
2799
+	c.Assert(err, checker.IsNil)
2800
+
2801
+	// hack to be able to side-load a container config
2802
+	out, err := d.Cmd("create", "busybox:latest")
2803
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2804
+	id := strings.TrimSpace(out)
2805
+
2806
+	out, err = d.Cmd("inspect", "--type=image", "--format={{.ID}}", "busybox:latest")
2807
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2808
+	c.Assert(d.Stop(), checker.IsNil)
2809
+	<-d.wait
2810
+
2811
+	imageID := strings.TrimSpace(out)
2812
+	volumeID := stringid.GenerateNonCryptoID()
2813
+	vfsPath := filepath.Join(d.root, "vfs", "dir", volumeID)
2814
+	c.Assert(os.MkdirAll(vfsPath, 0755), checker.IsNil)
2815
+
2816
+	config := []byte(`
2817
+		{
2818
+			"ID": "` + id + `",
2819
+			"Name": "hello",
2820
+			"Driver": "` + d.storageDriver + `",
2821
+			"Image": "` + imageID + `",
2822
+			"Config": {"Image": "busybox:latest"},
2823
+			"NetworkSettings": {},
2824
+			"Volumes": {
2825
+				"/bar":"/foo",
2826
+				"/foo": "` + vfsPath + `",
2827
+				"/quux":"/quux"
2828
+			},
2829
+			"VolumesRW": {
2830
+				"/bar": true,
2831
+				"/foo": true,
2832
+				"/quux": false
2833
+			}
2834
+		}
2835
+	`)
2836
+
2837
+	configPath := filepath.Join(d.root, "containers", id, "config.v2.json")
2838
+	err = ioutil.WriteFile(configPath, config, 600)
2839
+	err = d.Start()
2840
+	c.Assert(err, checker.IsNil)
2841
+
2842
+	out, err = d.Cmd("inspect", "--type=container", "--format={{ json .Mounts }}", id)
2843
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2844
+	type mount struct {
2845
+		Name        string
2846
+		Source      string
2847
+		Destination string
2848
+		Driver      string
2849
+		RW          bool
2850
+	}
2851
+
2852
+	ls := []mount{}
2853
+	err = json.NewDecoder(strings.NewReader(out)).Decode(&ls)
2854
+	c.Assert(err, checker.IsNil)
2855
+
2856
+	expected := []mount{
2857
+		{Source: "/foo", Destination: "/bar", RW: true},
2858
+		{Name: volumeID, Destination: "/foo", RW: true},
2859
+		{Source: "/quux", Destination: "/quux", RW: false},
2860
+	}
2861
+	c.Assert(ls, checker.HasLen, len(expected))
2862
+
2863
+	for _, m := range ls {
2864
+		var matched bool
2865
+		for _, x := range expected {
2866
+			if m.Source == x.Source && m.Destination == x.Destination && m.RW == x.RW || m.Name != x.Name {
2867
+				matched = true
2868
+				break
2869
+			}
2870
+		}
2871
+		c.Assert(matched, checker.True, check.Commentf("did find match for %+v", m))
2872
+	}
2873
+}