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 }