github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadget-service/service.go (about)

     1  // Copyright 2023-2024 The Inspektor Gadget 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 gadgetservice
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/google/uuid"
    30  	"google.golang.org/grpc"
    31  
    32  	"github.com/inspektor-gadget/inspektor-gadget/internal/version"
    33  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    34  	gadgetregistry "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-registry"
    35  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
    36  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    37  	"github.com/inspektor-gadget/inspektor-gadget/pkg/logger"
    38  	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
    39  	"github.com/inspektor-gadget/inspektor-gadget/pkg/runtime"
    40  	"github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/local"
    41  	"github.com/inspektor-gadget/inspektor-gadget/pkg/utils/experimental"
    42  )
    43  
    44  type RunConfig struct {
    45  	// SocketType can be either unix or tcp
    46  	SocketType string
    47  
    48  	// SocketPath must be the path to a unix socket or ip:port, depending on
    49  	// SocketType
    50  	SocketPath string
    51  
    52  	// If SocketGID != 0 and a unix socket is used, the ownership of that socket
    53  	// will be changed to the given SocketGID
    54  	SocketGID int
    55  }
    56  
    57  type Service struct {
    58  	api.UnimplementedBuiltInGadgetManagerServer
    59  	api.UnimplementedGadgetManagerServer
    60  	listener          net.Listener
    61  	runtime           runtime.Runtime
    62  	logger            logger.Logger
    63  	servers           map[*grpc.Server]struct{}
    64  	eventBufferLength uint64
    65  }
    66  
    67  func NewService(defaultLogger logger.Logger, length uint64) *Service {
    68  	return &Service{
    69  		servers:           map[*grpc.Server]struct{}{},
    70  		logger:            defaultLogger,
    71  		eventBufferLength: length,
    72  	}
    73  }
    74  
    75  func (s *Service) GetInfo(ctx context.Context, request *api.InfoRequest) (*api.InfoResponse, error) {
    76  	catalog, err := s.runtime.GetCatalog()
    77  	if err != nil {
    78  		return nil, fmt.Errorf("get catalog: %w", err)
    79  	}
    80  
    81  	catalogJSON, err := json.Marshal(catalog)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("marshal catalog: %w", err)
    84  	}
    85  	return &api.InfoResponse{
    86  		Version:       "1.0", // TODO
    87  		Catalog:       catalogJSON,
    88  		Experimental:  experimental.Enabled(),
    89  		ServerVersion: version.Version().String(),
    90  	}, nil
    91  }
    92  
    93  func (s *Service) RunBuiltInGadget(runGadget api.BuiltInGadgetManager_RunBuiltInGadgetServer) error {
    94  	ctrl, err := runGadget.Recv()
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	request := ctrl.GetRunRequest()
   100  	if request == nil {
   101  		return fmt.Errorf("expected first control message to be gadget run request")
   102  	}
   103  
   104  	// Create a new logger that logs to gRPC and falls back to the standard logger when it failed to send the message
   105  	logger := logger.NewFromGenericLogger(&Logger{
   106  		send:           runGadget.Send,
   107  		level:          logger.Level(request.LogLevel),
   108  		fallbackLogger: s.logger,
   109  	})
   110  
   111  	runtime := s.runtime
   112  
   113  	gadgetDesc := gadgetregistry.Get(request.GadgetCategory, request.GadgetName)
   114  	if gadgetDesc == nil {
   115  		return fmt.Errorf("gadget not found: %s/%s", request.GadgetCategory, request.GadgetName)
   116  	}
   117  
   118  	// Initialize Operators
   119  	err = operators.GetAll().Init(operators.GlobalParamsCollection())
   120  	if err != nil {
   121  		return fmt.Errorf("initialize operators: %w", err)
   122  	}
   123  
   124  	ops := operators.GetOperatorsForGadget(gadgetDesc)
   125  
   126  	operatorParams := ops.ParamCollection()
   127  
   128  	parser := gadgetDesc.Parser()
   129  
   130  	runtimeParams := runtime.ParamDescs().ToParams()
   131  	gType := gadgetDesc.Type()
   132  
   133  	gadgetParamDescs := gadgetDesc.ParamDescs()
   134  	// TODO: do we need to update gType before calling this?
   135  	gadgetParamDescs.Add(gadgets.GadgetParams(gadgetDesc, gType, parser)...)
   136  	gadgetParams := gadgetParamDescs.ToParams()
   137  	err = gadgets.ParamsFromMap(request.Params, gadgetParams, runtimeParams, operatorParams)
   138  	if err != nil {
   139  		return fmt.Errorf("setting parameters: %w", err)
   140  	}
   141  
   142  	// Create payload buffer
   143  	outputBuffer := make(chan *api.GadgetEvent, s.eventBufferLength)
   144  
   145  	seq := uint32(0)
   146  	var seqLock sync.Mutex
   147  
   148  	if parser != nil {
   149  		outputDone := make(chan bool)
   150  		defer func() {
   151  			outputDone <- true
   152  		}()
   153  
   154  		parser.SetLogCallback(logger.Logf)
   155  		parser.SetEventCallback(func(ev any) {
   156  			// Marshal messages to JSON
   157  			// Normally, it would be better to have this in the pump below rather than marshaling events that
   158  			// would be dropped anyway. However, we're optimistic that this occurs rarely and instead prevent using
   159  			// ev in another thread.
   160  			data, _ := json.Marshal(ev)
   161  			event := &api.GadgetEvent{
   162  				Type:    api.EventTypeGadgetPayload,
   163  				Payload: data,
   164  			}
   165  
   166  			seqLock.Lock()
   167  			seq++
   168  			event.Seq = seq
   169  
   170  			// Try to send event; if outputBuffer is full, it will be dropped by taking
   171  			// the default path.
   172  			select {
   173  			case outputBuffer <- event:
   174  			default:
   175  			}
   176  			seqLock.Unlock()
   177  		})
   178  
   179  		go func() {
   180  			// Message pump to handle slow readers
   181  			for {
   182  				select {
   183  				case ev := <-outputBuffer:
   184  					runGadget.Send(ev)
   185  				case <-outputDone:
   186  					return
   187  				}
   188  			}
   189  		}()
   190  	}
   191  
   192  	// Assign a unique ID - this will be used in the future
   193  	runID := uuid.New().String()
   194  
   195  	// Send Job ID to client
   196  	err = runGadget.Send(&api.GadgetEvent{
   197  		Type:    api.EventTypeGadgetJobID,
   198  		Payload: []byte(runID),
   199  	})
   200  	if err != nil {
   201  		logger.Warnf("sending JobID: %v", err)
   202  		return nil
   203  	}
   204  
   205  	// Create new Gadget Context
   206  	gadgetCtx := gadgetcontext.NewBuiltIn(
   207  		runGadget.Context(),
   208  		runID,
   209  		runtime,
   210  		runtimeParams,
   211  		gadgetDesc,
   212  		gadgetParams,
   213  		request.Args,
   214  		operatorParams,
   215  		parser,
   216  		logger,
   217  		time.Duration(request.Timeout),
   218  	)
   219  	defer gadgetCtx.Cancel()
   220  
   221  	// Handle commands sent by the client
   222  	go func() {
   223  		defer func() {
   224  			logger.Debugf("runner exited")
   225  		}()
   226  		for {
   227  			msg, err := runGadget.Recv()
   228  			if err != nil {
   229  				gadgetCtx.Cancel()
   230  				return
   231  			}
   232  			switch msg.Event.(type) {
   233  			case *api.BuiltInGadgetControlRequest_StopRequest:
   234  				gadgetCtx.Cancel()
   235  				return
   236  			default:
   237  				logger.Warn("unexpected request")
   238  			}
   239  		}
   240  	}()
   241  
   242  	// Hand over to runtime
   243  	results, err := runtime.RunBuiltInGadget(gadgetCtx)
   244  	if err != nil {
   245  		return fmt.Errorf("running gadget: %w", err)
   246  	}
   247  
   248  	// Send result, if any
   249  	for _, result := range results {
   250  		// TODO: when used with fan-out, we need to add the node in here
   251  		event := &api.GadgetEvent{
   252  			Type:    api.EventTypeGadgetResult,
   253  			Payload: result.Payload,
   254  		}
   255  		runGadget.Send(event)
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func newUnixListener(address string, gid int) (net.Listener, error) {
   262  	if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
   263  		return nil, fmt.Errorf("removing existing unix socket at %q: %w", address, err)
   264  	}
   265  
   266  	// If the given path is the default, try to create it and change its permissions; if it's not the default, it is
   267  	// up to the user to manage it
   268  	if "unix://"+address == api.DefaultDaemonPath {
   269  		dir := filepath.Dir(address)
   270  		if err := os.MkdirAll(dir, 0o710); err != nil && !errors.Is(err, os.ErrExist) {
   271  			return nil, fmt.Errorf("creating directory %q: %w", dir, err)
   272  		}
   273  		if err := os.Chown(dir, 0, gid); err != nil {
   274  			return nil, fmt.Errorf("chown directory %q: %w", dir, err)
   275  		}
   276  	}
   277  
   278  	// Set umask to 0o777 to avoid a race condition between creating the listener and applying its permissionss
   279  	oldMask := syscall.Umask(0o777)
   280  	defer syscall.Umask(oldMask)
   281  
   282  	listener, err := net.Listen("unix", address)
   283  	if err != nil {
   284  		return nil, fmt.Errorf("creating unix listener at %q: %w", address, err)
   285  	}
   286  	if err := os.Chown(address, 0, gid); err != nil {
   287  		listener.Close()
   288  		return nil, fmt.Errorf("chown unix socket %q: %w", address, err)
   289  	}
   290  	if err := os.Chmod(address, 0o660); err != nil {
   291  		listener.Close()
   292  		return nil, fmt.Errorf("chmod unix socket %q: %w", address, err)
   293  	}
   294  	return listener, nil
   295  }
   296  
   297  func (s *Service) Run(runConfig RunConfig, serverOptions ...grpc.ServerOption) error {
   298  	s.runtime = local.New()
   299  	defer s.runtime.Close()
   300  
   301  	// Use defaults for now - this will become more important when we fan-out requests also to other
   302  	//  gRPC runtimes
   303  	err := s.runtime.Init(s.runtime.GlobalParamDescs().ToParams())
   304  	if err != nil {
   305  		return fmt.Errorf("initializing runtime: %w", err)
   306  	}
   307  
   308  	switch runConfig.SocketType {
   309  	case "unix":
   310  		listener, err := newUnixListener(runConfig.SocketPath, runConfig.SocketGID)
   311  		if err != nil {
   312  			return fmt.Errorf("creating unix listener: %w", err)
   313  		}
   314  		s.listener = listener
   315  	case "tcp":
   316  		listener, err := net.Listen(runConfig.SocketType, runConfig.SocketPath)
   317  		if err != nil {
   318  			return fmt.Errorf("creating listener: %w", err)
   319  		}
   320  		s.listener = listener
   321  	default:
   322  		return fmt.Errorf("invalid socket type: %s", runConfig.SocketType)
   323  	}
   324  
   325  	server := grpc.NewServer(serverOptions...)
   326  	api.RegisterBuiltInGadgetManagerServer(server, s)
   327  	api.RegisterGadgetManagerServer(server, s)
   328  
   329  	s.servers[server] = struct{}{}
   330  
   331  	return server.Serve(s.listener)
   332  }
   333  
   334  func (s *Service) Close() {
   335  	for server := range s.servers {
   336  		server.Stop()
   337  		delete(s.servers, server)
   338  	}
   339  }