istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/endpoint/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 endpoint 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "net" 23 "net/http" 24 "os" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/google/uuid" 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/admin" 32 "google.golang.org/grpc/credentials" 33 "google.golang.org/grpc/credentials/insecure" 34 xdscreds "google.golang.org/grpc/credentials/xds" 35 "google.golang.org/grpc/health" 36 grpcHealth "google.golang.org/grpc/health/grpc_health_v1" 37 "google.golang.org/grpc/keepalive" 38 "google.golang.org/grpc/metadata" 39 "google.golang.org/grpc/peer" 40 "google.golang.org/grpc/reflection" 41 "google.golang.org/grpc/xds" 42 43 "istio.io/istio/pkg/env" 44 "istio.io/istio/pkg/test/echo" 45 "istio.io/istio/pkg/test/echo/common" 46 "istio.io/istio/pkg/test/echo/proto" 47 "istio.io/istio/pkg/test/echo/server/forwarder" 48 "istio.io/istio/pkg/test/util/retry" 49 ) 50 51 var _ Instance = &grpcInstance{} 52 53 // grpcServer is the intersection of used methods for grpc.Server and xds.GRPCServer 54 type grpcServer interface { 55 reflection.GRPCServer 56 Serve(listener net.Listener) error 57 Stop() 58 } 59 60 type grpcInstance struct { 61 Config 62 server grpcServer 63 cleanups []func() 64 f *forwarder.Instance 65 } 66 67 func newGRPC(config Config) Instance { 68 return &grpcInstance{ 69 Config: config, 70 f: forwarder.New(), 71 } 72 } 73 74 func (s *grpcInstance) GetConfig() Config { 75 return s.Config 76 } 77 78 func (s *grpcInstance) newServer(opts ...grpc.ServerOption) grpcServer { 79 if s.Port.XDSServer { 80 if len(s.Port.XDSTestBootstrap) > 0 { 81 opts = append(opts, xds.BootstrapContentsForTesting(s.Port.XDSTestBootstrap)) 82 } 83 epLog.Infof("Using xDS for serverside gRPC on %d", s.Port.Port) 84 grpcServer, err := xds.NewGRPCServer(opts...) 85 if err != nil { 86 return nil 87 } 88 return grpcServer 89 } 90 return grpc.NewServer(opts...) 91 } 92 93 func (s *grpcInstance) Start(onReady OnReadyFunc) error { 94 // Listen on the given port and update the port if it changed from what was passed in. 95 listener, p, err := listenOnAddress(s.ListenerIP, s.Port.Port) 96 if err != nil { 97 return err 98 } 99 // Store the actual listening port back to the argument. 100 s.Port.Port = p 101 102 opts := []grpc.ServerOption{ 103 grpc.KeepaliveParams(keepalive.ServerParameters{ 104 MaxConnectionIdle: idleTimeout, 105 }), 106 } 107 if s.Port.TLS { 108 epLog.Infof("Listening GRPC (over TLS) on %v", p) 109 // Create the TLS credentials 110 creds, errCreds := credentials.NewServerTLSFromFile(s.TLSCert, s.TLSKey) 111 if errCreds != nil { 112 epLog.Errorf("could not load TLS keys: %s", errCreds) 113 } 114 opts = append(opts, grpc.Creds(creds)) 115 } else if s.Port.XDSServer { 116 epLog.Infof("Listening GRPC (over xDS-configured mTLS) on %v", p) 117 creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{ 118 FallbackCreds: insecure.NewCredentials(), 119 }) 120 if err != nil { 121 return err 122 } 123 opts = append(opts, grpc.Creds(creds)) 124 } else { 125 epLog.Infof("Listening GRPC on %v", p) 126 } 127 s.server = s.newServer(opts...) 128 129 // add the standard grpc health check 130 healthServer := health.NewServer() 131 grpcHealth.RegisterHealthServer(s.server, healthServer) 132 133 proto.RegisterEchoTestServiceServer(s.server, &EchoGrpcHandler{ 134 Config: s.Config, 135 Forwarder: s.f, 136 }) 137 reflection.Register(s.server) 138 if val := env.Register("EXPOSE_GRPC_ADMIN", false, "").Get(); val { 139 cleanup, err := admin.Register(s.server) 140 if err != nil { 141 return err 142 } 143 s.cleanups = append(s.cleanups, cleanup) 144 } 145 // Start serving GRPC traffic. 146 go func() { 147 err := s.server.Serve(listener) 148 epLog.Warnf("Port %d listener terminated with error: %v", p, err) 149 }() 150 151 // Notify the WaitGroup once the port has transitioned to ready. 152 go s.awaitReady(func() { 153 healthServer.SetServingStatus("", grpcHealth.HealthCheckResponse_SERVING) 154 onReady() 155 }, listener) 156 157 return nil 158 } 159 160 func (s *grpcInstance) awaitReady(onReady OnReadyFunc, listener net.Listener) { 161 defer onReady() 162 163 err := retry.UntilSuccess(func() error { 164 cert, key, ca, err := s.certsFromBootstrapForReady() 165 if err != nil { 166 return err 167 } 168 req := &proto.ForwardEchoRequest{ 169 Url: "grpc://" + listener.Addr().String(), 170 Message: "hello", 171 TimeoutMicros: common.DurationToMicros(readyInterval), 172 } 173 if s.Port.XDSReadinessTLS { 174 // TODO: using the servers key/cert is not always valid, it may not be allowed to make requests to itself 175 req.CertFile = cert 176 req.KeyFile = key 177 req.CaCertFile = ca 178 req.InsecureSkipVerify = true 179 } 180 _, err = s.f.ForwardEcho(context.Background(), &forwarder.Config{ 181 XDSTestBootstrap: s.Port.XDSTestBootstrap, 182 Request: req, 183 }) 184 return err 185 }, retry.Timeout(readyTimeout), retry.Delay(readyInterval)) 186 if err != nil { 187 epLog.Errorf("readiness failed for GRPC endpoint %s: %v", listener.Addr().String(), err) 188 } else { 189 epLog.Infof("ready for GRPC endpoint %s", listener.Addr().String()) 190 } 191 } 192 193 // TODO (hack) we have to send certs OR use xds:///fqdn. We don't know our own fqdn, and even if we did 194 // we could send traffic to another instance. Instead we look into gRPC internals to authenticate with ourself. 195 func (s *grpcInstance) certsFromBootstrapForReady() (cert string, key string, ca string, err error) { 196 if !s.Port.XDSServer { 197 return 198 } 199 200 var bootstrapData []byte 201 if data := s.Port.XDSTestBootstrap; len(data) > 0 { 202 bootstrapData = data 203 } else if path := os.Getenv("GRPC_XDS_BOOTSTRAP"); len(path) > 0 { 204 bootstrapData, err = os.ReadFile(path) 205 } else if data := os.Getenv("GRPC_XDS_BOOTSTRAP_CONFIG"); len(data) > 0 { 206 bootstrapData = []byte(data) 207 } 208 var bootstrap Bootstrap 209 if uerr := json.Unmarshal(bootstrapData, &bootstrap); uerr != nil { 210 err = uerr 211 return 212 } 213 certs := bootstrap.FileWatcherProvider() 214 if certs == nil { 215 err = fmt.Errorf("no certs found in bootstrap") 216 return 217 } 218 cert = certs.CertificateFile 219 key = certs.PrivateKeyFile 220 ca = certs.CACertificateFile 221 return 222 } 223 224 func (s *grpcInstance) Close() error { 225 if s.server != nil { 226 s.server.Stop() 227 } 228 if s.f != nil { 229 _ = s.f.Close() 230 } 231 for _, cleanup := range s.cleanups { 232 cleanup() 233 } 234 return nil 235 } 236 237 type EchoGrpcHandler struct { 238 proto.UnimplementedEchoTestServiceServer 239 Config 240 Forwarder *forwarder.Instance 241 } 242 243 func (h *EchoGrpcHandler) Echo(ctx context.Context, req *proto.EchoRequest) (*proto.EchoResponse, error) { 244 defer common.Metrics.GrpcRequests.With(common.PortLabel.Value(strconv.Itoa(h.Port.Port))).Increment() 245 body := bytes.Buffer{} 246 md, ok := metadata.FromIncomingContext(ctx) 247 if ok { 248 for key, values := range md { 249 if strings.HasSuffix(key, "-bin") { 250 // Skip binary headers. 251 continue 252 } 253 254 field := key 255 256 if key == ":authority" { 257 for _, value := range values { 258 echo.HostField.Write(&body, value) 259 } 260 } 261 262 for _, value := range values { 263 echo.RequestHeaderField.WriteKeyValue(&body, field, value) 264 } 265 } 266 } 267 268 id := uuid.New() 269 epLog.WithLabels("message", req.GetMessage(), "headers", md, "id", id).Infof("GRPC Request") 270 271 portNumber := 0 272 if h.Port != nil { 273 portNumber = h.Port.Port 274 } 275 276 ip := "0.0.0.0" 277 if peerInfo, ok := peer.FromContext(ctx); ok { 278 ip, _, _ = net.SplitHostPort(peerInfo.Addr.String()) 279 } 280 281 echo.StatusCodeField.Write(&body, strconv.Itoa(http.StatusOK)) 282 echo.ServiceVersionField.Write(&body, h.Version) 283 echo.ServicePortField.Write(&body, strconv.Itoa(portNumber)) 284 echo.ClusterField.WriteNonEmpty(&body, h.Cluster) 285 echo.NamespaceField.WriteNonEmpty(&body, h.Namespace) 286 echo.IPField.Write(&body, ip) 287 echo.IstioVersionField.WriteNonEmpty(&body, h.IstioVersion) 288 echo.ProtocolField.Write(&body, "GRPC") 289 echo.Field("Echo").Write(&body, req.GetMessage()) 290 291 if hostname, err := os.Hostname(); err == nil { 292 echo.HostnameField.Write(&body, hostname) 293 } 294 295 epLog.WithLabels("id", id).Infof("GRPC Response") 296 return &proto.EchoResponse{Message: body.String()}, nil 297 } 298 299 func (h *EchoGrpcHandler) ForwardEcho(ctx context.Context, req *proto.ForwardEchoRequest) (*proto.ForwardEchoResponse, error) { 300 id := uuid.New() 301 l := epLog.WithLabels("url", req.Url, "id", id) 302 l.Infof("ForwardEcho request") 303 t0 := time.Now() 304 305 ret, err := h.Forwarder.ForwardEcho(ctx, &forwarder.Config{Request: req}) 306 if err == nil { 307 l.WithLabels("latency", time.Since(t0)).Infof("ForwardEcho response complete: %v", ret.GetOutput()) 308 } else { 309 l.WithLabels("latency", time.Since(t0)).Infof("ForwardEcho response failed: %v", err) 310 } 311 return ret, err 312 }