github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/pluginmanager_service/plugin_manager.go (about) 1 package pluginmanager_service 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "os/exec" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/hashicorp/go-hclog" 15 "github.com/hashicorp/go-plugin" 16 "github.com/jackc/pgx/v5/pgxpool" 17 "github.com/sethvargo/go-retry" 18 "github.com/spf13/viper" 19 "github.com/turbot/go-kit/helpers" 20 sdkgrpc "github.com/turbot/steampipe-plugin-sdk/v5/grpc" 21 sdkproto "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 22 sdkshared "github.com/turbot/steampipe-plugin-sdk/v5/grpc/shared" 23 sdkplugin "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 24 "github.com/turbot/steampipe-plugin-sdk/v5/sperr" 25 "github.com/turbot/steampipe/pkg/connection" 26 "github.com/turbot/steampipe/pkg/constants" 27 "github.com/turbot/steampipe/pkg/db/db_local" 28 "github.com/turbot/steampipe/pkg/error_helpers" 29 "github.com/turbot/steampipe/pkg/filepaths" 30 "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc" 31 pb "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc/proto" 32 pluginshared "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc/shared" 33 "github.com/turbot/steampipe/pkg/steampipeconfig" 34 "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" 35 "github.com/turbot/steampipe/pkg/utils" 36 ) 37 38 // PluginManager is the implementation of grpc.PluginManager 39 type PluginManager struct { 40 pb.UnimplementedPluginManagerServer 41 42 // map of running plugins keyed by plugin instance 43 runningPluginMap map[string]*runningPlugin 44 // map of connection configs, keyed by plugin instance 45 // this is populated at startup and updated when a connection config change is detected 46 pluginConnectionConfigMap map[string][]*sdkproto.ConnectionConfig 47 // map of connection configs, keyed by connection name 48 // this is populated at startup and updated when a connection config change is detected 49 connectionConfigMap connection.ConnectionConfigMap 50 // map of max cache size, keyed by plugin instance 51 pluginCacheSizeMap map[string]int64 52 53 // map lock 54 mut sync.RWMutex 55 56 // shutdown syncronozation 57 // do not start any plugins while shutting down 58 shutdownMut sync.Mutex 59 // do not shutdown until all plugins have loaded 60 startPluginWg sync.WaitGroup 61 62 logger hclog.Logger 63 messageServer *PluginMessageServer 64 65 // map of user configured rate limiter maps, keyed by plugin instance 66 // NOTE: this is populated from config 67 userLimiters connection.PluginLimiterMap 68 // map of plugin configured rate limiter maps (keyed by plugin instance) 69 // NOTE: if this is nil, that means the steampipe_rate_limiter tables has not been populated yet - 70 // the first time we refresh connections we must load all plugins and fetch their rate limiter defs 71 pluginLimiters connection.PluginLimiterMap 72 73 // map of plugin configs (keyed by plugin instance) 74 plugins connection.PluginMap 75 76 pool *pgxpool.Pool 77 } 78 79 func NewPluginManager(ctx context.Context, connectionConfig map[string]*sdkproto.ConnectionConfig, pluginConfigs connection.PluginMap, logger hclog.Logger) (*PluginManager, error) { 80 log.Printf("[INFO] NewPluginManager") 81 pluginManager := &PluginManager{ 82 logger: logger, 83 runningPluginMap: make(map[string]*runningPlugin), 84 connectionConfigMap: connectionConfig, 85 userLimiters: pluginConfigs.ToPluginLimiterMap(), 86 plugins: pluginConfigs, 87 } 88 89 pluginManager.messageServer = &PluginMessageServer{pluginManager: pluginManager} 90 91 // populate plugin connection config map 92 pluginManager.populatePluginConnectionConfigs() 93 // determine cache size for each plugin 94 pluginManager.setPluginCacheSizeMap() 95 96 // create a connection pool to connection refresh 97 // in testing, a size of 20 seemed optimal 98 poolsize := 20 99 pool, err := db_local.CreateConnectionPool(ctx, &db_local.CreateDbOptions{Username: constants.DatabaseSuperUser}, poolsize) 100 if err != nil { 101 return nil, err 102 } 103 pluginManager.pool = pool 104 105 if err := pluginManager.initialiseRateLimiterDefs(ctx); err != nil { 106 return nil, err 107 } 108 109 if err := pluginManager.initialisePluginColumns(ctx); err != nil { 110 return nil, err 111 } 112 return pluginManager, nil 113 } 114 115 // plugin interface functions 116 117 func (m *PluginManager) Serve() { 118 // create a plugin map, using ourselves as the implementation 119 pluginMap := map[string]plugin.Plugin{ 120 pluginshared.PluginName: &pluginshared.PluginManagerPlugin{Impl: m}, 121 } 122 plugin.Serve(&plugin.ServeConfig{ 123 HandshakeConfig: pluginshared.Handshake, 124 Plugins: pluginMap, 125 // enable gRPC serving for this plugin... 126 GRPCServer: plugin.DefaultGRPCServer, 127 }) 128 } 129 130 func (m *PluginManager) Get(req *pb.GetRequest) (_ *pb.GetResponse, err error) { 131 defer func() { 132 if r := recover(); r != nil { 133 err = sperr.ToError(r, sperr.WithMessage("unexpected error encountered")) 134 } 135 }() 136 log.Printf("[TRACE] PluginManager Get %p", req) 137 defer log.Printf("[TRACE] PluginManager Get DONE %p", req) 138 139 resp := newGetResponse() 140 141 // build a map of plugins to connection config for requested connections, and a lookup of the requested connections 142 plugins, requestedConnectionsLookup, err := m.buildRequiredPluginMap(req) 143 if err != nil { 144 return resp.GetResponse, err 145 } 146 147 log.Printf("[TRACE] PluginManager Get, connections: '%s'\n", req.Connections) 148 var pluginWg sync.WaitGroup 149 for pluginInstance, connectionConfigs := range plugins { 150 m.ensurePluginAsync(req, resp, pluginInstance, connectionConfigs, requestedConnectionsLookup, &pluginWg) 151 } 152 pluginWg.Wait() 153 154 log.Printf("[TRACE] PluginManager Get DONE") 155 return resp.GetResponse, nil 156 } 157 158 func (m *PluginManager) ensurePluginAsync(req *pb.GetRequest, resp *getResponse, pluginInstance string, connectionConfigs []*sdkproto.ConnectionConfig, requestedConnectionsLookup map[string]struct{}, pluginWg *sync.WaitGroup) { 159 pluginWg.Add(1) 160 go func() { 161 defer pluginWg.Done() 162 // ensure plugin is running 163 reattach, err := m.ensurePlugin(pluginInstance, connectionConfigs, req) 164 if err != nil { 165 log.Printf("[WARN] PluginManager Get failed for %s: %s (%p)", pluginInstance, err.Error(), resp) 166 resp.AddFailure(pluginInstance, err.Error()) 167 } else { 168 log.Printf("[TRACE] PluginManager Get succeeded for %s, pid %d (%p)", pluginInstance, reattach.Pid, resp) 169 170 // assign reattach for requested connections 171 // (NOTE: connectionConfigs contains ALL connections for the plugin) 172 for _, config := range connectionConfigs { 173 // if this connection was requested, copy reattach into responses 174 if _, connectionWasRequested := requestedConnectionsLookup[config.Connection]; connectionWasRequested { 175 resp.AddReattach(config.Connection, reattach) 176 } 177 } 178 } 179 }() 180 } 181 182 // build a map of plugins to connection config for requested connections, keyed by plugin instance, 183 // and a lookup of the requested connections 184 func (m *PluginManager) buildRequiredPluginMap(req *pb.GetRequest) (map[string][]*sdkproto.ConnectionConfig, map[string]struct{}, error) { 185 var plugins = make(map[string][]*sdkproto.ConnectionConfig) 186 // also make a map of target connections - used when assigning results to the response 187 var requestedConnectionsLookup = make(map[string]struct{}, len(req.Connections)) 188 for _, connectionName := range req.Connections { 189 // store connection in requested connection map 190 requestedConnectionsLookup[connectionName] = struct{}{} 191 192 connectionConfig, err := m.getConnectionConfig(connectionName) 193 if err != nil { 194 return nil, nil, err 195 } 196 pluginInstance := connectionConfig.PluginInstance 197 // if we have not added this plugin instance, add it now 198 if _, addedPlugin := plugins[pluginInstance]; !addedPlugin { 199 // now get ALL connection configs for this plugin 200 // (not just the requested connections) 201 plugins[pluginInstance] = m.pluginConnectionConfigMap[pluginInstance] 202 } 203 } 204 return plugins, requestedConnectionsLookup, nil 205 } 206 207 func (m *PluginManager) Pool() *pgxpool.Pool { 208 return m.pool 209 } 210 211 func (m *PluginManager) RefreshConnections(*pb.RefreshConnectionsRequest) (*pb.RefreshConnectionsResponse, error) { 212 log.Printf("[INFO] PluginManager RefreshConnections") 213 214 resp := &pb.RefreshConnectionsResponse{} 215 216 log.Printf("[INFO] calling RefreshConnections asyncronously") 217 218 go m.doRefresh() 219 return resp, nil 220 } 221 222 func (m *PluginManager) doRefresh() { 223 refreshResult := connection.RefreshConnections(context.Background(), m) 224 if refreshResult.Error != nil { 225 // NOTE: the RefreshConnectionState will already have sent a notification to the CLI 226 log.Printf("[WARN] RefreshConnections failed with error: %s", refreshResult.Error.Error()) 227 } 228 } 229 230 // OnConnectionConfigChanged is the callback function invoked by the connection watcher when the config changed 231 func (m *PluginManager) OnConnectionConfigChanged(ctx context.Context, configMap connection.ConnectionConfigMap, plugins map[string]*modconfig.Plugin) { 232 m.mut.Lock() 233 defer m.mut.Unlock() 234 235 log.Printf("[TRACE] OnConnectionConfigChanged: connections: %s plugin instances: %s", strings.Join(utils.SortedMapKeys(configMap), ","), strings.Join(utils.SortedMapKeys(plugins), ",")) 236 237 if err := m.handleConnectionConfigChanges(ctx, configMap); err != nil { 238 log.Printf("[WARN] handleConnectionConfigChanges failed: %s", err.Error()) 239 } 240 241 // update our plugin configs 242 if err := m.handlePluginInstanceChanges(ctx, plugins); err != nil { 243 log.Printf("[WARN] handlePluginInstanceChanges failed: %s", err.Error()) 244 } 245 246 if err := m.handleUserLimiterChanges(ctx, plugins); err != nil { 247 log.Printf("[WARN] handleUserLimiterChanges failed: %s", err.Error()) 248 } 249 } 250 251 func (m *PluginManager) GetConnectionConfig() connection.ConnectionConfigMap { 252 return m.connectionConfigMap 253 } 254 255 func (m *PluginManager) Shutdown(*pb.ShutdownRequest) (resp *pb.ShutdownResponse, err error) { 256 log.Printf("[INFO] PluginManager Shutdown") 257 defer log.Printf("[INFO] PluginManager Shutdown complete") 258 259 // lock shutdownMut before waiting for startPluginWg 260 // this enables us to exit from ensurePlugin early if needed 261 m.shutdownMut.Lock() 262 m.startPluginWg.Wait() 263 264 // close our pool 265 log.Printf("[INFO] PluginManager closing pool") 266 m.pool.Close() 267 268 m.mut.RLock() 269 defer func() { 270 m.mut.RUnlock() 271 if r := recover(); r != nil { 272 err = helpers.ToError(r) 273 } 274 }() 275 276 // kill all plugins in pluginMultiConnectionMap 277 for _, p := range m.runningPluginMap { 278 log.Printf("[INFO] Kill plugin %s (%p)", p.pluginInstance, p.client) 279 m.killPlugin(p) 280 } 281 282 return &pb.ShutdownResponse{}, nil 283 } 284 285 func (m *PluginManager) killPlugin(p *runningPlugin) { 286 log.Println("[DEBUG] PluginManager killPlugin start") 287 defer log.Println("[DEBUG] PluginManager killPlugin complete") 288 289 if p.client == nil { 290 log.Printf("[WARN] plugin %s has no client - cannot kill client", p.pluginInstance) 291 // shouldn't happen but has been observed in error situations 292 return 293 } 294 log.Printf("[INFO] PluginManager killing plugin %s (%v)", p.pluginInstance, p.reattach.Pid) 295 p.client.Kill() 296 } 297 298 func (m *PluginManager) ensurePlugin(pluginInstance string, connectionConfigs []*sdkproto.ConnectionConfig, req *pb.GetRequest) (reattach *pb.ReattachConfig, err error) { 299 /* call startPluginIfNeeded within a retry block 300 we will retry if: 301 - we enter the plugin startup flow, but discover another process has beaten us to it an is starting the plugin already 302 - plugin initialization fails 303 - there was a runningPlugin entry in our map but the pid did not exist 304 (i.e we thought the plugin was running, but it was not) 305 */ 306 307 backoff := retry.WithMaxRetries(5, retry.NewConstant(10*time.Millisecond)) 308 309 // ensure we do not shutdown until this has finished 310 m.startPluginWg.Add(1) 311 defer func() { 312 m.startPluginWg.Done() 313 if r := recover(); r != nil { 314 err = helpers.ToError(r) 315 } 316 }() 317 318 // do not install a plugin while shutting down 319 if m.shuttingDown() { 320 return nil, fmt.Errorf("plugin manager is shutting down") 321 } 322 323 log.Printf("[TRACE] PluginManager ensurePlugin %s (%p)", pluginInstance, req) 324 325 err = retry.Do(context.Background(), backoff, func(ctx context.Context) error { 326 reattach, err = m.startPluginIfNeeded(pluginInstance, connectionConfigs, req) 327 return err 328 }) 329 330 return 331 } 332 333 func (m *PluginManager) startPluginIfNeeded(pluginInstance string, connectionConfigs []*sdkproto.ConnectionConfig, req *pb.GetRequest) (*pb.ReattachConfig, error) { 334 // is this plugin already running 335 // lock access to plugin map 336 m.mut.RLock() 337 startingPlugin, ok := m.runningPluginMap[pluginInstance] 338 m.mut.RUnlock() 339 340 if ok { 341 log.Printf("[TRACE] startPluginIfNeeded got running plugin (%p)", req) 342 343 // wait for plugin to process connection config, and verify it is running 344 err := m.waitForPluginLoad(startingPlugin, req) 345 if err == nil { 346 // so plugin has loaded - we are done 347 348 // NOTE: ensure the connections assigned to this plugin are correct 349 // (may be out of sync if a connection is being added) 350 m.mut.Lock() 351 startingPlugin.reattach.UpdateConnections(connectionConfigs) 352 m.mut.Unlock() 353 354 log.Printf("[TRACE] waitForPluginLoad succeeded %s (%p)", pluginInstance, req) 355 return startingPlugin.reattach, nil 356 } 357 log.Printf("[TRACE] waitForPluginLoad failed %s (%p)", err.Error(), req) 358 359 // just return the error 360 return nil, err 361 } 362 363 // so the plugin is NOT loaded or loading 364 // fall through to plugin startup 365 log.Printf("[INFO] plugin %s NOT started or starting - start now (%p)", pluginInstance, req) 366 367 return m.startPlugin(pluginInstance, connectionConfigs, req) 368 } 369 370 func (m *PluginManager) startPlugin(pluginInstance string, connectionConfigs []*sdkproto.ConnectionConfig, req *pb.GetRequest) (_ *pb.ReattachConfig, err error) { 371 log.Printf("[DEBUG] startPlugin %s (%p) start", pluginInstance, req) 372 defer log.Printf("[DEBUG] startPlugin %s (%p) end", pluginInstance, req) 373 374 // add a new running plugin to pluginMultiConnectionMap 375 // (if someone beat us to it and added a starting plugin before we get the write lock, 376 // this will return a retryable error) 377 startingPlugin, err := m.addRunningPlugin(pluginInstance) 378 if err != nil { 379 log.Printf("[INFO] addRunningPlugin returned error %s (%p)", err.Error(), req) 380 return nil, err 381 } 382 383 log.Printf("[INFO] added running plugin (%p)", req) 384 385 // ensure we clean up the starting plugin in case of error 386 defer func() { 387 if err != nil { 388 m.mut.Lock() 389 // delete from map 390 delete(m.runningPluginMap, pluginInstance) 391 // set error on running plugin 392 startingPlugin.error = err 393 394 // close failed chan to signal to anyone waiting for the plugin to startup that it failed 395 close(startingPlugin.failed) 396 397 log.Printf("[INFO] startPluginProcess failed: %s (%p)", err.Error(), req) 398 // kill the client 399 if startingPlugin.client != nil { 400 log.Printf("[INFO] failed pid: %d (%p)", startingPlugin.client.ReattachConfig().Pid, req) 401 startingPlugin.client.Kill() 402 } 403 404 m.mut.Unlock() 405 } 406 }() 407 408 // OK so now proceed with plugin startup 409 410 log.Printf("[INFO] start plugin (%p)", req) 411 // now start the process 412 client, err := m.startPluginProcess(pluginInstance, connectionConfigs) 413 if err != nil { 414 // do not retry - no reason to think this will fix itself 415 return nil, err 416 } 417 418 startingPlugin.client = client 419 420 // set the connection configs and build a ReattachConfig 421 reattach, err := m.initializePlugin(connectionConfigs, client, req) 422 if err != nil { 423 log.Printf("[WARN] initializePlugin failed: %s (%p)", err.Error(), req) 424 return nil, err 425 } 426 startingPlugin.reattach = reattach 427 428 // close initialized chan to advertise that this plugin is ready 429 close(startingPlugin.initialized) 430 431 log.Printf("[INFO] PluginManager ensurePlugin complete, returning reattach config with PID: %d (%p)", reattach.Pid, req) 432 433 // and return 434 return reattach, nil 435 } 436 437 func (m *PluginManager) addRunningPlugin(pluginInstance string) (*runningPlugin, error) { 438 // add a new running plugin to pluginMultiConnectionMap 439 // this is a placeholder so no other thread tries to create start this plugin 440 441 // acquire write lock 442 m.mut.Lock() 443 defer m.mut.Unlock() 444 log.Printf("[TRACE] add running plugin for %s (if someone didn't beat us to it)", pluginInstance) 445 446 // check someone else has beaten us to it (there is a race condition to starting a plugin) 447 if _, ok := m.runningPluginMap[pluginInstance]; ok { 448 log.Printf("[TRACE] re checked map and found a starting plugin - return retryable error so we wait for this plugin") 449 // if so, just retry, which will wait for the loading plugin 450 return nil, retry.RetryableError(fmt.Errorf("another client has already started the plugin")) 451 } 452 453 // get the config for this instance 454 pluginConfig := m.plugins[pluginInstance] 455 if pluginConfig == nil { 456 // not expected 457 return nil, sperr.New("plugin manager has no config for plugin instance %s", pluginInstance) 458 } 459 // create the running plugin 460 startingPlugin := &runningPlugin{ 461 pluginInstance: pluginInstance, 462 imageRef: pluginConfig.Plugin, 463 initialized: make(chan struct{}), 464 failed: make(chan struct{}), 465 } 466 // write back 467 m.runningPluginMap[pluginInstance] = startingPlugin 468 469 log.Printf("[INFO] written running plugin to map") 470 471 return startingPlugin, nil 472 } 473 474 func (m *PluginManager) startPluginProcess(pluginInstance string, connectionConfigs []*sdkproto.ConnectionConfig) (*plugin.Client, error) { 475 // retrieve the plugin config 476 pluginConfig := m.plugins[pluginInstance] 477 // must be there (if no explicit config was specified, we create a default) 478 if pluginConfig == nil { 479 panic(fmt.Sprintf("no plugin config is stored for plugin instance %s", pluginInstance)) 480 } 481 482 imageRef := pluginConfig.Plugin 483 log.Printf("[INFO] ************ start plugin: %s, label: %s ********************\n", imageRef, pluginConfig.Instance) 484 485 // NOTE: pass pluginConfig.Alias as the pluginAlias 486 // - this is just used for the error message if we fail to load 487 pluginPath, err := filepaths.GetPluginPath(imageRef, pluginConfig.Alias) 488 if err != nil { 489 return nil, err 490 } 491 log.Printf("[INFO] ************ plugin path %s ********************\n", pluginPath) 492 493 // create the plugin map 494 pluginMap := map[string]plugin.Plugin{ 495 imageRef: &sdkshared.WrapperPlugin{}, 496 } 497 498 cmd := exec.Command(pluginPath) 499 m.setPluginMaxMemory(pluginConfig, cmd) 500 client := plugin.NewClient(&plugin.ClientConfig{ 501 HandshakeConfig: sdkshared.Handshake, 502 Plugins: pluginMap, 503 Cmd: cmd, 504 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 505 506 // pass our logger to the plugin client to ensure plugin logs end up in logfile 507 Logger: m.logger, 508 }) 509 510 if _, err := client.Start(); err != nil { 511 // attempt to retrieve error message encoded in the plugin stdout 512 err := grpc.HandleStartFailure(err) 513 return nil, err 514 } 515 516 return client, nil 517 518 } 519 520 func (m *PluginManager) setPluginMaxMemory(pluginConfig *modconfig.Plugin, cmd *exec.Cmd) { 521 maxMemoryBytes := pluginConfig.GetMaxMemoryBytes() 522 if maxMemoryBytes == 0 { 523 if viper.IsSet(constants.ArgMemoryMaxMbPlugin) { 524 maxMemoryBytes = viper.GetInt64(constants.ArgMemoryMaxMbPlugin) * 1024 * 1024 525 } 526 } 527 if maxMemoryBytes != 0 { 528 log.Printf("[INFO] Setting max memory for plugin '%s' to %d Mb", pluginConfig.Instance, maxMemoryBytes/(1024*1024)) 529 // set GOMEMLIMIT for the plugin command env 530 // TODO should I check for GOMEMLIMIT or does this just override 531 cmd.Env = append(os.Environ(), fmt.Sprintf("GOMEMLIMIT=%d", maxMemoryBytes)) 532 } 533 } 534 535 // set the connection configs and build a ReattachConfig 536 func (m *PluginManager) initializePlugin(connectionConfigs []*sdkproto.ConnectionConfig, client *plugin.Client, req *pb.GetRequest) (_ *pb.ReattachConfig, err error) { 537 // extract connection names 538 connectionNames := make([]string, len(connectionConfigs)) 539 for i, c := range connectionConfigs { 540 connectionNames[i] = c.Connection 541 } 542 exemplarConnectionConfig := connectionConfigs[0] 543 pluginName := exemplarConnectionConfig.Plugin 544 pluginInstance := exemplarConnectionConfig.PluginInstance 545 546 log.Printf("[INFO] initializePlugin %s pid %d (%p)", pluginName, client.ReattachConfig().Pid, req) 547 548 // build a client 549 pluginClient, err := sdkgrpc.NewPluginClient(client, pluginName) 550 if err != nil { 551 return nil, err 552 } 553 554 // fetch the supported operations 555 supportedOperations, _ := pluginClient.GetSupportedOperations() 556 // ignore errors - just create an empty support structure if needed 557 if supportedOperations == nil { 558 supportedOperations = &sdkproto.GetSupportedOperationsResponse{} 559 } 560 // if this plugin does not support multiple connections, we no longer support it 561 if !supportedOperations.MultipleConnections { 562 return nil, fmt.Errorf(error_helpers.PluginSdkCompatibilityError) 563 } 564 565 // provide opportunity to avoid setting connection configs if we are shutting down 566 if m.shuttingDown() { 567 log.Printf("[INFO] aborting plugin %s initialization - plugin manager is shutting down", pluginName) 568 return nil, fmt.Errorf("plugin manager is shutting down") 569 } 570 571 // send the connection config for all connections for this plugin 572 // this returns a list of all connections provided by this plugin 573 err = m.setAllConnectionConfigs(connectionConfigs, pluginClient, supportedOperations) 574 if err != nil { 575 log.Printf("[WARN] failed to set connection config for %s: %s", pluginName, err.Error()) 576 return nil, err 577 } 578 579 // if this plugin supports setting cache options, do so 580 if supportedOperations.SetCacheOptions { 581 err = m.setCacheOptions(pluginClient) 582 if err != nil { 583 log.Printf("[WARN] failed to set cache options for %s: %s", pluginName, err.Error()) 584 return nil, err 585 } 586 } 587 588 // if this plugin supports setting cache options, do so 589 if supportedOperations.RateLimiters { 590 err = m.setRateLimiters(pluginInstance, pluginClient) 591 if err != nil { 592 log.Printf("[WARN] failed to set rate limiters for %s: %s", pluginName, err.Error()) 593 return nil, err 594 } 595 } 596 597 reattach := pb.NewReattachConfig(pluginName, client.ReattachConfig(), pb.SupportedOperationsFromSdk(supportedOperations), connectionNames) 598 599 // if this plugin has a dynamic schema, add connections to message server 600 err = m.notifyNewDynamicSchemas(pluginClient, exemplarConnectionConfig, connectionNames) 601 if err != nil { 602 return nil, err 603 } 604 605 log.Printf("[INFO] initializePlugin complete pid %d", client.ReattachConfig().Pid) 606 return reattach, nil 607 } 608 609 // return whether the plugin manager is shutting down 610 func (m *PluginManager) shuttingDown() bool { 611 if !m.shutdownMut.TryLock() { 612 return true 613 } 614 m.shutdownMut.Unlock() 615 return false 616 } 617 618 // populate map of connection configs for each plugin instance 619 func (m *PluginManager) populatePluginConnectionConfigs() { 620 m.pluginConnectionConfigMap = make(map[string][]*sdkproto.ConnectionConfig) 621 for _, config := range m.connectionConfigMap { 622 m.pluginConnectionConfigMap[config.PluginInstance] = append(m.pluginConnectionConfigMap[config.PluginInstance], config) 623 } 624 } 625 626 // populate map of connection configs for each plugin 627 func (m *PluginManager) setPluginCacheSizeMap() { 628 m.pluginCacheSizeMap = make(map[string]int64, len(m.pluginConnectionConfigMap)) 629 630 // read the env var setting cache size 631 maxCacheSizeMb, _ := strconv.Atoi(os.Getenv(constants.EnvCacheMaxSize)) 632 633 // get total connection count for this pluginInstance (excluding aggregators) 634 numConnections := m.nonAggregatorConnectionCount() 635 636 log.Printf("[TRACE] PluginManager setPluginCacheSizeMap: %d %s.", numConnections, utils.Pluralize("connection", numConnections)) 637 log.Printf("[TRACE] Total cache size %dMb", maxCacheSizeMb) 638 639 for pluginInstance, connections := range m.pluginConnectionConfigMap { 640 var size int64 = 0 641 // if no max size is set, just set all plugins to zero (unlimited) 642 if maxCacheSizeMb > 0 { 643 // get connection count for this pluginInstance (excluding aggregators) 644 numPluginConnections := nonAggregatorConnectionCount(connections) 645 size = int64(float64(numPluginConnections) / float64(numConnections) * float64(maxCacheSizeMb)) 646 // make this at least 1 Mb (as zero means unlimited) 647 if size == 0 { 648 size = 1 649 } 650 log.Printf("[INFO] Plugin '%s', %d %s, max cache size %dMb", pluginInstance, numPluginConnections, utils.Pluralize("connection", numPluginConnections), size) 651 } 652 653 m.pluginCacheSizeMap[pluginInstance] = size 654 } 655 } 656 657 func (m *PluginManager) notifyNewDynamicSchemas(pluginClient *sdkgrpc.PluginClient, exemplarConnectionConfig *sdkproto.ConnectionConfig, connectionNames []string) error { 658 // fetch the schema for the first connection so we know if it is dynamic 659 schema, err := pluginClient.GetSchema(exemplarConnectionConfig.Connection) 660 if err != nil { 661 log.Printf("[WARN] failed to set fetch schema for %s: %s", exemplarConnectionConfig, err.Error()) 662 return err 663 } 664 if schema.Mode == sdkplugin.SchemaModeDynamic { 665 _ = m.messageServer.AddConnection(pluginClient, exemplarConnectionConfig.Plugin, connectionNames...) 666 } 667 return nil 668 } 669 670 func (m *PluginManager) waitForPluginLoad(p *runningPlugin, req *pb.GetRequest) error { 671 log.Printf("[TRACE] waitForPluginLoad (%p)", req) 672 // TODO make this configurable 673 pluginStartTimeoutSecs := 30 674 675 // wait for the plugin to be initialized 676 select { 677 case <-time.After(time.Duration(pluginStartTimeoutSecs) * time.Second): 678 log.Printf("[WARN] timed out waiting for %s to startup after %d seconds (%p)", p.pluginInstance, pluginStartTimeoutSecs, req) 679 // do not retry 680 return fmt.Errorf("timed out waiting for %s to startup after %d seconds (%p)", p.pluginInstance, pluginStartTimeoutSecs, req) 681 case <-p.initialized: 682 log.Printf("[TRACE] plugin initialized: pid %d (%p)", p.reattach.Pid, req) 683 case <-p.failed: 684 log.Printf("[TRACE] plugin pid %d failed %s (%p)", p.reattach.Pid, p.error.Error(), req) 685 // get error from running plugin 686 return p.error 687 } 688 689 // now double-check the plugins process IS running 690 if !p.client.Exited() { 691 // so the plugin is good 692 log.Printf("[INFO] waitForPluginLoad: %s is now loaded and ready (%p)", p.pluginInstance, req) 693 return nil 694 } 695 696 // so even though our data structure indicates the plugin is running, the client says the underlying pid has exited 697 // - it must have terminated for some reason 698 log.Printf("[INFO] waitForPluginLoad: pid %d exists in runningPluginMap but pid has exited (%p)", p.reattach.Pid, req) 699 700 // remove this plugin from the map 701 // NOTE: multiple thread may be trying to remove the failed plugin from the map 702 // - and then someone will add a new running plugin when the startup is retried 703 // So we must check the pid before deleting 704 m.mut.Lock() 705 if r, ok := m.runningPluginMap[p.pluginInstance]; ok { 706 // is the running plugin we read from the map the same as our running plugin? 707 // if not, it must already have been removed by another thread - do nothing 708 if r == p { 709 log.Printf("[INFO] delete plugin %s from runningPluginMap (%p)", p.pluginInstance, req) 710 delete(m.runningPluginMap, p.pluginInstance) 711 } 712 } 713 m.mut.Unlock() 714 715 // so the pid does not exist 716 err := fmt.Errorf("PluginManager found pid %d for plugin '%s' in plugin map but plugin process does not exist (%p)", p.reattach.Pid, p.pluginInstance, req) 717 // we need to start the plugin again - make the error retryable 718 return retry.RetryableError(err) 719 } 720 721 // set connection config for multiple connection 722 // NOTE: we DO NOT set connection config for aggregator connections 723 func (m *PluginManager) setAllConnectionConfigs(connectionConfigs []*sdkproto.ConnectionConfig, pluginClient *sdkgrpc.PluginClient, supportedOperations *sdkproto.GetSupportedOperationsResponse) error { 724 // TODO does this fail all connections if one fails 725 exemplarConnectionConfig := connectionConfigs[0] 726 pluginInstance := exemplarConnectionConfig.PluginInstance 727 728 req := &sdkproto.SetAllConnectionConfigsRequest{ 729 Configs: connectionConfigs, 730 // NOTE: set MaxCacheSizeMb to -1so that query cache is not created until we call SetCacheOptions (if supported) 731 MaxCacheSizeMb: -1, 732 } 733 // if plugin _does not_ support setting the cache options separately, pass the max size now 734 // (if it does support SetCacheOptions, it will be called after we return) 735 if !supportedOperations.SetCacheOptions { 736 req.MaxCacheSizeMb = m.pluginCacheSizeMap[pluginInstance] 737 } 738 739 _, err := pluginClient.SetAllConnectionConfigs(req) 740 return err 741 } 742 743 func (m *PluginManager) setCacheOptions(pluginClient *sdkgrpc.PluginClient) error { 744 req := &sdkproto.SetCacheOptionsRequest{ 745 Enabled: viper.GetBool(constants.ArgServiceCacheEnabled), 746 Ttl: viper.GetInt64(constants.ArgCacheMaxTtl), 747 MaxSizeMb: viper.GetInt64(constants.ArgMaxCacheSizeMb), 748 } 749 _, err := pluginClient.SetCacheOptions(req) 750 return err 751 } 752 753 func (m *PluginManager) setRateLimiters(pluginInstance string, pluginClient *sdkgrpc.PluginClient) error { 754 log.Printf("[INFO] setRateLimiters for plugin '%s'", pluginInstance) 755 var defs []*sdkproto.RateLimiterDefinition 756 757 for _, l := range m.userLimiters[pluginInstance] { 758 defs = append(defs, l.AsProto()) 759 } 760 761 req := &sdkproto.SetRateLimitersRequest{Definitions: defs} 762 763 _, err := pluginClient.SetRateLimiters(req) 764 return err 765 } 766 767 // update the schema for the specified connection 768 // called from the message server after receiving a PluginMessageType_SCHEMA_UPDATED message from plugin 769 func (m *PluginManager) updateConnectionSchema(ctx context.Context, connectionName string) { 770 log.Printf("[INFO] updateConnectionSchema connection %s", connectionName) 771 772 refreshResult := connection.RefreshConnections(ctx, m, connectionName) 773 if refreshResult.Error != nil { 774 log.Printf("[TRACE] error refreshing connections: %s", refreshResult.Error) 775 return 776 } 777 778 // also send a postgres notification 779 notification := steampipeconfig.NewSchemaUpdateNotification() 780 781 conn, err := m.pool.Acquire(ctx) 782 if err != nil { 783 log.Printf("[WARN] failed to send schema update notification: %s", err) 784 } 785 defer conn.Release() 786 787 err = db_local.SendPostgresNotification(ctx, conn.Conn(), notification) 788 if err != nil { 789 log.Printf("[WARN] failed to send schema update notification: %s", err) 790 } 791 } 792 793 func (m *PluginManager) nonAggregatorConnectionCount() int { 794 res := 0 795 for _, connections := range m.pluginConnectionConfigMap { 796 res += nonAggregatorConnectionCount(connections) 797 } 798 return res 799 } 800 801 // getPluginExemplarConnections returns a map of keyed by plugin full name with the value an exemplar connection 802 func (m *PluginManager) getPluginExemplarConnections() map[string]string { 803 res := make(map[string]string) 804 for _, c := range m.connectionConfigMap { 805 res[c.Plugin] = c.Connection 806 } 807 return res 808 } 809 810 func (m *PluginManager) tableExists(ctx context.Context, schema, table string) (bool, error) { 811 query := fmt.Sprintf(`SELECT EXISTS ( 812 SELECT FROM 813 pg_tables 814 WHERE 815 schemaname = '%s' AND 816 tablename = '%s' 817 );`, schema, table) 818 819 row := m.pool.QueryRow(ctx, query) 820 var exists bool 821 err := row.Scan(&exists) 822 823 if err != nil { 824 return false, err 825 } 826 return exists, nil 827 } 828 829 func nonAggregatorConnectionCount(connections []*sdkproto.ConnectionConfig) int { 830 res := 0 831 for _, c := range connections { 832 if len(c.ChildConnections) == 0 { 833 res++ 834 } 835 } 836 return res 837 838 }