Add HTTP client timeout.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>
(cherry picked from commit 83ca993c15)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
This commit is contained in:
Anusha Ragunathan 2016-11-21 09:24:01 -08:00 committed by Victor Vieux
parent abc0eea899
commit 0403addc5f
16 changed files with 96 additions and 23 deletions

View file

@ -11,7 +11,7 @@ import (
// Backend for Plugin // Backend for Plugin
type Backend interface { type Backend interface {
Disable(name string) error Disable(name string) error
Enable(name string) error Enable(name string, config *enginetypes.PluginEnableConfig) error
List() ([]enginetypes.Plugin, error) List() ([]enginetypes.Plugin, error)
Inspect(name string) (enginetypes.Plugin, error) Inspect(name string) (enginetypes.Plugin, error)
Remove(name string, config *enginetypes.PluginRmConfig) error Remove(name string, config *enginetypes.PluginRmConfig) error

View file

@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
@ -56,7 +57,18 @@ func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter,
} }
func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return pr.backend.Enable(vars["name"]) if err := httputils.ParseForm(r); err != nil {
return err
}
name := vars["name"]
timeout, err := strconv.Atoi(r.Form.Get("timeout"))
if err != nil {
return err
}
config := &types.PluginEnableConfig{Timeout: timeout}
return pr.backend.Enable(name, config)
} }
func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View file

@ -6307,7 +6307,7 @@ paths:
summary: "Install a plugin" summary: "Install a plugin"
operationId: "PostPluginsPull" operationId: "PostPluginsPull"
description: | description: |
Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginEnable). Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginsEnable).
produces: produces:
- "application/json" - "application/json"
responses: responses:
@ -6430,6 +6430,11 @@ paths:
description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted." description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
required: true required: true
type: "string" type: "string"
- name: "timeout"
in: "query"
description: "Set the HTTP client timeout (in seconds)"
type: "integer"
default: 0
tags: tags:
- "Plugins" - "Plugins"
/plugins/{name}/disable: /plugins/{name}/disable:

View file

