Browse code

cluster/executor: check mounts at start

While it is important to not create controllers for an invalid task,
certain properties should only be checked immediately before use. Early
host validation of mounts prevents resolution of the task Executor when
the mounts are not relevant to execution flow. In this case, we have a
check for the existence of a bind mount path in a creation function that
prevents a task controller from being resolved. Such early validation
prevents one from interacting directly with a controller and result in
unnecessary error reporting.

In accordance with the above, we move the validation of the existence of
host bind mount paths to the `Controller.Start` phase. We also call
these "checks", as they are valid mounts but reference non-existent
paths.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
(cherry picked from commit 92899ffac8ca1136e807dd234e8fa1dd49db7801)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>

Stephen J Day authored on 2017/02/04 11:05:20
Showing 5 changed files
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"encoding/json"
6 6
 	"fmt"
7 7
 	"io"
8
+	"os"
8 9
 	"strings"
9 10
 	"syscall"
10 11
 	"time"
... ...
@@ -258,7 +259,28 @@ func (c *containerAdapter) create(ctx context.Context) error {
258 258
 	return nil
259 259
 }
260 260
 
261
+// checkMounts ensures that the provided mounts won't have any host-specific
262
+// problems at start up. For example, we disallow bind mounts without an
263
+// existing path, which slightly different from the container API.
264
+func (c *containerAdapter) checkMounts() error {
265
+	spec := c.container.spec()
266
+	for _, mount := range spec.Mounts {
267
+		switch mount.Type {
268
+		case api.MountTypeBind:
269
+			if _, err := os.Stat(mount.Source); os.IsNotExist(err) {
270
+				return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source)
271
+			}
272
+		}
273
+	}
274
+
275
+	return nil
276
+}
277
+
261 278
 func (c *containerAdapter) start(ctx context.Context) error {
279
+	if err := c.checkMounts(); err != nil {
280
+		return err
281
+	}
282
+
262 283
 	return c.backend.ContainerStart(c.container.name(), nil, "", "")
263 284
 }
264 285
 
... ...
@@ -2,7 +2,6 @@ package container
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"os"
6 5
 	"path/filepath"
7 6
 
8 7
 	"github.com/docker/swarmkit/api"
... ...
@@ -24,9 +23,6 @@ func validateMounts(mounts []api.Mount) error {
24 24
 			if !filepath.IsAbs(mount.Source) {
25 25
 				return fmt.Errorf("invalid bind mount source, must be an absolute path: %s", mount.Source)
26 26
 			}
27
-			if _, err := os.Stat(mount.Source); os.IsNotExist(err) {
28
-				return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source)
29
-			}
30 27
 		case api.MountTypeVolume:
31 28
 			if filepath.IsAbs(mount.Source) {
32 29
 				return fmt.Errorf("invalid volume mount source, must not be an absolute path: %s", mount.Source)
... ...
@@ -42,10 +42,10 @@ func TestControllerValidateMountBind(t *testing.T) {
42 42
 	// with non-existing source
43 43
 	if _, err := newTestControllerWithMount(api.Mount{
44 44
 		Type:   api.MountTypeBind,
45
-		Source: "/some-non-existing-host-path/",
45
+		Source: testAbsNonExistent,
46 46
 		Target: testAbsPath,
47
-	}); err == nil || !strings.Contains(err.Error(), "invalid bind mount source") {
48
-		t.Fatalf("expected  error, got: %v", err)
47
+	}); err != nil {
48
+		t.Fatalf("controller should not error at creation: %v", err)
49 49
 	}
50 50
 
51 51
 	// with proper source
... ...
@@ -3,5 +3,6 @@
3 3
 package container
4 4
 
5 5
 const (
6
-	testAbsPath = "/foo"
6
+	testAbsPath        = "/foo"
7
+	testAbsNonExistent = "/some-non-existing-host-path/"
7 8
 )
... ...
@@ -1,5 +1,8 @@
1
+// +build windows
2
+
1 3
 package container
2 4
 
3 5
 const (
4
-	testAbsPath = `c:\foo`
6
+	testAbsPath        = `c:\foo`
7
+	testAbsNonExistent = `c:\some-non-existing-host-path\`
5 8
 )