istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/common/call.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 common 16 17 import ( 18 "context" 19 "fmt" 20 "net" 21 "strconv" 22 "time" 23 24 echoclient "istio.io/istio/pkg/test/echo" 25 "istio.io/istio/pkg/test/echo/common" 26 "istio.io/istio/pkg/test/echo/common/scheme" 27 "istio.io/istio/pkg/test/echo/proto" 28 "istio.io/istio/pkg/test/echo/server/forwarder" 29 "istio.io/istio/pkg/test/framework/components/echo" 30 "istio.io/istio/pkg/test/scopes" 31 "istio.io/istio/pkg/test/util/retry" 32 ) 33 34 type sendFunc func(req *proto.ForwardEchoRequest) (echoclient.Responses, error) 35 36 func callInternal(srcName string, from echo.Caller, opts echo.CallOptions, send sendFunc) (echo.CallResult, error) { 37 // Create the proto request. 38 req := newForwardRequest(opts) 39 sendAndValidate := func() (echo.CallResult, error) { 40 responses, err := send(req) 41 42 // Verify the number of responses matches the expected. 43 if err == nil && len(responses) != opts.Count { 44 err = fmt.Errorf("unexpected number of responses: expected %d, received %d", 45 opts.Count, len(responses)) 46 } 47 48 // Convert to a CallResult. 49 result := echo.CallResult{ 50 From: from, 51 Opts: opts, 52 Responses: responses, 53 } 54 55 // Return the results from the validator. 56 err = opts.Check(result, err) 57 if err != nil { 58 err = fmt.Errorf("call failed from %s to %s (using %s): %v", 59 srcName, getTargetURL(opts), opts.Scheme, err) 60 } 61 62 return result, err 63 } 64 65 if opts.Retry.NoRetry { 66 // Retry is disabled, just send once. 67 t0 := time.Now() 68 defer func() { 69 scopes.Framework.Debugf("echo call complete with duration %v", time.Since(t0)) 70 }() 71 return sendAndValidate() 72 } 73 74 // Retry the call until it succeeds or times out. 75 var result echo.CallResult 76 var err error 77 _, _ = retry.UntilComplete(func() (any, bool, error) { 78 result, err = sendAndValidate() 79 if err != nil { 80 return nil, false, err 81 } 82 return nil, true, nil 83 }, opts.Retry.Options...) 84 85 return result, err 86 } 87 88 type Caller struct { 89 f *forwarder.Instance 90 } 91 92 func NewCaller() *Caller { 93 return &Caller{ 94 f: forwarder.New(), 95 } 96 } 97 98 func (c *Caller) Close() error { 99 return c.f.Close() 100 } 101 102 func (c *Caller) CallEcho(from echo.Caller, opts echo.CallOptions) (echo.CallResult, error) { 103 if err := opts.FillDefaults(); err != nil { 104 return echo.CallResult{}, err 105 } 106 107 send := func(req *proto.ForwardEchoRequest) (echoclient.Responses, error) { 108 ctx, cancel := context.WithTimeout(context.Background(), opts.Timeout) 109 defer cancel() 110 111 ret, err := c.f.ForwardEcho(ctx, &forwarder.Config{ 112 Request: req, 113 Proxy: opts.HTTP.HTTPProxy, 114 PropagateResponse: opts.PropagateResponse, 115 }) 116 if err != nil { 117 return nil, err 118 } 119 resp := echoclient.ParseResponses(req, ret) 120 return resp, nil 121 } 122 return callInternal("TestRunner", from, opts, send) 123 } 124 125 func newForwardRequest(opts echo.CallOptions) *proto.ForwardEchoRequest { 126 return &proto.ForwardEchoRequest{ 127 Url: getTargetURL(opts), 128 Count: int32(opts.Count), 129 Headers: common.HTTPToProtoHeaders(opts.HTTP.Headers), 130 TimeoutMicros: common.DurationToMicros(opts.Timeout), 131 Message: opts.Message, 132 ExpectedResponse: opts.TCP.ExpectedResponse, 133 Http2: opts.HTTP.HTTP2, 134 Http3: opts.HTTP.HTTP3, 135 Method: opts.HTTP.Method, 136 ServerFirst: opts.Port.ServerFirst, 137 Cert: opts.TLS.Cert, 138 Key: opts.TLS.Key, 139 CaCert: opts.TLS.CaCert, 140 CertFile: opts.TLS.CertFile, 141 KeyFile: opts.TLS.KeyFile, 142 CaCertFile: opts.TLS.CaCertFile, 143 InsecureSkipVerify: opts.TLS.InsecureSkipVerify, 144 Alpn: getProtoALPN(opts.TLS.Alpn), 145 FollowRedirects: opts.HTTP.FollowRedirects, 146 ServerName: opts.TLS.ServerName, 147 NewConnectionPerRequest: opts.NewConnectionPerRequest, 148 ForceDNSLookup: opts.ForceDNSLookup, 149 Hbone: &proto.HBONE{ 150 Address: opts.HBONE.Address, 151 Headers: common.HTTPToProtoHeaders(opts.HBONE.Headers), 152 Cert: opts.HBONE.Cert, 153 Key: opts.HBONE.Key, 154 CaCert: opts.HBONE.CaCert, 155 CertFile: opts.HBONE.CertFile, 156 KeyFile: opts.HBONE.KeyFile, 157 CaCertFile: opts.HBONE.CaCertFile, 158 InsecureSkipVerify: opts.HBONE.InsecureSkipVerify, 159 }, 160 ProxyProtocolVersion: getProxyProtoVersion(opts.ProxyProtocolVersion), 161 } 162 } 163 164 func getProxyProtoVersion(protoVer int) proto.ProxyProtoVersion { 165 if protoVer == 1 { 166 return proto.ProxyProtoVersion_V1 167 } else if protoVer == 2 { 168 return proto.ProxyProtoVersion_V2 169 } 170 171 return proto.ProxyProtoVersion_NONE 172 } 173 174 func getProtoALPN(alpn []string) *proto.Alpn { 175 if alpn != nil { 176 return &proto.Alpn{ 177 Value: alpn, 178 } 179 } 180 return nil 181 } 182 183 // EchoClientProvider provides dynamic creation of Echo clients. This allows retries to potentially make 184 // use of different (ready) workloads for forward requests. 185 type EchoClientProvider func() (*echoclient.Client, error) 186 187 func ForwardEcho(srcName string, from echo.Caller, opts echo.CallOptions, clientProvider EchoClientProvider) (echo.CallResult, error) { 188 if err := opts.FillDefaults(); err != nil { 189 return echo.CallResult{}, err 190 } 191 192 res, err := callInternal(srcName, from, opts, func(req *proto.ForwardEchoRequest) (echoclient.Responses, error) { 193 c, err := clientProvider() 194 if err != nil { 195 return nil, err 196 } 197 return c.ForwardEcho(context.Background(), req) 198 }) 199 if err != nil { 200 return echo.CallResult{}, fmt.Errorf("failed calling %s->'%s': %v", 201 srcName, 202 getTargetURL(opts), 203 err) 204 } 205 return res, nil 206 } 207 208 func getTargetURL(opts echo.CallOptions) string { 209 port := opts.Port.ServicePort 210 addressAndPort := net.JoinHostPort(opts.Address, strconv.Itoa(port)) 211 // Forward a request from 'this' service to the destination service. 212 switch opts.Scheme { 213 case scheme.DNS: 214 return fmt.Sprintf("%s://%s", string(opts.Scheme), opts.Address) 215 case scheme.TCP, scheme.GRPC: 216 return fmt.Sprintf("%s://%s", string(opts.Scheme), addressAndPort) 217 case scheme.XDS: 218 return fmt.Sprintf("%s:///%s", string(opts.Scheme), addressAndPort) 219 default: 220 return fmt.Sprintf("%s://%s%s", string(opts.Scheme), addressAndPort, opts.HTTP.Path) 221 } 222 }