Skip to content

Commit c58cec8

Browse files
committed
gateway: compress frontend input payloads
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
1 parent 80fe7f0 commit c58cec8

3 files changed

Lines changed: 123 additions & 2 deletions

File tree

frontend/dockerfile/dockerfile_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ import (
1919
"runtime"
2020
"slices"
2121
"strings"
22+
"sync"
2223
"testing"
2324
"time"
2425

2526
cacheimporttypes "github.com/moby/buildkit/cache/remotecache/v1/types"
2627
"github.com/tonistiigi/fsutil"
2728
"golang.org/x/sync/errgroup"
29+
"google.golang.org/grpc"
30+
grpcgzip "google.golang.org/grpc/encoding/gzip"
31+
"google.golang.org/grpc/stats"
2832
"google.golang.org/grpc/status"
2933
"google.golang.org/protobuf/proto"
3034
"google.golang.org/protobuf/types/known/anypb"
@@ -141,6 +145,7 @@ var allTests = integration.TestFuncs(
141145
testFrontendUseForwardedSolveResults,
142146
testFrontendEvaluate,
143147
testFrontendInputs,
148+
testFrontendInputsCompressed,
144149
testErrorsSourceMap,
145150
testMultiArgs,
146151
testFrontendSubrequests,
@@ -7340,6 +7345,115 @@ COPY foo foo2
73407345
require.Equal(t, expected, actual)
73417346
}
73427347

7348+
type inputsPayloadStats struct {
7349+
mu sync.Mutex
7350+
payloads int
7351+
inCompressions []string
7352+
outCompressions []string
7353+
}
7354+
7355+
type inputsPayloadStatsMethodKey struct{}
7356+
7357+
func (s *inputsPayloadStats) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
7358+
return context.WithValue(ctx, inputsPayloadStatsMethodKey{}, info.FullMethodName)
7359+
}
7360+
7361+
func (s *inputsPayloadStats) HandleRPC(ctx context.Context, stat stats.RPCStats) {
7362+
method, _ := ctx.Value(inputsPayloadStatsMethodKey{}).(string)
7363+
if !strings.HasSuffix(method, "/LLBBridge/Inputs") {
7364+
return
7365+
}
7366+
switch stat := stat.(type) {
7367+
case *stats.InHeader:
7368+
if stat.Client {
7369+
s.mu.Lock()
7370+
defer s.mu.Unlock()
7371+
s.inCompressions = append(s.inCompressions, stat.Compression)
7372+
}
7373+
case *stats.OutHeader:
7374+
if stat.Client {
7375+
s.mu.Lock()
7376+
defer s.mu.Unlock()
7377+
s.outCompressions = append(s.outCompressions, stat.Compression)
7378+
}
7379+
case *stats.InPayload:
7380+
if stat.Client {
7381+
s.mu.Lock()
7382+
defer s.mu.Unlock()
7383+
s.payloads++
7384+
}
7385+
}
7386+
}
7387+
7388+
func (s *inputsPayloadStats) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
7389+
return ctx
7390+
}
7391+
7392+
func (s *inputsPayloadStats) HandleConn(context.Context, stats.ConnStats) {}
7393+
7394+
func (s *inputsPayloadStats) inputsPayloadCount() int {
7395+
s.mu.Lock()
7396+
defer s.mu.Unlock()
7397+
return s.payloads
7398+
}
7399+
7400+
func (s *inputsPayloadStats) inputsCompressions() (in, out []string) {
7401+
s.mu.Lock()
7402+
defer s.mu.Unlock()
7403+
return slices.Clone(s.inCompressions), slices.Clone(s.outCompressions)
7404+
}
7405+
7406+
func testFrontendInputsCompressed(t *testing.T, sb integration.Sandbox) {
7407+
integration.SkipOnPlatform(t, "windows")
7408+
f := getFrontend(t, sb)
7409+
if _, ok := f.(*clientFrontend); !ok {
7410+
t.Skip("only test with client frontend")
7411+
}
7412+
7413+
payloadStats := &inputsPayloadStats{}
7414+
c, err := client.New(sb.Context(), sb.Address(), client.WithGRPCDialOption(grpc.WithStatsHandler(payloadStats)))
7415+
require.NoError(t, err)
7416+
defer c.Close()
7417+
7418+
payload := bytes.Repeat([]byte("a"), 64<<10)
7419+
input := llb.Scratch().File(llb.Mkfile("/payload", 0600, payload))
7420+
7421+
destDir := t.TempDir()
7422+
dockerfile := []byte(`
7423+
FROM scratch
7424+
COPY payload payload
7425+
`)
7426+
dir := integration.Tmpdir(
7427+
t,
7428+
fstest.CreateFile("Dockerfile", dockerfile, 0600),
7429+
)
7430+
7431+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
7432+
Exports: []client.ExportEntry{
7433+
{
7434+
Type: client.ExporterLocal,
7435+
OutputDir: destDir,
7436+
},
7437+
},
7438+
LocalMounts: map[string]fsutil.FS{
7439+
dockerui.DefaultLocalNameDockerfile: dir,
7440+
},
7441+
FrontendInputs: map[string]llb.State{
7442+
dockerui.DefaultLocalNameContext: input,
7443+
},
7444+
}, nil)
7445+
require.NoError(t, err)
7446+
7447+
actual, err := os.ReadFile(filepath.Join(destDir, "payload"))
7448+
require.NoError(t, err)
7449+
require.Equal(t, payload, actual)
7450+
7451+
inCompressions, outCompressions := payloadStats.inputsCompressions()
7452+
require.Contains(t, outCompressions, grpcgzip.Name, "expected Inputs request to use gzip compression")
7453+
require.Contains(t, inCompressions, grpcgzip.Name, "expected Inputs response to use gzip compression")
7454+
require.NotZero(t, payloadStats.inputsPayloadCount(), "expected an Inputs response payload")
7455+
}
7456+
73437457
func testFrontendSubrequests(t *testing.T, sb integration.Sandbox) {
73447458
f := getFrontend(t, sb)
73457459
if _, ok := f.(*clientFrontend); !ok {

frontend/gateway/gateway.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
spb "google.golang.org/genproto/googleapis/rpc/status"
5858
"google.golang.org/grpc"
5959
"google.golang.org/grpc/codes"
60+
_ "google.golang.org/grpc/encoding/gzip" // register gzip compressor for gateway RPC responses
6061
"google.golang.org/grpc/health"
6162
"google.golang.org/grpc/health/grpc_health_v1"
6263
"google.golang.org/grpc/status"

frontend/gateway/grpcclient/client.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"google.golang.org/grpc"
3636
"google.golang.org/grpc/codes"
3737
"google.golang.org/grpc/credentials/insecure"
38+
"google.golang.org/grpc/encoding/gzip"
3839
"google.golang.org/grpc/status"
3940
)
4041

@@ -412,7 +413,12 @@ func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest) (res *
412413
}
413414
}
414415

415-
resp, err := c.client.Solve(ctx, req)
416+
var opts []grpc.CallOption
417+
if len(req.FrontendInputs) > 0 {
418+
opts = append(opts, grpc.UseCompressor(gzip.Name))
419+
}
420+
421+
resp, err := c.client.Solve(ctx, req, opts...)
416422
if err != nil {
417423
return nil, err
418424
}
@@ -800,7 +806,7 @@ func (c *grpcClient) Inputs(ctx context.Context) (map[string]llb.State, error) {
800806
return nil, err
801807
}
802808

803-
resp, err := c.client.Inputs(ctx, &pb.InputsRequest{})
809+
resp, err := c.client.Inputs(ctx, &pb.InputsRequest{}, grpc.UseCompressor(gzip.Name))
804810
if err != nil {
805811
return nil, err
806812
}

0 commit comments

Comments
 (0)