Browse code

Add HTTP client timeout.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>

Anusha Ragunathan authored on 2016/11/22 02:24:01
Showing 16 changed files
... ...
@@ -11,7 +11,7 @@ import (
11 11
 // Backend for Plugin
12 12
 type Backend interface {
13 13
 	Disable(name string) error
14
-	Enable(name string) error
14
+	Enable(name string, config *enginetypes.PluginEnableConfig) error
15 15
 	List() ([]enginetypes.Plugin, error)
16 16
 	Inspect(name string) (enginetypes.Plugin, error)
17 17
 	Remove(name string, config *enginetypes.PluginRmConfig) error
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6 6
 	"net/http"
7
+	"strconv"
7 8
 	"strings"
8 9
 
9 10
 	"github.com/docker/docker/api/server/httputils"
... ...
@@ -56,7 +57,18 @@ func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter,
56 56
 }
57 57
 
58 58
 func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
59
-	return pr.backend.Enable(vars["name"])
59
+	if err := httputils.ParseForm(r); err != nil {
60
+		return err
61
+	}
62
+
63
+	name := vars["name"]
64
+	timeout, err := strconv.Atoi(r.Form.Get("timeout"))
65
+	if err != nil {
66
+		return err
67
+	}
68
+	config := &types.PluginEnableConfig{Timeout: timeout}
69
+
70
+	return pr.backend.Enable(name, config)
60 71
 }
61 72
 
62 73
 func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -6307,7 +6307,7 @@ paths:
6307 6307
       summary: "Install a plugin"
6308 6308
       operationId: "PostPluginsPull"
6309 6309
       description: |
6310
-        Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginEnable).
6310
+        Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginsEnable).
6311 6311
       produces:
6312 6312
         - "application/json"
6313 6313
       responses:
... ...
@@ -6430,6 +6430,11 @@ paths:
6430 6430
           description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
6431 6431
           required: true
6432 6432
           type: "string"
6433
+        - name: "timeout"
6434
+          in: "query"
6435
+          description: "Set the HTTP client timeout (in seconds)"
6436
+          type: "integer"
6437
+          default: 0
6433 6438
       tags:
6434 6439
         - "Plugins"
6435 6440
   /plugins/{name}/disable:
... ...
@@ -332,6 +332,11 @@ type PluginRemoveOptions struct {
332 332
 	Force bool
333 333
 }
334 334
 
335
+// PluginEnableOptions holds parameters to enable plugins.
336
+type PluginEnableOptions struct {
337
+	Timeout int
338
+}
339
+
335 340
 // PluginInstallOptions holds parameters to install a plugin.
