mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 18:42:27 +08:00
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:
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user