Browse code

Merge pull request #38990 from tiborvass/http-grpc

Add undocumented /grpc endpoint and register BuildKit's controller

Tõnis Tiigi authored on 2019/04/03 10:13:13
Showing 10 changed files
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/pkg/stringid"
15 15
 	"github.com/pkg/errors"
16 16
 	"golang.org/x/sync/errgroup"
17
+	"google.golang.org/grpc"
17 18
 )
18 19
 
19 20
 // ImageComponent provides an interface for working with images
... ...
@@ -40,6 +41,13 @@ func NewBackend(components ImageComponent, builder Builder, fsCache *fscache.FSC
40 40
 	return &Backend{imageComponent: components, builder: builder, fsCache: fsCache, buildkit: buildkit}, nil
41 41
 }
42 42
 
43
+// RegisterGRPC registers buildkit controller to the grpc server.
44
+func (b *Backend) RegisterGRPC(s *grpc.Server) {
45
+	if b.buildkit != nil {
46
+		b.buildkit.RegisterGRPC(s)
47
+	}
48
+}
49
+
43 50
 // Build builds an image from a Source
44 51
 func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
45 52
 	options := config.Options
46 53
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
1
+
2
+import "google.golang.org/grpc"
3
+
4
+// Backend abstracts a registerable GRPC service.
5
+type Backend interface {
6
+	RegisterGRPC(*grpc.Server)
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
1
+
2
+import (
3
+	"github.com/docker/docker/api/server/router"
4
+	"golang.org/x/net/http2"
5
+	"google.golang.org/grpc"
6
+)
7
+
8
+type grpcRouter struct {
9
+	routes     []router.Route
10
+	grpcServer *grpc.Server
11
+	h2Server   *http2.Server
12
+}
13
+
14
+// NewRouter initializes a new grpc http router
15
+func NewRouter(backends ...Backend) router.Router {
16
+	r := &grpcRouter{
17
+		h2Server:   &http2.Server{},
18
+		grpcServer: grpc.NewServer(),
19
+	}
20
+	for _, b := range backends {
21
+		b.RegisterGRPC(r.grpcServer)
22
+	}
23
+	r.initRoutes()
24
+	return r
25
+}
26
+
27
+// Routes returns the available routers to the session controller
28
+func (r *grpcRouter) Routes() []router.Route {
29
+	return r.routes
30
+}
31
+
32
+func (r *grpcRouter) initRoutes() {
33
+	r.routes = []router.Route{
34
+		router.NewPostRoute("/grpc", r.serveGRPC),
35
+	}
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
1
+
2
+import (
3
+	"context"
4
+	"net/http"
5
+
6
+	"github.com/pkg/errors"
7
+	"golang.org/x/net/http2"
8
+)
9
+
10
+func (gr *grpcRouter) serveGRPC(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
11
+	h, ok := w.(http.Hijacker)
12
+	if !ok {
13
+		return errors.New("handler does not support hijack")
14
+	}
15
+	proto := r.Header.Get("Upgrade")
16
+	if proto == "" {
17
+		return errors.New("no upgrade proto in request")
18
+	}
19
+	if proto != "h2c" {
20
+		return errors.Errorf("protocol %s not supported", proto)
21
+	}
22
+
23
+	conn, _, err := h.Hijack()
24
+	if err != nil {
25
+		return err
26
+	}
27
+	resp := &http.Response{
28
+		StatusCode: http.StatusSwitchingProtocols,
29
+		ProtoMajor: 1,
30
+		ProtoMinor: 1,
31
+		Header:     http.Header{},
32
+	}
33
+	resp.Header.Set("Connection", "Upgrade")
34
+	resp.Header.Set("Upgrade", proto)
35
+
36
+	// set raw mode
37
+	conn.Write([]byte{})
38
+	resp.Write(conn)
39
+
40
+	// https://godoc.org/golang.org/x/net/http2#Server.ServeConn
41
+	// TODO: is it a problem that conn has already been written to?
42
+	gr.h2Server.ServeConn(conn, &http2.ServeConnOpts{Handler: gr.grpcServer})
43
+	return nil
44
+}
... ...
@@ -31,6 +31,7 @@ import (
31 31
 	"github.com/moby/buildkit/util/tracing"
32 32
 	"github.com/pkg/errors"
33 33
 	"golang.org/x/sync/errgroup"
34
+	"google.golang.org/grpc"
34 35
 	grpcmetadata "google.golang.org/grpc/metadata"
35 36
 )
36 37
 
... ...
@@ -104,6 +105,11 @@ func New(opt Opt) (*Builder, error) {
104 104
 	return b, nil
105 105
 }
106 106
 
107
+// RegisterGRPC registers controller to the grpc server.
108
+func (b *Builder) RegisterGRPC(s *grpc.Server) {
109
+	b.controller.Register(s)
110
+}
111
+
107 112
 // Cancel cancels a build using ID
108 113
 func (b *Builder) Cancel(ctx context.Context, id string) error {
109 114
 	b.mu.Lock()
... ...
@@ -38,6 +38,17 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
38 38
 	return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
39 39
 }
40 40
 
41
+// DialHijack returns a hijacked connection with negotiated protocol proto.
42
+func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
43
+	req, err := http.NewRequest("POST", url, nil)
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	req = cli.addHeaders(req, meta)
48
+
49
+	return cli.setupHijackConn(ctx, req, proto)
50
+}
51
+
41 52
 // fallbackDial is used when WithDialer() was not called.
42 53
 // See cli.Dialer().
43 54
 func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
... ...
@@ -38,7 +38,7 @@ type CommonAPIClient interface {
38 38
 	ServerVersion(ctx context.Context) (types.Version, error)
39 39
 	NegotiateAPIVersion(ctx context.Context)
40 40
 	NegotiateAPIVersionPing(types.Ping)
41
-	DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
41
+	DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error)
42 42
 	Dialer() func(context.Context) (net.Conn, error)
43 43
 	Close() error
44 44
 }
