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  }