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

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
/*
*
* Copyright 2018 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.
*
*/
// This binary can only run on Google Cloud Platform (GCP).
package main
import (
"context"
"flag"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/grpclog"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
var (
hsAddr = flag.String("alts_handshaker_service_address", "", "ALTS handshaker gRPC service address")
serverAddr = flag.String("server_address", ":8080", "The port on which the server is listening")
logger = grpclog.Component("interop")
)
func main() {
flag.Parse()
opts := alts.DefaultClientOptions()
if *hsAddr != "" {
opts.HandshakerServiceAddress = *hsAddr
}
altsTC := alts.NewClientCreds(opts)
conn, err := grpc.NewClient(*serverAddr, grpc.WithTransportCredentials(altsTC))
if err != nil {
logger.Fatalf("grpc.NewClient(%q) = %v", *serverAddr, err)
}
defer conn.Close()
grpcClient := testgrpc.NewTestServiceClient(conn)
// Call the EmptyCall API.
ctx := context.Background()
request := &testpb.Empty{}
// Block until the server is ready.
if _, err := grpcClient.EmptyCall(ctx, request, grpc.WaitForReady(true)); err != nil {
logger.Fatalf("grpc Client: EmptyCall(_, %v) failed: %v", request, err)
}
logger.Info("grpc Client: empty call succeeded")
// This sleep prevents the connection from being abruptly disconnected
// when running this binary (along with grpc_server) on GCP dev cluster.
time.Sleep(1 * time.Second)
}

View File

@@ -0,0 +1,88 @@
/*
*
* Copyright 2018 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.
*
*/
// This binary can only run on Google Cloud Platform (GCP).
package main
import (
"context"
"flag"
"net"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/interop"
"google.golang.org/grpc/tap"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
const (
udsAddrPrefix = "unix:"
)
var (
hsAddr = flag.String("alts_handshaker_service_address", "", "ALTS handshaker gRPC service address")
serverAddr = flag.String("server_address", ":8080", "The address on which the server is listening. Only two types of addresses are supported, 'host:port' and 'unix:/path'.")
logger = grpclog.Component("interop")
)
func main() {
flag.Parse()
// If the server address starts with `unix:`, then we have a UDS address.
network := "tcp"
address := *serverAddr
if strings.HasPrefix(address, udsAddrPrefix) {
network = "unix"
address = strings.TrimPrefix(address, udsAddrPrefix)
}
lis, err := net.Listen(network, address)
if err != nil {
logger.Fatalf("gRPC Server: failed to start the server at %v: %v", address, err)
}
opts := alts.DefaultServerOptions()
if *hsAddr != "" {
opts.HandshakerServiceAddress = *hsAddr
}
altsTC := alts.NewServerCreds(opts)
grpcServer := grpc.NewServer(grpc.Creds(altsTC), grpc.InTapHandle(authz))
testgrpc.RegisterTestServiceServer(grpcServer, interop.NewTestServer())
grpcServer.Serve(lis)
}
// authz shows how to access client information at the server side to perform
// application-layer authorization checks.
func authz(ctx context.Context, info *tap.Info) (context.Context, error) {
authInfo, err := alts.AuthInfoFromContext(ctx)
if err != nil {
return nil, err
}
// Access all alts.AuthInfo data:
logger.Infof("authInfo.ApplicationProtocol() = %v", authInfo.ApplicationProtocol())
logger.Infof("authInfo.RecordProtocol() = %v", authInfo.RecordProtocol())
logger.Infof("authInfo.SecurityLevel() = %v", authInfo.SecurityLevel())
logger.Infof("authInfo.PeerServiceAccount() = %v", authInfo.PeerServiceAccount())
logger.Infof("authInfo.LocalServiceAccount() = %v", authInfo.LocalServiceAccount())
logger.Infof("authInfo.PeerRPCVersions() = %v", authInfo.PeerRPCVersions())
logger.Infof("info.FullMethodName = %v", info.FullMethodName)
return ctx, nil
}

408
interop/client/client.go Normal file
View File

@@ -0,0 +1,408 @@
/*
*
* Copyright 2014 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 is an interop client.
//
// See interop test case descriptions [here].
//
// [here]: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"log"
"net"
"os"
"strconv"
"strings"
"time"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/credentials/google"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/credentials/oauth"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/interop"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/testdata"
_ "google.golang.org/grpc/balancer/grpclb" // Register the grpclb load balancing policy.
_ "google.golang.org/grpc/balancer/rls" // Register the RLS load balancing policy.
"google.golang.org/grpc/xds/googledirectpath" // Register xDS resolver required for c2p directpath.
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
const (
googleDefaultCredsName = "google_default_credentials"
computeEngineCredsName = "compute_engine_channel_creds"
)
var (
caFile = flag.String("ca_file", "", "The file containing the CA root cert file")
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true")
useALTS = flag.Bool("use_alts", false, "Connection uses ALTS if true (this option can only be used on GCP)")
customCredentialsType = flag.String("custom_credentials_type", "", "Custom creds to use, excluding TLS or ALTS")
altsHSAddr = flag.String("alts_handshaker_service_address", "", "ALTS handshaker gRPC service address")
testCA = flag.Bool("use_test_ca", false, "Whether to replace platform root CAs with test CA as the CA root")
serviceAccountKeyFile = flag.String("service_account_key_file", "", "Path to service account json key file")
oauthScope = flag.String("oauth_scope", "", "The scope for OAuth2 tokens")
defaultServiceAccount = flag.String("default_service_account", "", "Email of GCE default service account")
googleC2PUniverseDomain = flag.String("google_c2p_universe_domain", "", "Universe domain for google-c2p resolve")
serverHost = flag.String("server_host", "localhost", "The server host name")
serverPort = flag.Int("server_port", 10000, "The server port number")
serviceConfigJSON = flag.String("service_config_json", "", "Disables service config lookups and sets the provided string as the default service config.")
soakIterations = flag.Int("soak_iterations", 10, "The number of iterations to use for the two soak tests: rpc_soak and channel_soak")
soakMaxFailures = flag.Int("soak_max_failures", 0, "The number of iterations in soak tests that are allowed to fail (either due to non-OK status code or exceeding the per-iteration max acceptable latency).")
soakPerIterationMaxAcceptableLatencyMs = flag.Int("soak_per_iteration_max_acceptable_latency_ms", 1000, "The number of milliseconds a single iteration in the two soak tests (rpc_soak and channel_soak) should take.")
soakOverallTimeoutSeconds = flag.Int("soak_overall_timeout_seconds", 10, "The overall number of seconds after which a soak test should stop and fail, if the desired number of iterations have not yet completed.")
soakMinTimeMsBetweenRPCs = flag.Int("soak_min_time_ms_between_rpcs", 0, "The minimum time in milliseconds between consecutive RPCs in a soak test (rpc_soak or channel_soak), useful for limiting QPS")
soakRequestSize = flag.Int("soak_request_size", 271828, "The request size in a soak RPC. The default value is set based on the interop large unary test case.")
soakResponseSize = flag.Int("soak_response_size", 314159, "The response size in a soak RPC. The default value is set based on the interop large unary test case.")
soakNumThreads = flag.Int("soak_num_threads", 1, "The number of threads for concurrent execution of the soak tests (rpc_soak or channel_soak). The default value is set based on the interop large unary test case.")
tlsServerName = flag.String("server_host_override", "", "The server name used to verify the hostname returned by TLS handshake if it is not empty. Otherwise, --server_host is used.")
additionalMetadata = flag.String("additional_metadata", "", "Additional metadata to send in each request, as a semicolon-separated list of key:value pairs.")
testCase = flag.String("test_case", "large_unary",
`Configure different test cases. Valid options are:
empty_unary : empty (zero bytes) request and response;
large_unary : single request and (large) response;
client_streaming : request streaming with single response;
server_streaming : single request with response streaming;
ping_pong : full-duplex streaming;
empty_stream : full-duplex streaming with zero message;
timeout_on_sleeping_server: fullduplex streaming on a sleeping server;
compute_engine_creds: large_unary with compute engine auth;
service_account_creds: large_unary with service account auth;
jwt_token_creds: large_unary with jwt token auth;
per_rpc_creds: large_unary with per rpc token;
oauth2_auth_token: large_unary with oauth2 token auth;
google_default_credentials: large_unary with google default credentials
compute_engine_channel_credentials: large_unary with compute engine creds
cancel_after_begin: cancellation after metadata has been sent but before payloads are sent;
cancel_after_first_response: cancellation after receiving 1st message from the server;
status_code_and_message: status code propagated back to client;
special_status_message: Unicode and whitespace is correctly processed in status message;
custom_metadata: server will echo custom metadata;
unimplemented_method: client attempts to call unimplemented method;
unimplemented_service: client attempts to call unimplemented service;
pick_first_unary: all requests are sent to one server despite multiple servers are resolved;
orca_per_rpc: the client verifies ORCA per-RPC metrics are provided;
orca_oob: the client verifies ORCA out-of-band metrics are provided.`)
logger = grpclog.Component("interop")
)
type credsMode uint8
const (
credsNone credsMode = iota
credsTLS
credsALTS
credsGoogleDefaultCreds
credsComputeEngineCreds
)
// Parses the --additional_metadata flag and returns metadata to send on each RPC,
// formatted as per https://pkg.go.dev/google.golang.org/grpc/metadata#Pairs.
// Allow any character but semicolons in values. If the flag is empty, return a nil map.
func parseAdditionalMetadataFlag() []string {
if len(*additionalMetadata) == 0 {
return nil
}
r := *additionalMetadata
addMd := make([]string, 0)
for len(r) > 0 {
i := strings.Index(r, ":")
if i < 0 {
logger.Fatalf("Error parsing --additional_metadata flag: missing colon separator")
}
addMd = append(addMd, r[:i]) // append key
r = r[i+1:]
i = strings.Index(r, ";")
// append value
if i < 0 {
addMd = append(addMd, r)
break
}
addMd = append(addMd, r[:i])
r = r[i+1:]
}
return addMd
}
// createSoakTestConfig creates a shared configuration structure for soak tests.
func createBaseSoakConfig(serverAddr string) interop.SoakTestConfig {
return interop.SoakTestConfig{
RequestSize: *soakRequestSize,
ResponseSize: *soakResponseSize,
PerIterationMaxAcceptableLatency: time.Duration(*soakPerIterationMaxAcceptableLatencyMs) * time.Millisecond,
MinTimeBetweenRPCs: time.Duration(*soakMinTimeMsBetweenRPCs) * time.Millisecond,
OverallTimeout: time.Duration(*soakOverallTimeoutSeconds) * time.Second,
ServerAddr: serverAddr,
NumWorkers: *soakNumThreads,
Iterations: *soakIterations,
MaxFailures: *soakMaxFailures,
}
}
func main() {
flag.Parse()
logger.Infof("Client running with test case %q", *testCase)
var useGDC bool // use google default creds
var useCEC bool // use compute engine creds
if *customCredentialsType != "" {
switch *customCredentialsType {
case googleDefaultCredsName:
useGDC = true
case computeEngineCredsName:
useCEC = true
default:
logger.Fatalf("If set, custom_credentials_type can only be set to one of %v or %v",
googleDefaultCredsName, computeEngineCredsName)
}
}
if (*useTLS && *useALTS) || (*useTLS && useGDC) || (*useALTS && useGDC) || (*useTLS && useCEC) || (*useALTS && useCEC) {
logger.Fatalf("only one of TLS, ALTS, google default creds, or compute engine creds can be used")
}
ctx := context.Background()
var credsChosen credsMode
switch {
case *useTLS:
credsChosen = credsTLS
case *useALTS:
credsChosen = credsALTS
case useGDC:
credsChosen = credsGoogleDefaultCreds
case useCEC:
credsChosen = credsComputeEngineCreds
}
resolver.SetDefaultScheme("dns")
if len(*googleC2PUniverseDomain) > 0 {
if err := googledirectpath.SetUniverseDomain(*googleC2PUniverseDomain); err != nil {
log.Fatalf("googlec2p.SetUniverseDomain(%s) failed: %v", *googleC2PUniverseDomain, err)
}
}
serverAddr := *serverHost
if *serverPort != 0 {
serverAddr = net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
}
var opts []grpc.DialOption
switch credsChosen {
case credsTLS:
var roots *x509.CertPool
if *testCA {
if *caFile == "" {
*caFile = testdata.Path("ca.pem")
}
b, err := os.ReadFile(*caFile)
if err != nil {
logger.Fatalf("Failed to read root certificate file %q: %v", *caFile, err)
}
roots = x509.NewCertPool()
if !roots.AppendCertsFromPEM(b) {
logger.Fatalf("Failed to append certificates: %s", string(b))
}
}
var creds credentials.TransportCredentials
if *tlsServerName != "" {
creds = credentials.NewClientTLSFromCert(roots, *tlsServerName)
} else {
creds = credentials.NewTLS(&tls.Config{RootCAs: roots})
}
opts = append(opts, grpc.WithTransportCredentials(creds))
case credsALTS:
altsOpts := alts.DefaultClientOptions()
if *altsHSAddr != "" {
altsOpts.HandshakerServiceAddress = *altsHSAddr
}
altsTC := alts.NewClientCreds(altsOpts)
opts = append(opts, grpc.WithTransportCredentials(altsTC))
case credsGoogleDefaultCreds:
opts = append(opts, grpc.WithCredentialsBundle(google.NewDefaultCredentials()))
case credsComputeEngineCreds:
opts = append(opts, grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()))
case credsNone:
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
default:
logger.Fatal("Invalid creds")
}
if credsChosen == credsTLS {
if *testCase == "compute_engine_creds" {
opts = append(opts, grpc.WithPerRPCCredentials(oauth.NewComputeEngine()))
} else if *testCase == "service_account_creds" {
jwtCreds, err := oauth.NewServiceAccountFromFile(*serviceAccountKeyFile, *oauthScope)
if err != nil {
logger.Fatalf("Failed to create JWT credentials: %v", err)
}
opts = append(opts, grpc.WithPerRPCCredentials(jwtCreds))
} else if *testCase == "jwt_token_creds" {
jwtCreds, err := oauth.NewJWTAccessFromFile(*serviceAccountKeyFile)
if err != nil {
logger.Fatalf("Failed to create JWT credentials: %v", err)
}
opts = append(opts, grpc.WithPerRPCCredentials(jwtCreds))
} else if *testCase == "oauth2_auth_token" {
opts = append(opts, grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(interop.GetToken(ctx, *serviceAccountKeyFile, *oauthScope))}))
}
}
if len(*serviceConfigJSON) > 0 {
opts = append(opts, grpc.WithDisableServiceConfig(), grpc.WithDefaultServiceConfig(*serviceConfigJSON))
}
if addMd := parseAdditionalMetadataFlag(); addMd != nil {
unaryAddMd := func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = metadata.AppendToOutgoingContext(ctx, addMd...)
return invoker(ctx, method, req, reply, cc, opts...)
}
streamingAddMd := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx = metadata.AppendToOutgoingContext(ctx, addMd...)
return streamer(ctx, desc, cc, method, opts...)
}
opts = append(opts, grpc.WithUnaryInterceptor(unaryAddMd), grpc.WithStreamInterceptor(streamingAddMd))
}
conn, err := grpc.NewClient(serverAddr, opts...)
if err != nil {
logger.Fatalf("grpc.NewClient(%q) = %v", serverAddr, err)
}
defer conn.Close()
tc := testgrpc.NewTestServiceClient(conn)
ctxWithDeadline, cancel := context.WithTimeout(ctx, time.Duration(*soakOverallTimeoutSeconds)*time.Second)
defer cancel()
switch *testCase {
case "empty_unary":
interop.DoEmptyUnaryCall(ctx, tc)
logger.Infoln("EmptyUnaryCall done")
case "large_unary":
interop.DoLargeUnaryCall(ctx, tc)
logger.Infoln("LargeUnaryCall done")
case "client_streaming":
interop.DoClientStreaming(ctx, tc)
logger.Infoln("ClientStreaming done")
case "server_streaming":
interop.DoServerStreaming(ctx, tc)
logger.Infoln("ServerStreaming done")
case "ping_pong":
interop.DoPingPong(ctx, tc)
logger.Infoln("Pingpong done")
case "empty_stream":
interop.DoEmptyStream(ctx, tc)
logger.Infoln("Emptystream done")
case "timeout_on_sleeping_server":
interop.DoTimeoutOnSleepingServer(ctx, tc)
logger.Infoln("TimeoutOnSleepingServer done")
case "compute_engine_creds":
if credsChosen != credsTLS {
logger.Fatalf("TLS credentials need to be set for compute_engine_creds test case.")
}
interop.DoComputeEngineCreds(ctx, tc, *defaultServiceAccount, *oauthScope)
logger.Infoln("ComputeEngineCreds done")
case "service_account_creds":
if credsChosen != credsTLS {
logger.Fatalf("TLS credentials need to be set for service_account_creds test case.")
}
interop.DoServiceAccountCreds(ctx, tc, *serviceAccountKeyFile, *oauthScope)
logger.Infoln("ServiceAccountCreds done")
case "jwt_token_creds":
if credsChosen != credsTLS {
logger.Fatalf("TLS credentials need to be set for jwt_token_creds test case.")
}
interop.DoJWTTokenCreds(ctx, tc, *serviceAccountKeyFile)
logger.Infoln("JWTtokenCreds done")
case "per_rpc_creds":
if credsChosen != credsTLS {
logger.Fatalf("TLS credentials need to be set for per_rpc_creds test case.")
}
interop.DoPerRPCCreds(ctx, tc, *serviceAccountKeyFile, *oauthScope)
logger.Infoln("PerRPCCreds done")
case "oauth2_auth_token":
if credsChosen != credsTLS {
logger.Fatalf("TLS credentials need to be set for oauth2_auth_token test case.")
}
interop.DoOauth2TokenCreds(ctx, tc, *serviceAccountKeyFile, *oauthScope)
logger.Infoln("Oauth2TokenCreds done")
case "google_default_credentials":
if credsChosen != credsGoogleDefaultCreds {
logger.Fatalf("GoogleDefaultCredentials need to be set for google_default_credentials test case.")
}
interop.DoGoogleDefaultCredentials(ctx, tc, *defaultServiceAccount)
logger.Infoln("GoogleDefaultCredentials done")
case "compute_engine_channel_credentials":
if credsChosen != credsComputeEngineCreds {
logger.Fatalf("ComputeEngineCreds need to be set for compute_engine_channel_credentials test case.")
}
interop.DoComputeEngineChannelCredentials(ctx, tc, *defaultServiceAccount)
logger.Infoln("ComputeEngineChannelCredentials done")
case "cancel_after_begin":
interop.DoCancelAfterBegin(ctx, tc)
logger.Infoln("CancelAfterBegin done")
case "cancel_after_first_response":
interop.DoCancelAfterFirstResponse(ctx, tc)
logger.Infoln("CancelAfterFirstResponse done")
case "status_code_and_message":
interop.DoStatusCodeAndMessage(ctx, tc)
logger.Infoln("StatusCodeAndMessage done")
case "special_status_message":
interop.DoSpecialStatusMessage(ctx, tc)
logger.Infoln("SpecialStatusMessage done")
case "custom_metadata":
interop.DoCustomMetadata(ctx, tc)
logger.Infoln("CustomMetadata done")
case "unimplemented_method":
interop.DoUnimplementedMethod(ctx, conn)
logger.Infoln("UnimplementedMethod done")
case "unimplemented_service":
interop.DoUnimplementedService(ctx, testgrpc.NewUnimplementedServiceClient(conn))
logger.Infoln("UnimplementedService done")
case "pick_first_unary":
interop.DoPickFirstUnary(ctx, tc)
logger.Infoln("PickFirstUnary done")
case "rpc_soak":
rpcSoakConfig := createBaseSoakConfig(serverAddr)
rpcSoakConfig.ChannelForTest = func() (*grpc.ClientConn, func()) { return conn, func() {} }
interop.DoSoakTest(ctxWithDeadline, rpcSoakConfig)
logger.Infoln("RpcSoak done")
case "channel_soak":
channelSoakConfig := createBaseSoakConfig(serverAddr)
channelSoakConfig.ChannelForTest = func() (*grpc.ClientConn, func()) {
cc, err := grpc.NewClient(serverAddr, opts...)
if err != nil {
log.Fatalf("Failed to create shared channel: %v", err)
}
return cc, func() { cc.Close() }
}
interop.DoSoakTest(ctxWithDeadline, channelSoakConfig)
logger.Infoln("ChannelSoak done")
case "orca_per_rpc":
interop.DoORCAPerRPCTest(ctx, tc)
logger.Infoln("ORCAPerRPC done")
case "orca_oob":
interop.DoORCAOOBTest(ctx, tc)
logger.Infoln("ORCAOOB done")
default:
logger.Fatal("Unsupported test case: ", *testCase)
}
}

View File

@@ -0,0 +1,83 @@
/*
*
* Copyright 2018 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.
*
*/
// This file is for testing only. Runs a fake grpclb balancer server.
// The name of the service to load balance for and the addresses
// of that service are provided by command line flags.
package main
import (
"flag"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/testutils/fakegrpclb"
"google.golang.org/grpc/testdata"
)
var (
port = flag.Int("port", 10000, "Port to listen on.")
backendAddrs = flag.String("backend_addrs", "", "Comma separated list of backend IP/port addresses.")
useALTS = flag.Bool("use_alts", false, "Listen on ALTS credentials.")
useTLS = flag.Bool("use_tls", false, "Listen on TLS credentials, using a test certificate.")
shortStream = flag.Bool("short_stream", false, "End the balancer stream immediately after sending the first server list.")
serviceName = flag.String("service_name", "UNSET", "Name of the service being load balanced for.")
logger = grpclog.Component("interop")
)
func main() {
flag.Parse()
var opts []grpc.ServerOption
if *useTLS {
certFile := testdata.Path("server1.pem")
keyFile := testdata.Path("server1.key")
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
logger.Fatalf("Failed to generate credentials: %v", err)
}
opts = append(opts, grpc.Creds(creds))
} else if *useALTS {
altsOpts := alts.DefaultServerOptions()
altsTC := alts.NewServerCreds(altsOpts)
opts = append(opts, grpc.Creds(altsTC))
}
rawBackendAddrs := strings.Split(*backendAddrs, ",")
server, err := fakegrpclb.NewServer(fakegrpclb.ServerParams{
ListenPort: *port,
ServerOptions: opts,
LoadBalancedServiceName: *serviceName,
LoadBalancedServicePort: 443, // TODO: make this configurable?
BackendAddresses: rawBackendAddrs,
ShortStream: *shortStream,
})
if err != nil {
logger.Fatalf("Failed to create balancer server: %v", err)
}
// Serve() starts serving and blocks until Stop() is called. We don't need to
// call Stop() here since we want the server to run until we are killed.
if err := server.Serve(); err != nil {
logger.Fatalf("Failed to start balancer server: %v", err)
}
}

View File

@@ -0,0 +1,97 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/benchmark_service.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_grpc_testing_benchmark_service_proto protoreflect.FileDescriptor
const file_grpc_testing_benchmark_service_proto_rawDesc = "" +
"\n" +
"$grpc/testing/benchmark_service.proto\x12\fgrpc.testing\x1a\x1bgrpc/testing/messages.proto2\xa6\x03\n" +
"\x10BenchmarkService\x12F\n" +
"\tUnaryCall\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse\x12N\n" +
"\rStreamingCall\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse(\x010\x01\x12R\n" +
"\x13StreamingFromClient\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse(\x01\x12R\n" +
"\x13StreamingFromServer\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse0\x01\x12R\n" +
"\x11StreamingBothWays\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse(\x010\x01B*\n" +
"\x0fio.grpc.testingB\x15BenchmarkServiceProtoP\x01b\x06proto3"
var file_grpc_testing_benchmark_service_proto_goTypes = []any{
(*SimpleRequest)(nil), // 0: grpc.testing.SimpleRequest
(*SimpleResponse)(nil), // 1: grpc.testing.SimpleResponse
}
var file_grpc_testing_benchmark_service_proto_depIdxs = []int32{
0, // 0: grpc.testing.BenchmarkService.UnaryCall:input_type -> grpc.testing.SimpleRequest
0, // 1: grpc.testing.BenchmarkService.StreamingCall:input_type -> grpc.testing.SimpleRequest
0, // 2: grpc.testing.BenchmarkService.StreamingFromClient:input_type -> grpc.testing.SimpleRequest
0, // 3: grpc.testing.BenchmarkService.StreamingFromServer:input_type -> grpc.testing.SimpleRequest
0, // 4: grpc.testing.BenchmarkService.StreamingBothWays:input_type -> grpc.testing.SimpleRequest
1, // 5: grpc.testing.BenchmarkService.UnaryCall:output_type -> grpc.testing.SimpleResponse
1, // 6: grpc.testing.BenchmarkService.StreamingCall:output_type -> grpc.testing.SimpleResponse
1, // 7: grpc.testing.BenchmarkService.StreamingFromClient:output_type -> grpc.testing.SimpleResponse
1, // 8: grpc.testing.BenchmarkService.StreamingFromServer:output_type -> grpc.testing.SimpleResponse
1, // 9: grpc.testing.BenchmarkService.StreamingBothWays:output_type -> grpc.testing.SimpleResponse
5, // [5:10] is the sub-list for method output_type
0, // [0:5] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_grpc_testing_benchmark_service_proto_init() }
func file_grpc_testing_benchmark_service_proto_init() {
if File_grpc_testing_benchmark_service_proto != nil {
return
}
file_grpc_testing_messages_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_benchmark_service_proto_rawDesc), len(file_grpc_testing_benchmark_service_proto_rawDesc)),
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_grpc_testing_benchmark_service_proto_goTypes,
DependencyIndexes: file_grpc_testing_benchmark_service_proto_depIdxs,
}.Build()
File_grpc_testing_benchmark_service_proto = out.File
file_grpc_testing_benchmark_service_proto_goTypes = nil
file_grpc_testing_benchmark_service_proto_depIdxs = nil
}

