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  }