@ -332,6 +332,11 @@ type PluginRemoveOptions struct {
Force bool Force bool
} }
// PluginEnableOptions holds parameters to enable plugins.
type PluginEnableOptions struct {
Timeout int
}
// PluginInstallOptions holds parameters to install a plugin. // PluginInstallOptions holds parameters to install a plugin.
type PluginInstallOptions struct { type PluginInstallOptions struct {
Disabled bool Disabled bool

View file

@ -59,3 +59,8 @@ type ExecConfig struct {
type PluginRmConfig struct { type PluginRmConfig struct {
ForceRemove bool ForceRemove bool
} }
// PluginEnableConfig holds arguments for the plugin enable
type PluginEnableConfig struct {
Timeout int
}

View file

@ -3,6 +3,7 @@ package plugin
import ( import (
"fmt" "fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/docker/docker/reference" "github.com/docker/docker/reference"
@ -10,20 +11,32 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
type enableOpts struct {
timeout int
name string
}
func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command { func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts enableOpts
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "enable PLUGIN", Use: "enable PLUGIN",
Short: "Enable a plugin", Short: "Enable a plugin",
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runEnable(dockerCli, args[0]) opts.name = args[0]
return runEnable(dockerCli, &opts)
}, },
} }
flags := cmd.Flags()
flags.IntVar(&opts.timeout, "timeout", 0, "HTTP client timeout (in seconds)")
return cmd return cmd
} }
func runEnable(dockerCli *command.DockerCli, name string) error { func runEnable(dockerCli *command.DockerCli, opts *enableOpts) error {
name := opts.name
named, err := reference.ParseNamed(name) // FIXME: validate named, err := reference.ParseNamed(name) // FIXME: validate
if err != nil { if err != nil {
return err return err
@ -35,7 +48,11 @@ func runEnable(dockerCli *command.DockerCli, name string) error {
if !ok { if !ok {
return fmt.Errorf("invalid name: %s", named.String()) return fmt.Errorf("invalid name: %s", named.String())
} }
if err := dockerCli.Client().PluginEnable(context.Background(), ref.String()); err != nil { if opts.timeout < 0 {
return fmt.Errorf("negative timeout %d is invalid", opts.timeout)
}
if err := dockerCli.Client().PluginEnable(context.Background(), ref.String(), types.PluginEnableOptions{Timeout: opts.timeout}); err != nil {
return err return err
} }
fmt.Fprintln(dockerCli.Out(), name) fmt.Fprintln(dockerCli.Out(), name)

View file

@ -109,7 +109,7 @@ type NodeAPIClient interface {
type PluginAPIClient interface { type PluginAPIClient interface {
PluginList(ctx context.Context) (types.PluginsListResponse, error) PluginList(ctx context.Context) (types.PluginsListResponse, error)
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
PluginEnable(ctx context.Context, name string) error PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
PluginDisable(ctx context.Context, name string) error PluginDisable(ctx context.Context, name string) error
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
PluginPush(ctx context.Context, name string, registryAuth string) error PluginPush(ctx context.Context, name string, registryAuth string) error

View file

@ -1,12 +1,19 @@
package client package client
import ( import (
"net/url"
"strconv"
"github.com/docker/docker/api/types"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// PluginEnable enables a plugin // PluginEnable enables a plugin
func (cli *Client) PluginEnable(ctx context.Context, name string) error { func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error {
resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil) query := url.Values{}
query.Set("timeout", strconv.Itoa(options.Timeout))
resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err
} }

View file

@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/docker/api/types"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -16,7 +17,7 @@ func TestPluginEnableError(t *testing.T) {
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
} }
err := client.PluginEnable(context.Background(), "plugin_name") err := client.PluginEnable(context.Background(), "plugin_name", types.PluginEnableOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" { if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err) t.Fatalf("expected a Server Error, got %v", err)
} }
@ -40,7 +41,7 @@ func TestPluginEnable(t *testing.T) {
}), }),
} }
err := client.PluginEnable(context.Background(), "plugin_name") err := client.PluginEnable(context.Background(), "plugin_name", types.PluginEnableOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -62,7 +62,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
return nil return nil
} }
return cli.PluginEnable(ctx, name) return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
} }
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {

View file

@ -14,12 +14,12 @@ if [ ${#files[@]} -gt 0 ]; then
diffs="$(git status --porcelain -- api/types/ 2>/dev/null)" diffs="$(git status --porcelain -- api/types/ 2>/dev/null)"
if [ "$diffs" ]; then if [ "$diffs" ]; then
{ {
echo 'The result of hack/geneate-swagger-api.sh differs' echo 'The result of hack/generate-swagger-api.sh differs'
echo echo
echo "$diffs" echo "$diffs"
echo echo
echo 'Please update api/swagger.yaml with any api changes, then ' echo 'Please update api/swagger.yaml with any api changes, then '
echo 'run `hack/geneate-swagger-api.sh`.' echo 'run `hack/generate-swagger-api.sh`.'
} >&2 } >&2
false false
else else

View file

@ -19,8 +19,7 @@ const (
defaultTimeOut = 30 defaultTimeOut = 30
) )
// NewClient creates a new plugin client (http). func newTransport(addr string, tlsConfig *tlsconfig.Options) (transport.Transport, error) {
func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
tr := &http.Transport{} tr := &http.Transport{}
if tlsConfig != nil { if tlsConfig != nil {
@ -45,15 +44,33 @@ func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
} }
scheme := httpScheme(u) scheme := httpScheme(u)
clientTransport := transport.NewHTTPTransport(tr, scheme, socket) return transport.NewHTTPTransport(tr, scheme, socket), nil
return NewClientWithTransport(clientTransport), nil
} }
// NewClientWithTransport creates a new plugin client with a given transport. // NewClient creates a new plugin client (http).
func NewClientWithTransport(tr transport.Transport) *Client { func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
clientTransport, err := newTransport(addr, tlsConfig)
if err != nil {
return nil, err
}
return newClientWithTransport(clientTransport, 0), nil
}
// NewClientWithTimeout creates a new plugin client (http).
func NewClientWithTimeout(addr string, tlsConfig *tlsconfig.Options, timeoutInSecs int) (*Client, error) {
clientTransport, err := newTransport(addr, tlsConfig)
if err != nil {
return nil, err
}
return newClientWithTransport(clientTransport, timeoutInSecs), nil
}
// newClientWithTransport creates a new plugin client with a given transport.
func newClientWithTransport(tr transport.Transport, timeoutInSecs int) *Client {
return &Client{ return &Client{
http: &http.Client{ http: &http.Client{
Transport: tr, Transport: tr,
Timeout: time.Duration(timeoutInSecs) * time.Second,
}, },
requestFactory: tr, requestFactory: tr,
} }

View file

@ -36,11 +36,14 @@ func (pm *Manager) Disable(name string) error {
} }
// Enable activates a plugin, which implies that they are ready to be used by containers. // Enable activates a plugin, which implies that they are ready to be used by containers.
func (pm *Manager) Enable(name string) error { func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
p, err := pm.pluginStore.GetByName(name) p, err := pm.pluginStore.GetByName(name)
if err != nil { if err != nil {
return err return err
} }
p.TimeoutInSecs = config.Timeout
if err := pm.enable(p, false); err != nil { if err := pm.enable(p, false); err != nil {
return err return err
} }

View file

@ -19,7 +19,7 @@ func (pm *Manager) Disable(name string) error {
} }
// Enable activates a plugin, which implies that they are ready to be used by containers. // Enable activates a plugin, which implies that they are ready to be used by containers.
func (pm *Manager) Enable(name string) error { func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
return errNotSupported return errNotSupported
} }

View file

@ -31,7 +31,7 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
return err return err
} }
p.PClient, err = plugins.NewClient("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil) p.PClient, err = plugins.NewClientWithTimeout("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil, p.TimeoutInSecs)
if err != nil { if err != nil {
p.Lock() p.Lock()
p.Restart = false p.Restart = false

View file

@ -24,6 +24,7 @@ type Plugin struct {
Restart bool `json:"-"` Restart bool `json:"-"`
ExitChan chan bool `json:"-"` ExitChan chan bool `json:"-"`
LibRoot string `json:"-"` LibRoot string `json:"-"`
TimeoutInSecs int `json:"-"`
} }
const defaultPluginRuntimeDestination = "/run/docker/plugins" const defaultPluginRuntimeDestination = "/run/docker/plugins"