istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/forwarder/grpc.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package forwarder 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net" 22 "strconv" 23 "strings" 24 "time" 25 26 "google.golang.org/grpc" 27 "google.golang.org/grpc/credentials" 28 "google.golang.org/grpc/credentials/insecure" 29 "google.golang.org/grpc/metadata" 30 31 "istio.io/istio/pkg/test/echo" 32 "istio.io/istio/pkg/test/echo/common" 33 "istio.io/istio/pkg/test/echo/proto" 34 ) 35 36 var _ protocol = &grpcProtocol{} 37 38 type grpcProtocol struct { 39 e *executor 40 } 41 42 func newGRPCProtocol(e *executor) protocol { 43 return &grpcProtocol{e: e} 44 } 45 46 type grpcConnectionGetter func() (*grpc.ClientConn, func(), error) 47 48 func (c *grpcProtocol) ForwardEcho(ctx context.Context, cfg *Config) (*proto.ForwardEchoResponse, error) { 49 var getConn grpcConnectionGetter 50 if cfg.newConnectionPerRequest { 51 // Create a new connection per request. 52 getConn = func() (*grpc.ClientConn, func(), error) { 53 conn, err := newGRPCConnection(cfg) 54 if err != nil { 55 return nil, nil, err 56 } 57 return conn, func() { _ = conn.Close() }, nil 58 } 59 } else { 60 // Reuse the connection across all requests. 61 conn, err := newGRPCConnection(cfg) 62 if err != nil { 63 return nil, err 64 } 65 defer func() { _ = conn.Close() }() 66 getConn = func() (*grpc.ClientConn, func(), error) { 67 return conn, func() {}, nil 68 } 69 } 70 71 call := &grpcCall{ 72 e: c.e, 73 getConn: getConn, 74 } 75 return doForward(ctx, cfg, c.e, call.makeRequest) 76 } 77 78 func (c *grpcProtocol) Close() error { 79 return nil 80 } 81 82 type grpcCall struct { 83 e *executor 84 getConn grpcConnectionGetter 85 } 86 87 func (c *grpcCall) makeRequest(ctx context.Context, cfg *Config, requestID int) (string, error) { 88 conn, closeConn, err := c.getConn() 89 if err != nil { 90 return "", err 91 } 92 defer closeConn() 93 94 // Set the per-request timeout. 95 ctx, cancel := context.WithTimeout(ctx, cfg.timeout) 96 defer cancel() 97 98 // Add headers to the request context. 99 outMD := make(metadata.MD) 100 for k, v := range cfg.headers { 101 // Exclude the Host header from the GRPC context. 102 if !strings.EqualFold(hostHeader, k) { 103 outMD.Set(k, v...) 104 } 105 } 106 outMD.Set("X-Request-Id", strconv.Itoa(requestID)) 107 ctx = metadata.NewOutgoingContext(ctx, outMD) 108 109 var outBuffer bytes.Buffer 110 grpcReq := &proto.EchoRequest{ 111 Message: cfg.Request.Message, 112 } 113 // TODO(nmittler): This doesn't fit in with the field pattern. Do we need this? 114 outBuffer.WriteString(fmt.Sprintf("[%d] grpcecho.Echo(%v)\n", requestID, cfg.Request)) 115 116 start := time.Now() 117 client := proto.NewEchoTestServiceClient(conn) 118 var header metadata.MD 119 120 resp, err := client.Echo(ctx, grpcReq, grpc.Header(&header)) 121 if err != nil { 122 return "", err 123 } 124 125 echo.LatencyField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%v", time.Since(start))) 126 echo.ActiveRequestsField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%d", c.e.ActiveRequests())) 127 128 // When the underlying HTTP2 request returns status 404, GRPC 129 // request does not return an error in grpc-go. 130 // Instead, it just returns an empty response 131 for _, line := range strings.Split(resp.GetMessage(), "\n") { 132 if line != "" { 133 echo.WriteBodyLine(&outBuffer, requestID, line) 134 } 135 } 136 for k, v := range header { 137 for _, vv := range v { 138 echo.ResponseHeaderField.WriteKeyValueForRequest(&outBuffer, requestID, k, vv) 139 } 140 } 141 return outBuffer.String(), nil 142 } 143 144 func newGRPCConnection(cfg *Config) (*grpc.ClientConn, error) { 145 var security grpc.DialOption 146 if cfg.secure { 147 security = grpc.WithTransportCredentials(credentials.NewTLS(cfg.tlsConfig)) 148 } else { 149 security = grpc.WithTransportCredentials(insecure.NewCredentials()) 150 } 151 152 opts := []grpc.DialOption{ 153 grpc.WithAuthority(cfg.hostHeader), 154 grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { 155 return newDialer(cfg).DialContext(ctx, "tcp", addr) 156 }), 157 security, 158 } 159 160 // Strip off the scheme from the address (for regular gRPC). 161 address := cfg.Request.Url[len(cfg.scheme+"://"):] 162 163 // Connect to the GRPC server. 164 ctx, cancel := context.WithTimeout(context.Background(), common.ConnectionTimeout) 165 defer cancel() 166 return grpc.DialContext(ctx, address, opts...) 167 }