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 }