45 45
deleted file mode 100644
... ...
@@ -1,18 +0,0 @@
1
-package client // import "github.com/docker/docker/client"
2
-
3
-import (
4
-	"context"
5
-	"net"
6
-	"net/http"
7
-)
8
-
9
-// DialSession returns a connection that can be used communication with daemon
10
-func (cli *Client) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
11
-	req, err := http.NewRequest("POST", "/session", nil)
12
-	if err != nil {
13
-		return nil, err
14
-	}
15
-	req = cli.addHeaders(req, meta)
16
-
17
-	return cli.setupHijackConn(ctx, req, proto)
18
-}
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
22 22
 	"github.com/docker/docker/api/server/router/container"
23 23
 	distributionrouter "github.com/docker/docker/api/server/router/distribution"
24
+	grpcrouter "github.com/docker/docker/api/server/router/grpc"
24 25
 	"github.com/docker/docker/api/server/router/image"
25 26
 	"github.com/docker/docker/api/server/router/network"
26 27
 	pluginrouter "github.com/docker/docker/api/server/router/plugin"
... ...
@@ -481,6 +482,16 @@ func initRouter(opts routerOptions) {
481 481
 		distributionrouter.NewRouter(opts.daemon.ImageService()),
482 482
 	}
483 483
 
484
+	grpcBackends := []grpcrouter.Backend{}
485
+	for _, b := range []interface{}{opts.daemon, opts.buildBackend} {
486
+		if b, ok := b.(grpcrouter.Backend); ok {
487
+			grpcBackends = append(grpcBackends, b)
488
+		}
489
+	}
490
+	if len(grpcBackends) > 0 {
491
+		routers = append(routers, grpcrouter.NewRouter(grpcBackends...))
492
+	}
493
+
484 494
 	if opts.daemon.NetworkControllerEnabled() {
485 495
 		routers = append(routers, network.NewRouter(opts.daemon, opts.cluster))
486 496
 	}
... ...
@@ -3,6 +3,7 @@ package build
3 3
 import (
4 4
 	"context"
5 5
 	"io/ioutil"
6
+	"net"
6 7
 	"net/http"
7 8
 	"strings"
8 9
 	"testing"
... ...
@@ -109,7 +110,9 @@ func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost str
109 109
 	g, ctx := errgroup.WithContext(ctx)
110 110
 
111 111
 	g.Go(func() error {
112
-		return sess.Run(ctx, client.DialSession)
112
+		return sess.Run(ctx, func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
113
+			return client.DialHijack(ctx, "/session", "h2c", meta)
114
+		})
113 115
 	})
114 116
 
115 117
 	g.Go(func() error {