336 341
 type PluginInstallOptions struct {
337 342
 	Disabled              bool
... ...
@@ -59,3 +59,8 @@ type ExecConfig struct {
59 59
 type PluginRmConfig struct {
60 60
 	ForceRemove bool
61 61
 }
62
+
63
+// PluginEnableConfig holds arguments for the plugin enable
64
+type PluginEnableConfig struct {
65
+	Timeout int
66
+}
... ...
@@ -3,6 +3,7 @@ package plugin
3 3
 import (
4 4
 	"fmt"
5 5
 
6
+	"github.com/docker/docker/api/types"
6 7
 	"github.com/docker/docker/cli"
7 8
 	"github.com/docker/docker/cli/command"
8 9
 	"github.com/docker/docker/reference"
... ...
@@ -10,20 +11,32 @@ import (
10 10
 	"golang.org/x/net/context"
11 11
 )
12 12
 
13
+type enableOpts struct {
14
+	timeout int
15
+	name    string
16
+}
17
+
13 18
 func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command {
19
+	var opts enableOpts
20
+
14 21
 	cmd := &cobra.Command{
15 22
 		Use:   "enable PLUGIN",
16 23
 		Short: "Enable a plugin",
17 24
 		Args:  cli.ExactArgs(1),
18 25
 		RunE: func(cmd *cobra.Command, args []string) error {
19
-			return runEnable(dockerCli, args[0])
26
+			opts.name = args[0]
27
+			return runEnable(dockerCli, &opts)
20 28
 		},
21 29
 	}
22 30
 
31
+	flags := cmd.Flags()
32
+	flags.IntVar(&opts.timeout, "timeout", 0, "HTTP client timeout (in seconds)")
23 33
 	return cmd
24 34
 }
25 35
 
26
-func runEnable(dockerCli *command.DockerCli, name string) error {
36
+func runEnable(dockerCli *command.DockerCli, opts *enableOpts) error {
37
+	name := opts.name
38
+
27 39
 	named, err := reference.ParseNamed(name) // FIXME: validate
28 40
 	if err != nil {
29 41
 		return err
... ...
@@ -35,7 +48,11 @@ func runEnable(dockerCli *command.DockerCli, name string) error {
35 35
 	if !ok {
36 36
 		return fmt.Errorf("invalid name: %s", named.String())
37 37
 	}
38
-	if err := dockerCli.Client().PluginEnable(context.Background(), ref.String()); err != nil {
38
+	if opts.timeout < 0 {
39
+		return fmt.Errorf("negative timeout %d is invalid", opts.timeout)
40
+	}
41
+
42
+	if err := dockerCli.Client().PluginEnable(context.Background(), ref.String(), types.PluginEnableOptions{Timeout: opts.timeout}); err != nil {
39 43
 		return err
40 44
 	}
41 45
 	fmt.Fprintln(dockerCli.Out(), name)
... ...
@@ -109,7 +109,7 @@ type NodeAPIClient interface {
109 109
 type PluginAPIClient interface {
110 110
 	PluginList(ctx context.Context) (types.PluginsListResponse, error)
111 111
 	PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
112
-	PluginEnable(ctx context.Context, name string) error
112
+	PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
113 113
 	PluginDisable(ctx context.Context, name string) error
114 114
 	PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
115 115
 	PluginPush(ctx context.Context, name string, registryAuth string) error
... ...
@@ -1,12 +1,19 @@
1 1
 package client
2 2
 
3 3
 import (
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/docker/docker/api/types"
4 8
 	"golang.org/x/net/context"
5 9
 )
6 10
 
7 11
 // PluginEnable enables a plugin
8
-func (cli *Client) PluginEnable(ctx context.Context, name string) error {
9
-	resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil)
12
+func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error {
13
+	query := url.Values{}
14
+	query.Set("timeout", strconv.Itoa(options.Timeout))
15
+
16
+	resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil)
10 17
 	ensureReaderClosed(resp)
11 18
 	return err
12 19
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/docker/docker/api/types"
11 12
 	"golang.org/x/net/context"
12 13
 )
13 14
 
... ...
@@ -16,7 +17,7 @@ func TestPluginEnableError(t *testing.T) {
16 16
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
17 17
 	}
18 18
 
19
-	err := client.PluginEnable(context.Background(), "plugin_name")
19
+	err := client.PluginEnable(context.Background(), "plugin_name", types.PluginEnableOptions{})
20 20
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
21 21
 		t.Fatalf("expected a Server Error, got %v", err)
22 22
 	}
... ...
@@ -40,7 +41,7 @@ func TestPluginEnable(t *testing.T) {
40 40
 		}),
41 41
 	}
42 42
 
43
-	err := client.PluginEnable(context.Background(), "plugin_name")
43
+	err := client.PluginEnable(context.Background(), "plugin_name", types.PluginEnableOptions{})
44 44
 	if err != nil {
45 45
 		t.Fatal(err)
46 46
 	}
... ...
@@ -62,7 +62,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
62 62
 		return nil
63 63
 	}
64 64
 
65
-	return cli.PluginEnable(ctx, name)
65
+	return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
66 66
 }
67 67
 
68 68
 func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
... ...
@@ -14,12 +14,12 @@ if [ ${#files[@]} -gt 0 ]; then
14 14
 	diffs="$(git status --porcelain -- api/types/ 2>/dev/null)"
15 15
 	if [ "$diffs" ]; then
16 16
 		{
17
-			echo 'The result of hack/geneate-swagger-api.sh differs'
17
+			echo 'The result of hack/generate-swagger-api.sh differs'
18 18
 			echo
19 19
 			echo "$diffs"
20 20
 			echo
21 21
 			echo 'Please update api/swagger.yaml with any api changes, then '
22
-			echo 'run `hack/geneate-swagger-api.sh`.'
22
+			echo 'run `hack/generate-swagger-api.sh`.'
23 23
 		} >&2
24 24
 		false
25 25
 	else
... ...
@@ -19,8 +19,7 @@ const (
19 19
 	defaultTimeOut = 30
20 20
 )
21 21
 
22
-// NewClient creates a new plugin client (http).
23
-func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
22
+func newTransport(addr string, tlsConfig *tlsconfig.Options) (transport.Transport, error) {
24 23
 	tr := &http.Transport{}
25 24
 
26 25
 	if tlsConfig != nil {
... ...
@@ -45,15 +44,33 @@ func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
45 45
 	}
46 46
 	scheme := httpScheme(u)
47 47
 
48
-	clientTransport := transport.NewHTTPTransport(tr, scheme, socket)
49
-	return NewClientWithTransport(clientTransport), nil
48
+	return transport.NewHTTPTransport(tr, scheme, socket), nil
49
+}
50
+
51
+// NewClient creates a new plugin client (http).
52
+func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
53
+	clientTransport, err := newTransport(addr, tlsConfig)
54
+	if err != nil {
55
+		return nil, err
56
+	}
57
+	return newClientWithTransport(clientTransport, 0), nil
58
+}
59
+
60
+// NewClientWithTimeout creates a new plugin client (http).
61
+func NewClientWithTimeout(addr string, tlsConfig *tlsconfig.Options, timeoutInSecs int) (*Client, error) {
62
+	clientTransport, err := newTransport(addr, tlsConfig)
63
+	if err != nil {
64
+		return nil, err
65
+	}
66
+	return newClientWithTransport(clientTransport, timeoutInSecs), nil
50 67
 }
51 68
 
52
-// NewClientWithTransport creates a new plugin client with a given transport.
53
-func NewClientWithTransport(tr transport.Transport) *Client {
69
+// newClientWithTransport creates a new plugin client with a given transport.
70
+func newClientWithTransport(tr transport.Transport, timeoutInSecs int) *Client {
54 71
 	return &Client{
55 72
 		http: &http.Client{
56 73
 			Transport: tr,
74
+			Timeout:   time.Duration(timeoutInSecs) * time.Second,
57 75
 		},
58 76
 		requestFactory: tr,
59 77
 	}
... ...
@@ -36,11 +36,14 @@ func (pm *Manager) Disable(name string) error {
36 36
 }
37 37
 
38 38
 // Enable activates a plugin, which implies that they are ready to be used by containers.
39
-func (pm *Manager) Enable(name string) error {
39
+func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
40
+
40 41
 	p, err := pm.pluginStore.GetByName(name)
41 42
 	if err != nil {
42 43
 		return err
43 44
 	}
45
+
46
+	p.TimeoutInSecs = config.Timeout
44 47
 	if err := pm.enable(p, false); err != nil {
45 48
 		return err
46 49
 	}
... ...
@@ -19,7 +19,7 @@ func (pm *Manager) Disable(name string) error {
19 19
 }
20 20
 
21 21
 // Enable activates a plugin, which implies that they are ready to be used by containers.
22
-func (pm *Manager) Enable(name string) error {
22
+func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
23 23
 	return errNotSupported
24 24
 }
25 25
 
... ...
@@ -31,7 +31,7 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
31 31
 		return err
32 32
 	}
33 33
 
34
-	p.PClient, err = plugins.NewClient("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil)
34
+	p.PClient, err = plugins.NewClientWithTimeout("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil, p.TimeoutInSecs)
35 35
 	if err != nil {
36 36
 		p.Lock()
37 37
 		p.Restart = false
... ...
@@ -24,6 +24,7 @@ type Plugin struct {
24 24
 	Restart           bool            `json:"-"`
25 25
 	ExitChan          chan bool       `json:"-"`
26 26
 	LibRoot           string          `json:"-"`
27
+	TimeoutInSecs     int             `json:"-"`
27 28
 }
28 29
 
29 30
 const defaultPluginRuntimeDestination = "/run/docker/plugins"