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  }