blob: 262d7458aba5ba5efc952a8172e1989eb6b86a3e [file] [log] [blame]
// +build linux,!appengine
/*
*
* 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"
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")
unrouteLBAndBackendAddrsCmd = flag.String("unroute_lb_and_backend_addrs_cmd", "", "Command to make LB and backend address unroutable")
blackholeLBAndBackendAddrsCmd = flag.String("blackhole_lb_and_backend_addrs_cmd", "", "Command to make LB and backend addresses blackholed")
testCase = flag.String("test_case", "",
`Configure different test cases. Valid options are:
fast_fallback_before_startup : LB/backend connections fail fast before RPC's have been made;
fast_fallback_after_startup : LB/backend connections fail fast after RPC's have been made;
slow_fallback_before_startup : LB/backend connections black hole before RPC's have been made;
slow_fallback_after_startup : LB/backend connections black hole 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 testpb.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(network, address 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),
grpc.WithBlock(),
}
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.Dial(*serverURI, opts...)
if err != nil {
errorLog.Fatalf("Fail to dial: %v", 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 testpb.TestServiceClient, fallbackDeadline time.Time) {
fallbackRetryCount := 0
fellBack := false
for time.Now().Before(fallbackDeadline) {
g := doRPCAndGetPath(client, 1*time.Second)
if g == testpb.GrpclbRouteType_GRPCLB_ROUTE_TYPE_FALLBACK {
infoLog.Println("Made one successul 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 doFastFallbackBeforeStartup() {
runCmd(*unrouteLBAndBackendAddrsCmd)
fallbackDeadline := time.Now().Add(5 * time.Second)
conn := createTestConn()
defer conn.Close()
client := testpb.NewTestServiceClient(conn)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func doSlowFallbackBeforeStartup() {
runCmd(*blackholeLBAndBackendAddrsCmd)
fallbackDeadline := time.Now().Add(20 * time.Second)
conn := createTestConn()
defer conn.Close()
client := testpb.NewTestServiceClient(conn)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func doFastFallbackAfterStartup() {
conn := createTestConn()
defer conn.Close()
client := testpb.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(*unrouteLBAndBackendAddrsCmd)
fallbackDeadline := time.Now().Add(40 * time.Second)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func doSlowFallbackAfterStartup() {
conn := createTestConn()
defer conn.Close()
client := testpb.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(*blackholeLBAndBackendAddrsCmd)
fallbackDeadline := time.Now().Add(40 * time.Second)
waitForFallbackAndDoRPCs(client, fallbackDeadline)
}
func main() {
flag.Parse()
if len(*unrouteLBAndBackendAddrsCmd) == 0 {
errorLog.Fatalf("--unroute_lb_and_backend_addrs_cmd unset")
}
if len(*blackholeLBAndBackendAddrsCmd) == 0 {
errorLog.Fatalf("--blackhole_lb_and_backend_addrs_cmd unset")
}
switch *testCase {
case "fast_fallback_before_startup":
doFastFallbackBeforeStartup()
log.Printf("FastFallbackBeforeStartup done!\n")
case "fast_fallback_after_startup":
doFastFallbackAfterStartup()
log.Printf("FastFallbackAfterStartup done!\n")
case "slow_fallback_before_startup":
doSlowFallbackBeforeStartup()
log.Printf("SlowFallbackBeforeStartup done!\n")
case "slow_fallback_after_startup":
doSlowFallbackAfterStartup()
log.Printf("SlowFallbackAfterStartup done!\n")
default:
errorLog.Fatalf("Unsupported test case: %v", *testCase)
}
}