Basic streaming plugin support (#31940)

This pull request migrates testdata to coreplugin streaming capabilities,
this is mostly a working concept of streaming plugins at the moment, 
the work will continue in the following pull requests.
This commit is contained in:
Alexander Emelin
2021-03-23 20:24:08 +03:00
committed by GitHub
parent 1cd8981be4
commit 336bc559a3
31 changed files with 1204 additions and 290 deletions

View File

@ -19,6 +19,7 @@ type corePlugin struct {
backend.CheckHealthHandler
backend.CallResourceHandler
backend.QueryDataHandler
backend.StreamHandler
}
// New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin.
@ -30,6 +31,7 @@ func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc {
CheckHealthHandler: opts.CheckHealthHandler,
CallResourceHandler: opts.CallResourceHandler,
QueryDataHandler: opts.QueryDataHandler,
StreamHandler: opts.StreamHandler,
}, nil
}
}
@ -55,9 +57,7 @@ func (cp *corePlugin) DataQuery(ctx context.Context, dsInfo *models.DataSource,
}
func (cp *corePlugin) Start(ctx context.Context) error {
if cp.QueryDataHandler != nil {
cp.isDataPlugin = true
}
cp.isDataPlugin = cp.QueryDataHandler != nil
return nil
}
@ -92,3 +92,17 @@ func (cp *corePlugin) CallResource(ctx context.Context, req *backend.CallResourc
return backendplugin.ErrMethodNotImplemented
}
func (cp *corePlugin) CanSubscribeToStream(ctx context.Context, req *backend.SubscribeToStreamRequest) (*backend.SubscribeToStreamResponse, error) {
if cp.StreamHandler != nil {
return cp.StreamHandler.CanSubscribeToStream(ctx, req)
}
return nil, backendplugin.ErrMethodNotImplemented
}
func (cp *corePlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
if cp.StreamHandler != nil {
return cp.StreamHandler.RunStream(ctx, req, sender)
}
return backendplugin.ErrMethodNotImplemented
}

View File

