istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/client.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 echo
    16  
    17  import (
    18  	"context"
    19  	"crypto/tls"
    20  	"crypto/x509"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  	"time"
    25  
    26  	"go.uber.org/atomic"
    27  	"google.golang.org/grpc"
    28  	"google.golang.org/grpc/credentials"
    29  	"google.golang.org/grpc/credentials/insecure"
    30  	xdscreds "google.golang.org/grpc/credentials/xds"
    31  
    32  	"istio.io/istio/pkg/test/echo/common"
    33  	"istio.io/istio/pkg/test/echo/proto"
    34  )
    35  
    36  var _ io.Closer = &Client{}
    37  
    38  // Client of an Echo server that simplifies request/response processing for Forward commands.
    39  type Client struct {
    40  	conn   *grpc.ClientConn
    41  	client proto.EchoTestServiceClient
    42  }
    43  
    44  // New creates a new echo client.Instance that is connected to the given server address.
    45  func New(address string, tlsSettings *common.TLSSettings, extraDialOpts ...grpc.DialOption) (*Client, error) {
    46  	// Connect to the GRPC (command) endpoint of 'this' app.
    47  	// TODO: make use of common.ConnectionTimeout once it increases
    48  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    49  	defer cancel()
    50  	dialOptions := []grpc.DialOption{grpc.WithBlock()}
    51  	if tlsSettings != nil {
    52  		cert, err := tls.X509KeyPair([]byte(tlsSettings.ClientCert), []byte(tlsSettings.Key))
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  
    57  		var certPool *x509.CertPool
    58  		certPool, err = x509.SystemCertPool()
    59  		if err != nil {
    60  			return nil, fmt.Errorf("failed to fetch Cert from SystemCertPool: %v", err)
    61  		}
    62  
    63  		if tlsSettings.RootCert != "" && !certPool.AppendCertsFromPEM([]byte(tlsSettings.RootCert)) {
    64  			return nil, fmt.Errorf("failed to create cert pool")
    65  		}
    66  		cfg := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: certPool, MinVersion: tls.VersionTLS12})
    67  		// If provided, override the hostname
    68  		if tlsSettings.Hostname != "" {
    69  			dialOptions = append(dialOptions, grpc.WithAuthority(tlsSettings.Hostname))
    70  		}
    71  		dialOptions = append(dialOptions, grpc.WithTransportCredentials(cfg))
    72  	} else if strings.HasPrefix(address, "xds:///") {
    73  		creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		dialOptions = append(dialOptions, grpc.WithTransportCredentials(creds))
    78  	} else {
    79  		dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
    80  	}
    81  	dialOptions = append(dialOptions, extraDialOpts...)
    82  	conn, err := grpc.DialContext(ctx, address, dialOptions...)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	client := proto.NewEchoTestServiceClient(conn)
    87  
    88  	return &Client{
    89  		conn:   conn,
    90  		client: client,
    91  	}, nil
    92  }
    93  
    94  // Close the EchoClient and free any resources.
    95  func (c *Client) Close() error {
    96  	if c.conn != nil {
    97  		return c.conn.Close()
    98  	}
    99  	return nil
   100  }
   101  
   102  func (c *Client) Echo(ctx context.Context, request *proto.EchoRequest) (Response, error) {
   103  	resp, err := c.client.Echo(ctx, request)
   104  	if err != nil {
   105  		return Response{}, err
   106  	}
   107  	return parseResponse(resp.Message), nil
   108  }
   109  
   110  // ForwardEcho sends the given forward request and parses the response for easier processing. Only fails if the request fails.
   111  func (c *Client) ForwardEcho(ctx context.Context, request *proto.ForwardEchoRequest) (Responses, error) {
   112  	// Forward a request from 'this' service to the destination service.
   113  	GlobalEchoRequests.Add(uint64(request.Count))
   114  	resp, err := c.client.ForwardEcho(ctx, request)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return ParseResponses(request, resp), nil
   120  }
   121  
   122  // GlobalEchoRequests records how many echo calls we have made total, from all sources.
   123  // Note: go tests are distinct binaries per test suite, so this is the suite level number of calls
   124  var GlobalEchoRequests = atomic.NewUint64(0)