forked from grafana/grafana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Grafana Advisor: Datasource checks (grafana#99313)
- Loading branch information
1 parent
7d2eb83
commit b066a63
Showing
11 changed files
with
553 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package checkregistry | ||
|
||
import ( | ||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks" | ||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks/datasourcecheck" | ||
"github.com/grafana/grafana/pkg/plugins" | ||
"github.com/grafana/grafana/pkg/registry/apis/datasource" | ||
"github.com/grafana/grafana/pkg/services/datasources" | ||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" | ||
) | ||
|
||
type CheckService interface { | ||
Checks() []checks.Check | ||
} | ||
|
||
type Service struct { | ||
datasourceSvc datasources.DataSourceService | ||
pluginStore pluginstore.Store | ||
pluginContextProvider datasource.PluginContextWrapper | ||
pluginClient plugins.Client | ||
} | ||
|
||
func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store, | ||
pluginContextProvider datasource.PluginContextWrapper, pluginClient plugins.Client) *Service { | ||
return &Service{ | ||
datasourceSvc: datasourceSvc, | ||
pluginStore: pluginStore, | ||
pluginContextProvider: pluginContextProvider, | ||
pluginClient: pluginClient, | ||
} | ||
} | ||
|
||
func (s *Service) Checks() []checks.Check { | ||
return []checks.Check{ | ||
datasourcecheck.New( | ||
s.datasourceSvc, | ||
s.pluginStore, | ||
s.pluginContextProvider, | ||
s.pluginClient, | ||
), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package datasourcecheck | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/grafana/grafana-plugin-sdk-go/backend" | ||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" | ||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks" | ||
"github.com/grafana/grafana/pkg/plugins" | ||
"github.com/grafana/grafana/pkg/registry/apis/datasource" | ||
"github.com/grafana/grafana/pkg/services/datasources" | ||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" | ||
"github.com/grafana/grafana/pkg/util" | ||
"k8s.io/klog/v2" | ||
) | ||
|
||
func New( | ||
datasourceSvc datasources.DataSourceService, | ||
pluginStore pluginstore.Store, | ||
pluginContextProvider datasource.PluginContextWrapper, | ||
pluginClient plugins.Client, | ||
) checks.Check { | ||
return &check{ | ||
DatasourceSvc: datasourceSvc, | ||
PluginStore: pluginStore, | ||
PluginContextProvider: pluginContextProvider, | ||
PluginClient: pluginClient, | ||
} | ||
} | ||
|
||
type check struct { | ||
DatasourceSvc datasources.DataSourceService | ||
PluginStore pluginstore.Store | ||
PluginContextProvider datasource.PluginContextWrapper | ||
PluginClient plugins.Client | ||
} | ||
|
||
func (c *check) Type() string { | ||
return "datasource" | ||
} | ||
|
||
func (c *check) Run(ctx context.Context, obj *advisor.CheckSpec) (*advisor.CheckV0alpha1StatusReport, error) { | ||
// Optionally read the check input encoded in the object | ||
// fmt.Println(obj.Data) | ||
|
||
dss, err := c.DatasourceSvc.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
dsErrs := []advisor.CheckV0alpha1StatusReportErrors{} | ||
for _, ds := range dss { | ||
// Data source UID validation | ||
err := util.ValidateUID(ds.UID) | ||
if err != nil { | ||
dsErrs = append(dsErrs, advisor.CheckV0alpha1StatusReportErrors{ | ||
Severity: advisor.CheckStatusSeverityLow, | ||
Reason: fmt.Sprintf("Invalid UID: %s", ds.UID), | ||
Action: "Change UID", | ||
}) | ||
} | ||
|
||
// Health check execution | ||
pCtx, err := c.PluginContextProvider.PluginContextForDataSource(ctx, &backend.DataSourceInstanceSettings{ | ||
Type: ds.Type, | ||
UID: ds.UID, | ||
APIVersion: ds.APIVersion, | ||
}) | ||
if err != nil { | ||
klog.ErrorS(err, "Error creating plugin context", "datasource", ds.Name) | ||
continue | ||
} | ||
req := &backend.CheckHealthRequest{ | ||
PluginContext: pCtx, | ||
Headers: map[string]string{}, | ||
} | ||
resp, err := c.PluginClient.CheckHealth(ctx, req) | ||
if err != nil { | ||
fmt.Println("Error checking health", err) | ||
continue | ||
} | ||
if resp.Status != backend.HealthStatusOk { | ||
dsErrs = append(dsErrs, advisor.CheckV0alpha1StatusReportErrors{ | ||
Severity: advisor.CheckStatusSeverityHigh, | ||
Reason: fmt.Sprintf("Health check failed: %s", ds.Name), | ||
Action: "Check datasource", | ||
}) | ||
} | ||
} | ||
|
||
return &advisor.CheckV0alpha1StatusReport{ | ||
Count: int64(len(dss)), | ||
Errors: dsErrs, | ||
}, nil | ||
} |
114 changes: 114 additions & 0 deletions
114
apps/advisor/pkg/app/checks/datasourcecheck/check_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package datasourcecheck | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/grafana/grafana-plugin-sdk-go/backend" | ||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" | ||
"github.com/grafana/grafana/pkg/plugins" | ||
"github.com/grafana/grafana/pkg/registry/apis/datasource" | ||
"github.com/grafana/grafana/pkg/services/datasources" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestCheck_Run(t *testing.T) { | ||
t.Run("should return no errors when all datasources are healthy", func(t *testing.T) { | ||
datasources := []*datasources.DataSource{ | ||
{UID: "valid-uid-1", Type: "prometheus", Name: "Prometheus"}, | ||
{UID: "valid-uid-2", Type: "mysql", Name: "MySQL"}, | ||
} | ||
|
||
mockDatasourceSvc := &MockDatasourceSvc{dss: datasources} | ||
mockPluginContextProvider := &MockPluginContextProvider{pCtx: backend.PluginContext{}} | ||
mockPluginClient := &MockPluginClient{res: &backend.CheckHealthResult{Status: backend.HealthStatusOk}} | ||
|
||
check := &check{ | ||
DatasourceSvc: mockDatasourceSvc, | ||
PluginContextProvider: mockPluginContextProvider, | ||
PluginClient: mockPluginClient, | ||
} | ||
|
||
report, err := check.Run(context.Background(), &advisor.CheckSpec{}) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, int64(2), report.Count) | ||
assert.Empty(t, report.Errors) | ||
}) | ||
|
||
t.Run("should return errors when datasource UID is invalid", func(t *testing.T) { | ||
datasources := []*datasources.DataSource{ | ||
{UID: "invalid uid", Type: "prometheus", Name: "Prometheus"}, | ||
} | ||
|
||
mockDatasourceSvc := &MockDatasourceSvc{dss: datasources} | ||
mockPluginContextProvider := &MockPluginContextProvider{pCtx: backend.PluginContext{}} | ||
mockPluginClient := &MockPluginClient{res: &backend.CheckHealthResult{Status: backend.HealthStatusOk}} | ||
|
||
check := &check{ | ||
DatasourceSvc: mockDatasourceSvc, | ||
PluginContextProvider: mockPluginContextProvider, | ||
PluginClient: mockPluginClient, | ||
} | ||
|
||
report, err := check.Run(context.Background(), &advisor.CheckSpec{}) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, int64(1), report.Count) | ||
assert.Len(t, report.Errors, 1) | ||
assert.Equal(t, "Invalid UID: invalid uid", report.Errors[0].Reason) | ||
}) | ||
|
||
t.Run("should return errors when datasource health check fails", func(t *testing.T) { | ||
datasources := []*datasources.DataSource{ | ||
{UID: "valid-uid-1", Type: "prometheus", Name: "Prometheus"}, | ||
} | ||
|
||
mockDatasourceSvc := &MockDatasourceSvc{dss: datasources} | ||
mockPluginContextProvider := &MockPluginContextProvider{pCtx: backend.PluginContext{}} | ||
mockPluginClient := &MockPluginClient{res: &backend.CheckHealthResult{Status: backend.HealthStatusError}} | ||
|
||
check := &check{ | ||
DatasourceSvc: mockDatasourceSvc, | ||
PluginContextProvider: mockPluginContextProvider, | ||
PluginClient: mockPluginClient, | ||
} | ||
|
||
report, err := check.Run(context.Background(), &advisor.CheckSpec{}) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, int64(1), report.Count) | ||
assert.Len(t, report.Errors, 1) | ||
assert.Equal(t, "Health check failed: Prometheus", report.Errors[0].Reason) | ||
}) | ||
} | ||
|
||
type MockDatasourceSvc struct { | ||
datasources.DataSourceService | ||
|
||
dss []*datasources.DataSource | ||
} | ||
|
||
func (m *MockDatasourceSvc) GetAllDataSources(ctx context.Context, query *datasources.GetAllDataSourcesQuery) ([]*datasources.DataSource, error) { | ||
return m.dss, nil | ||
} | ||
|
||
type MockPluginContextProvider struct { | ||
datasource.PluginContextWrapper | ||
|
||
pCtx backend.PluginContext | ||
} | ||
|
||
func (m *MockPluginContextProvider) PluginContextForDataSource(ctx context.Context, datasourceSettings *backend.DataSourceInstanceSettings) (backend.PluginContext, error) { | ||
return m.pCtx, nil | ||
} | ||
|
||
type MockPluginClient struct { | ||
plugins.Client | ||
|
||
res *backend.CheckHealthResult | ||
} | ||
|
||
func (m *MockPluginClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { | ||
return m.res, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package checks | ||
|
||
import ( | ||
"context" | ||
|
||
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" | ||
) | ||
|
||
// Check defines the methods that a check must implement to be executed. | ||
type Check interface { | ||
Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec) (*advisorv0alpha1.CheckV0alpha1StatusReport, error) | ||
Type() string | ||
} |
Oops, something went wrong.