View File

@@ -0,0 +1,297 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/testing/benchmark_service.proto
package grpc_testing
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
BenchmarkService_UnaryCall_FullMethodName = "/grpc.testing.BenchmarkService/UnaryCall"
BenchmarkService_StreamingCall_FullMethodName = "/grpc.testing.BenchmarkService/StreamingCall"
BenchmarkService_StreamingFromClient_FullMethodName = "/grpc.testing.BenchmarkService/StreamingFromClient"
BenchmarkService_StreamingFromServer_FullMethodName = "/grpc.testing.BenchmarkService/StreamingFromServer"
BenchmarkService_StreamingBothWays_FullMethodName = "/grpc.testing.BenchmarkService/StreamingBothWays"
)
// BenchmarkServiceClient is the client API for BenchmarkService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BenchmarkServiceClient interface {
// One request followed by one response.
// The server returns the client payload as-is.
UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error)
// Repeated sequence of one request followed by one response.
// Should be called streaming ping-pong
// The server returns the client payload as-is on each response
StreamingCall(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SimpleRequest, SimpleResponse], error)
// Single-sided unbounded streaming from client to server
// The server returns the client payload as-is once the client does WritesDone
StreamingFromClient(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[SimpleRequest, SimpleResponse], error)
// Single-sided unbounded streaming from server to client
// The server repeatedly returns the client payload as-is
StreamingFromServer(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SimpleResponse], error)
// Two-sided unbounded streaming between server to client
// Both sides send the content of their own choice to the other
StreamingBothWays(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SimpleRequest, SimpleResponse], error)
}
type benchmarkServiceClient struct {
cc grpc.ClientConnInterface
}
func NewBenchmarkServiceClient(cc grpc.ClientConnInterface) BenchmarkServiceClient {
return &benchmarkServiceClient{cc}
}
func (c *benchmarkServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SimpleResponse)
err := c.cc.Invoke(ctx, BenchmarkService_UnaryCall_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *benchmarkServiceClient) StreamingCall(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SimpleRequest, SimpleResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BenchmarkService_ServiceDesc.Streams[0], BenchmarkService_StreamingCall_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SimpleRequest, SimpleResponse]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingCallClient = grpc.BidiStreamingClient[SimpleRequest, SimpleResponse]
func (c *benchmarkServiceClient) StreamingFromClient(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[SimpleRequest, SimpleResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BenchmarkService_ServiceDesc.Streams[1], BenchmarkService_StreamingFromClient_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SimpleRequest, SimpleResponse]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingFromClientClient = grpc.ClientStreamingClient[SimpleRequest, SimpleResponse]
func (c *benchmarkServiceClient) StreamingFromServer(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SimpleResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BenchmarkService_ServiceDesc.Streams[2], BenchmarkService_StreamingFromServer_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SimpleRequest, SimpleResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingFromServerClient = grpc.ServerStreamingClient[SimpleResponse]
func (c *benchmarkServiceClient) StreamingBothWays(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SimpleRequest, SimpleResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BenchmarkService_ServiceDesc.Streams[3], BenchmarkService_StreamingBothWays_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SimpleRequest, SimpleResponse]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingBothWaysClient = grpc.BidiStreamingClient[SimpleRequest, SimpleResponse]
// BenchmarkServiceServer is the server API for BenchmarkService service.
// All implementations must embed UnimplementedBenchmarkServiceServer
// for forward compatibility.
type BenchmarkServiceServer interface {
// One request followed by one response.
// The server returns the client payload as-is.
UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error)
// Repeated sequence of one request followed by one response.
// Should be called streaming ping-pong
// The server returns the client payload as-is on each response
StreamingCall(grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]) error
// Single-sided unbounded streaming from client to server
// The server returns the client payload as-is once the client does WritesDone
StreamingFromClient(grpc.ClientStreamingServer[SimpleRequest, SimpleResponse]) error
// Single-sided unbounded streaming from server to client
// The server repeatedly returns the client payload as-is
StreamingFromServer(*SimpleRequest, grpc.ServerStreamingServer[SimpleResponse]) error
// Two-sided unbounded streaming between server to client
// Both sides send the content of their own choice to the other
StreamingBothWays(grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]) error
mustEmbedUnimplementedBenchmarkServiceServer()
}
// UnimplementedBenchmarkServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedBenchmarkServiceServer struct{}
func (UnimplementedBenchmarkServiceServer) UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnaryCall not implemented")
}
func (UnimplementedBenchmarkServiceServer) StreamingCall(grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]) error {
return status.Error(codes.Unimplemented, "method StreamingCall not implemented")
}
func (UnimplementedBenchmarkServiceServer) StreamingFromClient(grpc.ClientStreamingServer[SimpleRequest, SimpleResponse]) error {
return status.Error(codes.Unimplemented, "method StreamingFromClient not implemented")
}
func (UnimplementedBenchmarkServiceServer) StreamingFromServer(*SimpleRequest, grpc.ServerStreamingServer[SimpleResponse]) error {
return status.Error(codes.Unimplemented, "method StreamingFromServer not implemented")
}
func (UnimplementedBenchmarkServiceServer) StreamingBothWays(grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]) error {
return status.Error(codes.Unimplemented, "method StreamingBothWays not implemented")
}
func (UnimplementedBenchmarkServiceServer) mustEmbedUnimplementedBenchmarkServiceServer() {}
func (UnimplementedBenchmarkServiceServer) testEmbeddedByValue() {}
// UnsafeBenchmarkServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BenchmarkServiceServer will
// result in compilation errors.
type UnsafeBenchmarkServiceServer interface {
mustEmbedUnimplementedBenchmarkServiceServer()
}
func RegisterBenchmarkServiceServer(s grpc.ServiceRegistrar, srv BenchmarkServiceServer) {
// If the following call panics, it indicates UnimplementedBenchmarkServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&BenchmarkService_ServiceDesc, srv)
}
func _BenchmarkService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SimpleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BenchmarkServiceServer).UnaryCall(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BenchmarkService_UnaryCall_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BenchmarkServiceServer).UnaryCall(ctx, req.(*SimpleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BenchmarkService_StreamingCall_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BenchmarkServiceServer).StreamingCall(&grpc.GenericServerStream[SimpleRequest, SimpleResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingCallServer = grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]
func _BenchmarkService_StreamingFromClient_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BenchmarkServiceServer).StreamingFromClient(&grpc.GenericServerStream[SimpleRequest, SimpleResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingFromClientServer = grpc.ClientStreamingServer[SimpleRequest, SimpleResponse]
func _BenchmarkService_StreamingFromServer_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SimpleRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BenchmarkServiceServer).StreamingFromServer(m, &grpc.GenericServerStream[SimpleRequest, SimpleResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingFromServerServer = grpc.ServerStreamingServer[SimpleResponse]
func _BenchmarkService_StreamingBothWays_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BenchmarkServiceServer).StreamingBothWays(&grpc.GenericServerStream[SimpleRequest, SimpleResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BenchmarkService_StreamingBothWaysServer = grpc.BidiStreamingServer[SimpleRequest, SimpleResponse]
// BenchmarkService_ServiceDesc is the grpc.ServiceDesc for BenchmarkService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BenchmarkService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.testing.BenchmarkService",
HandlerType: (*BenchmarkServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "UnaryCall",
Handler: _BenchmarkService_UnaryCall_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamingCall",
Handler: _BenchmarkService_StreamingCall_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "StreamingFromClient",
Handler: _BenchmarkService_StreamingFromClient_Handler,
ClientStreams: true,
},
{
StreamName: "StreamingFromServer",
Handler: _BenchmarkService_StreamingFromServer_Handler,
ServerStreams: true,
},
{
StreamName: "StreamingBothWays",
Handler: _BenchmarkService_StreamingBothWays_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "grpc/testing/benchmark_service.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
// Copyright 2017 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/core/stats.proto
package core
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Bucket struct {
state protoimpl.MessageState `protogen:"open.v1"`
Start float64 `protobuf:"fixed64,1,opt,name=start,proto3" json:"start,omitempty"`
Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Bucket) Reset() {
*x = Bucket{}
mi := &file_grpc_core_stats_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Bucket) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Bucket) ProtoMessage() {}
func (x *Bucket) ProtoReflect() protoreflect.Message {
mi := &file_grpc_core_stats_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Bucket.ProtoReflect.Descriptor instead.
func (*Bucket) Descriptor() ([]byte, []int) {
return file_grpc_core_stats_proto_rawDescGZIP(), []int{0}
}
func (x *Bucket) GetStart() float64 {
if x != nil {
return x.Start
}
return 0
}
func (x *Bucket) GetCount() uint64 {
if x != nil {
return x.Count
}
return 0
}
type Histogram struct {
state protoimpl.MessageState `protogen:"open.v1"`
Buckets []*Bucket `protobuf:"bytes,1,rep,name=buckets,proto3" json:"buckets,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Histogram) Reset() {
*x = Histogram{}
mi := &file_grpc_core_stats_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Histogram) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Histogram) ProtoMessage() {}
func (x *Histogram) ProtoReflect() protoreflect.Message {
mi := &file_grpc_core_stats_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Histogram.ProtoReflect.Descriptor instead.
func (*Histogram) Descriptor() ([]byte, []int) {
return file_grpc_core_stats_proto_rawDescGZIP(), []int{1}
}
func (x *Histogram) GetBuckets() []*Bucket {
if x != nil {
return x.Buckets
}
return nil
}
type Metric struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Types that are valid to be assigned to Value:
//
// *Metric_Count
// *Metric_Histogram
Value isMetric_Value `protobuf_oneof:"value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Metric) Reset() {
*x = Metric{}
mi := &file_grpc_core_stats_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Metric) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Metric) ProtoMessage() {}
func (x *Metric) ProtoReflect() protoreflect.Message {
mi := &file_grpc_core_stats_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Metric.ProtoReflect.Descriptor instead.
func (*Metric) Descriptor() ([]byte, []int) {
return file_grpc_core_stats_proto_rawDescGZIP(), []int{2}
}
func (x *Metric) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Metric) GetValue() isMetric_Value {
if x != nil {
return x.Value
}
return nil
}
func (x *Metric) GetCount() uint64 {
if x != nil {
if x, ok := x.Value.(*Metric_Count); ok {
return x.Count
}
}
return 0
}
func (x *Metric) GetHistogram() *Histogram {
if x != nil {
if x, ok := x.Value.(*Metric_Histogram); ok {
return x.Histogram
}
}
return nil
}
type isMetric_Value interface {
isMetric_Value()
}
type Metric_Count struct {
Count uint64 `protobuf:"varint,10,opt,name=count,proto3,oneof"`
}
type Metric_Histogram struct {
Histogram *Histogram `protobuf:"bytes,11,opt,name=histogram,proto3,oneof"`
}
func (*Metric_Count) isMetric_Value() {}
func (*Metric_Histogram) isMetric_Value() {}
type Stats struct {
state protoimpl.MessageState `protogen:"open.v1"`
Metrics []*Metric `protobuf:"bytes,1,rep,name=metrics,proto3" json:"metrics,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Stats) Reset() {
*x = Stats{}
mi := &file_grpc_core_stats_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Stats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Stats) ProtoMessage() {}
func (x *Stats) ProtoReflect() protoreflect.Message {
mi := &file_grpc_core_stats_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Stats.ProtoReflect.Descriptor instead.
func (*Stats) Descriptor() ([]byte, []int) {
return file_grpc_core_stats_proto_rawDescGZIP(), []int{3}
}
func (x *Stats) GetMetrics() []*Metric {
if x != nil {
return x.Metrics
}
return nil
}
var File_grpc_core_stats_proto protoreflect.FileDescriptor
const file_grpc_core_stats_proto_rawDesc = "" +
"\n" +
"\x15grpc/core/stats.proto\x12\tgrpc.core\"4\n" +
"\x06Bucket\x12\x14\n" +
"\x05start\x18\x01 \x01(\x01R\x05start\x12\x14\n" +
"\x05count\x18\x02 \x01(\x04R\x05count\"8\n" +
"\tHistogram\x12+\n" +
"\abuckets\x18\x01 \x03(\v2\x11.grpc.core.BucketR\abuckets\"s\n" +
"\x06Metric\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x05count\x18\n" +
" \x01(\x04H\x00R\x05count\x124\n" +
"\thistogram\x18\v \x01(\v2\x14.grpc.core.HistogramH\x00R\thistogramB\a\n" +
"\x05value\"4\n" +
"\x05Stats\x12+\n" +
"\ametrics\x18\x01 \x03(\v2\x11.grpc.core.MetricR\ametricsb\x06proto3"
var (
file_grpc_core_stats_proto_rawDescOnce sync.Once
file_grpc_core_stats_proto_rawDescData []byte
)
func file_grpc_core_stats_proto_rawDescGZIP() []byte {
file_grpc_core_stats_proto_rawDescOnce.Do(func() {
file_grpc_core_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_core_stats_proto_rawDesc), len(file_grpc_core_stats_proto_rawDesc)))
})
return file_grpc_core_stats_proto_rawDescData
}
var file_grpc_core_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_grpc_core_stats_proto_goTypes = []any{
(*Bucket)(nil), // 0: grpc.core.Bucket
(*Histogram)(nil), // 1: grpc.core.Histogram
(*Metric)(nil), // 2: grpc.core.Metric
(*Stats)(nil), // 3: grpc.core.Stats
}
var file_grpc_core_stats_proto_depIdxs = []int32{
0, // 0: grpc.core.Histogram.buckets:type_name -> grpc.core.Bucket
1, // 1: grpc.core.Metric.histogram:type_name -> grpc.core.Histogram
2, // 2: grpc.core.Stats.metrics:type_name -> grpc.core.Metric
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_grpc_core_stats_proto_init() }
func file_grpc_core_stats_proto_init() {
if File_grpc_core_stats_proto != nil {
return
}
file_grpc_core_stats_proto_msgTypes[2].OneofWrappers = []any{
(*Metric_Count)(nil),
(*Metric_Histogram)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_core_stats_proto_rawDesc), len(file_grpc_core_stats_proto_rawDesc)),
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_core_stats_proto_goTypes,
DependencyIndexes: file_grpc_core_stats_proto_depIdxs,
MessageInfos: file_grpc_core_stats_proto_msgTypes,
}.Build()
File_grpc_core_stats_proto = out.File
file_grpc_core_stats_proto_goTypes = nil
file_grpc_core_stats_proto_depIdxs = nil
}

View File

@@ -0,0 +1,135 @@
// Copyright 2015 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/empty.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// An empty message that you can re-use to avoid defining duplicated empty
// messages in your project. A typical example is to use it as argument or the
// return value of a service API. For instance:
//
// service Foo {
// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
// };
type Empty struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Empty) Reset() {
*x = Empty{}
mi := &file_grpc_testing_empty_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_empty_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return file_grpc_testing_empty_proto_rawDescGZIP(), []int{0}
}
var File_grpc_testing_empty_proto protoreflect.FileDescriptor
const file_grpc_testing_empty_proto_rawDesc = "" +
"\n" +
"\x18grpc/testing/empty.proto\x12\fgrpc.testing\"\a\n" +
"\x05EmptyB*\n" +
"\x1bio.grpc.testing.integrationB\vEmptyProtosb\x06proto3"
var (
file_grpc_testing_empty_proto_rawDescOnce sync.Once
file_grpc_testing_empty_proto_rawDescData []byte
)
func file_grpc_testing_empty_proto_rawDescGZIP() []byte {
file_grpc_testing_empty_proto_rawDescOnce.Do(func() {
file_grpc_testing_empty_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_testing_empty_proto_rawDesc), len(file_grpc_testing_empty_proto_rawDesc)))
})
return file_grpc_testing_empty_proto_rawDescData
}
var file_grpc_testing_empty_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_grpc_testing_empty_proto_goTypes = []any{
(*Empty)(nil), // 0: grpc.testing.Empty
}
var file_grpc_testing_empty_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_grpc_testing_empty_proto_init() }
func file_grpc_testing_empty_proto_init() {
if File_grpc_testing_empty_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_empty_proto_rawDesc), len(file_grpc_testing_empty_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_testing_empty_proto_goTypes,
DependencyIndexes: file_grpc_testing_empty_proto_depIdxs,
MessageInfos: file_grpc_testing_empty_proto_msgTypes,
}.Build()
File_grpc_testing_empty_proto = out.File
file_grpc_testing_empty_proto_goTypes = nil
file_grpc_testing_empty_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,352 @@
// Copyright 2015 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/payloads.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ByteBufferParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
ReqSize int32 `protobuf:"varint,1,opt,name=req_size,json=reqSize,proto3" json:"req_size,omitempty"`
RespSize int32 `protobuf:"varint,2,opt,name=resp_size,json=respSize,proto3" json:"resp_size,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ByteBufferParams) Reset() {
*x = ByteBufferParams{}
mi := &file_grpc_testing_payloads_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ByteBufferParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ByteBufferParams) ProtoMessage() {}
func (x *ByteBufferParams) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_payloads_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ByteBufferParams.ProtoReflect.Descriptor instead.
func (*ByteBufferParams) Descriptor() ([]byte, []int) {
return file_grpc_testing_payloads_proto_rawDescGZIP(), []int{0}
}
func (x *ByteBufferParams) GetReqSize() int32 {
if x != nil {
return x.ReqSize
}
return 0
}
func (x *ByteBufferParams) GetRespSize() int32 {
if x != nil {
return x.RespSize
}
return 0
}
type SimpleProtoParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
ReqSize int32 `protobuf:"varint,1,opt,name=req_size,json=reqSize,proto3" json:"req_size,omitempty"`
RespSize int32 `protobuf:"varint,2,opt,name=resp_size,json=respSize,proto3" json:"resp_size,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SimpleProtoParams) Reset() {
*x = SimpleProtoParams{}
mi := &file_grpc_testing_payloads_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SimpleProtoParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SimpleProtoParams) ProtoMessage() {}
func (x *SimpleProtoParams) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_payloads_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SimpleProtoParams.ProtoReflect.Descriptor instead.
func (*SimpleProtoParams) Descriptor() ([]byte, []int) {
return file_grpc_testing_payloads_proto_rawDescGZIP(), []int{1}
}
func (x *SimpleProtoParams) GetReqSize() int32 {
if x != nil {
return x.ReqSize
}
return 0
}
func (x *SimpleProtoParams) GetRespSize() int32 {
if x != nil {
return x.RespSize
}
return 0
}
type ComplexProtoParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ComplexProtoParams) Reset() {
*x = ComplexProtoParams{}
mi := &file_grpc_testing_payloads_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ComplexProtoParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ComplexProtoParams) ProtoMessage() {}
func (x *ComplexProtoParams) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_payloads_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ComplexProtoParams.ProtoReflect.Descriptor instead.
func (*ComplexProtoParams) Descriptor() ([]byte, []int) {
return file_grpc_testing_payloads_proto_rawDescGZIP(), []int{2}
}
type PayloadConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Payload:
//
// *PayloadConfig_BytebufParams
// *PayloadConfig_SimpleParams
// *PayloadConfig_ComplexParams
Payload isPayloadConfig_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PayloadConfig) Reset() {
*x = PayloadConfig{}
mi := &file_grpc_testing_payloads_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PayloadConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PayloadConfig) ProtoMessage() {}
func (x *PayloadConfig) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_payloads_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PayloadConfig.ProtoReflect.Descriptor instead.
func (*PayloadConfig) Descriptor() ([]byte, []int) {
return file_grpc_testing_payloads_proto_rawDescGZIP(), []int{3}
}
func (x *PayloadConfig) GetPayload() isPayloadConfig_Payload {
if x != nil {
return x.Payload
}
return nil
}
func (x *PayloadConfig) GetBytebufParams() *ByteBufferParams {
if x != nil {
if x, ok := x.Payload.(*PayloadConfig_BytebufParams); ok {
return x.BytebufParams
}
}
return nil
}
func (x *PayloadConfig) GetSimpleParams() *SimpleProtoParams {
if x != nil {
if x, ok := x.Payload.(*PayloadConfig_SimpleParams); ok {
return x.SimpleParams
}
}
return nil
}
func (x *PayloadConfig) GetComplexParams() *ComplexProtoParams {
if x != nil {
if x, ok := x.Payload.(*PayloadConfig_ComplexParams); ok {
return x.ComplexParams
}
}
return nil
}
type isPayloadConfig_Payload interface {
isPayloadConfig_Payload()
}
type PayloadConfig_BytebufParams struct {
BytebufParams *ByteBufferParams `protobuf:"bytes,1,opt,name=bytebuf_params,json=bytebufParams,proto3,oneof"`
}
type PayloadConfig_SimpleParams struct {
SimpleParams *SimpleProtoParams `protobuf:"bytes,2,opt,name=simple_params,json=simpleParams,proto3,oneof"`
}
type PayloadConfig_ComplexParams struct {
ComplexParams *ComplexProtoParams `protobuf:"bytes,3,opt,name=complex_params,json=complexParams,proto3,oneof"`
}
func (*PayloadConfig_BytebufParams) isPayloadConfig_Payload() {}
func (*PayloadConfig_SimpleParams) isPayloadConfig_Payload() {}
func (*PayloadConfig_ComplexParams) isPayloadConfig_Payload() {}
var File_grpc_testing_payloads_proto protoreflect.FileDescriptor
const file_grpc_testing_payloads_proto_rawDesc = "" +
"\n" +
"\x1bgrpc/testing/payloads.proto\x12\fgrpc.testing\"J\n" +
"\x10ByteBufferParams\x12\x19\n" +
"\breq_size\x18\x01 \x01(\x05R\areqSize\x12\x1b\n" +
"\tresp_size\x18\x02 \x01(\x05R\brespSize\"K\n" +
"\x11SimpleProtoParams\x12\x19\n" +
"\breq_size\x18\x01 \x01(\x05R\areqSize\x12\x1b\n" +
"\tresp_size\x18\x02 \x01(\x05R\brespSize\"\x14\n" +
"\x12ComplexProtoParams\"\xf6\x01\n" +
"\rPayloadConfig\x12G\n" +
"\x0ebytebuf_params\x18\x01 \x01(\v2\x1e.grpc.testing.ByteBufferParamsH\x00R\rbytebufParams\x12F\n" +
"\rsimple_params\x18\x02 \x01(\v2\x1f.grpc.testing.SimpleProtoParamsH\x00R\fsimpleParams\x12I\n" +
"\x0ecomplex_params\x18\x03 \x01(\v2 .grpc.testing.ComplexProtoParamsH\x00R\rcomplexParamsB\t\n" +
"\apayloadB\"\n" +
"\x0fio.grpc.testingB\rPayloadsProtoP\x01b\x06proto3"
var (
file_grpc_testing_payloads_proto_rawDescOnce sync.Once
file_grpc_testing_payloads_proto_rawDescData []byte
)
func file_grpc_testing_payloads_proto_rawDescGZIP() []byte {
file_grpc_testing_payloads_proto_rawDescOnce.Do(func() {
file_grpc_testing_payloads_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_testing_payloads_proto_rawDesc), len(file_grpc_testing_payloads_proto_rawDesc)))
})
return file_grpc_testing_payloads_proto_rawDescData
}
var file_grpc_testing_payloads_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_grpc_testing_payloads_proto_goTypes = []any{
(*ByteBufferParams)(nil), // 0: grpc.testing.ByteBufferParams
(*SimpleProtoParams)(nil), // 1: grpc.testing.SimpleProtoParams
(*ComplexProtoParams)(nil), // 2: grpc.testing.ComplexProtoParams
(*PayloadConfig)(nil), // 3: grpc.testing.PayloadConfig
}
var file_grpc_testing_payloads_proto_depIdxs = []int32{
0, // 0: grpc.testing.PayloadConfig.bytebuf_params:type_name -> grpc.testing.ByteBufferParams
1, // 1: grpc.testing.PayloadConfig.simple_params:type_name -> grpc.testing.SimpleProtoParams
2, // 2: grpc.testing.PayloadConfig.complex_params:type_name -> grpc.testing.ComplexProtoParams
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_grpc_testing_payloads_proto_init() }
func file_grpc_testing_payloads_proto_init() {
if File_grpc_testing_payloads_proto != nil {
return
}
file_grpc_testing_payloads_proto_msgTypes[3].OneofWrappers = []any{
(*PayloadConfig_BytebufParams)(nil),
(*PayloadConfig_SimpleParams)(nil),
(*PayloadConfig_ComplexParams)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_payloads_proto_rawDesc), len(file_grpc_testing_payloads_proto_rawDesc)),
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_testing_payloads_proto_goTypes,
DependencyIndexes: file_grpc_testing_payloads_proto_depIdxs,
MessageInfos: file_grpc_testing_payloads_proto_msgTypes,
}.Build()
File_grpc_testing_payloads_proto = out.File
file_grpc_testing_payloads_proto_goTypes = nil
file_grpc_testing_payloads_proto_depIdxs = nil
}

View File

@@ -0,0 +1,85 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/report_qps_scenario_service.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_grpc_testing_report_qps_scenario_service_proto protoreflect.FileDescriptor
const file_grpc_testing_report_qps_scenario_service_proto_rawDesc = "" +
"\n" +
".grpc/testing/report_qps_scenario_service.proto\x12\fgrpc.testing\x1a\x1agrpc/testing/control.proto2^\n" +
"\x18ReportQpsScenarioService\x12B\n" +
"\x0eReportScenario\x12\x1c.grpc.testing.ScenarioResult\x1a\x12.grpc.testing.VoidB2\n" +
"\x0fio.grpc.testingB\x1dReportQpsScenarioServiceProtoP\x01b\x06proto3"
var file_grpc_testing_report_qps_scenario_service_proto_goTypes = []any{
(*ScenarioResult)(nil), // 0: grpc.testing.ScenarioResult
(*Void)(nil), // 1: grpc.testing.Void
}
var file_grpc_testing_report_qps_scenario_service_proto_depIdxs = []int32{
0, // 0: grpc.testing.ReportQpsScenarioService.ReportScenario:input_type -> grpc.testing.ScenarioResult
1, // 1: grpc.testing.ReportQpsScenarioService.ReportScenario:output_type -> grpc.testing.Void
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_grpc_testing_report_qps_scenario_service_proto_init() }
func file_grpc_testing_report_qps_scenario_service_proto_init() {
if File_grpc_testing_report_qps_scenario_service_proto != nil {
return
}
file_grpc_testing_control_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_report_qps_scenario_service_proto_rawDesc), len(file_grpc_testing_report_qps_scenario_service_proto_rawDesc)),
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_grpc_testing_report_qps_scenario_service_proto_goTypes,
DependencyIndexes: file_grpc_testing_report_qps_scenario_service_proto_depIdxs,
}.Build()
File_grpc_testing_report_qps_scenario_service_proto = out.File
file_grpc_testing_report_qps_scenario_service_proto_goTypes = nil
file_grpc_testing_report_qps_scenario_service_proto_depIdxs = nil
}

View File

@@ -0,0 +1,141 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/testing/report_qps_scenario_service.proto
package grpc_testing
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
ReportQpsScenarioService_ReportScenario_FullMethodName = "/grpc.testing.ReportQpsScenarioService/ReportScenario"
)
// ReportQpsScenarioServiceClient is the client API for ReportQpsScenarioService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ReportQpsScenarioServiceClient interface {
// Report results of a QPS test benchmark scenario.
ReportScenario(ctx context.Context, in *ScenarioResult, opts ...grpc.CallOption) (*Void, error)
}
type reportQpsScenarioServiceClient struct {
cc grpc.ClientConnInterface
}
func NewReportQpsScenarioServiceClient(cc grpc.ClientConnInterface) ReportQpsScenarioServiceClient {
return &reportQpsScenarioServiceClient{cc}
}
func (c *reportQpsScenarioServiceClient) ReportScenario(ctx context.Context, in *ScenarioResult, opts ...grpc.CallOption) (*Void, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Void)
err := c.cc.Invoke(ctx, ReportQpsScenarioService_ReportScenario_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ReportQpsScenarioServiceServer is the server API for ReportQpsScenarioService service.
// All implementations must embed UnimplementedReportQpsScenarioServiceServer
// for forward compatibility.
type ReportQpsScenarioServiceServer interface {
// Report results of a QPS test benchmark scenario.
ReportScenario(context.Context, *ScenarioResult) (*Void, error)
mustEmbedUnimplementedReportQpsScenarioServiceServer()
}
// UnimplementedReportQpsScenarioServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedReportQpsScenarioServiceServer struct{}
func (UnimplementedReportQpsScenarioServiceServer) ReportScenario(context.Context, *ScenarioResult) (*Void, error) {
return nil, status.Error(codes.Unimplemented, "method ReportScenario not implemented")
}
func (UnimplementedReportQpsScenarioServiceServer) mustEmbedUnimplementedReportQpsScenarioServiceServer() {
}
func (UnimplementedReportQpsScenarioServiceServer) testEmbeddedByValue() {}
// UnsafeReportQpsScenarioServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ReportQpsScenarioServiceServer will
// result in compilation errors.
type UnsafeReportQpsScenarioServiceServer interface {
mustEmbedUnimplementedReportQpsScenarioServiceServer()
}
func RegisterReportQpsScenarioServiceServer(s grpc.ServiceRegistrar, srv ReportQpsScenarioServiceServer) {
// If the following call panics, it indicates UnimplementedReportQpsScenarioServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&ReportQpsScenarioService_ServiceDesc, srv)
}
func _ReportQpsScenarioService_ReportScenario_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ScenarioResult)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ReportQpsScenarioServiceServer).ReportScenario(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ReportQpsScenarioService_ReportScenario_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ReportQpsScenarioServiceServer).ReportScenario(ctx, req.(*ScenarioResult))
}
return interceptor(ctx, in, info, handler)
}
// ReportQpsScenarioService_ServiceDesc is the grpc.ServiceDesc for ReportQpsScenarioService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ReportQpsScenarioService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.testing.ReportQpsScenarioService",
HandlerType: (*ReportQpsScenarioServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ReportScenario",
Handler: _ReportQpsScenarioService_ReportScenario_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "grpc/testing/report_qps_scenario_service.proto",
}

View File

@@ -0,0 +1,525 @@
// Copyright 2015 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/stats.proto
package grpc_testing
import (
core "google.golang.org/grpc/interop/grpc_testing/core"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ServerStats struct {
state protoimpl.MessageState `protogen:"open.v1"`
// wall clock time change in seconds since last reset
TimeElapsed float64 `protobuf:"fixed64,1,opt,name=time_elapsed,json=timeElapsed,proto3" json:"time_elapsed,omitempty"`
// change in user time (in seconds) used by the server since last reset
TimeUser float64 `protobuf:"fixed64,2,opt,name=time_user,json=timeUser,proto3" json:"time_user,omitempty"`
// change in server time (in seconds) used by the server process and all
// threads since last reset
TimeSystem float64 `protobuf:"fixed64,3,opt,name=time_system,json=timeSystem,proto3" json:"time_system,omitempty"`
// change in total cpu time of the server (data from proc/stat)
TotalCpuTime uint64 `protobuf:"varint,4,opt,name=total_cpu_time,json=totalCpuTime,proto3" json:"total_cpu_time,omitempty"`
// change in idle time of the server (data from proc/stat)
IdleCpuTime uint64 `protobuf:"varint,5,opt,name=idle_cpu_time,json=idleCpuTime,proto3" json:"idle_cpu_time,omitempty"`
// Number of polls called inside completion queue
CqPollCount uint64 `protobuf:"varint,6,opt,name=cq_poll_count,json=cqPollCount,proto3" json:"cq_poll_count,omitempty"`
// Core library stats
CoreStats *core.Stats `protobuf:"bytes,7,opt,name=core_stats,json=coreStats,proto3" json:"core_stats,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ServerStats) Reset() {
*x = ServerStats{}
mi := &file_grpc_testing_stats_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ServerStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServerStats) ProtoMessage() {}
func (x *ServerStats) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_stats_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServerStats.ProtoReflect.Descriptor instead.
func (*ServerStats) Descriptor() ([]byte, []int) {
return file_grpc_testing_stats_proto_rawDescGZIP(), []int{0}
}
func (x *ServerStats) GetTimeElapsed() float64 {
if x != nil {
return x.TimeElapsed
}
return 0
}
func (x *ServerStats) GetTimeUser() float64 {
if x != nil {
return x.TimeUser
}
return 0
}
func (x *ServerStats) GetTimeSystem() float64 {
if x != nil {
return x.TimeSystem
}
return 0
}
func (x *ServerStats) GetTotalCpuTime() uint64 {
if x != nil {
return x.TotalCpuTime
}
return 0
}
func (x *ServerStats) GetIdleCpuTime() uint64 {
if x != nil {
return x.IdleCpuTime
}
return 0
}
func (x *ServerStats) GetCqPollCount() uint64 {
if x != nil {
return x.CqPollCount
}
return 0
}
func (x *ServerStats) GetCoreStats() *core.Stats {
if x != nil {
return x.CoreStats
}
return nil
}
// Histogram params based on grpc/support/histogram.c
type HistogramParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
Resolution float64 `protobuf:"fixed64,1,opt,name=resolution,proto3" json:"resolution,omitempty"` // first bucket is [0, 1 + resolution)
MaxPossible float64 `protobuf:"fixed64,2,opt,name=max_possible,json=maxPossible,proto3" json:"max_possible,omitempty"` // use enough buckets to allow this value
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HistogramParams) Reset() {
*x = HistogramParams{}
mi := &file_grpc_testing_stats_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HistogramParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HistogramParams) ProtoMessage() {}
func (x *HistogramParams) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_stats_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HistogramParams.ProtoReflect.Descriptor instead.
func (*HistogramParams) Descriptor() ([]byte, []int) {
return file_grpc_testing_stats_proto_rawDescGZIP(), []int{1}
}
func (x *HistogramParams) GetResolution() float64 {
if x != nil {
return x.Resolution
}
return 0
}
func (x *HistogramParams) GetMaxPossible() float64 {
if x != nil {
return x.MaxPossible
}
return 0
}
// Histogram data based on grpc/support/histogram.c
type HistogramData struct {
state protoimpl.MessageState `protogen:"open.v1"`
Bucket []uint32 `protobuf:"varint,1,rep,packed,name=bucket,proto3" json:"bucket,omitempty"`
MinSeen float64 `protobuf:"fixed64,2,opt,name=min_seen,json=minSeen,proto3" json:"min_seen,omitempty"`
MaxSeen float64 `protobuf:"fixed64,3,opt,name=max_seen,json=maxSeen,proto3" json:"max_seen,omitempty"`
Sum float64 `protobuf:"fixed64,4,opt,name=sum,proto3" json:"sum,omitempty"`
SumOfSquares float64 `protobuf:"fixed64,5,opt,name=sum_of_squares,json=sumOfSquares,proto3" json:"sum_of_squares,omitempty"`
Count float64 `protobuf:"fixed64,6,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HistogramData) Reset() {
*x = HistogramData{}
mi := &file_grpc_testing_stats_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HistogramData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HistogramData) ProtoMessage() {}
func (x *HistogramData) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_stats_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HistogramData.ProtoReflect.Descriptor instead.
func (*HistogramData) Descriptor() ([]byte, []int) {
return file_grpc_testing_stats_proto_rawDescGZIP(), []int{2}
}
func (x *HistogramData) GetBucket() []uint32 {
if x != nil {
return x.Bucket
}
return nil
}
func (x *HistogramData) GetMinSeen() float64 {
if x != nil {
return x.MinSeen
}
return 0
}
func (x *HistogramData) GetMaxSeen() float64 {
if x != nil {
return x.MaxSeen
}
return 0
}
func (x *HistogramData) GetSum() float64 {
if x != nil {
return x.Sum
}
return 0
}
func (x *HistogramData) GetSumOfSquares() float64 {
if x != nil {
return x.SumOfSquares
}
return 0
}
func (x *HistogramData) GetCount() float64 {
if x != nil {
return x.Count
}
return 0
}
type RequestResultCount struct {
state protoimpl.MessageState `protogen:"open.v1"`
StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RequestResultCount) Reset() {
*x = RequestResultCount{}
mi := &file_grpc_testing_stats_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RequestResultCount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RequestResultCount) ProtoMessage() {}
func (x *RequestResultCount) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_stats_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RequestResultCount.ProtoReflect.Descriptor instead.
func (*RequestResultCount) Descriptor() ([]byte, []int) {
return file_grpc_testing_stats_proto_rawDescGZIP(), []int{3}
}
func (x *RequestResultCount) GetStatusCode() int32 {
if x != nil {
return x.StatusCode
}
return 0
}
func (x *RequestResultCount) GetCount() int64 {
if x != nil {
return x.Count
}
return 0
}
type ClientStats struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Latency histogram. Data points are in nanoseconds.
Latencies *HistogramData `protobuf:"bytes,1,opt,name=latencies,proto3" json:"latencies,omitempty"`
// See ServerStats for details.
TimeElapsed float64 `protobuf:"fixed64,2,opt,name=time_elapsed,json=timeElapsed,proto3" json:"time_elapsed,omitempty"`
TimeUser float64 `protobuf:"fixed64,3,opt,name=time_user,json=timeUser,proto3" json:"time_user,omitempty"`
TimeSystem float64 `protobuf:"fixed64,4,opt,name=time_system,json=timeSystem,proto3" json:"time_system,omitempty"`
// Number of failed requests (one row per status code seen)
RequestResults []*RequestResultCount `protobuf:"bytes,5,rep,name=request_results,json=requestResults,proto3" json:"request_results,omitempty"`
// Number of polls called inside completion queue
CqPollCount uint64 `protobuf:"varint,6,opt,name=cq_poll_count,json=cqPollCount,proto3" json:"cq_poll_count,omitempty"`
// Core library stats
CoreStats *core.Stats `protobuf:"bytes,7,opt,name=core_stats,json=coreStats,proto3" json:"core_stats,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ClientStats) Reset() {
*x = ClientStats{}
mi := &file_grpc_testing_stats_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ClientStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientStats) ProtoMessage() {}
func (x *ClientStats) ProtoReflect() protoreflect.Message {
mi := &file_grpc_testing_stats_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClientStats.ProtoReflect.Descriptor instead.
func (*ClientStats) Descriptor() ([]byte, []int) {
return file_grpc_testing_stats_proto_rawDescGZIP(), []int{4}
}
func (x *ClientStats) GetLatencies() *HistogramData {
if x != nil {
return x.Latencies
}
return nil
}
func (x *ClientStats) GetTimeElapsed() float64 {
if x != nil {
return x.TimeElapsed
}
return 0
}
func (x *ClientStats) GetTimeUser() float64 {
if x != nil {
return x.TimeUser
}
return 0
}
func (x *ClientStats) GetTimeSystem() float64 {
if x != nil {
return x.TimeSystem
}
return 0
}
func (x *ClientStats) GetRequestResults() []*RequestResultCount {
if x != nil {
return x.RequestResults
}
return nil
}
func (x *ClientStats) GetCqPollCount() uint64 {
if x != nil {
return x.CqPollCount
}
return 0
}
func (x *ClientStats) GetCoreStats() *core.Stats {
if x != nil {
return x.CoreStats
}
return nil
}
var File_grpc_testing_stats_proto protoreflect.FileDescriptor
const file_grpc_testing_stats_proto_rawDesc = "" +
"\n" +
"\x18grpc/testing/stats.proto\x12\fgrpc.testing\x1a\x15grpc/core/stats.proto\"\x8d\x02\n" +
"\vServerStats\x12!\n" +
"\ftime_elapsed\x18\x01 \x01(\x01R\vtimeElapsed\x12\x1b\n" +
"\ttime_user\x18\x02 \x01(\x01R\btimeUser\x12\x1f\n" +
"\vtime_system\x18\x03 \x01(\x01R\n" +
"timeSystem\x12$\n" +
"\x0etotal_cpu_time\x18\x04 \x01(\x04R\ftotalCpuTime\x12\"\n" +
"\ridle_cpu_time\x18\x05 \x01(\x04R\vidleCpuTime\x12\"\n" +
"\rcq_poll_count\x18\x06 \x01(\x04R\vcqPollCount\x12/\n" +
"\n" +
"core_stats\x18\a \x01(\v2\x10.grpc.core.StatsR\tcoreStats\"T\n" +
"\x0fHistogramParams\x12\x1e\n" +
"\n" +
"resolution\x18\x01 \x01(\x01R\n" +
"resolution\x12!\n" +
"\fmax_possible\x18\x02 \x01(\x01R\vmaxPossible\"\xab\x01\n" +
"\rHistogramData\x12\x16\n" +
"\x06bucket\x18\x01 \x03(\rR\x06bucket\x12\x19\n" +
"\bmin_seen\x18\x02 \x01(\x01R\aminSeen\x12\x19\n" +
"\bmax_seen\x18\x03 \x01(\x01R\amaxSeen\x12\x10\n" +
"\x03sum\x18\x04 \x01(\x01R\x03sum\x12$\n" +
"\x0esum_of_squares\x18\x05 \x01(\x01R\fsumOfSquares\x12\x14\n" +
"\x05count\x18\x06 \x01(\x01R\x05count\"K\n" +
"\x12RequestResultCount\x12\x1f\n" +
"\vstatus_code\x18\x01 \x01(\x05R\n" +
"statusCode\x12\x14\n" +
"\x05count\x18\x02 \x01(\x03R\x05count\"\xc9\x02\n" +
"\vClientStats\x129\n" +
"\tlatencies\x18\x01 \x01(\v2\x1b.grpc.testing.HistogramDataR\tlatencies\x12!\n" +
"\ftime_elapsed\x18\x02 \x01(\x01R\vtimeElapsed\x12\x1b\n" +
"\ttime_user\x18\x03 \x01(\x01R\btimeUser\x12\x1f\n" +
"\vtime_system\x18\x04 \x01(\x01R\n" +
"timeSystem\x12I\n" +
"\x0frequest_results\x18\x05 \x03(\v2 .grpc.testing.RequestResultCountR\x0erequestResults\x12\"\n" +
"\rcq_poll_count\x18\x06 \x01(\x04R\vcqPollCount\x12/\n" +
"\n" +
"core_stats\x18\a \x01(\v2\x10.grpc.core.StatsR\tcoreStatsB\x1f\n" +
"\x0fio.grpc.testingB\n" +
"StatsProtoP\x01b\x06proto3"
var (
file_grpc_testing_stats_proto_rawDescOnce sync.Once
file_grpc_testing_stats_proto_rawDescData []byte
)
func file_grpc_testing_stats_proto_rawDescGZIP() []byte {
file_grpc_testing_stats_proto_rawDescOnce.Do(func() {
file_grpc_testing_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_testing_stats_proto_rawDesc), len(file_grpc_testing_stats_proto_rawDesc)))
})
return file_grpc_testing_stats_proto_rawDescData
}
var file_grpc_testing_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_grpc_testing_stats_proto_goTypes = []any{
(*ServerStats)(nil), // 0: grpc.testing.ServerStats
(*HistogramParams)(nil), // 1: grpc.testing.HistogramParams
(*HistogramData)(nil), // 2: grpc.testing.HistogramData
(*RequestResultCount)(nil), // 3: grpc.testing.RequestResultCount
(*ClientStats)(nil), // 4: grpc.testing.ClientStats
(*core.Stats)(nil), // 5: grpc.core.Stats
}
var file_grpc_testing_stats_proto_depIdxs = []int32{
5, // 0: grpc.testing.ServerStats.core_stats:type_name -> grpc.core.Stats
2, // 1: grpc.testing.ClientStats.latencies:type_name -> grpc.testing.HistogramData
3, // 2: grpc.testing.ClientStats.request_results:type_name -> grpc.testing.RequestResultCount
5, // 3: grpc.testing.ClientStats.core_stats:type_name -> grpc.core.Stats
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_grpc_testing_stats_proto_init() }
func file_grpc_testing_stats_proto_init() {
if File_grpc_testing_stats_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_stats_proto_rawDesc), len(file_grpc_testing_stats_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_testing_stats_proto_goTypes,
DependencyIndexes: file_grpc_testing_stats_proto_depIdxs,
MessageInfos: file_grpc_testing_stats_proto_msgTypes,
}.Build()
File_grpc_testing_stats_proto = out.File
file_grpc_testing_stats_proto_goTypes = nil
file_grpc_testing_stats_proto_depIdxs = nil
}

