istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/cmd/client/main.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 // An example implementation of a client. 16 17 package main 18 19 import ( 20 "context" 21 "fmt" 22 "net/url" 23 "os" 24 "strings" 25 "time" 26 27 "github.com/spf13/cobra" 28 // To install the xds resolvers and balancers. 29 _ "google.golang.org/grpc/xds" 30 wrappers "google.golang.org/protobuf/types/known/wrapperspb" 31 32 "istio.io/istio/pkg/cmd" 33 "istio.io/istio/pkg/log" 34 "istio.io/istio/pkg/test/echo/common" 35 "istio.io/istio/pkg/test/echo/proto" 36 "istio.io/istio/pkg/test/echo/server/forwarder" 37 ) 38 39 var ( 40 count int 41 timeout time.Duration 42 qps int 43 uds string 44 headers []string 45 msg string 46 expect string 47 expectSet bool 48 method string 49 http2 bool 50 http3 bool 51 insecureSkipVerify bool 52 alpn []string 53 serverName string 54 serverFirst bool 55 followRedirects bool 56 newConnectionPerRequest bool 57 forceDNSLookup bool 58 59 clientCert string 60 clientKey string 61 62 caFile string 63 64 hboneAddress string 65 hboneHeaders []string 66 hboneClientCert string 67 hboneClientKey string 68 hboneCaFile string 69 hboneInsecureSkipVerify bool 70 71 loggingOptions = log.DefaultOptions() 72 73 rootCmd = &cobra.Command{ 74 Use: "client", 75 Short: "Istio test Echo client.", 76 SilenceUsage: true, 77 Long: `Istio test Echo client used for generating traffic between instances of the Echo service. 78 For Kubernetes, this can be run from a source pod via "kubectl exec" with the desired flags. 79 In general, Echo's gRPC interface (ForwardEcho) should be preferred. This client is only needed in cases 80 where the network configuration doesn't support gRPC to the source pod.' 81 `, 82 Args: cobra.ExactArgs(1), 83 PersistentPreRunE: configureLogging, 84 Run: func(cmd *cobra.Command, args []string) { 85 expectSet = cmd.Flags().Changed("expect") 86 // Create a request from the flags. 87 request, err := getRequest(args[0]) 88 if err != nil { 89 log.Fatal(err) 90 } 91 92 // Create a forwarder. 93 f := forwarder.New() 94 defer func() { 95 _ = f.Close() 96 }() 97 98 // Forward the requests. 99 response, err := f.ForwardEcho(context.Background(), &forwarder.Config{ 100 Request: request, 101 UDS: uds, 102 }) 103 if err != nil { 104 log.Fatalf("Error %s\n", err) // nolint: revive 105 os.Exit(-1) 106 } 107 108 // Log the output to stdout. 109 for _, line := range response.Output { 110 fmt.Println(line) 111 } 112 113 log.Infof("All requests succeeded") 114 }, 115 } 116 ) 117 118 func configureLogging(_ *cobra.Command, _ []string) error { 119 if err := log.Configure(loggingOptions); err != nil { 120 return err 121 } 122 return nil 123 } 124 125 func init() { 126 rootCmd.PersistentFlags().IntVar(&count, "count", common.DefaultCount, "Number of times to make the request") 127 rootCmd.PersistentFlags().IntVar(&qps, "qps", 0, "Queries per second") 128 rootCmd.PersistentFlags().DurationVar(&timeout, "timeout", common.DefaultRequestTimeout, "Request timeout") 129 rootCmd.PersistentFlags().StringVar(&uds, "uds", "", 130 "Specify the Unix Domain Socket to connect to") 131 rootCmd.PersistentFlags().StringSliceVarP(&headers, "header", "H", headers, 132 "A list of http headers (use Host for authority) - 'name: value', following curl syntax") 133 rootCmd.PersistentFlags().StringVar(&caFile, "ca", "", "CA root cert file") 134 rootCmd.PersistentFlags().StringVar(&msg, "msg", "HelloWorld", 135 "message to send (for websockets)") 136 rootCmd.PersistentFlags().StringVar(&expect, "expect", "", 137 "message to expect (for tcp)") 138 rootCmd.PersistentFlags().StringVar(&method, "method", "", "method to use (for HTTP)") 139 rootCmd.PersistentFlags().BoolVar(&http2, "http2", false, 140 "send http requests as HTTP2 with prior knowledge") 141 rootCmd.PersistentFlags().BoolVar(&http3, "http3", false, 142 "send http requests as HTTP 3") 143 rootCmd.PersistentFlags().BoolVarP(&insecureSkipVerify, "insecure-skip-verify", "k", insecureSkipVerify, 144 "do not verify TLS") 145 rootCmd.PersistentFlags().BoolVar(&serverFirst, "server-first", false, 146 "Treat as a server first protocol; do not send request until magic string is received") 147 rootCmd.PersistentFlags().BoolVarP(&followRedirects, "follow-redirects", "L", false, 148 "If enabled, will follow 3xx redirects with the Location header") 149 rootCmd.PersistentFlags().BoolVar(&newConnectionPerRequest, "new-connection-per-request", false, 150 "If enabled, a new connection will be made to the server for each individual request. "+ 151 "If false, an attempt will be made to re-use the connection for the life of the forward request. "+ 152 "This is automatically set for DNS, TCP, TLS, and WebSocket protocols.") 153 rootCmd.PersistentFlags().BoolVar(&forceDNSLookup, "force-dns-lookup", false, 154 "If enabled, each request will force a DNS lookup. Only applies if new-connection-per-request is also enabled.") 155 rootCmd.PersistentFlags().StringVar(&clientCert, "client-cert", "", "client certificate file to use for request") 156 rootCmd.PersistentFlags().StringVar(&clientKey, "client-key", "", "client certificate key file to use for request") 157 rootCmd.PersistentFlags().StringSliceVarP(&alpn, "alpn", "", nil, "alpn to set") 158 rootCmd.PersistentFlags().StringVarP(&serverName, "server-name", "", serverName, "server name to set") 159 160 rootCmd.PersistentFlags().StringVar(&hboneAddress, "hbone", "", "address to send HBONE request to") 161 rootCmd.PersistentFlags().StringSliceVarP(&hboneHeaders, "hbone-header", "M", hboneHeaders, 162 "A list of http headers for HBONE connection (use Host for authority) - 'name: value', following curl syntax") 163 rootCmd.PersistentFlags().StringVar(&hboneCaFile, "hbone-ca", "", "CA root cert file used for the HBONE request") 164 rootCmd.PersistentFlags().StringVar(&hboneClientCert, "hbone-client-cert", "", "client certificate file used for the HBONE request") 165 rootCmd.PersistentFlags().StringVar(&hboneClientKey, "hbone-client-key", "", "client certificate key file used for the HBONE request") 166 rootCmd.PersistentFlags().BoolVar(&hboneInsecureSkipVerify, "hbone-insecure-skip-verify", hboneInsecureSkipVerify, "skip TLS verification of HBONE request") 167 168 loggingOptions.AttachCobraFlags(rootCmd) 169 170 cmd.AddFlags(rootCmd) 171 } 172 173 // Adds http scheme to and url if not set. This matches curl logic 174 func defaultScheme(u string) string { 175 p, err := url.Parse(u) 176 if err != nil { 177 return u 178 } 179 if p.Scheme == "" { 180 return "http://" + u 181 } 182 return u 183 } 184 185 func getRequest(url string) (*proto.ForwardEchoRequest, error) { 186 request := &proto.ForwardEchoRequest{ 187 Url: defaultScheme(url), 188 TimeoutMicros: common.DurationToMicros(timeout), 189 Count: int32(count), 190 Qps: int32(qps), 191 Message: msg, 192 Http2: http2, 193 Http3: http3, 194 ServerFirst: serverFirst, 195 FollowRedirects: followRedirects, 196 Method: method, 197 ServerName: serverName, 198 InsecureSkipVerify: insecureSkipVerify, 199 NewConnectionPerRequest: newConnectionPerRequest, 200 ForceDNSLookup: forceDNSLookup, 201 } 202 if len(hboneAddress) > 0 { 203 request.Hbone = &proto.HBONE{ 204 Address: hboneAddress, 205 CertFile: hboneClientCert, 206 KeyFile: hboneClientKey, 207 CaCertFile: hboneCaFile, 208 InsecureSkipVerify: hboneInsecureSkipVerify, 209 } 210 for _, header := range hboneHeaders { 211 parts := strings.SplitN(header, ":", 2) 212 // require name:value format 213 if len(parts) != 2 { 214 return nil, fmt.Errorf("invalid header format: %q (want name:value)", header) 215 } 216 217 request.Hbone.Headers = append(request.Hbone.Headers, &proto.Header{ 218 Key: parts[0], 219 Value: strings.Trim(parts[1], " "), 220 }) 221 } 222 } 223 224 if expectSet { 225 request.ExpectedResponse = &wrappers.StringValue{Value: expect} 226 } 227 228 if alpn != nil { 229 request.Alpn = &proto.Alpn{Value: alpn} 230 } 231 232 for _, header := range headers { 233 parts := strings.Split(header, ":") 234 235 // require name:value format 236 if len(parts) != 2 { 237 return nil, fmt.Errorf("invalid header format: %q (want name:value)", header) 238 } 239 240 request.Headers = append(request.Headers, &proto.Header{ 241 Key: parts[0], 242 Value: strings.Trim(parts[1], " "), 243 }) 244 } 245 246 if clientCert != "" && clientKey != "" { 247 request.CertFile = clientCert 248 request.KeyFile = clientKey 249 } 250 if caFile != "" { 251 request.CaCertFile = caFile 252 } 253 return request, nil 254 } 255 256 func main() { 257 if err := rootCmd.Execute(); err != nil { 258 os.Exit(-1) 259 } 260 }