QueryService: Add feature toggles to better support testing (#86493)

This commit is contained in:
Ryan McKinley
2024-04-19 12:26:21 +03:00
committed by GitHub
parent 8a5c0cfdc0
commit 5a8384a245
14 changed files with 191 additions and 42 deletions
+78
View File
@@ -0,0 +1,78 @@
# Query service
This query service aims to replace the existing /api/ds/query.
The key differences are:
1. This service has a stronger type system (not simplejson)
2. Same workflow regardless if expressions exist
3. Datasource settings+access is managed in each datasource, not at the beginning
### Current /api/ds/query workflow
```mermaid
sequenceDiagram
autonumber
actor User as User or Process
participant api as /api/ds/query
participant db as Storage<br/>(SQL)
participant ds as Datasource<br/>Plugin
participant expr as Expression<br/>Engine
User->>api: POST Query
loop Each query
api->>api: Parse query
api->>db: Get ds config<br>and secrets
db->>api:
end
alt No expressions
alt Single datasource
api->>ds: QueryData
else Multiple datasources
loop Each datasource (concurrently)
api->>ds: QueryData
end
api->>api: Wait for results
end
else Expressions exist
api->>expr: Calculate expressions graph
loop Each node (eg, refID)
alt Is query
expr->>ds: QueryData
else Is expression
expr->>expr: Process
end
end
end
api->>User: return results
```
### /apis/query.grafana.app (in single tenant grafana)
```mermaid
sequenceDiagram
autonumber
actor User as User or Process
participant api as /apis/query.grafana.app
participant ds as Datasource<br/>Handler/Plugin
participant db as Storage<br/>(SQL)
participant expr as Expression<br/>Engine
User->>api: POST Query
api->>api: Parse queries
api->>api: Calculate dependencies
loop Each datasource (concurrently)
api->>ds: QueryData
ds->>ds: Verify user access
ds->>db: Get settings <br> and secrets
end
loop Each expression
api->>expr: Execute
end
api->>api: Verify ResultExpectations
api->>User: return results
```
+1 -1
View File
@@ -40,7 +40,7 @@ type pluginRegistry struct {
var _ data.QueryDataClient = (*pluginClient)(nil)
var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil)
// NewDummyTestRunner creates a runner that only works with testdata
// NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack
func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider) data.QueryDataClient {
return &pluginClient{
pluginClient: p,
+7 -2
View File
@@ -9,7 +9,6 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
)
@@ -24,6 +23,9 @@ type pluginsStorage struct {
resourceInfo *common.ResourceInfo
tableConverter rest.TableConvertor
registry query.DataSourceApiServerRegistry
// Always return an empty list regardless what we think exists
returnEmptyList bool
}
func newPluginsStorage(reg query.DataSourceApiServerRegistry) *pluginsStorage {
@@ -46,7 +48,7 @@ func (s *pluginsStorage) NamespaceScoped() bool {
}
func (s *pluginsStorage) GetSingularName() string {
return example.DummyResourceInfo.GetSingularName()
return s.resourceInfo.GetSingularName()
}
func (s *pluginsStorage) NewList() runtime.Object {
@@ -58,5 +60,8 @@ func (s *pluginsStorage) ConvertToTable(ctx context.Context, object runtime.Obje
}
func (s *pluginsStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
if s.returnEmptyList {
return s.NewList(), nil
}
return s.registry.GetDatasourceApiServers(ctx)
}
+11 -4
View File
@@ -85,8 +85,9 @@ func RegisterAPIService(features featuremgmt.FeatureToggles,
tracer tracing.Tracer,
legacy service.LegacyDataSourceLookup,
) (*QueryAPIBuilder, error) {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil, nil // skip registration unless opting into experimental apis
if !(features.IsEnabledGlobally(featuremgmt.FlagQueryService) ||
features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) {
return nil, nil // skip registration unless explicitly added (or all experimental are added)
}
builder, err := NewQueryAPIBuilder(
@@ -132,10 +133,16 @@ func (b *QueryAPIBuilder) GetAPIGroupInfo(
gv := v0alpha1.SchemeGroupVersion
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(gv.Group, scheme, metav1.ParameterCodec, codecs)
plugins := newPluginsStorage(b.registry)
storage := map[string]rest.Storage{}
plugins := newPluginsStorage(b.registry)
storage[plugins.resourceInfo.StoragePath()] = plugins
if !b.features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
// The plugin registry is still experimental, and not yet accurate
// For standard k8s api discovery to work, at least one resource must be registered
// While this feature is under development, we can return an empty list for non-dev instances
plugins.returnEmptyList = true
}
apiGroupInfo.VersionedResourcesStorageMap[gv.Version] = storage
return &apiGroupInfo, nil