View File

@@ -0,0 +1,166 @@
// Copyright 2015-2016 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/test.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_grpc_testing_test_proto protoreflect.FileDescriptor
const file_grpc_testing_test_proto_rawDesc = "" +
"\n" +
"\x17grpc/testing/test.proto\x12\fgrpc.testing\x1a\x18grpc/testing/empty.proto\x1a\x1bgrpc/testing/messages.proto2\xcb\x05\n" +
"\vTestService\x125\n" +
"\tEmptyCall\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty\x12F\n" +
"\tUnaryCall\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse\x12O\n" +
"\x12CacheableUnaryCall\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse\x12l\n" +
"\x13StreamingOutputCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse0\x01\x12i\n" +
"\x12StreamingInputCall\x12'.grpc.testing.StreamingInputCallRequest\x1a(.grpc.testing.StreamingInputCallResponse(\x01\x12i\n" +
"\x0eFullDuplexCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse(\x010\x01\x12i\n" +
"\x0eHalfDuplexCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse(\x010\x01\x12=\n" +
"\x11UnimplementedCall\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty2U\n" +
"\x14UnimplementedService\x12=\n" +
"\x11UnimplementedCall\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty2\x89\x01\n" +
"\x10ReconnectService\x12;\n" +
"\x05Start\x12\x1d.grpc.testing.ReconnectParams\x1a\x13.grpc.testing.Empty\x128\n" +
"\x04Stop\x12\x13.grpc.testing.Empty\x1a\x1b.grpc.testing.ReconnectInfo2\x86\x02\n" +
"\x18LoadBalancerStatsService\x12c\n" +
"\x0eGetClientStats\x12&.grpc.testing.LoadBalancerStatsRequest\x1a'.grpc.testing.LoadBalancerStatsResponse\"\x00\x12\x84\x01\n" +
"\x19GetClientAccumulatedStats\x121.grpc.testing.LoadBalancerAccumulatedStatsRequest\x1a2.grpc.testing.LoadBalancerAccumulatedStatsResponse\"\x002\xcc\x01\n" +
"\vHookService\x120\n" +
"\x04Hook\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty\x12L\n" +
"\x0fSetReturnStatus\x12$.grpc.testing.SetReturnStatusRequest\x1a\x13.grpc.testing.Empty\x12=\n" +
"\x11ClearReturnStatus\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty2\xd5\x01\n" +
"\x16XdsUpdateHealthService\x126\n" +
"\n" +
"SetServing\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty\x129\n" +
"\rSetNotServing\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty\x12H\n" +
"\x0fSendHookRequest\x12\x19.grpc.testing.HookRequest\x1a\x1a.grpc.testing.HookResponse2{\n" +
"\x1fXdsUpdateClientConfigureService\x12X\n" +
"\tConfigure\x12$.grpc.testing.ClientConfigureRequest\x1a%.grpc.testing.ClientConfigureResponseB\x1d\n" +
"\x1bio.grpc.testing.integrationb\x06proto3"
var file_grpc_testing_test_proto_goTypes = []any{
(*Empty)(nil), // 0: grpc.testing.Empty
(*SimpleRequest)(nil), // 1: grpc.testing.SimpleRequest
(*StreamingOutputCallRequest)(nil), // 2: grpc.testing.StreamingOutputCallRequest
(*StreamingInputCallRequest)(nil), // 3: grpc.testing.StreamingInputCallRequest
(*ReconnectParams)(nil), // 4: grpc.testing.ReconnectParams
(*LoadBalancerStatsRequest)(nil), // 5: grpc.testing.LoadBalancerStatsRequest
(*LoadBalancerAccumulatedStatsRequest)(nil), // 6: grpc.testing.LoadBalancerAccumulatedStatsRequest
(*SetReturnStatusRequest)(nil), // 7: grpc.testing.SetReturnStatusRequest
(*HookRequest)(nil), // 8: grpc.testing.HookRequest
(*ClientConfigureRequest)(nil), // 9: grpc.testing.ClientConfigureRequest
(*SimpleResponse)(nil), // 10: grpc.testing.SimpleResponse
(*StreamingOutputCallResponse)(nil), // 11: grpc.testing.StreamingOutputCallResponse
(*StreamingInputCallResponse)(nil), // 12: grpc.testing.StreamingInputCallResponse
(*ReconnectInfo)(nil), // 13: grpc.testing.ReconnectInfo
(*LoadBalancerStatsResponse)(nil), // 14: grpc.testing.LoadBalancerStatsResponse
(*LoadBalancerAccumulatedStatsResponse)(nil), // 15: grpc.testing.LoadBalancerAccumulatedStatsResponse
(*HookResponse)(nil), // 16: grpc.testing.HookResponse
(*ClientConfigureResponse)(nil), // 17: grpc.testing.ClientConfigureResponse
}
var file_grpc_testing_test_proto_depIdxs = []int32{
0, // 0: grpc.testing.TestService.EmptyCall:input_type -> grpc.testing.Empty
1, // 1: grpc.testing.TestService.UnaryCall:input_type -> grpc.testing.SimpleRequest
1, // 2: grpc.testing.TestService.CacheableUnaryCall:input_type -> grpc.testing.SimpleRequest
2, // 3: grpc.testing.TestService.StreamingOutputCall:input_type -> grpc.testing.StreamingOutputCallRequest
3, // 4: grpc.testing.TestService.StreamingInputCall:input_type -> grpc.testing.StreamingInputCallRequest
2, // 5: grpc.testing.TestService.FullDuplexCall:input_type -> grpc.testing.StreamingOutputCallRequest
2, // 6: grpc.testing.TestService.HalfDuplexCall:input_type -> grpc.testing.StreamingOutputCallRequest
0, // 7: grpc.testing.TestService.UnimplementedCall:input_type -> grpc.testing.Empty
0, // 8: grpc.testing.UnimplementedService.UnimplementedCall:input_type -> grpc.testing.Empty
4, // 9: grpc.testing.ReconnectService.Start:input_type -> grpc.testing.ReconnectParams
0, // 10: grpc.testing.ReconnectService.Stop:input_type -> grpc.testing.Empty
5, // 11: grpc.testing.LoadBalancerStatsService.GetClientStats:input_type -> grpc.testing.LoadBalancerStatsRequest
6, // 12: grpc.testing.LoadBalancerStatsService.GetClientAccumulatedStats:input_type -> grpc.testing.LoadBalancerAccumulatedStatsRequest
0, // 13: grpc.testing.HookService.Hook:input_type -> grpc.testing.Empty
7, // 14: grpc.testing.HookService.SetReturnStatus:input_type -> grpc.testing.SetReturnStatusRequest
0, // 15: grpc.testing.HookService.ClearReturnStatus:input_type -> grpc.testing.Empty
0, // 16: grpc.testing.XdsUpdateHealthService.SetServing:input_type -> grpc.testing.Empty
0, // 17: grpc.testing.XdsUpdateHealthService.SetNotServing:input_type -> grpc.testing.Empty
8, // 18: grpc.testing.XdsUpdateHealthService.SendHookRequest:input_type -> grpc.testing.HookRequest
9, // 19: grpc.testing.XdsUpdateClientConfigureService.Configure:input_type -> grpc.testing.ClientConfigureRequest
0, // 20: grpc.testing.TestService.EmptyCall:output_type -> grpc.testing.Empty
10, // 21: grpc.testing.TestService.UnaryCall:output_type -> grpc.testing.SimpleResponse
10, // 22: grpc.testing.TestService.CacheableUnaryCall:output_type -> grpc.testing.SimpleResponse
11, // 23: grpc.testing.TestService.StreamingOutputCall:output_type -> grpc.testing.StreamingOutputCallResponse
12, // 24: grpc.testing.TestService.StreamingInputCall:output_type -> grpc.testing.StreamingInputCallResponse
11, // 25: grpc.testing.TestService.FullDuplexCall:output_type -> grpc.testing.StreamingOutputCallResponse
11, // 26: grpc.testing.TestService.HalfDuplexCall:output_type -> grpc.testing.StreamingOutputCallResponse
0, // 27: grpc.testing.TestService.UnimplementedCall:output_type -> grpc.testing.Empty
0, // 28: grpc.testing.UnimplementedService.UnimplementedCall:output_type -> grpc.testing.Empty
0, // 29: grpc.testing.ReconnectService.Start:output_type -> grpc.testing.Empty
13, // 30: grpc.testing.ReconnectService.Stop:output_type -> grpc.testing.ReconnectInfo
14, // 31: grpc.testing.LoadBalancerStatsService.GetClientStats:output_type -> grpc.testing.LoadBalancerStatsResponse
15, // 32: grpc.testing.LoadBalancerStatsService.GetClientAccumulatedStats:output_type -> grpc.testing.LoadBalancerAccumulatedStatsResponse
0, // 33: grpc.testing.HookService.Hook:output_type -> grpc.testing.Empty
0, // 34: grpc.testing.HookService.SetReturnStatus:output_type -> grpc.testing.Empty
0, // 35: grpc.testing.HookService.ClearReturnStatus:output_type -> grpc.testing.Empty
0, // 36: grpc.testing.XdsUpdateHealthService.SetServing:output_type -> grpc.testing.Empty
0, // 37: grpc.testing.XdsUpdateHealthService.SetNotServing:output_type -> grpc.testing.Empty
16, // 38: grpc.testing.XdsUpdateHealthService.SendHookRequest:output_type -> grpc.testing.HookResponse
17, // 39: grpc.testing.XdsUpdateClientConfigureService.Configure:output_type -> grpc.testing.ClientConfigureResponse
20, // [20:40] is the sub-list for method output_type
0, // [0:20] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_grpc_testing_test_proto_init() }
func file_grpc_testing_test_proto_init() {
if File_grpc_testing_test_proto != nil {
return
}
file_grpc_testing_empty_proto_init()
file_grpc_testing_messages_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_test_proto_rawDesc), len(file_grpc_testing_test_proto_rawDesc)),
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 7,
},
GoTypes: file_grpc_testing_test_proto_goTypes,
DependencyIndexes: file_grpc_testing_test_proto_depIdxs,
}.Build()
File_grpc_testing_test_proto = out.File
file_grpc_testing_test_proto_goTypes = nil
file_grpc_testing_test_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/testing/worker_service.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_grpc_testing_worker_service_proto protoreflect.FileDescriptor
const file_grpc_testing_worker_service_proto_rawDesc = "" +
"\n" +
"!grpc/testing/worker_service.proto\x12\fgrpc.testing\x1a\x1agrpc/testing/control.proto2\x97\x02\n" +
"\rWorkerService\x12E\n" +
"\tRunServer\x12\x18.grpc.testing.ServerArgs\x1a\x1a.grpc.testing.ServerStatus(\x010\x01\x12E\n" +
"\tRunClient\x12\x18.grpc.testing.ClientArgs\x1a\x1a.grpc.testing.ClientStatus(\x010\x01\x12B\n" +
"\tCoreCount\x12\x19.grpc.testing.CoreRequest\x1a\x1a.grpc.testing.CoreResponse\x124\n" +
"\n" +
"QuitWorker\x12\x12.grpc.testing.Void\x1a\x12.grpc.testing.VoidB'\n" +
"\x0fio.grpc.testingB\x12WorkerServiceProtoP\x01b\x06proto3"
var file_grpc_testing_worker_service_proto_goTypes = []any{
(*ServerArgs)(nil), // 0: grpc.testing.ServerArgs
(*ClientArgs)(nil), // 1: grpc.testing.ClientArgs
(*CoreRequest)(nil), // 2: grpc.testing.CoreRequest
(*Void)(nil), // 3: grpc.testing.Void
(*ServerStatus)(nil), // 4: grpc.testing.ServerStatus
(*ClientStatus)(nil), // 5: grpc.testing.ClientStatus
(*CoreResponse)(nil), // 6: grpc.testing.CoreResponse
}
var file_grpc_testing_worker_service_proto_depIdxs = []int32{
0, // 0: grpc.testing.WorkerService.RunServer:input_type -> grpc.testing.ServerArgs
1, // 1: grpc.testing.WorkerService.RunClient:input_type -> grpc.testing.ClientArgs
2, // 2: grpc.testing.WorkerService.CoreCount:input_type -> grpc.testing.CoreRequest
3, // 3: grpc.testing.WorkerService.QuitWorker:input_type -> grpc.testing.Void
4, // 4: grpc.testing.WorkerService.RunServer:output_type -> grpc.testing.ServerStatus
5, // 5: grpc.testing.WorkerService.RunClient:output_type -> grpc.testing.ClientStatus
6, // 6: grpc.testing.WorkerService.CoreCount:output_type -> grpc.testing.CoreResponse
3, // 7: grpc.testing.WorkerService.QuitWorker:output_type -> grpc.testing.Void
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_grpc_testing_worker_service_proto_init() }
func file_grpc_testing_worker_service_proto_init() {
if File_grpc_testing_worker_service_proto != nil {
return
}
file_grpc_testing_control_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_testing_worker_service_proto_rawDesc), len(file_grpc_testing_worker_service_proto_rawDesc)),
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_grpc_testing_worker_service_proto_goTypes,
DependencyIndexes: file_grpc_testing_worker_service_proto_depIdxs,
}.Build()
File_grpc_testing_worker_service_proto = out.File
file_grpc_testing_worker_service_proto_goTypes = nil
file_grpc_testing_worker_service_proto_depIdxs = nil
}