@ -74,6 +74,7 @@ func getV2PluginSet() goplugin.PluginSet {
"diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{},
"resource": &grpcplugin.ResourceGRPCPlugin{},
"data": &grpcplugin.DataGRPCPlugin{},
"stream": &grpcplugin.StreamGRPCPlugin{},
"renderer": &pluginextensionv2.RendererGRPCPlugin{},
}
}
@ -120,4 +121,5 @@ type LegacyClient struct {
type Client struct {
DataPlugin grpcplugin.DataClient
RendererPlugin pluginextensionv2.RendererPlugin
StreamClient grpcplugin.StreamClient
}

View File

@ -63,6 +63,14 @@ func (c *clientV1) CallResource(ctx context.Context, req *backend.CallResourceRe
return backendplugin.ErrMethodNotImplemented
}
func (c *clientV1) CanSubscribeToStream(ctx context.Context, request *backend.SubscribeToStreamRequest) (*backend.SubscribeToStreamResponse, error) {
return nil, backendplugin.ErrMethodNotImplemented
}
func (c *clientV1) RunStream(ctx context.Context, request *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
return backendplugin.ErrMethodNotImplemented
}
type datasourceV1QueryFunc func(ctx context.Context, req *datasourceV1.DatasourceRequest) (*datasourceV1.DatasourceResponse, error)
func (fn datasourceV1QueryFunc) Query(ctx context.Context, req *datasourceV1.DatasourceRequest) (*datasourceV1.DatasourceResponse, error) {

View File

@ -23,6 +23,7 @@ type clientV2 struct {
grpcplugin.DiagnosticsClient
grpcplugin.ResourceClient
grpcplugin.DataClient
grpcplugin.StreamClient
pluginextensionv2.RendererPlugin
}
@ -42,6 +43,11 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
return nil, err
}
rawStream, err := rpcClient.Dispense("stream")
if err != nil {
return nil, err
}
rawRenderer, err := rpcClient.Dispense("renderer")
if err != nil {
return nil, err
@ -66,6 +72,12 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
}
}
if rawStream != nil {
if plugin, ok := rawStream.(grpcplugin.StreamClient); ok {
c.StreamClient = plugin
}
}
if rawRenderer != nil {
if plugin, ok := rawRenderer.(pluginextensionv2.RendererPlugin); ok {
c.RendererPlugin = plugin
@ -76,6 +88,7 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
client := &Client{
DataPlugin: c.DataClient,
RendererPlugin: c.RendererPlugin,
StreamClient: c.StreamClient,
}
if err := descriptor.startFns.OnStart(descriptor.pluginID, client, logger); err != nil {
return nil, err
@ -158,6 +171,48 @@ func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRe
}
}
func (c *clientV2) CanSubscribeToStream(ctx context.Context, req *backend.SubscribeToStreamRequest) (*backend.SubscribeToStreamResponse, error) {
if c.StreamClient == nil {
return nil, backendplugin.ErrMethodNotImplemented
}
protoResp, err := c.StreamClient.CanSubscribeToStream(ctx, backend.ToProto().SubscribeToStreamRequest(req))
if err != nil {
return nil, err
}
return backend.FromProto().SubscribeToStreamResponse(protoResp), nil
}
func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
if c.StreamClient == nil {
return backendplugin.ErrMethodNotImplemented
}
protoReq := backend.ToProto().RunStreamRequest(req)
protoStream, err := c.StreamClient.RunStream(ctx, protoReq)
if err != nil {
if status.Code(err) == codes.Unimplemented {
return backendplugin.ErrMethodNotImplemented
}
return errutil.Wrap("Failed to call resource", err)
}
for {
protoResp, err := protoStream.Recv()
if err != nil {
if status.Code(err) == codes.Unimplemented {
return backendplugin.ErrMethodNotImplemented
}
if errors.Is(err, io.EOF) {
return nil
}
return errutil.Wrap("failed to receive call resource response", err)
}
if err := sender.Send(backend.FromProto().StreamPacket(protoResp)); err != nil {
return err
}
}
}
type dataClientQueryDataFunc func(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error)
func (fn dataClientQueryDataFunc) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error) {

View File

@ -15,6 +15,7 @@ type pluginClient interface {
backend.CollectMetricsHandler
backend.CheckHealthHandler
backend.CallResourceHandler
backend.StreamHandler
}
type grpcPlugin struct {
@ -138,3 +139,27 @@ func (p *grpcPlugin) CallResource(ctx context.Context, req *backend.CallResource
return pluginClient.CallResource(ctx, req, sender)
}
func (p *grpcPlugin) CanSubscribeToStream(ctx context.Context, request *backend.SubscribeToStreamRequest) (*backend.SubscribeToStreamResponse, error) {
p.mutex.RLock()
if p.client == nil || p.client.Exited() || p.pluginClient == nil {
p.mutex.RUnlock()
return nil, backendplugin.ErrPluginUnavailable
}
pluginClient := p.pluginClient
p.mutex.RUnlock()
return pluginClient.CanSubscribeToStream(ctx, request)
}
func (p *grpcPlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
p.mutex.RLock()
if p.client == nil || p.client.Exited() || p.pluginClient == nil {
p.mutex.RUnlock()
return backendplugin.ErrPluginUnavailable
}
pluginClient := p.pluginClient
p.mutex.RUnlock()
return pluginClient.RunStream(ctx, req, sender)
}

View File

@ -20,6 +20,8 @@ type Manager interface {
CheckHealth(ctx context.Context, pCtx backend.PluginContext) (*backend.CheckHealthResult, error)
// CallResource calls a plugin resource.
CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string)
// Get plugin by its ID.
Get(pluginID string) (Plugin, bool)
// GetDataPlugin gets a DataPlugin with a certain ID or nil if it doesn't exist.
// TODO: interface{} is the return type in order to break a dependency cycle. Should be plugins.DataPlugin.
GetDataPlugin(pluginID string) interface{}
@ -37,4 +39,5 @@ type Plugin interface {
backend.CollectMetricsHandler
backend.CheckHealthHandler
backend.CallResourceHandler
backend.StreamHandler
}

View File

@ -96,6 +96,11 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF
return nil
}
func (m *manager) Get(pluginID string) (backendplugin.Plugin, bool) {
p, ok := m.plugins[pluginID]
return p, ok
}
func (m *manager) getAWSEnvironmentVariables() []string {
variables := []string{}
if m.Cfg.AWSAssumeRoleEnabled {

View File

@ -396,6 +396,14 @@ func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourc
return backendplugin.ErrMethodNotImplemented
}
func (tp *testPlugin) CanSubscribeToStream(ctx context.Context, request *backend.SubscribeToStreamRequest) (*backend.SubscribeToStreamResponse, error) {
return nil, backendplugin.ErrMethodNotImplemented
}
func (tp *testPlugin) RunStream(ctx context.Context, request *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
return backendplugin.ErrMethodNotImplemented
}
type testLicensingService struct {
edition string
hasLicense bool