Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/grpc-interceptors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: grpc-interceptors

on:
push:
branches:
- master
- stable
pull_request:
branches:
- master
- stable

jobs:
grpc_interceptors_test:
name: gRPC interceptors (Go ${{ matrix.go }}, PHP ${{ matrix.php }}, OS ${{matrix.os}})
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
matrix:
php: ["8.5"]
go: [stable]
os: ["ubuntu-latest"]

steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5 # action page: <https://github.com/actions/setup-go>
with:
go-version: ${{ matrix.go }}

- name: Set up PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2 # action page: <https://github.com/shivammathur/setup-php>
with:
php-version: ${{ matrix.php }}
extensions: sockets

- name: Check out code
uses: actions/checkout@v4

- name: Get Composer Cache Directory
id: composer-cache
run: |
cd tests/php_test_files
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Init Composer Cache # Docs: <https://git.io/JfAKn#php---composer>
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: Install Composer dependencies
run: cd tests/php_test_files && composer update --prefer-dist --no-progress --ansi

- name: Init Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-

- name: Install Go dependencies
run: go mod download

- name: Run interceptors e2e test
run: cd tests && go test -timeout 20m -v -race -tags=debug grpc_interceptors_test.go
3 changes: 2 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type Config struct {
TLS *TLS `mapstructure:"tls"`

// Env is environment variables passed to the http pool
Env map[string]string `mapstructure:"env"`
Env map[string]string `mapstructure:"env"`
Interceptors []string `mapstructure:"interceptors"`

GrpcPool *pool.Config `mapstructure:"pool"`
MaxSendMsgSize int64 `mapstructure:"max_send_msg_size"`
Expand Down
22 changes: 22 additions & 0 deletions go.work.sum

Large diffs are not rendered by default.

26 changes: 19 additions & 7 deletions protoc_plugins/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:1.22-alpine as builder
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder

ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT

ENV CGO_ENABLED=0

WORKDIR /src
COPY protoc_plugins/ .
COPY protoc_plugins/go.mod protoc_plugins/go.sum ./
RUN go mod download
RUN go mod tidy

RUN go build -trimpath -ldflags "-s" -o protoc-gen-php-grpc-plugin protoc-gen-php-grpc/main.go
COPY protoc_plugins/ .

RUN set -eux; \
goarm=""; \
goamd64=""; \
if [ "${TARGETARCH:-}" = "arm" ] && [ -n "${TARGETVARIANT:-}" ]; then goarm="${TARGETVARIANT#v}"; fi; \
if [ "${TARGETARCH:-}" = "amd64" ] && [ -n "${TARGETVARIANT:-}" ]; then goamd64="${TARGETVARIANT#v}"; fi; \
GOOS="${TARGETOS:-linux}" GOARCH="${TARGETARCH:-amd64}" GOARM="${goarm}" GOAMD64="${goamd64}" \
go build -trimpath -ldflags "-s -w" -o /out/protoc-gen-php-grpc-plugin ./protoc-gen-php-grpc

FROM scratch

ARG APP_VERSION=""
ARG BUILD_TIME=""

# Runtime dependencies
LABEL org.opencontainers.image.title="protoc-gen-php-grpc"
LABEL org.opencontainers.image.description="protoc plugin for generating PHP gRPC service stubs"
LABEL org.opencontainers.image.url="https://roadrunner.dev"
LABEL org.opencontainers.image.source="https://github.com/roadrunner-server/grpc"
LABEL org.opencontainers.image.vendor="SpiralScout"
LABEL org.opencontainers.image.version="$APP_VERSION"
LABEL org.opencontainers.image.created="$BUILD_TIME"
LABEL org.opencontainers.image.version="${APP_VERSION}"
LABEL org.opencontainers.image.created="${BUILD_TIME}"
LABEL org.opencontainers.image.licenses="MIT"

COPY --from=builder /src/protoc-gen-php-grpc-plugin /
COPY --from=builder /out/protoc-gen-php-grpc-plugin /protoc-gen-php-grpc-plugin

ENTRYPOINT ["/protoc-gen-php-grpc-plugin"]
4 changes: 1 addition & 3 deletions protoc_plugins/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module github.com/roadrunner-server/grpc/protoc_plugins/v5

go 1.23

toolchain go1.23.1
go 1.26

require (
github.com/stretchr/testify v1.11.1
Expand Down
3 changes: 3 additions & 0 deletions protoc_plugins/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
17 changes: 17 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@
]
}
},
"interceptors": {
"description": "List of registered unary gRPC interceptor plugin names.",
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"examples": [
[
"sample-grpc-interceptor"
],
[
"auth-interceptor",
"ratelimit-interceptor"
]
]
},
"tls": {
"description": "GRPC TLS configuration",
"type": "object",
Expand Down
32 changes: 21 additions & 11 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,30 @@ func (p *Plugin) createGRPCserver(interceptors map[string]api.Interceptor) (*grp
grpc.UnaryServerInterceptor(p.interceptor),
}

for _, interceptor := range interceptors {
unaryInterceptors = append(
unaryInterceptors,
interceptor.UnaryServerInterceptor(),
// if we have interceptors in the config, we need to chain them with our interceptor, and add them to the server options
if len(p.config.Interceptors) > 0 && len(interceptors) > 0 {
// we need to loop backwards, since the first interceptor in the list should be the last one to execute, and the last one should be the first to execute
for i := len(p.config.Interceptors) - 1; i >= 0; i-- {
name := p.config.Interceptors[i]
if _, ok := interceptors[name]; !ok {
// we should raise an error here, since we may silently ignore let's say auth interceptor, which is critical for security
return nil, errors.E(op, errors.Str(fmt.Sprintf("interceptor %s is not registered", name)))
}

unaryInterceptors = append(
unaryInterceptors,
interceptors[name].UnaryServerInterceptor(),
)
}

opts = append(
opts,
grpc.ChainUnaryInterceptor(
unaryInterceptors...,
),
)
}

opts = append(
opts,
grpc.ChainUnaryInterceptor(
unaryInterceptors...,
),
)

opts = append(opts, grpc.StatsHandler(otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(p.tracer), otelgrpc.WithPropagators(p.prop))))
server := grpc.NewServer(opts...)

Expand Down
38 changes: 38 additions & 0 deletions tests/configs/.rr-grpc-rq-interceptors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: '3'

rpc:
listen: "tcp://127.0.0.1:6011"

server:
command: "php php_test_files/worker-grpc.php"
relay: "pipes"
relay_timeout: "20s"

logs:
mode: development
level: error

grpc:
listen: "tcp://127.0.0.1:9011"

proto:
- "proto/service/service.proto"

interceptors:
- "interceptor2"
- "interceptor1"

max_send_msg_size: 50
max_recv_msg_size: 50
max_connection_idle: 0s
max_connection_age: 0s
max_connection_age_grace: 0s
max_concurrent_streams: 10
ping_time: 1s
timeout: 200s

pool:
num_workers: 2
max_jobs: 0
allocate_timeout: 60s
destroy_timeout: 60s
2 changes: 2 additions & 0 deletions tests/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package grpc_test contains end-to-end tests for the gRPC plugin.
package grpc_test
117 changes: 117 additions & 0 deletions tests/grpc_interceptors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package grpc_test

import (
"context"
"log/slog"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"testing"
"time"

"tests/interceptor1"
"tests/interceptor2"
mocklogger "tests/mock"
"tests/proto/service"

"github.com/roadrunner-server/config/v5"
"github.com/roadrunner-server/endure/v2"
grpcPlugin "github.com/roadrunner-server/grpc/v5"
rpcPlugin "github.com/roadrunner-server/rpc/v5"
"github.com/roadrunner-server/server/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func TestGrpcInterceptors(t *testing.T) {
cont := endure.New(slog.LevelDebug)

cfg := &config.Plugin{
Version: "2023.3.0",
Path: "configs/.rr-grpc-rq-interceptors.yaml",
}

l, observed := mocklogger.ZapTestLogger(zap.DebugLevel)
err := cont.RegisterAll(
cfg,
&interceptor1.Plugin{},
&interceptor2.Plugin{},
&grpcPlugin.Plugin{},
&rpcPlugin.Plugin{},
&server.Plugin{},
l,
)
assert.NoError(t, err)

err = cont.Init()
if err != nil {
t.Fatal(err)
}

ch, err := cont.Serve()
assert.NoError(t, err)

sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

wg := &sync.WaitGroup{}
wg.Add(1)

stopCh := make(chan struct{}, 1)

go func() {
defer wg.Done()
for {
select {
case e := <-ch:
assert.Fail(t, "error", e.Error.Error())
err = cont.Stop()
if err != nil {
assert.FailNow(t, "error", err.Error())
}
case <-sig:
err = cont.Stop()
if err != nil {
assert.FailNow(t, "error", err.Error())
}
return
case <-stopCh:
err = cont.Stop()
if err != nil {
assert.FailNow(t, "error", err.Error())
}
return
}
}
}()

time.Sleep(time.Second)

conn, err := grpc.NewClient("127.0.0.1:9011", grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
require.NotNil(t, conn)

client := service.NewEchoClient(conn)
resp, err := client.Ping(context.Background(), &service.Message{Msg: "hello"})
require.NoError(t, err)
require.Equal(t, "HELLO", resp.Msg)
_ = conn.Close()

stopCh <- struct{}{}
wg.Wait()

interceptor1Log := observed.FilterMessageSnippet("interceptor1 created marker")
require.Equal(t, 1, interceptor1Log.Len())

interceptor2Log := observed.FilterMessageSnippet("interceptor2 received marker")
require.Equal(t, 1, interceptor2Log.Len())

marker, ok := interceptor2Log.All()[0].ContextMap()["marker"].(string)
require.True(t, ok)
require.True(t, strings.HasPrefix(marker, interceptor1.MarkerPrefix))
}
3 changes: 2 additions & 1 deletion tests/grpc_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,8 @@ func TestGRPCMetrics(t *testing.T) {

func sendReset(address string) func(t *testing.T) {
return func(t *testing.T) {
conn, err := net.Dial("tcp", address)
var d net.Dialer
conn, err := d.DialContext(context.Background(), "tcp", address)
assert.NoError(t, err)
client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn))
// WorkerList contains list of workers.
Expand Down
2 changes: 2 additions & 0 deletions tests/interceptor1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package interceptor1 provides a simple test interceptor used in e2e tests.
package interceptor1
Loading
Loading