View File

@@ -0,0 +1,269 @@
// Copyright 2015 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.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/testing/worker_service.proto
package grpc_testing
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
WorkerService_RunServer_FullMethodName = "/grpc.testing.WorkerService/RunServer"
WorkerService_RunClient_FullMethodName = "/grpc.testing.WorkerService/RunClient"
WorkerService_CoreCount_FullMethodName = "/grpc.testing.WorkerService/CoreCount"
WorkerService_QuitWorker_FullMethodName = "/grpc.testing.WorkerService/QuitWorker"
)
// WorkerServiceClient is the client API for WorkerService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WorkerServiceClient interface {
// Start server with specified workload.
// First request sent specifies the ServerConfig followed by ServerStatus
// response. After that, a "Mark" can be sent anytime to request the latest
// stats. Closing the stream will initiate shutdown of the test server
// and once the shutdown has finished, the OK status is sent to terminate
// this RPC.
RunServer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ServerArgs, ServerStatus], error)
// Start client with specified workload.
// First request sent specifies the ClientConfig followed by ClientStatus
// response. After that, a "Mark" can be sent anytime to request the latest
// stats. Closing the stream will initiate shutdown of the test client
// and once the shutdown has finished, the OK status is sent to terminate
// this RPC.
RunClient(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ClientArgs, ClientStatus], error)
// Just return the core count - unary call
CoreCount(ctx context.Context, in *CoreRequest, opts ...grpc.CallOption) (*CoreResponse, error)
// Quit this worker
QuitWorker(ctx context.Context, in *Void, opts ...grpc.CallOption) (*Void, error)
}
type workerServiceClient struct {
cc grpc.ClientConnInterface
}
func NewWorkerServiceClient(cc grpc.ClientConnInterface) WorkerServiceClient {
return &workerServiceClient{cc}
}
func (c *workerServiceClient) RunServer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ServerArgs, ServerStatus], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &WorkerService_ServiceDesc.Streams[0], WorkerService_RunServer_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[ServerArgs, ServerStatus]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type WorkerService_RunServerClient = grpc.BidiStreamingClient[ServerArgs, ServerStatus]
func (c *workerServiceClient) RunClient(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ClientArgs, ClientStatus], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &WorkerService_ServiceDesc.Streams[1], WorkerService_RunClient_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[ClientArgs, ClientStatus]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type WorkerService_RunClientClient = grpc.BidiStreamingClient[ClientArgs, ClientStatus]
func (c *workerServiceClient) CoreCount(ctx context.Context, in *CoreRequest, opts ...grpc.CallOption) (*CoreResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CoreResponse)
err := c.cc.Invoke(ctx, WorkerService_CoreCount_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *workerServiceClient) QuitWorker(ctx context.Context, in *Void, opts ...grpc.CallOption) (*Void, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Void)
err := c.cc.Invoke(ctx, WorkerService_QuitWorker_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// WorkerServiceServer is the server API for WorkerService service.
// All implementations must embed UnimplementedWorkerServiceServer
// for forward compatibility.
type WorkerServiceServer interface {
// Start server with specified workload.
// First request sent specifies the ServerConfig followed by ServerStatus
// response. After that, a "Mark" can be sent anytime to request the latest
// stats. Closing the stream will initiate shutdown of the test server
// and once the shutdown has finished, the OK status is sent to terminate
// this RPC.
RunServer(grpc.BidiStreamingServer[ServerArgs, ServerStatus]) error
// Start client with specified workload.
// First request sent specifies the ClientConfig followed by ClientStatus
// response. After that, a "Mark" can be sent anytime to request the latest
// stats. Closing the stream will initiate shutdown of the test client
// and once the shutdown has finished, the OK status is sent to terminate
// this RPC.
RunClient(grpc.BidiStreamingServer[ClientArgs, ClientStatus]) error
// Just return the core count - unary call
CoreCount(context.Context, *CoreRequest) (*CoreResponse, error)
// Quit this worker
QuitWorker(context.Context, *Void) (*Void, error)
mustEmbedUnimplementedWorkerServiceServer()
}
// UnimplementedWorkerServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedWorkerServiceServer struct{}
func (UnimplementedWorkerServiceServer) RunServer(grpc.BidiStreamingServer[ServerArgs, ServerStatus]) error {
return status.Error(codes.Unimplemented, "method RunServer not implemented")
}
func (UnimplementedWorkerServiceServer) RunClient(grpc.BidiStreamingServer[ClientArgs, ClientStatus]) error {
return status.Error(codes.Unimplemented, "method RunClient not implemented")
}
func (UnimplementedWorkerServiceServer) CoreCount(context.Context, *CoreRequest) (*CoreResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CoreCount not implemented")
}
func (UnimplementedWorkerServiceServer) QuitWorker(context.Context, *Void) (*Void, error) {
return nil, status.Error(codes.Unimplemented, "method QuitWorker not implemented")
}
func (UnimplementedWorkerServiceServer) mustEmbedUnimplementedWorkerServiceServer() {}
func (UnimplementedWorkerServiceServer) testEmbeddedByValue() {}
// UnsafeWorkerServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WorkerServiceServer will
// result in compilation errors.
type UnsafeWorkerServiceServer interface {
mustEmbedUnimplementedWorkerServiceServer()
}
func RegisterWorkerServiceServer(s grpc.ServiceRegistrar, srv WorkerServiceServer) {
// If the following call panics, it indicates UnimplementedWorkerServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&WorkerService_ServiceDesc, srv)
}
func _WorkerService_RunServer_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(WorkerServiceServer).RunServer(&grpc.GenericServerStream[ServerArgs, ServerStatus]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type WorkerService_RunServerServer = grpc.BidiStreamingServer[ServerArgs, ServerStatus]
func _WorkerService_RunClient_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(WorkerServiceServer).RunClient(&grpc.GenericServerStream[ClientArgs, ClientStatus]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type WorkerService_RunClientServer = grpc.BidiStreamingServer[ClientArgs, ClientStatus]
func _WorkerService_CoreCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CoreRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkerServiceServer).CoreCount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkerService_CoreCount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkerServiceServer).CoreCount(ctx, req.(*CoreRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WorkerService_QuitWorker_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Void)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkerServiceServer).QuitWorker(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkerService_QuitWorker_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkerServiceServer).QuitWorker(ctx, req.(*Void))
}
return interceptor(ctx, in, info, handler)
}
// WorkerService_ServiceDesc is the grpc.ServiceDesc for WorkerService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WorkerService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.testing.WorkerService",
HandlerType: (*WorkerServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CoreCount",
Handler: _WorkerService_CoreCount_Handler,
},
{
MethodName: "QuitWorker",
Handler: _WorkerService_QuitWorker_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "RunServer",
Handler: _WorkerService_RunServer_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "RunClient",
Handler: _WorkerService_RunClient_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "grpc/testing/worker_service.proto",
}

View File

