github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/runtime/grpc/oci.go (about)

     1  // Copyright 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 grpcruntime
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"sync"
    23  	"time"
    24  
    25  	"google.golang.org/protobuf/proto"
    26  
    27  	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
    28  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/logger"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/runtime"
    32  )
    33  
    34  func (r *Runtime) GetGadgetInfo(gadgetCtx runtime.GadgetContext, runtimeParams *params.Params, paramValues api.ParamValues) (*api.GadgetInfo, error) {
    35  	if runtimeParams == nil {
    36  		runtimeParams = r.ParamDescs().ToParams()
    37  	}
    38  
    39  	conn, err := r.getConnToRandomTarget(gadgetCtx.Context(), runtimeParams)
    40  	if err != nil {
    41  		return nil, fmt.Errorf("dialing random target: %w", err)
    42  	}
    43  	defer conn.Close()
    44  	client := api.NewGadgetManagerClient(conn)
    45  
    46  	in := &api.GetGadgetInfoRequest{
    47  		ParamValues: paramValues,
    48  		ImageName:   gadgetCtx.ImageName(),
    49  		Version:     api.VersionGadgetInfo,
    50  	}
    51  	out, err := client.GetGadgetInfo(gadgetCtx.Context(), in)
    52  	if err != nil {
    53  		return nil, fmt.Errorf("getting gadget info: %w", err)
    54  	}
    55  
    56  	err = gadgetCtx.LoadGadgetInfo(out.GadgetInfo, paramValues, false)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("initializing local operators: %w", err)
    59  	}
    60  
    61  	return gadgetCtx.SerializeGadgetInfo()
    62  }
    63  
    64  func (r *Runtime) RunGadget(gadgetCtx runtime.GadgetContext, runtimeParams *params.Params, paramValues api.ParamValues) error {
    65  	if runtimeParams == nil {
    66  		runtimeParams = r.ParamDescs().ToParams()
    67  	}
    68  
    69  	gadgetCtx.Logger().Debugf("Params")
    70  	for k, v := range paramValues {
    71  		gadgetCtx.Logger().Debugf("- %s: %q", k, v)
    72  	}
    73  
    74  	targets, err := r.getTargets(gadgetCtx.Context(), runtimeParams)
    75  	if err != nil {
    76  		return fmt.Errorf("getting target nodes: %w", err)
    77  	}
    78  	_, err = r.runGadgetOnTargets(gadgetCtx, paramValues, targets)
    79  	return err
    80  }
    81  
    82  func (r *Runtime) runGadgetOnTargets(
    83  	gadgetCtx runtime.GadgetContext,
    84  	paramMap map[string]string,
    85  	targets []target,
    86  ) (runtime.CombinedGadgetResult, error) {
    87  	results := make(runtime.CombinedGadgetResult, len(targets))
    88  	var resultsLock sync.Mutex
    89  
    90  	wg := sync.WaitGroup{}
    91  	for _, t := range targets {
    92  		wg.Add(1)
    93  		go func(target target) {
    94  			gadgetCtx.Logger().Debugf("running gadget on node %q", target.node)
    95  			res, err := r.runGadget(gadgetCtx, target, paramMap)
    96  			resultsLock.Lock()
    97  			results[target.node] = &runtime.GadgetResult{
    98  				Payload: res,
    99  				Error:   err,
   100  			}
   101  			resultsLock.Unlock()
   102  			wg.Done()
   103  		}(t)
   104  	}
   105  
   106  	wg.Wait()
   107  	return results, results.Err()
   108  }
   109  
   110  func (r *Runtime) runGadget(gadgetCtx runtime.GadgetContext, target target, allParams map[string]string) ([]byte, error) {
   111  	// Notice that we cannot use gadgetCtx.Context() here, as that would - when cancelled by the user - also cancel the
   112  	// underlying gRPC connection. That would then lead to results not being received anymore (mostly for profile
   113  	// gadgets.)
   114  	connCtx, cancel := context.WithCancel(context.Background())
   115  	defer cancel()
   116  
   117  	timeout := time.Second * time.Duration(r.globalParams.Get(ParamConnectionTimeout).AsUint16())
   118  	dialCtx, cancelDial := context.WithTimeout(gadgetCtx.Context(), timeout)
   119  	defer cancelDial()
   120  
   121  	conn, err := r.dialContext(dialCtx, target, timeout)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("dialing target on node %q: %w", target.node, err)
   124  	}
   125  	defer conn.Close()
   126  	client := api.NewGadgetManagerClient(conn)
   127  
   128  	runRequest := &api.GadgetRunRequest{
   129  		ImageName:   gadgetCtx.ImageName(),
   130  		ParamValues: allParams,
   131  		Args:        gadgetCtx.Args(),
   132  		LogLevel:    uint32(gadgetCtx.Logger().GetLevel()),
   133  		Timeout:     int64(gadgetCtx.Timeout()),
   134  		Version:     api.VersionGadgetRunProtocol,
   135  	}
   136  
   137  	runClient, err := client.RunGadget(connCtx)
   138  	if err != nil && !errors.Is(err, context.Canceled) {
   139  		return nil, err
   140  	}
   141  
   142  	controlRequest := &api.GadgetControlRequest{Event: &api.GadgetControlRequest_RunRequest{RunRequest: runRequest}}
   143  	err = runClient.Send(controlRequest)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	doneChan := make(chan error)
   149  
   150  	var result []byte
   151  	expectedSeq := uint32(1)
   152  
   153  	go func() {
   154  		dsMap := make(map[uint32]datasource.DataSource)
   155  		dsNameMap := make(map[string]uint32)
   156  		initialized := false
   157  		for {
   158  			ev, err := runClient.Recv()
   159  			if err != nil {
   160  				gadgetCtx.Logger().Debugf("%-20s | runClient returned with %v", target.node, err)
   161  				if !errors.Is(err, io.EOF) {
   162  					doneChan <- err
   163  					return
   164  				}
   165  				doneChan <- nil
   166  				return
   167  			}
   168  			switch ev.Type {
   169  			case api.EventTypeGadgetPayload:
   170  				if !initialized {
   171  					gadgetCtx.Logger().Warnf("%-20s | received payload without being initialized", target.node)
   172  					continue
   173  				}
   174  				if expectedSeq != ev.Seq {
   175  					gadgetCtx.Logger().Warnf("%-20s | expected seq %d, got %d, %d messages dropped", target.node, expectedSeq, ev.Seq, ev.Seq-expectedSeq)
   176  				}
   177  				expectedSeq = ev.Seq + 1
   178  				if ds, ok := dsMap[ev.DataSourceID]; ok && ds != nil {
   179  					d := ds.NewData()
   180  					err := proto.Unmarshal(ev.Payload, d.Raw())
   181  					if err != nil {
   182  						gadgetCtx.Logger().Debugf("error unmarshaling payload: %v", err)
   183  						continue
   184  					}
   185  					ds.EmitAndRelease(d)
   186  				}
   187  			case api.EventTypeGadgetResult:
   188  				gadgetCtx.Logger().Debugf("%-20s | got result from server", target.node)
   189  				result = ev.Payload
   190  			case api.EventTypeGadgetJobID: // not needed right now
   191  			case api.EventTypeGadgetInfo:
   192  				gi := &api.GadgetInfo{}
   193  				err = proto.Unmarshal(ev.Payload, gi)
   194  				if err != nil {
   195  					gadgetCtx.Logger().Warnf("unmarshaling gadget info: %v", err)
   196  					continue
   197  				}
   198  				for _, ds := range gi.DataSources {
   199  					dsNameMap[ds.Name] = ds.Id
   200  				}
   201  
   202  				// Try to load gadget info; if gadget info has already been loaded and this one
   203  				// doesn't match, this will terminate this particular client session
   204  				err = gadgetCtx.LoadGadgetInfo(gi, allParams, true)
   205  				if err != nil {
   206  					gadgetCtx.Logger().Warnf("deserizalize gadget info: %v", err)
   207  					continue
   208  				}
   209  				gadgetCtx.Logger().Debugf("loaded gadget info")
   210  				for _, ds := range gadgetCtx.GetDataSources() {
   211  					gadgetCtx.Logger().Debugf("registered ds %s", ds.Name())
   212  					dsMap[dsNameMap[ds.Name()]] = ds
   213  				}
   214  				initialized = true
   215  			default:
   216  				if ev.Type >= 1<<api.EventLogShift {
   217  					gadgetCtx.Logger().Log(logger.Level(ev.Type>>api.EventLogShift), fmt.Sprintf("%-20s | %s", target.node, string(ev.Payload)))
   218  					continue
   219  				}
   220  				gadgetCtx.Logger().Warnf("unknown payload type %d: %s", ev.Type, ev.Payload)
   221  			}
   222  		}
   223  	}()
   224  
   225  	var runErr error
   226  	select {
   227  	case doneErr := <-doneChan:
   228  		gadgetCtx.Logger().Debugf("%-20s | done from server side (%v)", target.node, doneErr)
   229  		runErr = doneErr
   230  	case <-gadgetCtx.Context().Done():
   231  		// Send stop request
   232  		gadgetCtx.Logger().Debugf("%-20s | sending stop request", target.node)
   233  		controlRequest := &api.GadgetControlRequest{Event: &api.GadgetControlRequest_StopRequest{StopRequest: &api.GadgetStopRequest{}}}
   234  		runClient.Send(controlRequest)
   235  
   236  		// Wait for done or timeout
   237  		select {
   238  		case doneErr := <-doneChan:
   239  			gadgetCtx.Logger().Debugf("%-20s | done after cancel request (%v)", target.node, doneErr)
   240  			runErr = doneErr
   241  		case <-time.After(ResultTimeout * time.Second):
   242  			return nil, fmt.Errorf("timed out while getting result")
   243  		}
   244  	}
   245  	return result, runErr
   246  }