snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
37
interop/xds/client/Dockerfile
Normal file
37
interop/xds/client/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright 2021 gRPC authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Dockerfile for building the xDS interop client. To build the image, run the
|
||||
# following command from grpc-go directory:
|
||||
# docker build -t <TAG> -f interop/xds/client/Dockerfile .
|
||||
|
||||
FROM golang:1.25-alpine as build
|
||||
|
||||
# Make a grpc-go directory and copy the repo into it.
|
||||
WORKDIR /go/src/grpc-go
|
||||
COPY . .
|
||||
|
||||
# Build a static binary without cgo so that we can copy just the binary in the
|
||||
# final image, and can get rid of Go compiler and gRPC-Go dependencies.
|
||||
RUN cd interop/xds/client && go build -tags osusergo,netgo .
|
||||
|
||||
# Second stage of the build which copies over only the client binary and skips
|
||||
# the Go compiler and gRPC repo from the earlier stage. This significantly
|
||||
# reduces the docker image size.
|
||||
FROM alpine
|
||||
COPY --from=build /go/src/grpc-go/interop/xds/client/client .
|
||||
ENV GRPC_GO_LOG_VERBOSITY_LEVEL=99
|
||||
ENV GRPC_GO_LOG_SEVERITY_LEVEL="info"
|
||||
ENV GRPC_GO_LOG_FORMATTER="json"
|
||||
ENTRYPOINT ["./client"]
|
||||
554
interop/xds/client/client.go
Normal file
554
interop/xds/client/client.go
Normal file
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client for xDS interop tests.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/admin"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/credentials/xds"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
_ "google.golang.org/grpc/interop/xds" // to register Custom LB.
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry/csm"
|
||||
"google.golang.org/grpc/status"
|
||||
_ "google.golang.org/grpc/xds"
|
||||
|
||||
testgrpc "google.golang.org/grpc/interop/grpc_testing"
|
||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rpcCfgs.Store([]*rpcConfig{{typ: unaryCall}})
|
||||
}
|
||||
|
||||
type statsWatcherKey struct {
|
||||
startID int32
|
||||
endID int32
|
||||
}
|
||||
|
||||
// rpcInfo contains the rpc type and the hostname where the response is received
|
||||
// from.
|
||||
type rpcInfo struct {
|
||||
typ string
|
||||
hostname string
|
||||
}
|
||||
|
||||
type statsWatcher struct {
|
||||
rpcsByPeer map[string]int32
|
||||
rpcsByType map[string]map[string]int32
|
||||
numFailures int32
|
||||
remainingRPCs int32
|
||||
chanHosts chan *rpcInfo
|
||||
}
|
||||
|
||||
func (watcher *statsWatcher) buildResp() *testpb.LoadBalancerStatsResponse {
|
||||
rpcsByType := make(map[string]*testpb.LoadBalancerStatsResponse_RpcsByPeer, len(watcher.rpcsByType))
|
||||
for t, rpcsByPeer := range watcher.rpcsByType {
|
||||
rpcsByType[t] = &testpb.LoadBalancerStatsResponse_RpcsByPeer{
|
||||
RpcsByPeer: rpcsByPeer,
|
||||
}
|
||||
}
|
||||
|
||||
return &testpb.LoadBalancerStatsResponse{
|
||||
NumFailures: watcher.numFailures + watcher.remainingRPCs,
|
||||
RpcsByPeer: watcher.rpcsByPeer,
|
||||
RpcsByMethod: rpcsByType,
|
||||
}
|
||||
}
|
||||
|
||||
type accumulatedStats struct {
|
||||
mu sync.Mutex
|
||||
numRPCsStartedByMethod map[string]int32
|
||||
numRPCsSucceededByMethod map[string]int32
|
||||
numRPCsFailedByMethod map[string]int32
|
||||
rpcStatusByMethod map[string]map[int32]int32
|
||||
}
|
||||
|
||||
func convertRPCName(in string) string {
|
||||
switch in {
|
||||
case unaryCall:
|
||||
return testpb.ClientConfigureRequest_UNARY_CALL.String()
|
||||
case emptyCall:
|
||||
return testpb.ClientConfigureRequest_EMPTY_CALL.String()
|
||||
}
|
||||
logger.Warningf("unrecognized rpc type: %s", in)
|
||||
return in
|
||||
}
|
||||
|
||||
// copyStatsMap makes a copy of the map.
|
||||
func copyStatsMap(originalMap map[string]int32) map[string]int32 {
|
||||
newMap := make(map[string]int32, len(originalMap))
|
||||
for k, v := range originalMap {
|
||||
newMap[k] = v
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// copyStatsIntMap makes a copy of the map.
|
||||
func copyStatsIntMap(originalMap map[int32]int32) map[int32]int32 {
|
||||
newMap := make(map[int32]int32, len(originalMap))
|
||||
for k, v := range originalMap {
|
||||
newMap[k] = v
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
func (as *accumulatedStats) makeStatsMap() map[string]*testpb.LoadBalancerAccumulatedStatsResponse_MethodStats {
|
||||
m := make(map[string]*testpb.LoadBalancerAccumulatedStatsResponse_MethodStats)
|
||||
for k, v := range as.numRPCsStartedByMethod {
|
||||
m[k] = &testpb.LoadBalancerAccumulatedStatsResponse_MethodStats{RpcsStarted: v}
|
||||
}
|
||||
for k, v := range as.rpcStatusByMethod {
|
||||
if m[k] == nil {
|
||||
m[k] = &testpb.LoadBalancerAccumulatedStatsResponse_MethodStats{}
|
||||
}
|
||||
m[k].Result = copyStatsIntMap(v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (as *accumulatedStats) buildResp() *testpb.LoadBalancerAccumulatedStatsResponse {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
return &testpb.LoadBalancerAccumulatedStatsResponse{
|
||||
NumRpcsStartedByMethod: copyStatsMap(as.numRPCsStartedByMethod),
|
||||
NumRpcsSucceededByMethod: copyStatsMap(as.numRPCsSucceededByMethod),
|
||||
NumRpcsFailedByMethod: copyStatsMap(as.numRPCsFailedByMethod),
|
||||
StatsPerMethod: as.makeStatsMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (as *accumulatedStats) startRPC(rpcType string) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
as.numRPCsStartedByMethod[convertRPCName(rpcType)]++
|
||||
}
|
||||
|
||||
func (as *accumulatedStats) finishRPC(rpcType string, err error) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
name := convertRPCName(rpcType)
|
||||
if as.rpcStatusByMethod[name] == nil {
|
||||
as.rpcStatusByMethod[name] = make(map[int32]int32)
|
||||
}
|
||||
as.rpcStatusByMethod[name][int32(status.Convert(err).Code())]++
|
||||
if err != nil {
|
||||
as.numRPCsFailedByMethod[name]++
|
||||
return
|
||||
}
|
||||
as.numRPCsSucceededByMethod[name]++
|
||||
}
|
||||
|
||||
var (
|
||||
failOnFailedRPC = flag.Bool("fail_on_failed_rpc", false, "Fail client if any RPCs fail after first success")
|
||||
numChannels = flag.Int("num_channels", 1, "Num of channels")
|
||||
printResponse = flag.Bool("print_response", false, "Write RPC response to stdout")
|
||||
qps = flag.Int("qps", 1, "QPS per channel, for each type of RPC")
|
||||
rpc = flag.String("rpc", "UnaryCall", "Types of RPCs to make, ',' separated string. RPCs can be EmptyCall or UnaryCall. Deprecated: Use Configure RPC to XdsUpdateClientConfigureServiceServer instead.")
|
||||
rpcMetadata = flag.String("metadata", "", "The metadata to send with RPC, in format EmptyCall:key1:value1,UnaryCall:key2:value2. Deprecated: Use Configure RPC to XdsUpdateClientConfigureServiceServer instead.")
|
||||
rpcTimeout = flag.Duration("rpc_timeout", 20*time.Second, "Per RPC timeout")
|
||||
server = flag.String("server", "localhost:8080", "Address of server to connect to")
|
||||
statsPort = flag.Int("stats_port", 8081, "Port to expose peer distribution stats service")
|
||||
secureMode = flag.Bool("secure_mode", false, "If true, retrieve security configuration from the management server. Else, use insecure credentials.")
|
||||
enableCSMObservability = flag.Bool("enable_csm_observability", false, "Whether to enable CSM Observability")
|
||||
requestPayloadSize = flag.Int("request_payload_size", 0, "Ask the server to respond with SimpleResponse.payload.body of the given length (may not be implemented on the server).")
|
||||
responsePayloadSize = flag.Int("response_payload_size", 0, "Ask the server to respond with SimpleResponse.payload.body of the given length (may not be implemented on the server).")
|
||||
|
||||
rpcCfgs atomic.Value
|
||||
|
||||
mu sync.Mutex
|
||||
currentRequestID int32
|
||||
watchers = make(map[statsWatcherKey]*statsWatcher)
|
||||
|
||||
accStats = accumulatedStats{
|
||||
numRPCsStartedByMethod: make(map[string]int32),
|
||||
numRPCsSucceededByMethod: make(map[string]int32),
|
||||
numRPCsFailedByMethod: make(map[string]int32),
|
||||
rpcStatusByMethod: make(map[string]map[int32]int32),
|
||||
}
|
||||
|
||||
// 0 or 1 representing an RPC has succeeded. Use hasRPCSucceeded and
|
||||
// setRPCSucceeded to access in a safe manner.
|
||||
rpcSucceeded uint32
|
||||
|
||||
logger = grpclog.Component("interop")
|
||||
)
|
||||
|
||||
type statsService struct {
|
||||
testgrpc.UnimplementedLoadBalancerStatsServiceServer
|
||||
}
|
||||
|
||||
func hasRPCSucceeded() bool {
|
||||
return atomic.LoadUint32(&rpcSucceeded) > 0
|
||||
}
|
||||
|
||||
func setRPCSucceeded() {
|
||||
atomic.StoreUint32(&rpcSucceeded, 1)
|
||||
}
|
||||
|
||||
// Wait for the next LoadBalancerStatsRequest.GetNumRpcs to start and complete,
|
||||
// and return the distribution of remote peers. This is essentially a clientside
|
||||
// LB reporting mechanism that is designed to be queried by an external test
|
||||
// driver when verifying that the client is distributing RPCs as expected.
|
||||
func (s *statsService) GetClientStats(ctx context.Context, in *testpb.LoadBalancerStatsRequest) (*testpb.LoadBalancerStatsResponse, error) {
|
||||
mu.Lock()
|
||||
watcherKey := statsWatcherKey{currentRequestID, currentRequestID + in.GetNumRpcs()}
|
||||
watcher, ok := watchers[watcherKey]
|
||||
if !ok {
|
||||
watcher = &statsWatcher{
|
||||
rpcsByPeer: make(map[string]int32),
|
||||
rpcsByType: make(map[string]map[string]int32),
|
||||
numFailures: 0,
|
||||
remainingRPCs: in.GetNumRpcs(),
|
||||
chanHosts: make(chan *rpcInfo),
|
||||
}
|
||||
watchers[watcherKey] = watcher
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(in.GetTimeoutSec())*time.Second)
|
||||
defer cancel()
|
||||
|
||||
defer func() {
|
||||
mu.Lock()
|
||||
delete(watchers, watcherKey)
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
// Wait until the requested RPCs have all been recorded or timeout occurs.
|
||||
for {
|
||||
select {
|
||||
case info := <-watcher.chanHosts:
|
||||
if info != nil {
|
||||
watcher.rpcsByPeer[info.hostname]++
|
||||
|
||||
rpcsByPeerForType := watcher.rpcsByType[info.typ]
|
||||
if rpcsByPeerForType == nil {
|
||||
rpcsByPeerForType = make(map[string]int32)
|
||||
watcher.rpcsByType[info.typ] = rpcsByPeerForType
|
||||
}
|
||||
rpcsByPeerForType[info.hostname]++
|
||||
} else {
|
||||
watcher.numFailures++
|
||||
}
|
||||
watcher.remainingRPCs--
|
||||
if watcher.remainingRPCs == 0 {
|
||||
return watcher.buildResp(), nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
logger.Info("Timed out, returning partial stats")
|
||||
return watcher.buildResp(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statsService) GetClientAccumulatedStats(context.Context, *testpb.LoadBalancerAccumulatedStatsRequest) (*testpb.LoadBalancerAccumulatedStatsResponse, error) {
|
||||
return accStats.buildResp(), nil
|
||||
}
|
||||
|
||||
type configureService struct {
|
||||
testgrpc.UnimplementedXdsUpdateClientConfigureServiceServer
|
||||
}
|
||||
|
||||
func (s *configureService) Configure(_ context.Context, in *testpb.ClientConfigureRequest) (*testpb.ClientConfigureResponse, error) {
|
||||
rpcsToMD := make(map[testpb.ClientConfigureRequest_RpcType][]string)
|
||||
for _, typ := range in.GetTypes() {
|
||||
rpcsToMD[typ] = nil
|
||||
}
|
||||
for _, md := range in.GetMetadata() {
|
||||
typ := md.GetType()
|
||||
strs, ok := rpcsToMD[typ]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rpcsToMD[typ] = append(strs, md.GetKey(), md.GetValue())
|
||||
}
|
||||
cfgs := make([]*rpcConfig, 0, len(rpcsToMD))
|
||||
for typ, md := range rpcsToMD {
|
||||
var rpcType string
|
||||
switch typ {
|
||||
case testpb.ClientConfigureRequest_UNARY_CALL:
|
||||
rpcType = unaryCall
|
||||
case testpb.ClientConfigureRequest_EMPTY_CALL:
|
||||
rpcType = emptyCall
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported RPC type: %v", typ)
|
||||
}
|
||||
cfgs = append(cfgs, &rpcConfig{
|
||||
typ: rpcType,
|
||||
md: metadata.Pairs(md...),
|
||||
timeout: in.GetTimeoutSec(),
|
||||
})
|
||||
}
|
||||
rpcCfgs.Store(cfgs)
|
||||
return &testpb.ClientConfigureResponse{}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
unaryCall string = "UnaryCall"
|
||||
emptyCall string = "EmptyCall"
|
||||
)
|
||||
|
||||
func parseRPCTypes(rpcStr string) []string {
|
||||
if len(rpcStr) == 0 {
|
||||
return []string{unaryCall}
|
||||
}
|
||||
|
||||
rpcs := strings.Split(rpcStr, ",")
|
||||
ret := make([]string, 0, len(rpcStr))
|
||||
for _, r := range rpcs {
|
||||
switch r {
|
||||
case unaryCall, emptyCall:
|
||||
ret = append(ret, r)
|
||||
default:
|
||||
flag.PrintDefaults()
|
||||
log.Fatalf("unsupported RPC type: %v", r)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type rpcConfig struct {
|
||||
typ string
|
||||
md metadata.MD
|
||||
timeout int32
|
||||
}
|
||||
|
||||
// parseRPCMetadata turns EmptyCall:key1:value1 into
|
||||
//
|
||||
// {typ: emptyCall, md: {key1:value1}}.
|
||||
func parseRPCMetadata(rpcMetadataStr string, rpcs []string) []*rpcConfig {
|
||||
rpcMetadataSplit := strings.Split(rpcMetadataStr, ",")
|
||||
rpcsToMD := make(map[string][]string)
|
||||
for _, rm := range rpcMetadataSplit {
|
||||
rmSplit := strings.Split(rm, ":")
|
||||
if len(rmSplit)%2 != 1 {
|
||||
log.Fatalf("invalid metadata config %v, want EmptyCall:key1:value1", rm)
|
||||
}
|
||||
rpcsToMD[rmSplit[0]] = append(rpcsToMD[rmSplit[0]], rmSplit[1:]...)
|
||||
}
|
||||
ret := make([]*rpcConfig, 0, len(rpcs))
|
||||
for _, rpcT := range rpcs {
|
||||
rpcC := &rpcConfig{
|
||||
typ: rpcT,
|
||||
}
|
||||
if md := rpcsToMD[string(rpcT)]; len(md) > 0 {
|
||||
rpcC.md = metadata.Pairs(md...)
|
||||
}
|
||||
ret = append(ret, rpcC)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *enableCSMObservability {
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
provider := metric.NewMeterProvider(
|
||||
metric.WithReader(exporter),
|
||||
)
|
||||
var addr string
|
||||
var ok bool
|
||||
if addr, ok = os.LookupEnv("OTEL_EXPORTER_PROMETHEUS_HOST"); !ok {
|
||||
addr = ""
|
||||
}
|
||||
var port string
|
||||
if port, ok = os.LookupEnv("OTEL_EXPORTER_PROMETHEUS_PORT"); !ok {
|
||||
port = "9464"
|
||||
}
|
||||
go func() {
|
||||
if err := http.ListenAndServe(addr+":"+port, promhttp.Handler()); err != nil {
|
||||
logger.Fatalf("error listening: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
cleanup := csm.EnableObservability(ctx, opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}})
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
rpcCfgs.Store(parseRPCMetadata(*rpcMetadata, parseRPCTypes(*rpc)))
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *statsPort))
|
||||
if err != nil {
|
||||
logger.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
defer s.Stop()
|
||||
testgrpc.RegisterLoadBalancerStatsServiceServer(s, &statsService{})
|
||||
testgrpc.RegisterXdsUpdateClientConfigureServiceServer(s, &configureService{})
|
||||
reflection.Register(s)
|
||||
cleanup, err := admin.Register(s)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to register admin: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
go s.Serve(lis)
|
||||
|
||||
creds := insecure.NewCredentials()
|
||||
if *secureMode {
|
||||
var err error
|
||||
creds, err = xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create xDS credentials: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
clients := make([]testgrpc.TestServiceClient, *numChannels)
|
||||
for i := 0; i < *numChannels; i++ {
|
||||
conn, err := grpc.NewClient(*server, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
logger.Fatalf("grpc.NewClient(%q) = %v", *server, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
clients[i] = testgrpc.NewTestServiceClient(conn)
|
||||
}
|
||||
ticker := time.NewTicker(time.Second / time.Duration(*qps**numChannels))
|
||||
defer ticker.Stop()
|
||||
sendRPCs(clients, ticker)
|
||||
}
|
||||
|
||||
func makeOneRPC(c testgrpc.TestServiceClient, cfg *rpcConfig) (*peer.Peer, *rpcInfo, error) {
|
||||
timeout := *rpcTimeout
|
||||
if cfg.timeout != 0 {
|
||||
timeout = time.Duration(cfg.timeout) * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if len(cfg.md) != 0 {
|
||||
ctx = metadata.NewOutgoingContext(ctx, cfg.md)
|
||||
}
|
||||
info := rpcInfo{typ: cfg.typ}
|
||||
|
||||
var (
|
||||
p peer.Peer
|
||||
header metadata.MD
|
||||
err error
|
||||
)
|
||||
accStats.startRPC(cfg.typ)
|
||||
switch cfg.typ {
|
||||
case unaryCall:
|
||||
sr := &testpb.SimpleRequest{FillServerId: true}
|
||||
if *requestPayloadSize > 0 {
|
||||
sr.Payload = &testpb.Payload{Body: make([]byte, *requestPayloadSize)}
|
||||
}
|
||||
if *responsePayloadSize > 0 {
|
||||
sr.ResponseSize = int32(*responsePayloadSize)
|
||||
}
|
||||
sr.ResponseSize = int32(*responsePayloadSize)
|
||||
var resp *testpb.SimpleResponse
|
||||
resp, err = c.UnaryCall(ctx, sr, grpc.Peer(&p), grpc.Header(&header))
|
||||
// For UnaryCall, also read hostname from response, in case the server
|
||||
// isn't updated to send headers.
|
||||
if resp != nil {
|
||||
info.hostname = resp.Hostname
|
||||
}
|
||||
case emptyCall:
|
||||
_, err = c.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p), grpc.Header(&header))
|
||||
}
|
||||
accStats.finishRPC(cfg.typ, err)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
hosts := header["hostname"]
|
||||
if len(hosts) > 0 {
|
||||
info.hostname = hosts[0]
|
||||
}
|
||||
return &p, &info, err
|
||||
}
|
||||
|
||||
func sendRPCs(clients []testgrpc.TestServiceClient, ticker *time.Ticker) {
|
||||
var i int
|
||||
for range ticker.C {
|
||||
// Get and increment request ID, and save a list of watchers that are
|
||||
// interested in this RPC.
|
||||
mu.Lock()
|
||||
savedRequestID := currentRequestID
|
||||
currentRequestID++
|
||||
savedWatchers := []*statsWatcher{}
|
||||
for key, value := range watchers {
|
||||
if key.startID <= savedRequestID && savedRequestID < key.endID {
|
||||
savedWatchers = append(savedWatchers, value)
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// Get the RPC metadata configurations from the Configure RPC.
|
||||
cfgs := rpcCfgs.Load().([]*rpcConfig)
|
||||
|
||||
c := clients[i]
|
||||
for _, cfg := range cfgs {
|
||||
go func(cfg *rpcConfig) {
|
||||
p, info, err := makeOneRPC(c, cfg)
|
||||
|
||||
for _, watcher := range savedWatchers {
|
||||
// This sends an empty string if the RPC failed.
|
||||
watcher.chanHosts <- info
|
||||
}
|
||||
if err != nil && *failOnFailedRPC && hasRPCSucceeded() {
|
||||
logger.Fatalf("RPC failed: %v", err)
|
||||
}
|
||||
if err == nil {
|
||||
setRPCSucceeded()
|
||||
}
|
||||
if *printResponse {
|
||||
if err == nil {
|
||||
if cfg.typ == unaryCall {
|
||||
// Need to keep this format, because some tests are
|
||||
// relying on stdout.
|
||||
fmt.Printf("Greeting: Hello world, this is %s, from %v\n", info.hostname, p.Addr)
|
||||
} else {
|
||||
fmt.Printf("RPC %q, from host %s, addr %v\n", cfg.typ, info.hostname, p.Addr)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("RPC %q, failed with %v\n", cfg.typ, err)
|
||||
}
|
||||
}
|
||||
}(cfg)
|
||||
}
|
||||
i = (i + 1) % len(clients)
|
||||
}
|
||||
}
|
||||
140
interop/xds/custom_lb.go
Normal file
140
interop/xds/custom_lb.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package xds contains various xds interop helpers for usage in interop tests.
|
||||
package xds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/balancer/roundrobin"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
func init() {
|
||||
balancer.Register(rpcBehaviorBB{})
|
||||
}
|
||||
|
||||
const name = "test.RpcBehaviorLoadBalancer"
|
||||
|
||||
type rpcBehaviorBB struct{}
|
||||
|
||||
func (rpcBehaviorBB) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
|
||||
b := &rpcBehaviorLB{
|
||||
ClientConn: cc,
|
||||
}
|
||||
// round_robin child to complete balancer tree with a usable leaf policy and
|
||||
// have RPCs actually work.
|
||||
builder := balancer.Get(roundrobin.Name)
|
||||
if builder == nil {
|
||||
// Shouldn't happen, defensive programming. Registered from import of
|
||||
// roundrobin package.
|
||||
return nil
|
||||
}
|
||||
rr := builder.Build(b, bOpts)
|
||||
if rr == nil {
|
||||
// Shouldn't happen, defensive programming.
|
||||
return nil
|
||||
}
|
||||
b.Balancer = rr
|
||||
return b
|
||||
}
|
||||
|
||||
func (rpcBehaviorBB) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||
lbCfg := &lbConfig{}
|
||||
if err := json.Unmarshal(s, lbCfg); err != nil {
|
||||
return nil, fmt.Errorf("rpc-behavior-lb: unable to marshal lbConfig: %s, error: %v", string(s), err)
|
||||
}
|
||||
return lbCfg, nil
|
||||
|
||||
}
|
||||
|
||||
func (rpcBehaviorBB) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
type lbConfig struct {
|
||||
serviceconfig.LoadBalancingConfig `json:"-"`
|
||||
RPCBehavior string `json:"rpcBehavior,omitempty"`
|
||||
}
|
||||
|
||||
// rpcBehaviorLB is a load balancer that wraps a round robin balancer and
|
||||
// appends the rpc-behavior metadata field to any metadata in pick results based
|
||||
// on what is specified in configuration.
|
||||
type rpcBehaviorLB struct {
|
||||
// embed a ClientConn to wrap only UpdateState() operation
|
||||
balancer.ClientConn
|
||||
// embed a Balancer to wrap only UpdateClientConnState() operation
|
||||
balancer.Balancer
|
||||
|
||||
mu sync.Mutex
|
||||
cfg *lbConfig
|
||||
}
|
||||
|
||||
func (b *rpcBehaviorLB) UpdateClientConnState(s balancer.ClientConnState) error {
|
||||
lbCfg, ok := s.BalancerConfig.(*lbConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("test.RpcBehaviorLoadBalancer:received config with unexpected type %T: %s", s.BalancerConfig, pretty.ToJSON(s.BalancerConfig))
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.cfg = lbCfg
|
||||
b.mu.Unlock()
|
||||
return b.Balancer.UpdateClientConnState(balancer.ClientConnState{
|
||||
ResolverState: s.ResolverState,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *rpcBehaviorLB) UpdateState(state balancer.State) {
|
||||
b.mu.Lock()
|
||||
rpcBehavior := b.cfg.RPCBehavior
|
||||
b.mu.Unlock()
|
||||
|
||||
b.ClientConn.UpdateState(balancer.State{
|
||||
ConnectivityState: state.ConnectivityState,
|
||||
Picker: newRPCBehaviorPicker(state.Picker, rpcBehavior),
|
||||
})
|
||||
}
|
||||
|
||||
// rpcBehaviorPicker wraps a picker and adds the rpc-behavior metadata field
|
||||
// into the child pick result's metadata.
|
||||
type rpcBehaviorPicker struct {
|
||||
childPicker balancer.Picker
|
||||
rpcBehavior string
|
||||
}
|
||||
|
||||
// Pick appends the rpc-behavior metadata entry to the pick result of the child.
|
||||
func (p *rpcBehaviorPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
|
||||
pr, err := p.childPicker.Pick(info)
|
||||
if err != nil {
|
||||
return balancer.PickResult{}, err
|
||||
}
|
||||
pr.Metadata = metadata.Join(pr.Metadata, metadata.Pairs("rpc-behavior", p.rpcBehavior))
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func newRPCBehaviorPicker(childPicker balancer.Picker, rpcBehavior string) *rpcBehaviorPicker {
|
||||
return &rpcBehaviorPicker{
|
||||
childPicker: childPicker,
|
||||
rpcBehavior: rpcBehavior,
|
||||
}
|
||||
}
|
||||
135
interop/xds/custom_lb_test.go
Normal file
135
interop/xds/custom_lb_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package xds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
"google.golang.org/grpc/internal/stubserver"
|
||||
"google.golang.org/grpc/internal/testutils"
|
||||
testgrpc "google.golang.org/grpc/interop/grpc_testing"
|
||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
var defaultTestTimeout = 5 * time.Second
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// TestCustomLB tests the Custom LB for the interop client. It configures the
|
||||
// custom lb as the top level Load Balancing policy of the channel, then asserts
|
||||
// it can successfully make an RPC and also that the rpc behavior the Custom LB
|
||||
// is configured with makes it's way to the server in metadata.
|
||||
func (s) TestCustomLB(t *testing.T) {
|
||||
errCh := testutils.NewChannel()
|
||||
// Setup a backend which verifies the expected rpc-behavior metadata is
|
||||
// present in the request.
|
||||
backend := &stubserver.StubServer{
|
||||
UnaryCallF: func(ctx context.Context, _ *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
errCh.Send(errors.New("failed to receive metadata"))
|
||||
return &testpb.SimpleResponse{}, nil
|
||||
}
|
||||
rpcBMD := md.Get("rpc-behavior")
|
||||
if len(rpcBMD) != 1 {
|
||||
errCh.Send(fmt.Errorf("received %d values for metadata key \"rpc-behavior\", want 1", len(rpcBMD)))
|
||||
return &testpb.SimpleResponse{}, nil
|
||||
}
|
||||
wantVal := "error-code-0"
|
||||
if rpcBMD[0] != wantVal {
|
||||
errCh.Send(fmt.Errorf("metadata val for key \"rpc-behavior\": got val %v, want val %v", rpcBMD[0], wantVal))
|
||||
return &testpb.SimpleResponse{}, nil
|
||||
}
|
||||
// Success.
|
||||
errCh.Send(nil)
|
||||
return &testpb.SimpleResponse{}, nil
|
||||
},
|
||||
}
|
||||
if err := backend.StartServer(); err != nil {
|
||||
t.Fatalf("Failed to start backend: %v", err)
|
||||
}
|
||||
t.Logf("Started good TestService backend at: %q", backend.Address)
|
||||
defer backend.Stop()
|
||||
|
||||
lbCfgJSON := `{
|
||||
"loadBalancingConfig": [
|
||||
{
|
||||
"test.RpcBehaviorLoadBalancer": {
|
||||
"rpcBehavior": "error-code-0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lbCfgJSON)
|
||||
mr := manual.NewBuilderWithScheme("customlb-e2e")
|
||||
defer mr.Close()
|
||||
mr.InitialState(resolver.State{
|
||||
Addresses: []resolver.Address{
|
||||
{Addr: backend.Address},
|
||||
},
|
||||
ServiceConfig: sc,
|
||||
})
|
||||
|
||||
cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
t.Fatalf("grpc.NewClient() failed: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
testServiceClient := testgrpc.NewTestServiceClient(cc)
|
||||
|
||||
// Make a Unary RPC. This RPC should be successful due to the round_robin
|
||||
// leaf balancer. Also, the custom load balancer should inject the
|
||||
// "rpc-behavior" string it is configured with into the metadata sent to
|
||||
// server.
|
||||
if _, err := testServiceClient.UnaryCall(ctx, &testpb.SimpleRequest{}); err != nil {
|
||||
t.Fatalf("EmptyCall() failed: %v", err)
|
||||
}
|
||||
|
||||
val, err := errCh.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("error receiving from errCh: %v", err)
|
||||
}
|
||||
|
||||
// Should receive nil on the error channel which implies backend verified it
|
||||
// correctly received the correct "rpc-behavior" metadata.
|
||||
if err, ok := val.(error); ok {
|
||||
t.Fatalf("error in backend verifications on metadata received: %v", err)
|
||||
}
|
||||
}
|
||||
56
interop/xds/go.mod
Normal file
56
interop/xds/go.mod
Normal file
@@ -0,0 +1,56 @@
|
||||
module google.golang.org/grpc/interop/xds
|
||||
|
||||
go 1.25.0
|
||||
|
||||
replace google.golang.org/grpc => ../..
|
||||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.24.1
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.67.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0
|
||||
google.golang.org/grpc v1.83.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.3 // indirect
|
||||
cloud.google.com/go/auth v0.23.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.23.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.70.1 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.45.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect
|
||||
go.opentelemetry.io/otel v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.45.0 // indirect
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/api v0.293.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
)
|
||||
119
interop/xds/go.sum
Normal file
119
interop/xds/go.sum
Normal file
@@ -0,0 +1,119 @@
|
||||
cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0=
|
||||
cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go/auth v0.23.1 h1:1tPpBPG02lQHmoiAvs9egyCASqXP0xgobptjZzov/Jg=
|
||||
cloud.google.com/go/auth v0.23.1/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0 h1:bN1gA3of5bXtbnLsRPrwfmbbe7A5UWFlcTHseujLnpc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0/go.mod h1:Yj5vHEz/aAepZGliRJsA6uvHAVAQyEwajq9ORCHPxzM=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be h1:SWe0x6yfglnxuvOiYgTTnNq7QD/yvqthOh1RBI8Bj8w=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be/go.mod h1:PYEOlng9XcrulfyWpm49jECTPV0LT4q8cO7fLW/xwgk=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
||||
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
|
||||
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
|
||||
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
||||
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
|
||||
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.45.0 h1:9jR0ZPRok9ryaOQ2Wx8rg5F7Aon59mxrqbVI60/vlBk=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.45.0/go.mod h1:VSme3o2fvSg5bVg0dRzyHaj4Z5EVhG+g2Fde6LKzmQA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04=
|
||||
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
|
||||
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.67.0 h1:7IefDa35e6V3NoiqIeLDMDxMFyZDk5qcoC0Ax4cC16E=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.67.0/go.mod h1:nsPI1awTg5Vmg1YrommL2mVarVGlqc4yXOoKAkPRD0c=
|
||||
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
|
||||
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
|
||||
go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns=
|
||||
go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
|
||||
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
|
||||
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.293.0 h1:p9XIWOf63U4OgYx120ZwVU8+vl4XTPmWfgVPnmOAS9w=
|
||||
google.golang.org/api v0.293.0/go.mod h1:6n5tjEB1gzwniZTepZ0g5u+wM7Bof5GeULCx/zh8ZE0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 h1:lrupDmKL3p5kEX1M92oan027eCKcouzjuPbH6YBK+Rs=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
37
interop/xds/server/Dockerfile
Normal file
37
interop/xds/server/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright 2021 gRPC authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Dockerfile for building the xDS interop server. To build the image, run the
|
||||
# following command from grpc-go directory:
|
||||
# docker build -t <TAG> -f interop/xds/server/Dockerfile .
|
||||
|
||||
FROM golang:1.25-alpine as build
|
||||
|
||||
# Make a grpc-go directory and copy the repo into it.
|
||||
WORKDIR /go/src/grpc-go
|
||||
COPY . .
|
||||
|
||||
# Build a static binary without cgo so that we can copy just the binary in the
|
||||
# final image, and can get rid of the Go compiler and gRPC-Go dependencies.
|
||||
RUN cd interop/xds/server && go build -tags osusergo,netgo .
|
||||
|
||||
# Second stage of the build which copies over only the server binary and skips
|
||||
# the Go compiler and gRPC repo from the earlier stage. This significantly
|
||||
# reduces the docker image size.
|
||||
FROM alpine
|
||||
COPY --from=build /go/src/grpc-go/interop/xds/server/server .
|
||||
ENV GRPC_GO_LOG_VERBOSITY_LEVEL=99
|
||||
ENV GRPC_GO_LOG_SEVERITY_LEVEL="info"
|
||||
ENV GRPC_GO_LOG_FORMATTER="json"
|
||||
ENTRYPOINT ["./server"]
|
||||
342
interop/xds/server/server.go
Normal file
342
interop/xds/server/server.go
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is the server used for xDS interop tests.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/admin"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry/csm"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/grpc/xds"
|
||||
|
||||
xdscreds "google.golang.org/grpc/credentials/xds"
|
||||
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
testgrpc "google.golang.org/grpc/interop/grpc_testing"
|
||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 8080, "Listening port for test service")
|
||||
maintenancePort = flag.Int("maintenance_port", 8081, "Listening port for maintenance services like health, reflection, channelz etc when -secure_mode is true. When -secure_mode is false, all these services will be registered on -port")
|
||||
serverID = flag.String("server_id", "go_server", "Server ID included in response")
|
||||
secureMode = flag.Bool("secure_mode", false, "If true, retrieve security configuration from the management server. Else, use insecure credentials.")
|
||||
hostNameOverride = flag.String("host_name_override", "", "If set, use this as the hostname instead of the real hostname")
|
||||
enableCSMObservability = flag.Bool("enable_csm_observability", false, "Whether to enable CSM Observability")
|
||||
|
||||
logger = grpclog.Component("interop")
|
||||
)
|
||||
|
||||
const (
|
||||
rpcBehaviorMDKey = "rpc-behavior"
|
||||
grpcPreviousRPCAttemptsMDKey = "grpc-previous-rpc-attempts"
|
||||
sleepPfx = "sleep-"
|
||||
keepOpenVal = "keep-open"
|
||||
errorCodePfx = "error-code-"
|
||||
succeedOnRetryPfx = "succeed-on-retry-attempt-"
|
||||
hostnamePfx = "hostname="
|
||||
)
|
||||
|
||||
func getHostname() string {
|
||||
if *hostNameOverride != "" {
|
||||
return *hostNameOverride
|
||||
}
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get hostname: %v", err)
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
|
||||
// testServiceImpl provides an implementation of the TestService defined in
|
||||
// grpc.testing package.
|
||||
type testServiceImpl struct {
|
||||
testgrpc.UnimplementedTestServiceServer
|
||||
hostname string
|
||||
serverID string
|
||||
}
|
||||
|
||||
func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
|
||||
grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
|
||||
return &testpb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *testServiceImpl) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||
response := &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}
|
||||
if in.ResponseSize > 0 {
|
||||
response.Payload = &testpb.Payload{
|
||||
Body: make([]byte, in.ResponseSize),
|
||||
}
|
||||
}
|
||||
|
||||
forLoop:
|
||||
for _, headerVal := range getRPCBehaviorMetadata(ctx) {
|
||||
// A value can have a prefix "hostname=<string>" followed by a space.
|
||||
// In that case, the rest of the value should only be applied
|
||||
// if the specified hostname matches the server's hostname.
|
||||
if strings.HasPrefix(headerVal, hostnamePfx) {
|
||||
splitVal := strings.Split(headerVal, " ")
|
||||
if len(splitVal) <= 1 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'hostname=<string> <header>=<value>' instead", headerVal)
|
||||
}
|
||||
|
||||
if s.hostname != splitVal[0][len(hostnamePfx):] {
|
||||
continue forLoop
|
||||
}
|
||||
headerVal = splitVal[1]
|
||||
}
|
||||
|
||||
switch {
|
||||
// If the value matches "sleep-<int>", the server should wait
|
||||
// the specified number of seconds before resuming
|
||||
// behavior matching and RPC processing.
|
||||
case strings.HasPrefix(headerVal, sleepPfx):
|
||||
sleep, err := strconv.Atoi(headerVal[len(sleepPfx):])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'sleep-<int>' instead", headerVal)
|
||||
}
|
||||
time.Sleep(time.Duration(sleep) * time.Second)
|
||||
|
||||
// If the value matches "keep-open", the server should
|
||||
// never respond to the request and behavior matching ends.
|
||||
case strings.HasPrefix(headerVal, keepOpenVal):
|
||||
<-ctx.Done()
|
||||
return nil, nil
|
||||
|
||||
// If the value matches "error-code-<int>", the server should
|
||||
// respond with the specified status code and behavior matching ends.
|
||||
case strings.HasPrefix(headerVal, errorCodePfx):
|
||||
code, err := strconv.Atoi(headerVal[len(errorCodePfx):])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'error-code-<int>' instead", headerVal)
|
||||
}
|
||||
return nil, status.Errorf(codes.Code(code), "rpc failed as per the rpc-behavior header value: %v", headerVal)
|
||||
|
||||
// If the value matches "success-on-retry-attempt-<int>", and the
|
||||
// value of the "grpc-previous-rpc-attempts" metadata field is equal to
|
||||
// the specified number, the normal RPC processing should resume
|
||||
// and behavior matching ends.
|
||||
case strings.HasPrefix(headerVal, succeedOnRetryPfx):
|
||||
wantRetry, err := strconv.Atoi(headerVal[len(succeedOnRetryPfx):])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'success-on-retry-attempt-<int>' instead", headerVal)
|
||||
}
|
||||
|
||||
mdRetry := getMetadataValues(ctx, grpcPreviousRPCAttemptsMDKey)
|
||||
curRetry, err := strconv.Atoi(mdRetry[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid format for grpc-previous-rpc-attempts header: %v", mdRetry[0])
|
||||
}
|
||||
|
||||
if curRetry == wantRetry {
|
||||
break forLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
|
||||
return response, status.Error(codes.OK, "")
|
||||
}
|
||||
|
||||
func getRPCBehaviorMetadata(ctx context.Context) []string {
|
||||
mdRPCBehavior := getMetadataValues(ctx, rpcBehaviorMDKey)
|
||||
var rpcBehaviorMetadata []string
|
||||
for _, mdVal := range mdRPCBehavior {
|
||||
splitVals := strings.Split(mdVal, ",")
|
||||
|
||||
for _, val := range splitVals {
|
||||
headerVal := strings.TrimSpace(val)
|
||||
if headerVal == "" {
|
||||
continue
|
||||
}
|
||||
rpcBehaviorMetadata = append(rpcBehaviorMetadata, headerVal)
|
||||
}
|
||||
}
|
||||
return rpcBehaviorMetadata
|
||||
}
|
||||
|
||||
func getMetadataValues(ctx context.Context, metadataKey string) []string {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
logger.Error("Failed to retrieve metadata from incoming RPC context")
|
||||
return nil
|
||||
}
|
||||
return md.Get(metadataKey)
|
||||
}
|
||||
|
||||
// xdsUpdateHealthServiceImpl provides an implementation of the
|
||||
// XdsUpdateHealthService defined in grpc.testing package.
|
||||
type xdsUpdateHealthServiceImpl struct {
|
||||
testgrpc.UnimplementedXdsUpdateHealthServiceServer
|
||||
healthServer *health.Server
|
||||
}
|
||||
|
||||
func (x *xdsUpdateHealthServiceImpl) SetServing(_ context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
|
||||
x.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
|
||||
return &testpb.Empty{}, nil
|
||||
|
||||
}
|
||||
|
||||
func (x *xdsUpdateHealthServiceImpl) SetNotServing(_ context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
|
||||
x.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING)
|
||||
return &testpb.Empty{}, nil
|
||||
}
|
||||
|
||||
func xdsServingModeCallback(addr net.Addr, args xds.ServingModeChangeArgs) {
|
||||
logger.Infof("Serving mode callback for xDS server at %q invoked with mode: %q, err: %v", addr.String(), args.Mode, args.Err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *enableCSMObservability {
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
var addr string
|
||||
var ok bool
|
||||
if addr, ok = os.LookupEnv("OTEL_EXPORTER_PROMETHEUS_HOST"); !ok {
|
||||
addr = ""
|
||||
}
|
||||
var port string
|
||||
if port, ok = os.LookupEnv("OTEL_EXPORTER_PROMETHEUS_PORT"); !ok {
|
||||
port = "9464"
|
||||
}
|
||||
go func() {
|
||||
if err := http.ListenAndServe(addr+":"+port, promhttp.Handler()); err != nil {
|
||||
logger.Fatalf("error listening: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
provider := metric.NewMeterProvider(
|
||||
metric.WithReader(exporter),
|
||||
)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
cleanup := csm.EnableObservability(ctx, opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}})
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
if *secureMode && *port == *maintenancePort {
|
||||
logger.Fatal("-port and -maintenance_port must be different when -secure_mode is set")
|
||||
}
|
||||
|
||||
testService := &testServiceImpl{hostname: getHostname(), serverID: *serverID}
|
||||
healthServer := health.NewServer()
|
||||
updateHealthService := &xdsUpdateHealthServiceImpl{healthServer: healthServer}
|
||||
|
||||
// If -secure_mode is not set, expose all services on -port with a regular
|
||||
// gRPC server.
|
||||
if !*secureMode {
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
lis, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
|
||||
}
|
||||
|
||||
server := grpc.NewServer()
|
||||
testgrpc.RegisterTestServiceServer(server, testService)
|
||||
healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
|
||||
healthgrpc.RegisterHealthServer(server, healthServer)
|
||||
testgrpc.RegisterXdsUpdateHealthServiceServer(server, updateHealthService)
|
||||
reflection.Register(server)
|
||||
cleanup, err := admin.Register(server)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to register admin services: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
if err := server.Serve(lis); err != nil {
|
||||
logger.Errorf("Serve() failed: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create a listener on -port to expose the test service.
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
testLis, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
|
||||
}
|
||||
|
||||
// Create server-side xDS credentials with a plaintext fallback.
|
||||
creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create xDS credentials: %v", err)
|
||||
}
|
||||
|
||||
// Create an xDS enabled gRPC server, register the test service
|
||||
// implementation and start serving.
|
||||
testServer, err := xds.NewGRPCServer(grpc.Creds(creds), xds.ServingModeCallback(xdsServingModeCallback))
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create an xDS enabled gRPC server: %v", err)
|
||||
}
|
||||
testgrpc.RegisterTestServiceServer(testServer, testService)
|
||||
go func() {
|
||||
if err := testServer.Serve(testLis); err != nil {
|
||||
logger.Errorf("test server Serve() failed: %v", err)
|
||||
}
|
||||
}()
|
||||
defer testServer.Stop()
|
||||
|
||||
// Create a listener on -maintenance_port to expose other services.
|
||||
addr = fmt.Sprintf(":%d", *maintenancePort)
|
||||
maintenanceLis, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
|
||||
}
|
||||
|
||||
// Create a regular gRPC server and register the maintenance services on
|
||||
// it and start serving.
|
||||
maintenanceServer := grpc.NewServer()
|
||||
healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
|
||||
healthgrpc.RegisterHealthServer(maintenanceServer, healthServer)
|
||||
testgrpc.RegisterXdsUpdateHealthServiceServer(maintenanceServer, updateHealthService)
|
||||
reflection.Register(maintenanceServer)
|
||||
cleanup, err := admin.Register(maintenanceServer)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to register admin services: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
if err := maintenanceServer.Serve(maintenanceLis); err != nil {
|
||||
logger.Errorf("maintenance server Serve() failed: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user