github.com/getgauge/gauge@v1.6.9/runner/grpcRunner.go (about) 1 /*---------------------------------------------------------------- 2 * Copyright (c) ThoughtWorks, Inc. 3 * Licensed under the Apache License, Version 2.0 4 * See LICENSE in the project root for license information. 5 *----------------------------------------------------------------*/ 6 7 package runner 8 9 import ( 10 "context" 11 "fmt" 12 "io" 13 "net" 14 "os/exec" 15 "strings" 16 "time" 17 18 "github.com/getgauge/gauge-proto/go/gauge_messages" 19 gm "github.com/getgauge/gauge-proto/go/gauge_messages" 20 "github.com/getgauge/gauge/config" 21 "github.com/getgauge/gauge/logger" 22 "github.com/getgauge/gauge/manifest" 23 errdetails "google.golang.org/genproto/googleapis/rpc/errdetails" 24 "google.golang.org/grpc" 25 "google.golang.org/grpc/codes" 26 "google.golang.org/grpc/credentials/insecure" 27 "google.golang.org/grpc/status" 28 ) 29 30 const ( 31 host = "127.0.0.1" 32 oneGB = 1024 * 1024 * 1024 33 ) 34 35 // GrpcRunner handles grpc messages. 36 type GrpcRunner struct { 37 cmd *exec.Cmd 38 conn *grpc.ClientConn 39 LegacyClient gm.LspServiceClient 40 RunnerClient gm.RunnerClient 41 Timeout time.Duration 42 info *RunnerInfo 43 IsExecuting bool 44 } 45 46 //nolint:staticcheck 47 func (r *GrpcRunner) invokeLegacyLSPService(message *gm.Message) (*gm.Message, error) { 48 switch message.MessageType { 49 case gm.Message_CacheFileRequest: 50 _, err := r.LegacyClient.CacheFile(context.Background(), message.CacheFileRequest) 51 return &gm.Message{}, err 52 case gm.Message_StepNamesRequest: 53 response, err := r.LegacyClient.GetStepNames(context.Background(), message.StepNamesRequest) 54 return &gm.Message{StepNamesResponse: response}, err 55 case gm.Message_StepPositionsRequest: 56 response, err := r.LegacyClient.GetStepPositions(context.Background(), message.StepPositionsRequest) 57 return &gm.Message{StepPositionsResponse: response}, err 58 case gm.Message_ImplementationFileListRequest: 59 response, err := r.LegacyClient.GetImplementationFiles(context.Background(), &gm.Empty{}) 60 return &gm.Message{ImplementationFileListResponse: response}, err 61 case gm.Message_StubImplementationCodeRequest: 62 response, err := r.LegacyClient.ImplementStub(context.Background(), message.StubImplementationCodeRequest) 63 return &gm.Message{FileDiff: response}, err 64 case gm.Message_StepValidateRequest: 65 response, err := r.LegacyClient.ValidateStep(context.Background(), message.StepValidateRequest) 66 return &gm.Message{MessageType: gm.Message_StepValidateResponse, StepValidateResponse: response}, err 67 case gm.Message_RefactorRequest: 68 response, err := r.LegacyClient.Refactor(context.Background(), message.RefactorRequest) 69 return &gm.Message{MessageType: gm.Message_RefactorResponse, RefactorResponse: response}, err 70 case gm.Message_StepNameRequest: 71 response, err := r.LegacyClient.GetStepName(context.Background(), message.StepNameRequest) 72 return &gm.Message{MessageType: gm.Message_StepNameResponse, StepNameResponse: response}, err 73 case gm.Message_ImplementationFileGlobPatternRequest: 74 response, err := r.LegacyClient.GetGlobPatterns(context.Background(), &gm.Empty{}) 75 return &gm.Message{MessageType: gm.Message_ImplementationFileGlobPatternRequest, ImplementationFileGlobPatternResponse: response}, err 76 case gm.Message_KillProcessRequest: 77 _, err := r.LegacyClient.KillProcess(context.Background(), message.KillProcessRequest) 78 return &gm.Message{}, err 79 default: 80 return nil, nil 81 } 82 } 83 84 func (r *GrpcRunner) invokeServiceFor(message *gm.Message) (*gm.Message, error) { 85 switch message.MessageType { 86 case gm.Message_SuiteDataStoreInit: 87 response, err := r.RunnerClient.InitializeSuiteDataStore(context.Background(), message.SuiteDataStoreInitRequest) 88 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 89 case gm.Message_SpecDataStoreInit: 90 response, err := r.RunnerClient.InitializeSpecDataStore(context.Background(), message.SpecDataStoreInitRequest) 91 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 92 case gm.Message_ScenarioDataStoreInit: 93 response, err := r.RunnerClient.InitializeScenarioDataStore(context.Background(), message.ScenarioDataStoreInitRequest) 94 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 95 case gm.Message_ExecutionStarting: 96 response, err := r.RunnerClient.StartExecution(context.Background(), message.ExecutionStartingRequest) 97 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 98 case gm.Message_SpecExecutionStarting: 99 response, err := r.RunnerClient.StartSpecExecution(context.Background(), message.SpecExecutionStartingRequest) 100 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 101 case gm.Message_ScenarioExecutionStarting: 102 response, err := r.RunnerClient.StartScenarioExecution(context.Background(), message.ScenarioExecutionStartingRequest) 103 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 104 case gm.Message_StepExecutionStarting: 105 response, err := r.RunnerClient.StartStepExecution(context.Background(), message.StepExecutionStartingRequest) 106 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 107 case gm.Message_ExecuteStep: 108 response, err := r.RunnerClient.ExecuteStep(context.Background(), message.ExecuteStepRequest) 109 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 110 case gm.Message_StepExecutionEnding: 111 response, err := r.RunnerClient.FinishStepExecution(context.Background(), message.StepExecutionEndingRequest) 112 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 113 case gm.Message_ScenarioExecutionEnding: 114 response, err := r.RunnerClient.FinishScenarioExecution(context.Background(), message.ScenarioExecutionEndingRequest) 115 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 116 case gm.Message_SpecExecutionEnding: 117 response, err := r.RunnerClient.FinishSpecExecution(context.Background(), message.SpecExecutionEndingRequest) 118 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 119 case gm.Message_ExecutionEnding: 120 response, err := r.RunnerClient.FinishExecution(context.Background(), message.ExecutionEndingRequest) 121 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 122 123 case gm.Message_CacheFileRequest: 124 _, err := r.RunnerClient.CacheFile(context.Background(), message.CacheFileRequest) 125 return &gm.Message{}, err 126 case gm.Message_StepNamesRequest: 127 response, err := r.RunnerClient.GetStepNames(context.Background(), message.StepNamesRequest) 128 return &gm.Message{StepNamesResponse: response}, err 129 case gm.Message_StepPositionsRequest: 130 response, err := r.RunnerClient.GetStepPositions(context.Background(), message.StepPositionsRequest) 131 return &gm.Message{StepPositionsResponse: response}, err 132 case gm.Message_ImplementationFileListRequest: 133 response, err := r.RunnerClient.GetImplementationFiles(context.Background(), &gm.Empty{}) 134 return &gm.Message{ImplementationFileListResponse: response}, err 135 case gm.Message_StubImplementationCodeRequest: 136 response, err := r.RunnerClient.ImplementStub(context.Background(), message.StubImplementationCodeRequest) 137 return &gm.Message{FileDiff: response}, err 138 case gm.Message_RefactorRequest: 139 response, err := r.RunnerClient.Refactor(context.Background(), message.RefactorRequest) 140 return &gm.Message{MessageType: gm.Message_RefactorResponse, RefactorResponse: response}, err 141 case gm.Message_StepNameRequest: 142 response, err := r.RunnerClient.GetStepName(context.Background(), message.StepNameRequest) 143 return &gm.Message{MessageType: gm.Message_StepNameResponse, StepNameResponse: response}, err 144 case gm.Message_ImplementationFileGlobPatternRequest: 145 response, err := r.RunnerClient.GetGlobPatterns(context.Background(), &gm.Empty{}) 146 return &gm.Message{MessageType: gm.Message_ImplementationFileGlobPatternRequest, ImplementationFileGlobPatternResponse: response}, err 147 case gm.Message_StepValidateRequest: 148 response, err := r.RunnerClient.ValidateStep(context.Background(), message.StepValidateRequest) 149 return &gm.Message{MessageType: gm.Message_StepValidateResponse, StepValidateResponse: response}, err 150 case gm.Message_KillProcessRequest: 151 _, _ = r.RunnerClient.Kill(context.Background(), message.KillProcessRequest) 152 return nil, nil 153 case gm.Message_ConceptExecutionStarting: 154 response, err := r.RunnerClient.NotifyConceptExecutionStarting(context.Background(), message.ConceptExecutionStartingRequest) 155 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 156 case gm.Message_ConceptExecutionEnding: 157 response, err := r.RunnerClient.NotifyConceptExecutionEnding(context.Background(), message.ConceptExecutionEndingRequest) 158 return &gm.Message{MessageType: gm.Message_ExecutionStatusResponse, ExecutionStatusResponse: response}, err 159 default: 160 return nil, nil 161 } 162 } 163 164 func (r *GrpcRunner) invokeRPC(message *gm.Message, resChan chan *gm.Message, errChan chan error) { 165 var res *gm.Message 166 var err error 167 if r.LegacyClient != nil { 168 res, err = r.invokeLegacyLSPService(message) 169 } else { 170 res, err = r.invokeServiceFor(message) 171 } 172 if err != nil { 173 errChan <- err 174 } else { 175 resChan <- res 176 } 177 } 178 179 func (r *GrpcRunner) executeMessage(message *gm.Message, timeout time.Duration) (*gm.Message, error) { 180 resChan := make(chan *gm.Message) 181 errChan := make(chan error) 182 go r.invokeRPC(message, resChan, errChan) 183 184 timer := setupTimer(timeout, errChan, message.GetMessageType().String()) 185 defer stopTimer(timer) 186 187 select { 188 case response := <-resChan: 189 return response, nil 190 case err := <-errChan: 191 return nil, err 192 } 193 } 194 195 // ExecuteMessageWithTimeout process request and give back the response 196 func (r *GrpcRunner) ExecuteMessageWithTimeout(message *gm.Message) (*gm.Message, error) { 197 return r.executeMessage(message, r.Timeout) 198 } 199 200 // ExecuteAndGetStatus executes a given message and response without timeout. 201 func (r *GrpcRunner) ExecuteAndGetStatus(m *gm.Message) *gm.ProtoExecutionResult { 202 if r.Info().Killed { 203 return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: "Runner is not Alive"} 204 } 205 res, err := r.executeMessage(m, 0) 206 if err != nil { 207 e, ok := status.FromError(err) 208 if ok { 209 var stackTrace = "" 210 for _, detail := range e.Details() { 211 if t, ok := detail.(*errdetails.DebugInfo); ok { 212 for _, entry := range t.GetStackEntries() { 213 stackTrace = fmt.Sprintf("%s%s\n", stackTrace, entry) 214 } 215 } 216 } 217 var data = strings.Split(e.Message(), "||") 218 var message = data[0] 219 if len(data) > 1 && stackTrace == "" { 220 stackTrace = data[1] 221 } 222 if e.Code() == codes.Unavailable { 223 r.Info().Killed = true 224 return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: message, StackTrace: stackTrace} 225 } 226 return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: message, StackTrace: stackTrace} 227 } 228 return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: err.Error()} 229 } 230 if res != nil { 231 return res.ExecutionStatusResponse.ExecutionResult 232 } else { 233 return nil 234 } 235 } 236 237 // Alive check if the runner process is still alive 238 func (r *GrpcRunner) Alive() bool { 239 ps := r.cmd.ProcessState 240 return ps == nil || !ps.Exited() 241 } 242 243 // Kill closes the grpc connection and kills the process 244 func (r *GrpcRunner) Kill() error { 245 if r.IsExecuting { 246 return nil 247 } 248 m := &gm.Message{ 249 MessageType: gm.Message_KillProcessRequest, 250 KillProcessRequest: &gm.KillProcessRequest{}, 251 } 252 m, err := r.executeMessage(m, r.Timeout) 253 if m == nil || err != nil { 254 return err 255 } 256 if r.conn == nil && r.cmd == nil { 257 return nil 258 } 259 defer r.conn.Close() 260 if r.Alive() { 261 exited := make(chan bool, 1) 262 go func() { 263 for { 264 if r.Alive() { 265 time.Sleep(100 * time.Millisecond) 266 } else { 267 exited <- true 268 return 269 } 270 } 271 }() 272 273 select { 274 case done := <-exited: 275 if done { 276 logger.Debugf(true, "Runner with PID:%d has exited", r.cmd.Process.Pid) 277 return nil 278 } 279 case <-time.After(config.PluginKillTimeout()): 280 logger.Warningf(true, "Killing runner with PID:%d forcefully", r.cmd.Process.Pid) 281 return r.cmd.Process.Kill() 282 } 283 } 284 return nil 285 } 286 287 // Connection return the client connection 288 func (r *GrpcRunner) Connection() net.Conn { 289 return nil 290 } 291 292 // IsMultithreaded tells if the runner has multithreaded capability 293 func (r *GrpcRunner) IsMultithreaded() bool { 294 return r.info.Multithreaded 295 } 296 297 // Info gives the information about runner 298 func (r *GrpcRunner) Info() *RunnerInfo { 299 return r.info 300 } 301 302 // Pid return the runner's command pid 303 func (r *GrpcRunner) Pid() int { 304 return r.cmd.Process.Pid 305 } 306 307 // StartGrpcRunner makes a connection with grpc server 308 func StartGrpcRunner(m *manifest.Manifest, stdout, stderr io.Writer, timeout time.Duration, shouldWriteToStdout bool) (*GrpcRunner, error) { 309 portChan := make(chan string) 310 errChan := make(chan error) 311 logWriter := &logger.LogWriter{ 312 Stderr: logger.NewCustomWriter(portChan, stderr, m.Language, true), 313 Stdout: logger.NewCustomWriter(portChan, stdout, m.Language, false), 314 } 315 cmd, info, err := runRunnerCommand(m, "0", false, logWriter) 316 if err != nil { 317 return nil, fmt.Errorf("Error occurred while starting runner process.\nError : %w", err) 318 } 319 320 go func() { 321 err = cmd.Wait() 322 if err != nil { 323 e := fmt.Errorf("Error occurred while waiting for runner process to finish.\nError : %w", err) 324 logger.Error(true, e.Error()) 325 errChan <- e 326 } 327 errChan <- nil 328 }() 329 330 var port string 331 select { 332 case port = <-portChan: 333 close(portChan) 334 case err = <-errChan: 335 return nil, err 336 case <-time.After(config.RunnerConnectionTimeout()): 337 return nil, fmt.Errorf("Timed out connecting to %s", m.Language) 338 } 339 logger.Debugf(true, "Attempting to connect to grpc server at port: %s", port) 340 conn, err := grpc.NewClient(fmt.Sprintf("%s:%s", host, port), 341 grpc.WithTransportCredentials(insecure.NewCredentials()), 342 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(oneGB), grpc.MaxCallSendMsgSize(oneGB))) 343 logger.Debugf(true, "Successfully made the connection with runner with port: %s", port) 344 if err != nil { 345 return nil, err 346 } 347 r := &GrpcRunner{cmd: cmd, conn: conn, Timeout: timeout, info: info} 348 349 if info.GRPCSupport { 350 r.RunnerClient = gm.NewRunnerClient(conn) 351 } else { 352 r.LegacyClient = gm.NewLspServiceClient(conn) 353 } 354 return r, nil 355 } 356 357 func setupTimer(timeout time.Duration, errChan chan error, messageType string) *time.Timer { 358 if timeout > 0 { 359 return time.AfterFunc(timeout, func() { 360 errChan <- fmt.Errorf("request timed out for message %s", messageType) 361 }) 362 } 363 return nil 364 } 365 366 func stopTimer(timer *time.Timer) { 367 if timer != nil && !timer.Stop() { 368 <-timer.C 369 } 370 }