github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/grpcservice/service.go (about)

     1  // Copyright 2017 Google Inc.
     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  //     https://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 grpcservice defines a service.Service which passes all received messages to
    16  // a destination host using grpc.
    17  package grpcservice
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sync"
    24  	"time"
    25  
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/credentials"
    29  	"google.golang.org/grpc/credentials/insecure"
    30  	"google.golang.org/grpc/status"
    31  
    32  	"github.com/google/fleetspeak/fleetspeak/src/server/service"
    33  
    34  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    35  	ggrpc "github.com/google/fleetspeak/fleetspeak/src/server/grpcservice/proto/fleetspeak_grpcservice"
    36  	gpb "github.com/google/fleetspeak/fleetspeak/src/server/grpcservice/proto/fleetspeak_grpcservice"
    37  	spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    38  )
    39  
    40  // GRPCService is a service.Service which forwards all received
    41  // messages to an implementation of ggrpc.Processor.
    42  type GRPCService struct {
    43  	sctx   service.Context
    44  	conn   *grpc.ClientConn
    45  	client ggrpc.ProcessorClient
    46  	l      sync.RWMutex
    47  }
    48  
    49  // NewGRPCService returns a service.Service which forwards received
    50  // messages to c. Implementations which wish to implement transport
    51  // security or otherwise control the connection used should define a
    52  // service.Factory based on this.
    53  func NewGRPCService(c *grpc.ClientConn) *GRPCService {
    54  	ret := &GRPCService{
    55  		conn: c,
    56  	}
    57  	if c != nil {
    58  		ret.client = ggrpc.NewProcessorClient(c)
    59  	}
    60  	return ret
    61  }
    62  
    63  func (s *GRPCService) Start(sctx service.Context) error {
    64  	s.sctx = sctx
    65  	return nil
    66  }
    67  
    68  func (s *GRPCService) Stop() error {
    69  	s.l.Lock()
    70  	defer s.l.Unlock()
    71  
    72  	if s.conn != nil {
    73  		s.conn.Close()
    74  	}
    75  	s.conn = nil
    76  	s.client = nil
    77  
    78  	return nil
    79  }
    80  
    81  // Update replaces the current connection with the given one, may be nil to
    82  // indicate that a connection is currently unavailable.
    83  func (s *GRPCService) Update(c *grpc.ClientConn) {
    84  	s.l.Lock()
    85  	defer s.l.Unlock()
    86  
    87  	if s.conn != nil {
    88  		s.conn.Close()
    89  	}
    90  	if c == nil {
    91  		s.conn = nil
    92  		s.client = nil
    93  	} else {
    94  		s.conn = c
    95  		s.client = ggrpc.NewProcessorClient(c)
    96  	}
    97  }
    98  
    99  func (s *GRPCService) ProcessMessage(ctx context.Context, m *fspb.Message) error {
   100  	var err error
   101  	d := time.Second
   102  
   103  	s.l.RLock()
   104  	defer s.l.RUnlock()
   105  	if s.client == nil {
   106  		return service.TemporaryError{E: errors.New("connection unavailable")}
   107  	}
   108  
   109  	// TODO: Remove retry logic when possible.
   110  L:
   111  	for {
   112  		_, err = s.client.Process(ctx, m)
   113  		if err == nil {
   114  			return nil
   115  		}
   116  		t := time.NewTimer(d)
   117  		select {
   118  		case <-ctx.Done():
   119  			t.Stop()
   120  			break L
   121  		case <-t.C:
   122  			d = d * 2
   123  		}
   124  	}
   125  	// Tell Fleetspeak to retry (minutes later) in the most obviously retryable
   126  	// cases. Assume permission issues are configuration errors which will be
   127  	// fixed eventually.
   128  	if s, ok := status.FromError(err); ok {
   129  		c := s.Code()
   130  		if c == codes.DeadlineExceeded ||
   131  			c == codes.Unavailable ||
   132  			c == codes.Aborted ||
   133  			c == codes.Canceled ||
   134  			c == codes.Unauthenticated ||
   135  			c == codes.PermissionDenied {
   136  			err = service.TemporaryError{E: err}
   137  		}
   138  	}
   139  	return err
   140  }
   141  
   142  // Factory is a server.ServiceFactory that creates a GRPCService.
   143  //
   144  // cfg must contain a fleetspeak.grpcservice.Config message describing
   145  // how to dial the target grpc server.
   146  func Factory(cfg *spb.ServiceConfig) (service.Service, error) {
   147  	conf := &gpb.Config{}
   148  	if err := cfg.Config.UnmarshalTo(conf); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	switch {
   153  	case conf.Insecure:
   154  		con, err := grpc.DialContext(context.Background(), conf.Target, grpc.WithTransportCredentials(insecure.NewCredentials()))
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  		return NewGRPCService(con), nil
   159  	case conf.CertFile != "":
   160  		cred, err := credentials.NewClientTLSFromFile(conf.CertFile, "")
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		con, err := grpc.DialContext(context.Background(), conf.Target, grpc.WithTransportCredentials(cred))
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		return NewGRPCService(con), nil
   169  	default:
   170  		return nil, fmt.Errorf("GRPCService requires either insecure or cert_file to be set, got: %+v", conf.String())
   171  	}
   172  }