@@ -0,0 +1,192 @@
/*
*
* Copyright 2019 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 grpclb_fallback is an interop test client for grpclb fallback.
package main
import (
"context"
"flag"
"log"
"net"
"os"
"os/exec"
"syscall"
"time"
"golang.org/x/sys/unix"
"google.golang.org/grpc"
_ "google.golang.org/grpc/balancer/grpclb"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/credentials/google"
_ "google.golang.org/grpc/xds/googledirectpath"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
var (
customCredentialsType = flag.String("custom_credentials_type", "", "Client creds to use")
serverURI = flag.String("server_uri", "dns:///staging-grpc-directpath-fallback-test.googleapis.com:443", "The server host name")
induceFallbackCmd = flag.String("induce_fallback_cmd", "", "Command to induce fallback e.g. by making certain addresses unroutable")
fallbackDeadlineSeconds = flag.Int("fallback_deadline_seconds", 1, "How long to wait for fallback to happen after induce_fallback_cmd")
testCase = flag.String("test_case", "",
`Configure different test cases. Valid options are:
fallback_before_startup : LB/backend connections fail before RPC's have been made;
fallback_after_startup : LB/backend connections fail after RPC's have been made;`)
infoLog = log.New(os.Stderr, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
errorLog = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
)
func doRPCAndGetPath(client testgrpc.TestServiceClient, timeout time.Duration) testpb.GrpclbRouteType {
infoLog.Printf("doRPCAndGetPath timeout:%v\n", timeout)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req := &testpb.SimpleRequest{
FillGrpclbRouteType: true,
}
reply, err := client.UnaryCall(ctx, req)
if err != nil {
infoLog.Printf("doRPCAndGetPath error:%v\n", err)
return testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_UNKNOWN
}
g := reply.GetGrpclbRouteType()
infoLog.Printf("doRPCAndGetPath got grpclb route type: %v\n", g)
if g != testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_FALLBACK && g != testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_BACKEND {
errorLog.Fatalf("Expected grpclb route type to be either backend or fallback; got: %d", g)
}
return g
}
func dialTCPUserTimeout(ctx context.Context, addr string) (net.Conn, error) {
control := func(_, _ string, c syscall.RawConn) error {
var syscallErr error
controlErr := c.Control(func(fd uintptr) {
syscallErr = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, 20000)
})
if syscallErr != nil {
errorLog.Fatalf("syscall error setting sockopt TCP_USER_TIMEOUT: %v", syscallErr)
}
if controlErr != nil {
errorLog.Fatalf("control error setting sockopt TCP_USER_TIMEOUT: %v", syscallErr)
}
return nil
}
d := &net.Dialer{
Control: control,
}
return d.DialContext(ctx, "tcp", addr)
}
func createTestConn() *grpc.ClientConn {
opts := []grpc.DialOption{
grpc.WithContextDialer(dialTCPUserTimeout),
}
switch *customCredentialsType {
case "tls":
creds := credentials.NewClientTLSFromCert(nil, "")
opts = append(opts, grpc.WithTransportCredentials(creds))
case "alts":
creds := alts.NewClientCreds(alts.DefaultClientOptions())
opts = append(opts, grpc.WithTransportCredentials(creds))
case "google_default_credentials":
opts = append(opts, grpc.WithCredentialsBundle(google.NewDefaultCredentials()))
case "compute_engine_channel_creds":
opts = append(opts, grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()))
default:
errorLog.Fatalf("Invalid --custom_credentials_type:%v", *customCredentialsType)
}
conn, err := grpc.NewClient(*serverURI, opts...)
if err != nil {
errorLog.Fatalf("grpc.NewClient(%q) = %v", *serverURI, err)
}
return conn
}
func runCmd(command string) {
infoLog.Printf("Running cmd:|%v|\n", command)
if err := exec.Command("bash", "-c", command).Run(); err != nil {
errorLog.Fatalf("error running cmd:|%v| : %v", command, err)
}
}
func waitForFallbackAndDoRPCs(client testgrpc.TestServiceClient, fallbackDeadline time.Time) {
fallbackRetryCount := 0
fellBack := false
for time.Now().Before(fallbackDeadline) {
g := doRPCAndGetPath(client, 20*time.Second)
if g == testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_FALLBACK {
infoLog.Println("Made one successful RPC to a fallback. Now expect the same for the rest.")
fellBack = true
break
} else if g == testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_BACKEND {
errorLog.Fatalf("Got RPC type backend. This suggests an error in test implementation")
} else {
infoLog.Println("Retryable RPC failure on iteration:", fallbackRetryCount)
}
fallbackRetryCount++
}
if !fellBack {
infoLog.Fatalf("Didn't fall back before deadline: %v\n", fallbackDeadline)
}
for i := 0; i < 30; i++ {
if g := doRPCAndGetPath(client, 20*time.Second); g != testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_FALLBACK {
errorLog.Fatalf("Expected RPC to take grpclb route type FALLBACK. Got: %v", g)
}
time.Sleep(time.Second)
}
}
func doFallbackBeforeStartup() {
runCmd(*induceFallbackCmd)
fallbackDeadline := time.Now().Add(time.Duration(*fallbackDeadlineSeconds) * time.Second)
conn := createTestConn()
defer conn.Close()
client := testgrpc.NewTestServiceClient(conn)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func doFallbackAfterStartup() {
conn := createTestConn()
defer conn.Close()
client := testgrpc.NewTestServiceClient(conn)
if g := doRPCAndGetPath(client, 20*time.Second); g != testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_BACKEND {
errorLog.Fatalf("Expected RPC to take grpclb route type BACKEND. Got: %v", g)
}
runCmd(*induceFallbackCmd)
fallbackDeadline := time.Now().Add(time.Duration(*fallbackDeadlineSeconds) * time.Second)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func main() {
flag.Parse()
if len(*induceFallbackCmd) == 0 {
errorLog.Fatalf("--induce_fallback_cmd unset")
}
switch *testCase {
case "fallback_before_startup":
doFallbackBeforeStartup()
log.Printf("FallbackBeforeStartup done!\n")
case "fallback_after_startup":
doFallbackAfterStartup()
log.Printf("FallbackAfterStartup done!\n")
default:
errorLog.Fatalf("Unsupported test case: %v", *testCase)
}
}

View File

@@ -0,0 +1,165 @@
/*
*
* Copyright 2016 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 http2 is used to test http2 error edge cases like GOAWAYs and
// RST_STREAMs
//
// Documentation:
// https://github.com/grpc/grpc/blob/master/doc/negative-http2-interop-test-descriptions.md
package main
import (
"context"
"flag"
"net"
"strconv"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/interop"
"google.golang.org/grpc/status"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
var (
serverHost = flag.String("server_host", "localhost", "The server host name")
serverPort = flag.Int("server_port", 8080, "The server port number")
testCase = flag.String("test_case", "goaway",
`Configure different test cases. Valid options are:
goaway : client sends two requests, the server will send a goaway in between;
rst_after_header : server will send rst_stream after it sends headers;
rst_during_data : server will send rst_stream while sending data;
rst_after_data : server will send rst_stream after sending data;
ping : server will send pings between each http2 frame;
max_streams : server will ensure that the max_concurrent_streams limit is upheld;`)
largeReqSize = 271828
largeRespSize = 314159
logger = grpclog.Component("interop")
)
func largeSimpleRequest() *testpb.SimpleRequest {
pl := interop.ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
return &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
}
}
// sends two unary calls. The server asserts that the calls use different connections.
func goaway(ctx context.Context, tc testgrpc.TestServiceClient) {
interop.DoLargeUnaryCall(ctx, tc)
// sleep to ensure that the client has time to recv the GOAWAY.
// TODO(ncteisen): make this less hacky.
time.Sleep(1 * time.Second)
interop.DoLargeUnaryCall(ctx, tc)
}
func rstAfterHeader(tc testgrpc.TestServiceClient) {
req := largeSimpleRequest()
reply, err := tc.UnaryCall(context.Background(), req)
if reply != nil {
logger.Fatal("Client received reply despite server sending rst stream after header")
}
if status.Code(err) != codes.Internal {
logger.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, status.Code(err), codes.Internal)
}
}
func rstDuringData(tc testgrpc.TestServiceClient) {
req := largeSimpleRequest()
reply, err := tc.UnaryCall(context.Background(), req)
if reply != nil {
logger.Fatal("Client received reply despite server sending rst stream during data")
}
if status.Code(err) != codes.Unknown {
logger.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, status.Code(err), codes.Unknown)
}
}
func rstAfterData(tc testgrpc.TestServiceClient) {
req := largeSimpleRequest()
reply, err := tc.UnaryCall(context.Background(), req)
if reply != nil {
logger.Fatal("Client received reply despite server sending rst stream after data")
}
if status.Code(err) != codes.Internal {
logger.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, status.Code(err), codes.Internal)
}
}
func ping(ctx context.Context, tc testgrpc.TestServiceClient) {
// The server will assert that every ping it sends was ACK-ed by the client.
interop.DoLargeUnaryCall(ctx, tc)
}
func maxStreams(ctx context.Context, tc testgrpc.TestServiceClient) {
interop.DoLargeUnaryCall(ctx, tc)
var wg sync.WaitGroup
for i := 0; i < 15; i++ {
wg.Add(1)
go func() {
defer wg.Done()
interop.DoLargeUnaryCall(ctx, tc)
}()
}
wg.Wait()
}
func main() {
flag.Parse()
serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.NewClient(serverAddr, opts...)
if err != nil {
logger.Fatalf("grpc.NewClient(%q) = %v", serverAddr, err)
}
defer conn.Close()
tc := testgrpc.NewTestServiceClient(conn)
ctx := context.Background()
switch *testCase {
case "goaway":
goaway(ctx, tc)
logger.Infoln("goaway done")
case "rst_after_header":
rstAfterHeader(tc)
logger.Infoln("rst_after_header done")
case "rst_during_data":
rstDuringData(tc)
logger.Infoln("rst_during_data done")
case "rst_after_data":
rstAfterData(tc)
logger.Infoln("rst_after_data done")
case "ping":
ping(ctx, tc)
logger.Infoln("ping done")
case "max_streams":
maxStreams(ctx, tc)
logger.Infoln("max_streams done")
default:
logger.Fatal("Unsupported test case: ", *testCase)
}
}

140
interop/interop_test.sh Executable file
View File

@@ -0,0 +1,140 @@
#!/bin/bash
#
# Copyright 2019 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.
#
set -e +x
export TMPDIR=$(mktemp -d)
trap "rm -rf ${TMPDIR}" EXIT
clean () {
for i in {1..10}; do
jobs -p | xargs -n1 pkill -P
# A simple "wait" just hangs sometimes. Running `jobs` seems to help.
sleep 1
if jobs | read; then
return
fi
done
echo "$(tput setaf 1) clean failed to kill tests $(tput sgr 0)"
jobs
pstree
exit 1
}
fail () {
echo "$(tput setaf 1) $(date): $1 $(tput sgr 0)"
clean
exit 1
}
pass () {
echo "$(tput setaf 2) $(date): $1 $(tput sgr 0)"
}
withTimeout () {
timer=$1
shift
# Run command in the background.
cmd=$(printf '%q ' "$@")
eval "$cmd" &
wpid=$!
# Kill after $timer seconds.
sleep $timer && kill $wpid &
kpid=$!
# Wait for the background thread.
wait $wpid
res=$?
# Kill the killer pid in case it's still running.
kill $kpid || true
wait $kpid || true
return $res
}
# Don't run some tests that need a special environment:
# "google_default_credentials"
# "compute_engine_channel_credentials"
# "compute_engine_creds"
# "service_account_creds"
# "jwt_token_creds"
# "oauth2_auth_token"
# "per_rpc_creds"
# "pick_first_unary"
CASES=(
"empty_unary"
"large_unary"
"client_streaming"
"server_streaming"
"ping_pong"
"empty_stream"
"timeout_on_sleeping_server"
"cancel_after_begin"
"cancel_after_first_response"
"status_code_and_message"
"special_status_message"
"custom_metadata"
"unimplemented_method"
"unimplemented_service"
"orca_per_rpc"
"orca_oob"
"rpc_soak"
"channel_soak"
)
# Build server
echo "$(tput setaf 4) $(date): building server $(tput sgr 0)"
if ! go build -o /dev/null ./interop/server; then
fail "failed to build server"
else
pass "successfully built server"
fi
# Build client
echo "$(tput setaf 4) $(date): building client $(tput sgr 0)"
if ! go build -o /dev/null ./interop/client; then
fail "failed to build client"
else
pass "successfully built client"
fi
# Start server
SERVER_LOG="$(mktemp)"
GRPC_GO_LOG_SEVERITY_LEVEL=info go run ./interop/server --use_tls &> $SERVER_LOG &
for case in ${CASES[@]}; do
echo "$(tput setaf 4) $(date): testing: ${case} $(tput sgr 0)"
CLIENT_LOG="$(mktemp)"
if ! GRPC_GO_LOG_SEVERITY_LEVEL=info withTimeout 20 go run ./interop/client \
--use_tls \
--server_host_override=foo.test.google.fr \
--use_test_ca --test_case="${case}" \
--service_config_json='{ "loadBalancingConfig": [{ "test_backend_metrics_load_balancer": {} }]}' \
&> $CLIENT_LOG; then
fail "FAIL: test case ${case}
got server log:
$(cat $SERVER_LOG)
got client log:
$(cat $CLIENT_LOG)
"
else
pass "PASS: test case ${case}"
fi
done
clean

View File

@@ -0,0 +1,53 @@
# Copyright 2022 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.
#
# Stage 1: Build the interop test client and server
#
FROM golang:1.25-bullseye as build
WORKDIR /grpc-go
COPY . .
WORKDIR /grpc-go/interop/observability
RUN go build -o server/ server/server.go && \
go build -o client/ client/client.go
#
# Stage 2:
#
# - Copy only the necessary files to reduce Docker image size.
# - Have an ENTRYPOINT script which will launch the interop test client or server
# with the given parameters.
#
FROM golang:1.24-bullseye
ENV GRPC_GO_LOG_SEVERITY_LEVEL info
ENV GRPC_GO_LOG_VERBOSITY_LEVEL 2
WORKDIR /grpc-go/interop/observability/server
COPY --from=build /grpc-go/interop/observability/server/server .
WORKDIR /grpc-go/interop/observability/client
COPY --from=build /grpc-go/interop/observability/client/client .
WORKDIR /grpc-go/interop/observability
COPY --from=build /grpc-go/interop/observability/run.sh .
ENTRYPOINT ["/grpc-go/interop/observability/run.sh"]

View File

@@ -0,0 +1,26 @@
#!/bin/bash
# Copyright 2022 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.
set -ex
cd "$(dirname "$0")"/../..
# Environment Variables:
#
# TAG_NAME: the docker image tag name
#
echo Building ${TAG_NAME}
docker build --no-cache -t ${TAG_NAME} -f ./interop/observability/Dockerfile .

View File

@@ -0,0 +1,84 @@
/*
*
* Copyright 2022 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 is an interop client for Observability.
//
// See interop test case descriptions [here].
//
// [here]: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
package main
import (
"context"
"flag"
"log"
"net"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/gcp/observability"
"google.golang.org/grpc/interop"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
var (
serverHost = flag.String("server_host", "localhost", "The server host name")
serverPort = flag.Int("server_port", 10000, "The server port number")
testCase = flag.String("test_case", "large_unary", "The action to perform")
numTimes = flag.Int("num_times", 1, "Number of times to run the test case")
)
func main() {
err := observability.Start(context.Background())
if err != nil {
log.Fatalf("observability start failed: %v", err)
}
defer observability.End()
flag.Parse()
serverAddr := *serverHost
if *serverPort != 0 {
serverAddr = net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
}
conn, err := grpc.NewClient(serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("grpc.NewClient(%q) = %v", serverAddr, err)
}
defer conn.Close()
tc := testgrpc.NewTestServiceClient(conn)
ctx := context.Background()
for i := 0; i < *numTimes; i++ {
if *testCase == "ping_pong" {
interop.DoPingPong(ctx, tc)
} else if *testCase == "large_unary" {
interop.DoLargeUnaryCall(ctx, tc)
} else if *testCase == "custom_metadata" {
interop.DoCustomMetadata(ctx, tc)
} else {
log.Fatalf("Invalid test case: %s", *testCase)
}
}
// TODO(stanleycheung): remove this once the observability exporter plugin is able to
// gracefully flush observability data to cloud at shutdown
// TODO(stanleycheung): see if we can reduce the number 65
const exporterSleepDuration = 65 * time.Second
log.Printf("Sleeping %v before closing...", exporterSleepDuration)
time.Sleep(exporterSleepDuration)
}

View File

@@ -0,0 +1,73 @@
module google.golang.org/grpc/interop/observability
go 1.25.0
require (
google.golang.org/grpc v1.83.0
google.golang.org/grpc/gcp/observability v1.0.1
)
require (
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.23.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/logging v1.19.1 // indirect
cloud.google.com/go/longrunning v1.2.0 // indirect
cloud.google.com/go/monitoring v1.30.0 // indirect
cloud.google.com/go/trace v1.16.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.15-0.20230702191903-2de6d2748484 // indirect
github.com/aws/aws-sdk-go-v2 v1.43.6 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.37 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.36 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.37 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.5.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.33.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.45.6 // indirect
github.com/aws/smithy-go v1.27.8 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.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/protoc-gen-validate v1.3.3 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // 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
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.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/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
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.293.0 // indirect
google.golang.org/genproto v0.0.0-20260817212433-ac3dfec99bb1 // 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/grpc/stats/opencensus v1.0.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
)
replace google.golang.org/grpc => ../..
replace google.golang.org/grpc/gcp/observability => ../../gcp/observability
replace google.golang.org/grpc/stats/opencensus => ../../stats/opencensus

29
interop/observability/run.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Copyright 2022 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.
set -ex
cd "$(dirname "$0")"/../..
if [ "$1" = "server" ] ; then
/grpc-go/interop/observability/server/server "${@:2}"
elif [ "$1" = "client" ] ; then
/grpc-go/interop/observability/client/client "${@:2}"
else
echo "Invalid action: $1. Usage:"
echo " $ .../run.sh [server|client] --server_host=<hostname> --server_port=<port> ..."
exit 1
fi

View File

@@ -0,0 +1,60 @@
/*
*
* Copyright 2022 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 an interop server for Observability.
//
// See interop test case descriptions [here].
//
// [here]: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/gcp/observability"
"google.golang.org/grpc/interop"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
var (
port = flag.Int("port", 10000, "The server port")
)
func main() {
err := observability.Start(context.Background())
if err != nil {
log.Fatalf("observability start failed: %v", err)
}
defer observability.End()
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
server := grpc.NewServer()
defer server.Stop()
testgrpc.RegisterTestServiceServer(server, interop.NewTestServer())
log.Printf("Observability interop server listening on %v", lis.Addr())
server.Serve(lis)
}

256
interop/orcalb.go Normal file
View File

@@ -0,0 +1,256 @@
/*
*
* 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 interop
import (
"context"
"fmt"
"sync"
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/endpointsharding"
"google.golang.org/grpc/balancer/pickfirst"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/grpclog"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/orca"
"google.golang.org/grpc/resolver"
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
)
var orcaLogger = grpclog.Component("orca")
func init() {
balancer.Register(orcabb{})
}
type orcabb struct{}
// Build creates a new orcab balancer.
func (orcabb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
b := &orcab{
ClientConn: cc,
stopOOBListeners: make(map[balancer.SubConn]func()),
oobState: &oobState{
reports: make(map[balancer.SubConn]*v3orcapb.OrcaLoadReport),
},
}
b.logger = internalgrpclog.NewPrefixLogger(orcaLogger, fmt.Sprintf("[%p] ", b))
b.child = endpointsharding.NewBalancer(b, bOpts, balancer.Get(pickfirst.Name).Build, endpointsharding.Options{})
b.logger.Infof("Created")
return b
}
func (orcabb) Name() string {
return "test_backend_metrics_load_balancer"
}
// orcab is the balancer for the test_backend_metrics_load_balancer policy.
// It delegates SubConn management to endpointsharding + pick_first and
// intercepts NewSubConn calls to register OOB listeners on READY SubConns.
type orcab struct {
// The following fields are initialized at build time and read-only after
// that and therefore do not need to be guarded by a mutex.
balancer.ClientConn // Embeds to intercept NewSubConn and UpdateState calls
child balancer.Balancer
oobState *oobState
logger *internalgrpclog.PrefixLogger
// mu guards the fields below.
mu sync.Mutex
stopOOBListeners map[balancer.SubConn]func()
}
func (b *orcab) UpdateClientConnState(s balancer.ClientConnState) error {
// Delegate to the child endpoint sharding balancer, which distributes
// state updates to its pick_first children.
return b.child.UpdateClientConnState(s)
}
func (b *orcab) ResolverError(err error) {
// Will cause an inline picker update from endpoint sharding.
b.child.ResolverError(err)
}
func (b *orcab) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
orcaLogger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
}
func (b *orcab) ExitIdle() {
// Propagate to the child endpoint sharding balancer.
b.child.ExitIdle()
}
func (b *orcab) Close() {
b.mu.Lock()
listeners := b.stopOOBListeners
b.stopOOBListeners = nil
b.mu.Unlock()
for _, stop := range listeners {
stop()
}
b.child.Close()
}
// NewSubConn intercepts SubConn creation from pick_first children to wrap the
// StateListener for OOB listener management on READY transitions.
func (b *orcab) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
// The variable sc is declared before the closure so the closure captures
// the variable, not its (zero) value. After ClientConn.NewSubConn assigns
// to sc, the closure sees the real SubConn when invoked.
var sc balancer.SubConn
oldListener := opts.StateListener
opts.StateListener = func(state balancer.SubConnState) {
b.updateSubConnState(sc, state)
if oldListener != nil {
oldListener(state)
}
}
sc, err := b.ClientConn.NewSubConn(addrs, opts)
if err != nil {
return nil, err
}
return sc, nil
}
func (b *orcab) updateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
b.mu.Lock()
if b.stopOOBListeners == nil {
b.mu.Unlock()
return
}
if state.ConnectivityState == connectivity.Ready {
oldStop := b.stopOOBListeners[sc]
if oldStop != nil {
oldStop()
}
stop := orca.RegisterOOBListener(sc, &orcaOOBListener{subConn: sc, balancer: b}, orca.OOBListenerOptions{ReportInterval: time.Second})
b.stopOOBListeners[sc] = stop
b.mu.Unlock()
return
}
stop := b.stopOOBListeners[sc]
if stop != nil {
delete(b.stopOOBListeners, sc)
defer func() { stop() }()
}
b.mu.Unlock()
if state.ConnectivityState == connectivity.Shutdown {
b.oobState.mu.Lock()
delete(b.oobState.reports, sc)
b.oobState.mu.Unlock()
}
}
// UpdateState intercepts state updates from endpointsharding to wrap the
// picker with ORCA load report handling.
func (b *orcab) UpdateState(state balancer.State) {
// If READY, wrap the picker to inject the ORCA Done callback; otherwise,
// pass through the child picker as-is.
if state.ConnectivityState == connectivity.Ready {
state = balancer.State{
ConnectivityState: state.ConnectivityState,
Picker: &orcaPicker{childPicker: state.Picker, oobState: b.oobState},
}
}
b.ClientConn.UpdateState(state)
}
type orcaOOBListener struct {
subConn balancer.SubConn
balancer *orcab
}
// OnLoadReport implements orca.OOBListener.
func (l *orcaOOBListener) OnLoadReport(r *v3orcapb.OrcaLoadReport) {
l.balancer.oobState.mu.Lock()
defer l.balancer.oobState.mu.Unlock()
l.balancer.oobState.reports[l.subConn] = r
}
type oobState struct {
mu sync.Mutex
reports map[balancer.SubConn]*v3orcapb.OrcaLoadReport
}
type orcaPicker struct {
childPicker balancer.Picker
oobState *oobState
}
func (p *orcaPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
res, err := p.childPicker.Pick(info)
if err != nil {
return res, err
}
p.oobState.mu.Lock()
lr := p.oobState.reports[res.SubConn]
p.oobState.mu.Unlock()
origDone := res.Done
res.Done = func(di balancer.DoneInfo) {
perRPCLR, _ := di.ServerLoad.(*v3orcapb.OrcaLoadReport)
if perRPCLR != nil && (perRPCLR.CpuUtilization != 0 || perRPCLR.MemUtilization != 0 || len(perRPCLR.Utilization) > 0 || len(perRPCLR.RequestCost) > 0) {
setContextCMR(info.Ctx, perRPCLR)
} else if lr != nil {
setContextCMR(info.Ctx, lr)
}
if origDone != nil {
origDone(di)
}
}
return res, nil
}
func setContextCMR(ctx context.Context, lr *v3orcapb.OrcaLoadReport) {
if r := orcaResultFromContext(ctx); r != nil {
*r = lr
}
}
type orcaKey string
var orcaCtxKey = orcaKey("orcaResult")
// contextWithORCAResult sets a key in ctx with a pointer to an ORCA load
// report that is to be filled in by the "test_backend_metrics_load_balancer"
// LB policy's Picker's Done callback.
//
// If a per-call load report is provided from the server for the call, result
// will be filled with that, otherwise the most recent OOB load report is used.
// If no OOB report has been received, result is not modified.
func contextWithORCAResult(ctx context.Context, result **v3orcapb.OrcaLoadReport) context.Context {
return context.WithValue(ctx, orcaCtxKey, result)
}
// orcaResultFromContext returns the ORCA load report stored in the context.
// The LB policy uses this to communicate the load report back to the interop
// client application.
func orcaResultFromContext(ctx context.Context) **v3orcapb.OrcaLoadReport {
v := ctx.Value(orcaCtxKey)
if v == nil {
return nil
}
return v.(**v3orcapb.OrcaLoadReport)
}

363
interop/orcalb_test.go Normal file
View File

@@ -0,0 +1,363 @@
/*
*
* Copyright 2026 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 interop
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"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"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
"google.golang.org/grpc/orca"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
"google.golang.org/protobuf/testing/protocmp"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
const defaultTestTimeout = 30 * time.Second
type orcaServer struct {
*stubserver.StubServer
metricsRecorder orca.ServerMetricsRecorder
}
func startORCAServer(t *testing.T) *orcaServer {
t.Helper()
smr := orca.NewServerMetricsRecorder()
ts := NewTestServer(NewTestServerOptions{MetricsRecorder: smr})
stub := &stubserver.StubServer{
UnaryCallF: func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
return ts.UnaryCall(ctx, in)
},
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
return ts.FullDuplexCall(stream)
},
}
sopts := []grpc.ServerOption{orca.CallMetricsServerOption(nil)}
oso := orca.ServiceOptions{
ServerMetricsProvider: smr,
MinReportingInterval: time.Second,
}
internal.ORCAAllowAnyMinReportingInterval.(func(so *orca.ServiceOptions))(&oso)
sopts = append(sopts, stubserver.RegisterServiceServerOption(func(s grpc.ServiceRegistrar) {
if err := orca.Register(s, oso); err != nil {
t.Fatalf("Failed to register ORCA service: %v", err)
}
}))
if err := stub.StartServer(sopts...); err != nil {
t.Fatalf("Error starting server: %v", err)
}
t.Cleanup(stub.Stop)
return &orcaServer{
StubServer: stub,
metricsRecorder: smr,
}
}
func orcaSvcConfig() string {
return `{"loadBalancingConfig": [{"test_backend_metrics_load_balancer": {}}]}`
}
// TestORCAPerRPCReport verifies that per-call ORCA load reports flow from the
// server through the orcaPicker's Done callback into the context.
func (s) TestORCAPerRPCReport(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srv := startORCAServer(t)
if err := srv.StartClient(grpc.WithDefaultServiceConfig(orcaSvcConfig())); err != nil {
t.Fatalf("Error starting client: %v", err)
}
DoORCAPerRPCTest(ctx, srv.Client)
}
// TestORCAOOBReport verifies that OOB ORCA load reports flow through
// OnLoadReport and are returned by the picker when no per-call report is
// present.
func (s) TestORCAOOBReport(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srv := startORCAServer(t)
if err := srv.StartClient(grpc.WithDefaultServiceConfig(orcaSvcConfig())); err != nil {
t.Fatalf("Error starting client: %v", err)
}
DoORCAOOBTest(ctx, srv.Client)
}
// TestORCAOOBFallback verifies the fallback behavior: when a per-call report
// is present but has all zero fields, the picker uses the most recent OOB
// report instead.
func (s) TestORCAOOBFallback(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srv := startORCAServer(t)
if err := srv.StartClient(grpc.WithDefaultServiceConfig(orcaSvcConfig())); err != nil {
t.Fatalf("Error starting client: %v", err)
}
stream, err := srv.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("FullDuplexCall failed: %v", err)
}
req := &testpb.StreamingOutputCallRequest{
OrcaOobReport: &testpb.TestOrcaReport{
CpuUtilization: 0.73,
MemoryUtilization: 0.55,
Utilization: map[string]float64{"util": 0.42},
},
ResponseParameters: []*testpb.ResponseParameters{{Size: 1}},
}
if err := stream.Send(req); err != nil {
t.Fatalf("stream.Send failed: %v", err)
}
if _, err := stream.Recv(); err != nil {
t.Fatalf("stream.Recv failed: %v", err)
}
oobWant := &v3orcapb.OrcaLoadReport{
CpuUtilization: 0.73,
MemUtilization: 0.55,
Utilization: map[string]float64{"util": 0.42},
}
pollORCAResult(ctx, t, srv.Client, oobWant)
// OrcaPerQueryReport is nil: per-call report has all-zero fields,
// triggering the OOB fallback path in orcaPicker.
orcaRes := &v3orcapb.OrcaLoadReport{}
if _, err := srv.Client.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{}); err != nil {
t.Fatalf("UnaryCall failed: %v", err)
}
if diff := cmp.Diff(orcaRes, oobWant, protocmp.Transform()); diff != "" {
t.Fatalf("ORCA load report with zero per-call mismatch (-got +want):\n%s", diff)
}
}
// TestEndpoints_MultipleAddresses verifies client behavior when an endpoint has
// multiple addresses and the first is unreachable. It validates that the
// pick_first falls through the address list and connects via the first
// reachable address, so per-call ORCA reports flow correctly.
func (s) TestEndpoints_MultipleAddresses(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srv := startORCAServer(t)
if err := srv.StartClient(grpc.WithDefaultServiceConfig(orcaSvcConfig())); err != nil {
t.Fatalf("Error starting client: %v", err)
}
// First address is unreachable; pick_first falls through to the good one.
state := resolver.State{
Endpoints: []resolver.Endpoint{
{Addresses: []resolver.Address{
{Addr: "bad-address"},
{Addr: srv.Address},
}},
},
}
srv.R.UpdateState(state)
// Transient errors while pick_first probes the bad address are expected.
want := &v3orcapb.OrcaLoadReport{CpuUtilization: 0.42, MemUtilization: 0.21}
for ; ctx.Err() == nil; <-time.After(100 * time.Millisecond) {
orcaRes := &v3orcapb.OrcaLoadReport{}
req := &testpb.SimpleRequest{
OrcaPerQueryReport: &testpb.TestOrcaReport{
CpuUtilization: 0.42,
MemoryUtilization: 0.21,
},
}
if _, err := srv.Client.UnaryCall(contextWithORCAResult(ctx, &orcaRes), req); err != nil {
continue
}
if diff := cmp.Diff(orcaRes, want, protocmp.Transform()); diff != "" {
t.Fatalf("Per-RPC ORCA mismatch (-got +want):\n%s", diff)
}
return
}
t.Fatalf("timed out waiting for connection through good address")
}
// TestMultipleEndpoints_OOBListeners verifies that N endpoints produce N
// independent pick_first instances and N independent OOB listeners, each
// receiving reports from its respective server.
func (s) TestMultipleEndpoints_OOBListeners(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srvA := startORCAServer(t)
srvB := startORCAServer(t)
srvA.metricsRecorder.SetCPUUtilization(0.1)
srvB.metricsRecorder.SetCPUUtilization(0.9)
r := manual.NewBuilderWithScheme("test")
cc, err := grpc.Dial(r.Scheme()+":///test",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithResolvers(r),
grpc.WithDefaultServiceConfig(orcaSvcConfig()),
)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer cc.Close()
tc := testgrpc.NewTestServiceClient(cc)
// One endpoint per server: endpointsharding creates two independent
// pick_first children, each with its own OOB listener.
state := resolver.State{
Endpoints: []resolver.Endpoint{
{Addresses: []resolver.Address{{Addr: srvA.Address}}},
{Addresses: []resolver.Address{{Addr: srvB.Address}}},
},
}
r.UpdateState(state)
// No per-call report triggers OOB fallback. With two active listeners,
// b.report alternates between servers; both values must eventually appear.
wantA := &v3orcapb.OrcaLoadReport{CpuUtilization: 0.1}
wantB := &v3orcapb.OrcaLoadReport{CpuUtilization: 0.9}
seenA, seenB := false, false
for ; ctx.Err() == nil && (!seenA || !seenB); <-time.After(100 * time.Millisecond) {
orcaRes := &v3orcapb.OrcaLoadReport{}
if _, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{}); err != nil {
continue
}
if diff := cmp.Diff(orcaRes, wantA, protocmp.Transform()); diff == "" {
seenA = true
} else if diff := cmp.Diff(orcaRes, wantB, protocmp.Transform()); diff == "" {
seenB = true
}
}
if ctx.Err() != nil {
t.Fatalf("timed out waiting for OOB reports from both endpoints; seenA=%v seenB=%v", seenA, seenB)
}
}
// TestEndpointUpdate verifies client behavior in response to a resolver update
// that changes an endpoint's address. It ensures that the client disconnects
// from the old address and connects to the new address via pick_first. It
// also validates that the OOB listener for the old connection is stopped,
// and a new OOB listener is registered for the new connection once it becomes
// READY.
func (s) TestEndpointUpdate(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
srvA := startORCAServer(t)
srvB := startORCAServer(t)
r := manual.NewBuilderWithScheme("test")
cc, err := grpc.Dial(r.Scheme()+":///test",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithResolvers(r),
grpc.WithDefaultServiceConfig(orcaSvcConfig()),
)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer cc.Close()
tc := testgrpc.NewTestServiceClient(cc)
state := resolver.State{
Endpoints: []resolver.Endpoint{
{Addresses: []resolver.Address{{Addr: srvA.Address}}},
},
}
r.UpdateState(state)
orcaRes := &v3orcapb.OrcaLoadReport{}
req := &testpb.SimpleRequest{
OrcaPerQueryReport: &testpb.TestOrcaReport{
CpuUtilization: 0.11,
MemoryUtilization: 0.22,
},
}
if _, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), req); err != nil {
t.Fatalf("UnaryCall to srvA failed: %v", err)
}
wantA := &v3orcapb.OrcaLoadReport{CpuUtilization: 0.11, MemUtilization: 0.22}
if diff := cmp.Diff(orcaRes, wantA, protocmp.Transform()); diff != "" {
t.Fatalf("ORCA from srvA mismatch (-got +want):\n%s", diff)
}
// Distinctive OOB metric on srvB to confirm the switch via OOB fallback.
srvB.metricsRecorder.SetCPUUtilization(0.77)
state = resolver.State{
Endpoints: []resolver.Endpoint{
{Addresses: []resolver.Address{{Addr: srvB.Address}}},
},
}
r.UpdateState(state)
// No per-call report forces OOB fallback. Once srvB's metric appears,
// all three are confirmed: pick_first reconnected, new OOB listener
// registered for srvB, old one for srvA stopped.
wantB := &v3orcapb.OrcaLoadReport{CpuUtilization: 0.77}
for ; ctx.Err() == nil; <-time.After(100 * time.Millisecond) {
orcaRes := &v3orcapb.OrcaLoadReport{}
if _, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{}); err != nil {
continue
}
if diff := cmp.Diff(orcaRes, wantB, protocmp.Transform()); diff == "" {
return
}
t.Logf("ORCA after endpoint update = %v; want %v; retrying...", orcaRes, wantB)
}
t.Fatalf("timed out waiting for srvB OOB report after endpoint update")
}
func pollORCAResult(ctx context.Context, t *testing.T, tc testgrpc.TestServiceClient, want *v3orcapb.OrcaLoadReport) {
t.Helper()
for ; ctx.Err() == nil; <-time.After(time.Second) {
orcaRes := &v3orcapb.OrcaLoadReport{}
if _, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{}); err != nil {
t.Fatalf("UnaryCall failed: %v", err)
}
if diff := cmp.Diff(orcaRes, want, protocmp.Transform()); diff == "" {
return
}
t.Logf("ORCA load report = %v; want %v; retrying...", orcaRes, want)
}
t.Fatalf("Timed out waiting for expected ORCA load report %v", want)
}

97
interop/server/server.go Normal file
View File

@@ -0,0 +1,97 @@
/*
*
* Copyright 2014 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 an interop server.
//
// See interop test case descriptions [here].
//
// [here]: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
package main
import (
"flag"
"net"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/alts"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/interop"
"google.golang.org/grpc/orca"
"google.golang.org/grpc/testdata"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
var (
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
useALTS = flag.Bool("use_alts", false, "Connection uses ALTS if true (this option can only be used on GCP)")
altsHSAddr = flag.String("alts_handshaker_service_address", "", "ALTS handshaker gRPC service address")
certFile = flag.String("tls_cert_file", "", "The TLS cert file")
keyFile = flag.String("tls_key_file", "", "The TLS key file")
port = flag.Int("port", 10000, "The server port")
logger = grpclog.Component("interop")
)
func main() {
flag.Parse()
if *useTLS && *useALTS {
logger.Fatal("-use_tls and -use_alts cannot be both set to true")
}
p := strconv.Itoa(*port)
lis, err := net.Listen("tcp", ":"+p)
if err != nil {
logger.Fatalf("failed to listen: %v", err)
}
logger.Infof("interop server listening on %v", lis.Addr())
opts := []grpc.ServerOption{orca.CallMetricsServerOption(nil)}
if *useTLS {
if *certFile == "" {
*certFile = testdata.Path("server1.pem")
}
if *keyFile == "" {
*keyFile = testdata.Path("server1.key")
}
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
if err != nil {
logger.Fatalf("Failed to generate credentials: %v", err)
}
opts = append(opts, grpc.Creds(creds))
} else if *useALTS {
altsOpts := alts.DefaultServerOptions()
if *altsHSAddr != "" {
altsOpts.HandshakerServiceAddress = *altsHSAddr
}
altsTC := alts.NewServerCreds(altsOpts)
opts = append(opts, grpc.Creds(altsTC))
}
server := grpc.NewServer(opts...)
metricsRecorder := orca.NewServerMetricsRecorder()
sopts := orca.ServiceOptions{
MinReportingInterval: time.Second,
ServerMetricsProvider: metricsRecorder,
}
internal.ORCAAllowAnyMinReportingInterval.(func(*orca.ServiceOptions))(&sopts)
orca.Register(server, sopts)
testgrpc.RegisterTestServiceServer(server, interop.NewTestServer(interop.NewTestServerOptions{MetricsRecorder: metricsRecorder}))
server.Serve(lis)
}

203
interop/soak_tests.go Normal file
View File

@@ -0,0 +1,203 @@
/*
*
* Copyright 2014 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 interop
import (
"bytes"
"context"
"fmt"
"os"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/benchmark/stats"
"google.golang.org/grpc/peer"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
// SoakWorkerResults stores the aggregated results for a specific worker during the soak test.
type SoakWorkerResults struct {
iterationsSucceeded int
Failures int
Latencies *stats.Histogram
}
// SoakIterationConfig holds the parameters required for a single soak iteration.
type SoakIterationConfig struct {
RequestSize int // The size of the request payload in bytes.
ResponseSize int // The expected size of the response payload in bytes.
Client testgrpc.TestServiceClient // The gRPC client to make the call.
CallOptions []grpc.CallOption // Call options for the RPC.
}
// SoakTestConfig holds the configuration for the entire soak test.
type SoakTestConfig struct {
RequestSize int
ResponseSize int
PerIterationMaxAcceptableLatency time.Duration
MinTimeBetweenRPCs time.Duration
OverallTimeout time.Duration
ServerAddr string
NumWorkers int
Iterations int
MaxFailures int
ChannelForTest func() (*grpc.ClientConn, func())
}
func doOneSoakIteration(ctx context.Context, config SoakIterationConfig) (latency time.Duration, err error) {
start := time.Now()
// Do a large-unary RPC.
// Create the request payload.
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, config.RequestSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(config.ResponseSize),
Payload: pl,
}
// Perform the GRPC call.
var reply *testpb.SimpleResponse
reply, err = config.Client.UnaryCall(ctx, req, config.CallOptions...)
if err != nil {
err = fmt.Errorf("/TestService/UnaryCall RPC failed: %s", err)
return 0, err
}
// Validate response.
t := reply.GetPayload().GetType()
s := len(reply.GetPayload().GetBody())
if t != testpb.PayloadType_COMPRESSABLE || s != config.ResponseSize {
err = fmt.Errorf("got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, config.ResponseSize)
return 0, err
}
// Calculate latency and return result.
latency = time.Since(start)
return latency, nil
}
func executeSoakTestInWorker(ctx context.Context, config SoakTestConfig, startTime time.Time, workerID int, soakWorkerResults *SoakWorkerResults) {
timeoutDuration := config.OverallTimeout
soakIterationsPerWorker := config.Iterations / config.NumWorkers
if soakWorkerResults.Latencies == nil {
soakWorkerResults.Latencies = stats.NewHistogram(stats.HistogramOptions{
NumBuckets: 20,
GrowthFactor: 1,
BaseBucketSize: 1,
MinValue: 0,
})
}
for i := 0; i < soakIterationsPerWorker; i++ {
if ctx.Err() != nil {
return
}
if time.Since(startTime) >= timeoutDuration {
fmt.Printf("Test exceeded overall timeout of %v, stopping...\n", config.OverallTimeout)
return
}
earliestNextStart := time.After(config.MinTimeBetweenRPCs)
currentChannel, cleanup := config.ChannelForTest()
defer cleanup()
client := testgrpc.NewTestServiceClient(currentChannel)
var p peer.Peer
iterationConfig := SoakIterationConfig{
RequestSize: config.RequestSize,
ResponseSize: config.ResponseSize,
Client: client,
CallOptions: []grpc.CallOption{grpc.Peer(&p)},
}
latency, err := doOneSoakIteration(ctx, iterationConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Worker %d: soak iteration: %d elapsed_ms: %d peer: %v server_uri: %s failed: %s\n", workerID, i, 0, p.Addr, config.ServerAddr, err)
soakWorkerResults.Failures++
<-earliestNextStart
continue
}
if latency > config.PerIterationMaxAcceptableLatency {
fmt.Fprintf(os.Stderr, "Worker %d: soak iteration: %d elapsed_ms: %d peer: %v server_uri: %s exceeds max acceptable latency: %d\n", workerID, i, latency.Milliseconds(), p.Addr, config.ServerAddr, config.PerIterationMaxAcceptableLatency.Milliseconds())
soakWorkerResults.Failures++
<-earliestNextStart
continue
}
// Success: log the details of the iteration.
soakWorkerResults.Latencies.Add(latency.Milliseconds())
soakWorkerResults.iterationsSucceeded++
fmt.Fprintf(os.Stderr, "Worker %d: soak iteration: %d elapsed_ms: %d peer: %v server_uri: %s succeeded\n", workerID, i, latency.Milliseconds(), p.Addr, config.ServerAddr)
<-earliestNextStart
}
}
// DoSoakTest runs large unary RPCs in a loop for a configurable number of times, with configurable failure thresholds.
// If resetChannel is false, then each RPC will be performed on tc. Otherwise, each RPC will be performed on a new
// stub that is created with the provided server address and dial options.
// TODO(mohanli-ml): Create SoakTestOptions as a parameter for this method.
func DoSoakTest(ctx context.Context, soakConfig SoakTestConfig) {
if soakConfig.Iterations%soakConfig.NumWorkers != 0 {
fmt.Fprintf(os.Stderr, "soakIterations must be evenly divisible by soakNumWThreads\n")
}
startTime := time.Now()
var wg sync.WaitGroup
soakWorkerResults := make([]SoakWorkerResults, soakConfig.NumWorkers)
for i := 0; i < soakConfig.NumWorkers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
executeSoakTestInWorker(ctx, soakConfig, startTime, workerID, &soakWorkerResults[workerID])
}(i)
}
// Wait for all goroutines to complete.
wg.Wait()
// Handle results.
totalSuccesses := 0
totalFailures := 0
latencies := stats.NewHistogram(stats.HistogramOptions{
NumBuckets: 20,
GrowthFactor: 1,
BaseBucketSize: 1,
MinValue: 0,
})
for _, worker := range soakWorkerResults {
totalSuccesses += worker.iterationsSucceeded
totalFailures += worker.Failures
if worker.Latencies != nil {
// Add latencies from the worker's Histogram to the main latencies.
latencies.Merge(worker.Latencies)
}
}
var b bytes.Buffer
totalIterations := totalSuccesses + totalFailures
latencies.Print(&b)
fmt.Fprintf(os.Stderr,
"(server_uri: %s) soak test successes: %d / %d iterations. Total failures: %d. Latencies in milliseconds: %s\n",
soakConfig.ServerAddr, totalSuccesses, soakConfig.Iterations, totalFailures, b.String())
if totalIterations != soakConfig.Iterations {
logger.Fatalf("Soak test consumed all %v of time and quit early, ran %d out of %d iterations.\n", soakConfig.OverallTimeout, totalIterations, soakConfig.Iterations)
}
if totalFailures > soakConfig.MaxFailures {
logger.Fatalf("Soak test total failures: %d exceeded max failures threshold: %d\n", totalFailures, soakConfig.MaxFailures)
}
if soakConfig.ChannelForTest != nil {
_, cleanup := soakConfig.ChannelForTest()
defer cleanup()
}
}

View File

@@ -0,0 +1,364 @@
/*
*
* Copyright 2016 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.
*
*/
// client starts an interop client to do stress test and a metrics server to report qps.
package main
import (
"context"
"flag"
"fmt"
rand "math/rand/v2"
"net"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/google"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/interop"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/grpc/testdata"
_ "google.golang.org/grpc/xds/googledirectpath" // Register xDS resolver required for c2p directpath.
testgrpc "google.golang.org/grpc/interop/grpc_testing"
metricspb "google.golang.org/grpc/interop/stress/grpc_testing"
)
const (
googleDefaultCredsName = "google_default_credentials"
computeEngineCredsName = "compute_engine_channel_creds"
)
var (
serverAddresses = flag.String("server_addresses", "localhost:8080", "a list of server addresses")
testCases = flag.String("test_cases", "", "a list of test cases along with the relative weights")
testDurationSecs = flag.Int("test_duration_secs", -1, "test duration in seconds")
numChannelsPerServer = flag.Int("num_channels_per_server", 1, "Number of channels (i.e connections) to each server")
numStubsPerChannel = flag.Int("num_stubs_per_channel", 1, "Number of client stubs per each connection to server")
metricsPort = flag.Int("metrics_port", 8081, "The port at which the stress client exposes QPS metrics")
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
testCA = flag.Bool("use_test_ca", false, "Whether to replace platform root CAs with test CA as the CA root")
tlsServerName = flag.String("server_host_override", "foo.test.google.fr", "The server name use to verify the hostname returned by TLS handshake if it is not empty. Otherwise, --server_host is used.")
caFile = flag.String("ca_file", "", "The file containing the CA root cert file")
customCredentialsType = flag.String("custom_credentials_type", "", "Custom credentials type to use")
totalNumCalls int64
logger = grpclog.Component("stress")
)
// testCaseWithWeight contains the test case type and its weight.
type testCaseWithWeight struct {
name string
weight int
}
// parseTestCases converts test case string to a list of struct testCaseWithWeight.
func parseTestCases(testCaseString string) []testCaseWithWeight {
testCaseStrings := strings.Split(testCaseString, ",")
testCases := make([]testCaseWithWeight, len(testCaseStrings))
for i, str := range testCaseStrings {
testCaseNameAndWeight := strings.Split(str, ":")
if len(testCaseNameAndWeight) != 2 {
panic(fmt.Sprintf("invalid test case with weight: %s", str))
}
// Check if test case is supported.
testCaseName := strings.ToLower(testCaseNameAndWeight[0])
switch testCaseName {
case
"empty_unary",
"large_unary",
"client_streaming",
"server_streaming",
"ping_pong",
"empty_stream",
"timeout_on_sleeping_server",
"cancel_after_begin",
"cancel_after_first_response",
"status_code_and_message",
"custom_metadata":
default:
panic(fmt.Sprintf("unknown test type: %s", testCaseNameAndWeight[0]))
}
testCases[i].name = testCaseName
w, err := strconv.Atoi(testCaseNameAndWeight[1])
if err != nil {
panic(fmt.Sprintf("%v", err))
}
testCases[i].weight = w
}
return testCases
}
// weightedRandomTestSelector defines a weighted random selector for test case types.
type weightedRandomTestSelector struct {
tests []testCaseWithWeight
totalWeight int
}
// newWeightedRandomTestSelector constructs a weightedRandomTestSelector with the given list of testCaseWithWeight.
func newWeightedRandomTestSelector(tests []testCaseWithWeight) *weightedRandomTestSelector {
var totalWeight int
for _, t := range tests {
totalWeight += t.weight
}
return &weightedRandomTestSelector{tests, totalWeight}
}
func (selector weightedRandomTestSelector) getNextTest() string {
random := rand.IntN(selector.totalWeight)
var weightSofar int
for _, test := range selector.tests {
weightSofar += test.weight
if random < weightSofar {
return test.name
}
}
panic("no test case selected by weightedRandomTestSelector")
}
// gauge stores the qps of one interop client (one stub).
type gauge struct {
mutex sync.RWMutex
val int64
}
func (g *gauge) set(v int64) {
g.mutex.Lock()
defer g.mutex.Unlock()
g.val = v
}
func (g *gauge) get() int64 {
g.mutex.RLock()
defer g.mutex.RUnlock()
return g.val
}
// server implements metrics server functions.
type server struct {
metricspb.UnimplementedMetricsServiceServer
mutex sync.RWMutex
// gauges is a map from /stress_test/server_<n>/channel_<n>/stub_<n>/qps to its qps gauge.
gauges map[string]*gauge
}
// newMetricsServer returns a new metrics server.
func newMetricsServer() *server {
return &server{gauges: make(map[string]*gauge)}
}
// GetAllGauges returns all gauges.
func (s *server) GetAllGauges(_ *metricspb.EmptyMessage, stream metricspb.MetricsService_GetAllGaugesServer) error {
s.mutex.RLock()
defer s.mutex.RUnlock()
for name, gauge := range s.gauges {
if err := stream.Send(&metricspb.GaugeResponse{Name: name, Value: &metricspb.GaugeResponse_LongValue{LongValue: gauge.get()}}); err != nil {
return err
}
}
return nil
}
// GetGauge returns the gauge for the given name.
func (s *server) GetGauge(_ context.Context, in *metricspb.GaugeRequest) (*metricspb.GaugeResponse, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
if g, ok := s.gauges[in.Name]; ok {
return &metricspb.GaugeResponse{Name: in.Name, Value: &metricspb.GaugeResponse_LongValue{LongValue: g.get()}}, nil
}
return nil, status.Errorf(codes.InvalidArgument, "gauge with name %s not found", in.Name)
}
// createGauge creates a gauge using the given name in metrics server.
func (s *server) createGauge(name string) *gauge {
s.mutex.Lock()
defer s.mutex.Unlock()
if _, ok := s.gauges[name]; ok {
// gauge already exists.
panic(fmt.Sprintf("gauge %s already exists", name))
}
var g gauge
s.gauges[name] = &g
return &g
}
func startServer(server *server, port int) {
lis, err := net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
logger.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
metricspb.RegisterMetricsServiceServer(s, server)
s.Serve(lis)
}
// performRPCs uses weightedRandomTestSelector to select test case and runs the tests.
func performRPCs(gauge *gauge, conn *grpc.ClientConn, selector *weightedRandomTestSelector, stop <-chan bool) {
client := testgrpc.NewTestServiceClient(conn)
var numCalls int64
ctx := context.Background()
startTime := time.Now()
for {
test := selector.getNextTest()
switch test {
case "empty_unary":
interop.DoEmptyUnaryCall(ctx, client)
case "large_unary":
interop.DoLargeUnaryCall(ctx, client)
case "client_streaming":
interop.DoClientStreaming(ctx, client)
case "server_streaming":
interop.DoServerStreaming(ctx, client)
case "ping_pong":
interop.DoPingPong(ctx, client)
case "empty_stream":
interop.DoEmptyStream(ctx, client)
case "timeout_on_sleeping_server":
interop.DoTimeoutOnSleepingServer(ctx, client)
case "cancel_after_begin":
interop.DoCancelAfterBegin(ctx, client)
case "cancel_after_first_response":
interop.DoCancelAfterFirstResponse(ctx, client)
case "status_code_and_message":
interop.DoStatusCodeAndMessage(ctx, client)
case "custom_metadata":
interop.DoCustomMetadata(ctx, client)
}
numCalls++
defer func() { atomic.AddInt64(&totalNumCalls, numCalls) }()
gauge.set(int64(float64(numCalls) / time.Since(startTime).Seconds()))
select {
case <-stop:
return
default:
}
}
}
func logParameterInfo(addresses []string, tests []testCaseWithWeight) {
logger.Infof("server_addresses: %s", *serverAddresses)
logger.Infof("test_cases: %s", *testCases)
logger.Infof("test_duration_secs: %d", *testDurationSecs)
logger.Infof("num_channels_per_server: %d", *numChannelsPerServer)
logger.Infof("num_stubs_per_channel: %d", *numStubsPerChannel)
logger.Infof("metrics_port: %d", *metricsPort)
logger.Infof("use_tls: %t", *useTLS)
logger.Infof("use_test_ca: %t", *testCA)
logger.Infof("server_host_override: %s", *tlsServerName)
logger.Infof("custom_credentials_type: %s", *customCredentialsType)
logger.Infoln("addresses:")
for i, addr := range addresses {
logger.Infof("%d. %s\n", i+1, addr)
}
logger.Infoln("tests:")
for i, test := range tests {
logger.Infof("%d. %v\n", i+1, test)
}
}
func newConn(address string, useTLS, testCA bool, tlsServerName string) (*grpc.ClientConn, error) {
var opts []grpc.DialOption
if *customCredentialsType != "" {
if *customCredentialsType == googleDefaultCredsName {
opts = append(opts, grpc.WithCredentialsBundle(google.NewDefaultCredentials()))
} else if *customCredentialsType == computeEngineCredsName {
opts = append(opts, grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()))
} else {
logger.Fatalf("Unknown custom credentials: %v", *customCredentialsType)
}
} else if useTLS {
var sn string
if tlsServerName != "" {
sn = tlsServerName
}
var creds credentials.TransportCredentials
if testCA {
var err error
if *caFile == "" {
*caFile = testdata.Path("x509/server_ca_cert.pem")
}
creds, err = credentials.NewClientTLSFromFile(*caFile, sn)
if err != nil {
logger.Fatalf("Failed to create TLS credentials: %v", err)
}
} else {
creds = credentials.NewClientTLSFromCert(nil, sn)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
return grpc.NewClient(address, opts...)
}
func main() {
flag.Parse()
resolver.SetDefaultScheme("dns")
addresses := strings.Split(*serverAddresses, ",")
tests := parseTestCases(*testCases)
logParameterInfo(addresses, tests)
testSelector := newWeightedRandomTestSelector(tests)
metricsServer := newMetricsServer()
var wg sync.WaitGroup
wg.Add(len(addresses) * *numChannelsPerServer * *numStubsPerChannel)
stop := make(chan bool)
for serverIndex, address := range addresses {
for connIndex := 0; connIndex < *numChannelsPerServer; connIndex++ {
conn, err := newConn(address, *useTLS, *testCA, *tlsServerName)
if err != nil {
logger.Fatalf("Fail to dial: %v", err)
}
defer conn.Close()
for clientIndex := 0; clientIndex < *numStubsPerChannel; clientIndex++ {
name := fmt.Sprintf("/stress_test/server_%d/channel_%d/stub_%d/qps", serverIndex+1, connIndex+1, clientIndex+1)
go func() {
defer wg.Done()
g := metricsServer.createGauge(name)
performRPCs(g, conn, testSelector, stop)
}()
}
}
}
go startServer(metricsServer, *metricsPort)
if *testDurationSecs > 0 {
time.Sleep(time.Duration(*testDurationSecs) * time.Second)
close(stop)
}
wg.Wait()
fmt.Fprintf(os.Stdout, "Total calls made: %v\n", totalNumCalls)
logger.Infof(" ===== ALL DONE ===== ")
}

View File

@@ -0,0 +1,309 @@
// Copyright 2015-2016 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.
// Contains the definitions for a metrics service and the type of metrics
// exposed by the service.
//
// Currently, 'Gauge' (i.e a metric that represents the measured value of
// something at an instant of time) is the only metric type supported by the
// service.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: interop/stress/grpc_testing/metrics.proto
package grpc_testing
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Response message containing the gauge name and value
type GaugeResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Types that are valid to be assigned to Value:
//
// *GaugeResponse_LongValue
// *GaugeResponse_DoubleValue
// *GaugeResponse_StringValue
Value isGaugeResponse_Value `protobuf_oneof:"value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GaugeResponse) Reset() {
*x = GaugeResponse{}
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GaugeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GaugeResponse) ProtoMessage() {}
func (x *GaugeResponse) ProtoReflect() protoreflect.Message {
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GaugeResponse.ProtoReflect.Descriptor instead.
func (*GaugeResponse) Descriptor() ([]byte, []int) {
return file_interop_stress_grpc_testing_metrics_proto_rawDescGZIP(), []int{0}
}
func (x *GaugeResponse) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GaugeResponse) GetValue() isGaugeResponse_Value {
if x != nil {
return x.Value
}
return nil
}
func (x *GaugeResponse) GetLongValue() int64 {
if x != nil {
if x, ok := x.Value.(*GaugeResponse_LongValue); ok {
return x.LongValue
}
}
return 0
}
func (x *GaugeResponse) GetDoubleValue() float64 {
if x != nil {
if x, ok := x.Value.(*GaugeResponse_DoubleValue); ok {
return x.DoubleValue
}
}
return 0
}
func (x *GaugeResponse) GetStringValue() string {
if x != nil {
if x, ok := x.Value.(*GaugeResponse_StringValue); ok {
return x.StringValue
}
}
return ""
}
type isGaugeResponse_Value interface {
isGaugeResponse_Value()
}
type GaugeResponse_LongValue struct {
LongValue int64 `protobuf:"varint,2,opt,name=long_value,json=longValue,proto3,oneof"`
}
type GaugeResponse_DoubleValue struct {
DoubleValue float64 `protobuf:"fixed64,3,opt,name=double_value,json=doubleValue,proto3,oneof"`
}
type GaugeResponse_StringValue struct {
StringValue string `protobuf:"bytes,4,opt,name=string_value,json=stringValue,proto3,oneof"`
}
func (*GaugeResponse_LongValue) isGaugeResponse_Value() {}
func (*GaugeResponse_DoubleValue) isGaugeResponse_Value() {}
func (*GaugeResponse_StringValue) isGaugeResponse_Value() {}
// Request message containing the gauge name
type GaugeRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GaugeRequest) Reset() {
*x = GaugeRequest{}
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GaugeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GaugeRequest) ProtoMessage() {}
func (x *GaugeRequest) ProtoReflect() protoreflect.Message {
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GaugeRequest.ProtoReflect.Descriptor instead.
func (*GaugeRequest) Descriptor() ([]byte, []int) {
return file_interop_stress_grpc_testing_metrics_proto_rawDescGZIP(), []int{1}
}
func (x *GaugeRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type EmptyMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EmptyMessage) Reset() {
*x = EmptyMessage{}
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EmptyMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EmptyMessage) ProtoMessage() {}
func (x *EmptyMessage) ProtoReflect() protoreflect.Message {
mi := &file_interop_stress_grpc_testing_metrics_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EmptyMessage.ProtoReflect.Descriptor instead.
func (*EmptyMessage) Descriptor() ([]byte, []int) {
return file_interop_stress_grpc_testing_metrics_proto_rawDescGZIP(), []int{2}
}
var File_interop_stress_grpc_testing_metrics_proto protoreflect.FileDescriptor
const file_interop_stress_grpc_testing_metrics_proto_rawDesc = "" +
"\n" +
")interop/stress/grpc_testing/metrics.proto\x12\fgrpc.testing\"\x97\x01\n" +
"\rGaugeResponse\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" +
"\n" +
"long_value\x18\x02 \x01(\x03H\x00R\tlongValue\x12#\n" +
"\fdouble_value\x18\x03 \x01(\x01H\x00R\vdoubleValue\x12#\n" +
"\fstring_value\x18\x04 \x01(\tH\x00R\vstringValueB\a\n" +
"\x05value\"\"\n" +
"\fGaugeRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"\x0e\n" +
"\fEmptyMessage2\xa0\x01\n" +
"\x0eMetricsService\x12I\n" +
"\fGetAllGauges\x12\x1a.grpc.testing.EmptyMessage\x1a\x1b.grpc.testing.GaugeResponse0\x01\x12C\n" +
"\bGetGauge\x12\x1a.grpc.testing.GaugeRequest\x1a\x1b.grpc.testing.GaugeResponseB4Z2google.golang.org/grpc/interop/stress/grpc_testingb\x06proto3"
var (
file_interop_stress_grpc_testing_metrics_proto_rawDescOnce sync.Once
file_interop_stress_grpc_testing_metrics_proto_rawDescData []byte
)
func file_interop_stress_grpc_testing_metrics_proto_rawDescGZIP() []byte {
file_interop_stress_grpc_testing_metrics_proto_rawDescOnce.Do(func() {
file_interop_stress_grpc_testing_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_interop_stress_grpc_testing_metrics_proto_rawDesc), len(file_interop_stress_grpc_testing_metrics_proto_rawDesc)))
})
return file_interop_stress_grpc_testing_metrics_proto_rawDescData
}
var file_interop_stress_grpc_testing_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_interop_stress_grpc_testing_metrics_proto_goTypes = []any{
(*GaugeResponse)(nil), // 0: grpc.testing.GaugeResponse
(*GaugeRequest)(nil), // 1: grpc.testing.GaugeRequest
(*EmptyMessage)(nil), // 2: grpc.testing.EmptyMessage
}
var file_interop_stress_grpc_testing_metrics_proto_depIdxs = []int32{
2, // 0: grpc.testing.MetricsService.GetAllGauges:input_type -> grpc.testing.EmptyMessage
1, // 1: grpc.testing.MetricsService.GetGauge:input_type -> grpc.testing.GaugeRequest
0, // 2: grpc.testing.MetricsService.GetAllGauges:output_type -> grpc.testing.GaugeResponse
0, // 3: grpc.testing.MetricsService.GetGauge:output_type -> grpc.testing.GaugeResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_interop_stress_grpc_testing_metrics_proto_init() }
func file_interop_stress_grpc_testing_metrics_proto_init() {
if File_interop_stress_grpc_testing_metrics_proto != nil {
return
}
file_interop_stress_grpc_testing_metrics_proto_msgTypes[0].OneofWrappers = []any{
(*GaugeResponse_LongValue)(nil),
(*GaugeResponse_DoubleValue)(nil),
(*GaugeResponse_StringValue)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_interop_stress_grpc_testing_metrics_proto_rawDesc), len(file_interop_stress_grpc_testing_metrics_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_interop_stress_grpc_testing_metrics_proto_goTypes,
DependencyIndexes: file_interop_stress_grpc_testing_metrics_proto_depIdxs,
MessageInfos: file_interop_stress_grpc_testing_metrics_proto_msgTypes,
}.Build()
File_interop_stress_grpc_testing_metrics_proto = out.File
file_interop_stress_grpc_testing_metrics_proto_goTypes = nil
file_interop_stress_grpc_testing_metrics_proto_depIdxs = nil
}

View File

@@ -0,0 +1,51 @@
// Copyright 2015-2016 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.
// Contains the definitions for a metrics service and the type of metrics
// exposed by the service.
//
// Currently, 'Gauge' (i.e a metric that represents the measured value of
// something at an instant of time) is the only metric type supported by the
// service.
syntax = "proto3";
option go_package = "google.golang.org/grpc/interop/stress/grpc_testing";
package grpc.testing;
// Response message containing the gauge name and value
message GaugeResponse {
string name = 1;
oneof value {
int64 long_value = 2;
double double_value = 3;
string string_value = 4;
}
}
// Request message containing the gauge name
message GaugeRequest {
string name = 1;
}
message EmptyMessage {}
service MetricsService {
// Returns the values of all the gauges that are currently being maintained by
// the service
rpc GetAllGauges(EmptyMessage) returns (stream GaugeResponse);
// Returns the value of one gauge
rpc GetGauge(GaugeRequest) returns (GaugeResponse);
}

View File

@@ -0,0 +1,190 @@
// Copyright 2015-2016 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.
// Contains the definitions for a metrics service and the type of metrics
// exposed by the service.
//
// Currently, 'Gauge' (i.e a metric that represents the measured value of
// something at an instant of time) is the only metric type supported by the
// service.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: interop/stress/grpc_testing/metrics.proto
package grpc_testing
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
MetricsService_GetAllGauges_FullMethodName = "/grpc.testing.MetricsService/GetAllGauges"
MetricsService_GetGauge_FullMethodName = "/grpc.testing.MetricsService/GetGauge"
)
// MetricsServiceClient is the client API for MetricsService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MetricsServiceClient interface {
// Returns the values of all the gauges that are currently being maintained by
// the service
GetAllGauges(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GaugeResponse], error)
// Returns the value of one gauge
GetGauge(ctx context.Context, in *GaugeRequest, opts ...grpc.CallOption) (*GaugeResponse, error)
}
type metricsServiceClient struct {
cc grpc.ClientConnInterface
}
func NewMetricsServiceClient(cc grpc.ClientConnInterface) MetricsServiceClient {
return &metricsServiceClient{cc}
}
func (c *metricsServiceClient) GetAllGauges(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GaugeResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &MetricsService_ServiceDesc.Streams[0], MetricsService_GetAllGauges_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[EmptyMessage, GaugeResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MetricsService_GetAllGaugesClient = grpc.ServerStreamingClient[GaugeResponse]
func (c *metricsServiceClient) GetGauge(ctx context.Context, in *GaugeRequest, opts ...grpc.CallOption) (*GaugeResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GaugeResponse)
err := c.cc.Invoke(ctx, MetricsService_GetGauge_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// MetricsServiceServer is the server API for MetricsService service.
// All implementations must embed UnimplementedMetricsServiceServer
// for forward compatibility.
type MetricsServiceServer interface {
// Returns the values of all the gauges that are currently being maintained by
// the service
GetAllGauges(*EmptyMessage, grpc.ServerStreamingServer[GaugeResponse]) error
// Returns the value of one gauge
GetGauge(context.Context, *GaugeRequest) (*GaugeResponse, error)
mustEmbedUnimplementedMetricsServiceServer()
}
// UnimplementedMetricsServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedMetricsServiceServer struct{}
func (UnimplementedMetricsServiceServer) GetAllGauges(*EmptyMessage, grpc.ServerStreamingServer[GaugeResponse]) error {
return status.Error(codes.Unimplemented, "method GetAllGauges not implemented")
}
func (UnimplementedMetricsServiceServer) GetGauge(context.Context, *GaugeRequest) (*GaugeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetGauge not implemented")
}
func (UnimplementedMetricsServiceServer) mustEmbedUnimplementedMetricsServiceServer() {}
func (UnimplementedMetricsServiceServer) testEmbeddedByValue() {}
// UnsafeMetricsServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MetricsServiceServer will
// result in compilation errors.
type UnsafeMetricsServiceServer interface {
mustEmbedUnimplementedMetricsServiceServer()
}
func RegisterMetricsServiceServer(s grpc.ServiceRegistrar, srv MetricsServiceServer) {
// If the following call panics, it indicates UnimplementedMetricsServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&MetricsService_ServiceDesc, srv)
}
func _MetricsService_GetAllGauges_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(EmptyMessage)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(MetricsServiceServer).GetAllGauges(m, &grpc.GenericServerStream[EmptyMessage, GaugeResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MetricsService_GetAllGaugesServer = grpc.ServerStreamingServer[GaugeResponse]
func _MetricsService_GetGauge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GaugeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MetricsServiceServer).GetGauge(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MetricsService_GetGauge_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MetricsServiceServer).GetGauge(ctx, req.(*GaugeRequest))
}
return interceptor(ctx, in, info, handler)
}
// MetricsService_ServiceDesc is the grpc.ServiceDesc for MetricsService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MetricsService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.testing.MetricsService",
HandlerType: (*MetricsServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetGauge",
Handler: _MetricsService_GetGauge_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "GetAllGauges",
Handler: _MetricsService_GetAllGauges_Handler,
ServerStreams: true,
},
},
Metadata: "interop/stress/grpc_testing/metrics.proto",
}

View File

@@ -0,0 +1,86 @@
/*
*
* Copyright 2016 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 metrics_client is a client to retrieve metrics from the server.
package main
import (
"context"
"flag"
"fmt"
"io"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog"
metricspb "google.golang.org/grpc/interop/stress/grpc_testing"
)
var (
metricsServerAddress = flag.String("metrics_server_address", "", "The metrics server addresses in the format <hostname>:<port>")
totalOnly = flag.Bool("total_only", false, "If true, this prints only the total value of all gauges")
logger = grpclog.Component("stress")
)
func printMetrics(client metricspb.MetricsServiceClient, totalOnly bool) {
stream, err := client.GetAllGauges(context.Background(), &metricspb.EmptyMessage{})
if err != nil {
logger.Fatalf("failed to call GetAllGauges: %v", err)
}
var (
overallQPS int64
rpcStatus error
)
for {
gaugeResponse, err := stream.Recv()
if err != nil {
rpcStatus = err
break
}
if _, ok := gaugeResponse.GetValue().(*metricspb.GaugeResponse_LongValue); !ok {
panic(fmt.Sprintf("gauge %s is not a long value", gaugeResponse.Name))
}
v := gaugeResponse.GetLongValue()
if !totalOnly {
logger.Infof("%s: %d", gaugeResponse.Name, v)
}
overallQPS += v
}
if rpcStatus != io.EOF {
logger.Fatalf("failed to finish server streaming: %v", rpcStatus)
}
logger.Infof("overall qps: %d", overallQPS)
}
func main() {
flag.Parse()
if *metricsServerAddress == "" {
logger.Fatal("-metrics_server_address is unset")
}
conn, err := grpc.NewClient(*metricsServerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
logger.Fatalf("grpc.NewClient(%q) = %v", metricsServerAddress, err)
}
defer conn.Close()
c := metricspb.NewMetricsServiceClient(conn)
printMetrics(c, *totalOnly)
}

990
interop/test_utils.go Normal file
View File

@@ -0,0 +1,990 @@
/*
*
* Copyright 2014 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 interop contains functions used by interop client/server.
//
// See interop test case descriptions [here].
//
// [here]: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
package interop
import (
"context"
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/orca"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
var (
reqSizes = []int{27182, 8, 1828, 45904}
respSizes = []int{31415, 9, 2653, 58979}
largeReqSize = 271828
largeRespSize = 314159
initialMetadataKey = "x-grpc-test-echo-initial"
trailingMetadataKey = "x-grpc-test-echo-trailing-bin"
logger = grpclog.Component("interop")
)
// ClientNewPayload returns a payload of the given type and size.
func ClientNewPayload(t testpb.PayloadType, size int) *testpb.Payload {
if size < 0 {
logger.Fatalf("Requested a response with invalid length %d", size)
}
body := make([]byte, size)
switch t {
case testpb.PayloadType_COMPRESSABLE:
default:
logger.Fatalf("Unsupported payload type: %d", t)
}
return &testpb.Payload{
Type: t,
Body: body,
}
}
// DoEmptyUnaryCall performs a unary RPC with empty request and response messages.
func DoEmptyUnaryCall(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
reply, err := tc.EmptyCall(ctx, &testpb.Empty{}, args...)
if err != nil {
logger.Fatal("/TestService/EmptyCall RPC failed: ", err)
}
if !proto.Equal(&testpb.Empty{}, reply) {
logger.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{})
}
}
// DoLargeUnaryCall performs a unary RPC with large payload in the request and response.
func DoLargeUnaryCall(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
}
reply, err := tc.UnaryCall(ctx, req, args...)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
t := reply.GetPayload().GetType()
s := len(reply.GetPayload().GetBody())
if t != testpb.PayloadType_COMPRESSABLE || s != largeRespSize {
logger.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, largeRespSize)
}
}
// DoClientStreaming performs a client streaming RPC.
func DoClientStreaming(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
stream, err := tc.StreamingInputCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err)
}
var sum int
for _, s := range reqSizes {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, s)
req := &testpb.StreamingInputCallRequest{
Payload: pl,
}
if err := stream.Send(req); err != nil {
logger.Fatalf("%v has error %v while sending %v", stream, err, req)
}
sum += s
}
reply, err := stream.CloseAndRecv()
if err != nil {
logger.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
if reply.GetAggregatedPayloadSize() != int32(sum) {
logger.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum)
}
}
// DoServerStreaming performs a server streaming RPC.
func DoServerStreaming(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
respParam := make([]*testpb.ResponseParameters, len(respSizes))
for i, s := range respSizes {
respParam[i] = &testpb.ResponseParameters{
Size: int32(s),
}
}
req := &testpb.StreamingOutputCallRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseParameters: respParam,
}
stream, err := tc.StreamingOutputCall(ctx, req, args...)
if err != nil {
logger.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err)
}
var rpcStatus error
var respCnt int
var index int
for {
reply, err := stream.Recv()
if err != nil {
rpcStatus = err
break
}
t := reply.GetPayload().GetType()
if t != testpb.PayloadType_COMPRESSABLE {
logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
}
size := len(reply.GetPayload().GetBody())
if size != respSizes[index] {
logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
}
index++
respCnt++
}
if rpcStatus != io.EOF {
logger.Fatalf("Failed to finish the server streaming rpc: %v", rpcStatus)
}
if respCnt != len(respSizes) {
logger.Fatalf("Got %d reply, want %d", len(respSizes), respCnt)
}
}
// DoPingPong performs ping-pong style bi-directional streaming RPC.
func DoPingPong(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
}
var index int
for index < len(reqSizes) {
respParam := []*testpb.ResponseParameters{
{
Size: int32(respSizes[index]),
},
}
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
req := &testpb.StreamingOutputCallRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseParameters: respParam,
Payload: pl,
}
if err := stream.Send(req); err != nil {
logger.Fatalf("%v has error %v while sending %v", stream, err, req)
}
reply, err := stream.Recv()
if err != nil {
logger.Fatalf("%v.Recv() = %v", stream, err)
}
t := reply.GetPayload().GetType()
if t != testpb.PayloadType_COMPRESSABLE {
logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
}
size := len(reply.GetPayload().GetBody())
if size != respSizes[index] {
logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
}
index++
}
if err := stream.CloseSend(); err != nil {
logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil)
}
if _, err := stream.Recv(); err != io.EOF {
logger.Fatalf("%v failed to complele the ping pong test: %v", stream, err)
}
}
// DoEmptyStream sets up a bi-directional streaming with zero message.
func DoEmptyStream(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
}
if err := stream.CloseSend(); err != nil {
logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil)
}
if _, err := stream.Recv(); err != io.EOF {
logger.Fatalf("%v failed to complete the empty stream test: %v", stream, err)
}
}
// DoTimeoutOnSleepingServer performs an RPC on a sleep server which causes RPC timeout.
func DoTimeoutOnSleepingServer(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
defer cancel()
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
if status.Code(err) == codes.DeadlineExceeded {
return
}
logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
}
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
req := &testpb.StreamingOutputCallRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
Payload: pl,
}
if err := stream.Send(req); err != nil && err != io.EOF {
logger.Fatalf("%v.Send(_) = %v", stream, err)
}
if _, err := stream.Recv(); status.Code(err) != codes.DeadlineExceeded {
logger.Fatalf("%v.Recv() = _, %v, want error code %d", stream, err, codes.DeadlineExceeded)
}
}
// DoComputeEngineCreds performs a unary RPC with compute engine auth.
func DoComputeEngineCreds(ctx context.Context, tc testgrpc.TestServiceClient, serviceAccount, oauthScope string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
user := reply.GetUsername()
scope := reply.GetOauthScope()
if user != serviceAccount {
logger.Fatalf("Got user name %q, want %q.", user, serviceAccount)
}
if !strings.Contains(oauthScope, scope) {
logger.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, oauthScope)
}
}
func getServiceAccountJSONKey(keyFile string) []byte {
jsonKey, err := os.ReadFile(keyFile)
if err != nil {
logger.Fatalf("Failed to read the service account key file: %v", err)
}
return jsonKey
}
// DoServiceAccountCreds performs a unary RPC with service account auth.
func DoServiceAccountCreds(ctx context.Context, tc testgrpc.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
user := reply.GetUsername()
scope := reply.GetOauthScope()
if !strings.Contains(string(jsonKey), user) {
logger.Fatalf("Got user name %q which is NOT a substring of %q.", user, jsonKey)
}
if !strings.Contains(oauthScope, scope) {
logger.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, oauthScope)
}
}
// DoJWTTokenCreds performs a unary RPC with JWT token auth.
func DoJWTTokenCreds(ctx context.Context, tc testgrpc.TestServiceClient, serviceAccountKeyFile string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
user := reply.GetUsername()
if !strings.Contains(string(jsonKey), user) {
logger.Fatalf("Got user name %q which is NOT a substring of %q.", user, jsonKey)
}
}
// GetToken obtains an OAUTH token from the input.
func GetToken(ctx context.Context, serviceAccountKeyFile string, oauthScope string) *oauth2.Token {
jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
config, err := google.JWTConfigFromJSON(jsonKey, oauthScope)
if err != nil {
logger.Fatalf("Failed to get the config: %v", err)
}
token, err := config.TokenSource(ctx).Token()
if err != nil {
logger.Fatalf("Failed to get the token: %v", err)
}
return token
}
// DoOauth2TokenCreds performs a unary RPC with OAUTH2 token auth.
func DoOauth2TokenCreds(ctx context.Context, tc testgrpc.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
user := reply.GetUsername()
scope := reply.GetOauthScope()
if !strings.Contains(string(jsonKey), user) {
logger.Fatalf("Got user name %q which is NOT a substring of %q.", user, jsonKey)
}
if !strings.Contains(oauthScope, scope) {
logger.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, oauthScope)
}
}
// DoPerRPCCreds performs a unary RPC with per RPC OAUTH2 token.
func DoPerRPCCreds(ctx context.Context, tc testgrpc.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
token := GetToken(ctx, serviceAccountKeyFile, oauthScope)
kv := map[string]string{"authorization": token.Type() + " " + token.AccessToken}
ctx = metadata.NewOutgoingContext(ctx, metadata.MD{"authorization": []string{kv["authorization"]}})
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
user := reply.GetUsername()
scope := reply.GetOauthScope()
if !strings.Contains(string(jsonKey), user) {
logger.Fatalf("Got user name %q which is NOT a substring of %q.", user, jsonKey)
}
if !strings.Contains(oauthScope, scope) {
logger.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, oauthScope)
}
}
// DoGoogleDefaultCredentials performs a unary RPC with google default credentials
func DoGoogleDefaultCredentials(ctx context.Context, tc testgrpc.TestServiceClient, defaultServiceAccount string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
if reply.GetUsername() != defaultServiceAccount {
logger.Fatalf("Got user name %q; wanted %q. ", reply.GetUsername(), defaultServiceAccount)
}
}
// DoComputeEngineChannelCredentials performs a unary RPC with compute engine channel credentials
func DoComputeEngineChannelCredentials(ctx context.Context, tc testgrpc.TestServiceClient, defaultServiceAccount string) {
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(largeRespSize),
Payload: pl,
FillUsername: true,
FillOauthScope: true,
}
reply, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
if reply.GetUsername() != defaultServiceAccount {
logger.Fatalf("Got user name %q; wanted %q. ", reply.GetUsername(), defaultServiceAccount)
}
}
var testMetadata = metadata.MD{
"key1": []string{"value1"},
"key2": []string{"value2"},
}
// DoCancelAfterBegin cancels the RPC after metadata has been sent but before payloads are sent.
func DoCancelAfterBegin(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
ctx, cancel := context.WithCancel(metadata.NewOutgoingContext(ctx, testMetadata))
stream, err := tc.StreamingInputCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err)
}
cancel()
_, err = stream.CloseAndRecv()
if status.Code(err) != codes.Canceled {
logger.Fatalf("%v.CloseAndRecv() got error code %d, want %d", stream, status.Code(err), codes.Canceled)
}
}
// DoCancelAfterFirstResponse cancels the RPC after receiving the first message from the server.
func DoCancelAfterFirstResponse(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
ctx, cancel := context.WithCancel(ctx)
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
}
respParam := []*testpb.ResponseParameters{
{
Size: 31415,
},
}
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
req := &testpb.StreamingOutputCallRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseParameters: respParam,
Payload: pl,
}
if err := stream.Send(req); err != nil {
logger.Fatalf("%v has error %v while sending %v", stream, err, req)
}
if _, err := stream.Recv(); err != nil {
logger.Fatalf("%v.Recv() = %v", stream, err)
}
cancel()
if _, err := stream.Recv(); status.Code(err) != codes.Canceled {
logger.Fatalf("%v compleled with error code %d, want %d", stream, status.Code(err), codes.Canceled)
}
}
var (
initialMetadataValue = "test_initial_metadata_value"
trailingMetadataValue = "\x0a\x0b\x0a\x0b\x0a\x0b"
customMetadata = metadata.Pairs(
initialMetadataKey, initialMetadataValue,
trailingMetadataKey, trailingMetadataValue,
)
)
func validateMetadata(header, trailer metadata.MD) {
if len(header[initialMetadataKey]) != 1 {
logger.Fatalf("Expected exactly one header from server. Received %d", len(header[initialMetadataKey]))
}
if header[initialMetadataKey][0] != initialMetadataValue {
logger.Fatalf("Got header %s; want %s", header[initialMetadataKey][0], initialMetadataValue)
}
if len(trailer[trailingMetadataKey]) != 1 {
logger.Fatalf("Expected exactly one trailer from server. Received %d", len(trailer[trailingMetadataKey]))
}
if trailer[trailingMetadataKey][0] != trailingMetadataValue {
logger.Fatalf("Got trailer %s; want %s", trailer[trailingMetadataKey][0], trailingMetadataValue)
}
}
// DoCustomMetadata checks that metadata is echoed back to the client.
func DoCustomMetadata(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
// Testing with UnaryCall.
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 1)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(1),
Payload: pl,
}
ctx = metadata.NewOutgoingContext(ctx, customMetadata)
var header, trailer metadata.MD
args = append(args, grpc.Header(&header), grpc.Trailer(&trailer))
reply, err := tc.UnaryCall(
ctx,
req,
args...,
)
if err != nil {
logger.Fatal("/TestService/UnaryCall RPC failed: ", err)
}
t := reply.GetPayload().GetType()
s := len(reply.GetPayload().GetBody())
if t != testpb.PayloadType_COMPRESSABLE || s != 1 {
logger.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, 1)
}
validateMetadata(header, trailer)
// Testing with FullDuplex.
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.FullDuplexCall(_) = _, %v, want <nil>", tc, err)
}
respParam := []*testpb.ResponseParameters{
{
Size: 1,
},
}
streamReq := &testpb.StreamingOutputCallRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseParameters: respParam,
Payload: pl,
}
if err := stream.Send(streamReq); err != nil {
logger.Fatalf("%v has error %v while sending %v", stream, err, streamReq)
}
streamHeader, err := stream.Header()
if err != nil {
logger.Fatalf("%v.Header() = %v", stream, err)
}
if _, err := stream.Recv(); err != nil {
logger.Fatalf("%v.Recv() = %v", stream, err)
}
if err := stream.CloseSend(); err != nil {
logger.Fatalf("%v.CloseSend() = %v, want <nil>", stream, err)
}
if _, err := stream.Recv(); err != io.EOF {
logger.Fatalf("%v failed to complete the custom metadata test: %v", stream, err)
}
streamTrailer := stream.Trailer()
validateMetadata(streamHeader, streamTrailer)
}
// DoStatusCodeAndMessage checks that the status code is propagated back to the client.
func DoStatusCodeAndMessage(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
var code int32 = 2
msg := "test status message"
expectedErr := status.Error(codes.Code(code), msg)
respStatus := &testpb.EchoStatus{
Code: code,
Message: msg,
}
// Test UnaryCall.
req := &testpb.SimpleRequest{
ResponseStatus: respStatus,
}
if _, err := tc.UnaryCall(ctx, req, args...); err == nil || err.Error() != expectedErr.Error() {
logger.Fatalf("%v.UnaryCall(_, %v) = _, %v, want _, %v", tc, req, err, expectedErr)
}
// Test FullDuplexCall.
stream, err := tc.FullDuplexCall(ctx, args...)
if err != nil {
logger.Fatalf("%v.FullDuplexCall(_) = _, %v, want <nil>", tc, err)
}
streamReq := &testpb.StreamingOutputCallRequest{
ResponseStatus: respStatus,
}
if err := stream.Send(streamReq); err != nil {
logger.Fatalf("%v has error %v while sending %v, want <nil>", stream, err, streamReq)
}
if err := stream.CloseSend(); err != nil {
logger.Fatalf("%v.CloseSend() = %v, want <nil>", stream, err)
}
if _, err = stream.Recv(); err.Error() != expectedErr.Error() {
logger.Fatalf("%v.Recv() returned error %v, want %v", stream, err, expectedErr)
}
}
// DoSpecialStatusMessage verifies Unicode and whitespace is correctly processed
// in status message.
func DoSpecialStatusMessage(ctx context.Context, tc testgrpc.TestServiceClient, args ...grpc.CallOption) {
const (
code int32 = 2
msg string = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
)
expectedErr := status.Error(codes.Code(code), msg)
req := &testpb.SimpleRequest{
ResponseStatus: &testpb.EchoStatus{
Code: code,
Message: msg,
},
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if _, err := tc.UnaryCall(ctx, req, args...); err == nil || err.Error() != expectedErr.Error() {
logger.Fatalf("%v.UnaryCall(_, %v) = _, %v, want _, %v", tc, req, err, expectedErr)
}
}
// DoUnimplementedService attempts to call a method from an unimplemented service.
func DoUnimplementedService(ctx context.Context, tc testgrpc.UnimplementedServiceClient) {
_, err := tc.UnimplementedCall(ctx, &testpb.Empty{})
if status.Code(err) != codes.Unimplemented {
logger.Fatalf("%v.UnimplementedCall() = _, %v, want _, %v", tc, status.Code(err), codes.Unimplemented)
}
}
// DoUnimplementedMethod attempts to call an unimplemented method.
func DoUnimplementedMethod(ctx context.Context, cc *grpc.ClientConn) {
var req, reply proto.Message
if err := cc.Invoke(ctx, "/grpc.testing.TestService/UnimplementedCall", req, reply); err == nil || status.Code(err) != codes.Unimplemented {
logger.Fatalf("ClientConn.Invoke(_, _, _, _, _) = %v, want error code %s", err, codes.Unimplemented)
}
}
// DoPickFirstUnary runs multiple RPCs (rpcCount) and checks that all requests
// are sent to the same backend.
func DoPickFirstUnary(ctx context.Context, tc testgrpc.TestServiceClient) {
const rpcCount = 100
pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 1)
req := &testpb.SimpleRequest{
ResponseType: testpb.PayloadType_COMPRESSABLE,
ResponseSize: int32(1),
Payload: pl,
FillServerId: true,
}
// TODO(mohanli): Revert the timeout back to 10s once TD migrates to xdstp.
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var serverID string
for i := 0; i < rpcCount; i++ {
resp, err := tc.UnaryCall(ctx, req)
if err != nil {
logger.Fatalf("iteration %d, failed to do UnaryCall: %v", i, err)
}
id := resp.ServerId
if id == "" {
logger.Fatalf("iteration %d, got empty server ID", i)
}
if i == 0 {
serverID = id
continue
}
if serverID != id {
logger.Fatalf("iteration %d, got different server ids: %q vs %q", i, serverID, id)
}
}
}
type testServer struct {
testgrpc.UnimplementedTestServiceServer
orcaMu sync.Mutex
metricsRecorder orca.ServerMetricsRecorder
}
// NewTestServerOptions contains options that control the behavior of the test
// server returned by NewTestServer.
type NewTestServerOptions struct {
MetricsRecorder orca.ServerMetricsRecorder
}
// NewTestServer creates a test server for test service. opts carries optional
// settings and does not need to be provided. If multiple opts are provided,
// only the first one is used.
func NewTestServer(opts ...NewTestServerOptions) testgrpc.TestServiceServer {
if len(opts) > 0 {
return &testServer{metricsRecorder: opts[0].MetricsRecorder}
}
return &testServer{}
}
func (s *testServer) EmptyCall(context.Context, *testpb.Empty) (*testpb.Empty, error) {
return new(testpb.Empty), nil
}
func serverNewPayload(t testpb.PayloadType, size int32) (*testpb.Payload, error) {
if size < 0 {
return nil, fmt.Errorf("requested a response with invalid length %d", size)
}
body := make([]byte, size)
switch t {
case testpb.PayloadType_COMPRESSABLE:
default:
return nil, fmt.Errorf("unsupported payload type: %d", t)
}
return &testpb.Payload{
Type: t,
Body: body,
}, nil
}
func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
st := in.GetResponseStatus()
if md, ok := metadata.FromIncomingContext(ctx); ok {
if initialMetadata, ok := md[initialMetadataKey]; ok {
header := metadata.Pairs(initialMetadataKey, initialMetadata[0])
grpc.SendHeader(ctx, header)
}
if trailingMetadata, ok := md[trailingMetadataKey]; ok {
trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0])
grpc.SetTrailer(ctx, trailer)
}
}
if st != nil && st.Code != 0 {
return nil, status.Error(codes.Code(st.Code), st.Message)
}
pl, err := serverNewPayload(in.GetResponseType(), in.GetResponseSize())
if err != nil {
return nil, err
}
if r, orcaData := orca.CallMetricsRecorderFromContext(ctx), in.GetOrcaPerQueryReport(); r != nil && orcaData != nil {
// Transfer the request's per-Call ORCA data to the call metrics
// recorder in the context, if present.
setORCAMetrics(r, orcaData)
}
return &testpb.SimpleResponse{
Payload: pl,
}, nil
}
func setORCAMetrics(r orca.ServerMetricsRecorder, orcaData *testpb.TestOrcaReport) {
r.SetCPUUtilization(orcaData.CpuUtilization)
r.SetMemoryUtilization(orcaData.MemoryUtilization)
if rq, ok := r.(orca.CallMetricsRecorder); ok {
for k, v := range orcaData.RequestCost {
rq.SetRequestCost(k, v)
}
}
for k, v := range orcaData.Utilization {
r.SetNamedUtilization(k, v)
}
}
func (s *testServer) StreamingOutputCall(args *testpb.StreamingOutputCallRequest, stream testgrpc.TestService_StreamingOutputCallServer) error {
cs := args.GetResponseParameters()
for _, c := range cs {
if us := c.GetIntervalUs(); us > 0 {
time.Sleep(time.Duration(us) * time.Microsecond)
}
pl, err := serverNewPayload(args.GetResponseType(), c.GetSize())
if err != nil {
return err
}
if err := stream.Send(&testpb.StreamingOutputCallResponse{
Payload: pl,
}); err != nil {
return err
}
}
return nil
}
func (s *testServer) StreamingInputCall(stream testgrpc.TestService_StreamingInputCallServer) error {
var sum int
for {
in, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&testpb.StreamingInputCallResponse{
AggregatedPayloadSize: int32(sum),
})
}
if err != nil {
return err
}
p := in.GetPayload().GetBody()
sum += len(p)
}
}
func (s *testServer) FullDuplexCall(stream testgrpc.TestService_FullDuplexCallServer) error {
if md, ok := metadata.FromIncomingContext(stream.Context()); ok {
if initialMetadata, ok := md[initialMetadataKey]; ok {
header := metadata.Pairs(initialMetadataKey, initialMetadata[0])
stream.SendHeader(header)
}
if trailingMetadata, ok := md[trailingMetadataKey]; ok {
trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0])
stream.SetTrailer(trailer)
}
}
hasORCALock := false
for {
in, err := stream.Recv()
if err == io.EOF {
// read done.
return nil
}
if err != nil {
return err
}
st := in.GetResponseStatus()
if st != nil && st.Code != 0 {
return status.Error(codes.Code(st.Code), st.Message)
}
if r, orcaData := s.metricsRecorder, in.GetOrcaOobReport(); r != nil && orcaData != nil {
// Transfer the request's OOB ORCA data to the server metrics recorder
// in the server, if present.
if !hasORCALock {
s.orcaMu.Lock()
defer s.orcaMu.Unlock()
hasORCALock = true
}
setORCAMetrics(r, orcaData)
}
cs := in.GetResponseParameters()
for _, c := range cs {
if us := c.GetIntervalUs(); us > 0 {
time.Sleep(time.Duration(us) * time.Microsecond)
}
pl, err := serverNewPayload(in.GetResponseType(), c.GetSize())
if err != nil {
return err
}
if err := stream.Send(&testpb.StreamingOutputCallResponse{
Payload: pl,
}); err != nil {
return err
}
}
}
}
func (s *testServer) HalfDuplexCall(stream testgrpc.TestService_HalfDuplexCallServer) error {
var msgBuf []*testpb.StreamingOutputCallRequest
for {
in, err := stream.Recv()
if err == io.EOF {
// read done.
break
}
if err != nil {
return err
}
msgBuf = append(msgBuf, in)
}
for _, m := range msgBuf {
cs := m.GetResponseParameters()
for _, c := range cs {
if us := c.GetIntervalUs(); us > 0 {
time.Sleep(time.Duration(us) * time.Microsecond)
}
pl, err := serverNewPayload(m.GetResponseType(), c.GetSize())
if err != nil {
return err
}
if err := stream.Send(&testpb.StreamingOutputCallResponse{
Payload: pl,
}); err != nil {
return err
}
}
}
return nil
}
// DoORCAPerRPCTest performs a unary RPC that enables ORCA per-call reporting
// and verifies the load report sent back to the LB policy's Done callback.
func DoORCAPerRPCTest(ctx context.Context, tc testgrpc.TestServiceClient) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
orcaRes := &v3orcapb.OrcaLoadReport{}
_, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{
OrcaPerQueryReport: &testpb.TestOrcaReport{
CpuUtilization: 0.8210,
MemoryUtilization: 0.5847,
RequestCost: map[string]float64{"cost": 3456.32},
Utilization: map[string]float64{"util": 0.30499},
},
})
if err != nil {
logger.Fatalf("/TestService/UnaryCall RPC failed: ", err)
}
want := &v3orcapb.OrcaLoadReport{
CpuUtilization: 0.8210,
MemUtilization: 0.5847,
RequestCost: map[string]float64{"cost": 3456.32},
Utilization: map[string]float64{"util": 0.30499},
}
if !proto.Equal(orcaRes, want) {
logger.Fatalf("/TestService/UnaryCall RPC received ORCA load report %+v; want %+v", orcaRes, want)
}
}
// DoORCAOOBTest performs a streaming RPC that enables ORCA OOB reporting and
// verifies the load report sent to the LB policy's OOB listener.
func DoORCAOOBTest(ctx context.Context, tc testgrpc.TestServiceClient) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
stream, err := tc.FullDuplexCall(ctx)
if err != nil {
logger.Fatalf("/TestService/FullDuplexCall received error starting stream: %v", err)
}
err = stream.Send(&testpb.StreamingOutputCallRequest{
OrcaOobReport: &testpb.TestOrcaReport{
CpuUtilization: 0.8210,
MemoryUtilization: 0.5847,
Utilization: map[string]float64{"util": 0.30499},
},
ResponseParameters: []*testpb.ResponseParameters{{Size: 1}},
})
if err != nil {
logger.Fatalf("/TestService/FullDuplexCall received error sending: %v", err)
}
_, err = stream.Recv()
if err != nil {
logger.Fatalf("/TestService/FullDuplexCall received error receiving: %v", err)
}
want := &v3orcapb.OrcaLoadReport{
CpuUtilization: 0.8210,
MemUtilization: 0.5847,
Utilization: map[string]float64{"util": 0.30499},
}
checkORCAMetrics(ctx, tc, want)
err = stream.Send(&testpb.StreamingOutputCallRequest{
OrcaOobReport: &testpb.TestOrcaReport{
CpuUtilization: 0.29309,
MemoryUtilization: 0.2,
Utilization: map[string]float64{"util": 0.2039},
},
ResponseParameters: []*testpb.ResponseParameters{{Size: 1}},
})
if err != nil {
logger.Fatalf("/TestService/FullDuplexCall received error sending: %v", err)
}
_, err = stream.Recv()
if err != nil {
logger.Fatalf("/TestService/FullDuplexCall received error receiving: %v", err)
}
want = &v3orcapb.OrcaLoadReport{
CpuUtilization: 0.29309,
MemUtilization: 0.2,
Utilization: map[string]float64{"util": 0.2039},
}
checkORCAMetrics(ctx, tc, want)
}
func checkORCAMetrics(ctx context.Context, tc testgrpc.TestServiceClient, want *v3orcapb.OrcaLoadReport) {
for ctx.Err() == nil {
orcaRes := &v3orcapb.OrcaLoadReport{}
if _, err := tc.UnaryCall(contextWithORCAResult(ctx, &orcaRes), &testpb.SimpleRequest{}); err != nil {
logger.Fatalf("/TestService/UnaryCall RPC failed: ", err)
}
if proto.Equal(orcaRes, want) {
return
}
logger.Infof("/TestService/UnaryCall RPC received ORCA load report %+v; want %+v", orcaRes, want)
time.Sleep(time.Second)
}
logger.Fatalf("timed out waiting for expected ORCA load report")
}

View 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"]

View 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
View 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,
}
}

View 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
View 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
View 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=

View 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"]

View 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)
}
}

View File

@@ -0,0 +1,152 @@
/*
*
* Copyright 2014 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 is an interop client.
package main
import (
"context"
"flag"
"log"
"strings"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/google"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/interop"
_ "google.golang.org/grpc/balancer/grpclb" // Register the grpclb load balancing policy.
_ "google.golang.org/grpc/balancer/rls" // Register the RLS load balancing policy.
_ "google.golang.org/grpc/xds/googledirectpath" // Register xDS resolver required for c2p directpath.
testgrpc "google.golang.org/grpc/interop/grpc_testing"
)
const (
computeEngineCredsName = "compute_engine_channel_creds"
insecureCredsName = "INSECURE_CREDENTIALS"
)
var (
serverURIs = flag.String("server_uris", "", "Comma-separated list of sever URIs to make RPCs to")
credentialsTypes = flag.String("credentials_types", "", "Comma-separated list of credentials, each entry is used for the server of the corresponding index in server_uris. Supported values: compute_engine_channel_creds, INSECURE_CREDENTIALS")
soakIterations = flag.Int("soak_iterations", 10, "The number of iterations to use for the two soak tests: rpc_soak and channel_soak")
soakMaxFailures = flag.Int("soak_max_failures", 0, "The number of iterations in soak tests that are allowed to fail (either due to non-OK status code or exceeding the per-iteration max acceptable latency).")
soakPerIterationMaxAcceptableLatencyMs = flag.Int("soak_per_iteration_max_acceptable_latency_ms", 1000, "The number of milliseconds a single iteration in the two soak tests (rpc_soak and channel_soak) should take.")
soakOverallTimeoutSeconds = flag.Int("soak_overall_timeout_seconds", 10, "The overall number of seconds after which a soak test should stop and fail, if the desired number of iterations have not yet completed.")
soakMinTimeMsBetweenRPCs = flag.Int("soak_min_time_ms_between_rpcs", 0, "The minimum time in milliseconds between consecutive RPCs in a soak test (rpc_soak or channel_soak), useful for limiting QPS")
soakRequestSize = flag.Int("soak_request_size", 271828, "The request size in a soak RPC. The default value is set based on the interop large unary test case.")
soakResponseSize = flag.Int("soak_response_size", 314159, "The response size in a soak RPC. The default value is set based on the interop large unary test case.")
soakNumThreads = flag.Int("soak_num_threads", 1, "The number of threads for concurrent execution of the soak tests (rpc_soak or channel_soak). The default value is set based on the interop large unary test case.")
testCase = flag.String("test_case", "rpc_soak",
`Configure different test cases. Valid options are:
rpc_soak: sends --soak_iterations large_unary RPCs;
channel_soak: sends --soak_iterations RPCs, rebuilding the channel each time`)
logger = grpclog.Component("interop")
)
type clientConfig struct {
conn *grpc.ClientConn
tc testgrpc.TestServiceClient
opts []grpc.DialOption
uri string
}
func main() {
flag.Parse()
// validate flags
uris := strings.Split(*serverURIs, ",")
creds := strings.Split(*credentialsTypes, ",")
if len(uris) != len(creds) {
logger.Fatalf("Number of entries in --server_uris (%d) != number of entries in --credentials_types (%d)", len(uris), len(creds))
}
for _, c := range creds {
if c != computeEngineCredsName && c != insecureCredsName {
logger.Fatalf("Unsupported credentials type: %v", c)
}
}
var clients []clientConfig
for i := range uris {
var opts []grpc.DialOption
switch creds[i] {
case computeEngineCredsName:
opts = append(opts, grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()))
case insecureCredsName:
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
cc, err := grpc.NewClient(uris[i], opts...)
if err != nil {
logger.Fatalf("grpc.NewClient(%q) = %v", uris[i], err)
}
defer cc.Close()
clients = append(clients, clientConfig{
conn: cc,
tc: testgrpc.NewTestServiceClient(cc),
opts: opts,
uri: uris[i],
})
}
// run soak tests with the different clients
logger.Infof("Clients running with test case %q", *testCase)
var wg sync.WaitGroup
var channelForTest func() (*grpc.ClientConn, func())
ctx := context.Background()
for i := range clients {
wg.Add(1)
go func(c clientConfig) {
ctxWithDeadline, cancel := context.WithTimeout(ctx, time.Duration(*soakOverallTimeoutSeconds)*time.Second)
defer cancel()
switch *testCase {
case "rpc_soak":
channelForTest = func() (*grpc.ClientConn, func()) { return c.conn, func() {} }
case "channel_soak":
channelForTest = func() (*grpc.ClientConn, func()) {
cc, err := grpc.NewClient(c.uri, c.opts...)
if err != nil {
log.Fatalf("Failed to create shared channel: %v", err)
}
return cc, func() { cc.Close() }
}
default:
logger.Fatal("Unsupported test case: ", *testCase)
}
soakConfig := interop.SoakTestConfig{
RequestSize: *soakRequestSize,
ResponseSize: *soakResponseSize,
PerIterationMaxAcceptableLatency: time.Duration(*soakPerIterationMaxAcceptableLatencyMs) * time.Millisecond,
MinTimeBetweenRPCs: time.Duration(*soakMinTimeMsBetweenRPCs) * time.Millisecond,
OverallTimeout: time.Duration(*soakOverallTimeoutSeconds) * time.Second,
ServerAddr: c.uri,
NumWorkers: *soakNumThreads,
Iterations: *soakIterations,
MaxFailures: *soakMaxFailures,
ChannelForTest: channelForTest,
}
interop.DoSoakTest(ctxWithDeadline, soakConfig)
logger.Infof("%s test done for server: %s", *testCase, c.uri)
wg.Done()
}(clients[i])
}
wg.Wait()
logger.Infoln("All clients done!")
}