Skip to content

Commit

Permalink
Advisor: Run check steps in parallel (grafana#100200)
Browse files Browse the repository at this point in the history
  • Loading branch information
andresmgot authored Feb 7, 2025
1 parent 6dc98db commit e291140
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 150 deletions.
106 changes: 47 additions & 59 deletions apps/advisor/pkg/app/checks/datasourcecheck/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"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"
)

type check struct {
Expand Down Expand Up @@ -76,26 +75,23 @@ func (s *uidValidationStep) Description() string {
return "Check if the UID of each data source is valid."
}

func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) {
dsErrs := []advisor.CheckReportError{}
for _, i := range items {
ds, ok := i.(*datasources.DataSource)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}
// Data source UID validation
err := util.ValidateUID(ds.UID)
if err != nil {
dsErrs = append(dsErrs, checks.NewCheckReportError(
advisor.CheckReportErrorSeverityLow,
fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name),
"Check the <a href='https://grafana.com/docs/grafana/latest/upgrade-guide/upgrade-v11.2/#grafana-data-source-uid-format-enforcement' target=_blank>documentation</a> for more information.",
s.ID(),
ds.UID,
))
}
func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any) (*advisor.CheckReportError, error) {
ds, ok := i.(*datasources.DataSource)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}
return dsErrs, nil
// Data source UID validation
err := util.ValidateUID(ds.UID)
if err != nil {
return checks.NewCheckReportError(
advisor.CheckReportErrorSeverityLow,
fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name),
"Check the <a href='https://grafana.com/docs/grafana/latest/upgrade-guide/upgrade-v11.2/#grafana-data-source-uid-format-enforcement' target=_blank>documentation</a> for more information.",
s.ID(),
ds.UID,
), nil
}
return nil, nil
}

type healthCheckStep struct {
Expand All @@ -115,46 +111,38 @@ func (s *healthCheckStep) ID() string {
return "health-check"
}

func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) {
dsErrs := []advisor.CheckReportError{}
for _, i := range items {
ds, ok := i.(*datasources.DataSource)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}

// Health check execution
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
pCtx, err := s.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds)
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 := s.PluginClient.CheckHealth(ctx, req)
if err != nil {
fmt.Println("Error checking health", err)
continue
}
if resp.Status != backend.HealthStatusOk {
dsErrs = append(dsErrs, checks.NewCheckReportError(
advisor.CheckReportErrorSeverityHigh,
fmt.Sprintf("Health check failed for %s", ds.Name),
fmt.Sprintf(
"Go to the <a href='/connections/datasources/edit/%s'>data source configuration</a>"+
" and address the issues reported.", ds.UID),
s.ID(),
ds.UID,
))
}
func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any) (*advisor.CheckReportError, error) {
ds, ok := i.(*datasources.DataSource)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}

// Health check execution
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
pCtx, err := s.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds)
if err != nil {
return nil, fmt.Errorf("failed to get plugin context: %w", err)
}
req := &backend.CheckHealthRequest{
PluginContext: pCtx,
Headers: map[string]string{},
}
resp, err := s.PluginClient.CheckHealth(ctx, req)
if err != nil || resp.Status != backend.HealthStatusOk {
return checks.NewCheckReportError(
advisor.CheckReportErrorSeverityHigh,
fmt.Sprintf("Health check failed for %s", ds.Name),
fmt.Sprintf(
"Go to the <a href='/connections/datasources/edit/%s'>data source configuration</a>"+
" and address the issues reported.", ds.UID),
s.ID(),
ds.UID,
), nil
}
return dsErrs, nil
return nil, nil
}

type pluginContextProvider interface {
Expand Down
30 changes: 21 additions & 9 deletions apps/advisor/pkg/app/checks/datasourcecheck/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ func TestCheck_Run(t *testing.T) {
assert.NoError(t, err)
errs := []advisor.CheckReportError{}
for _, step := range check.Steps() {
stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items)
assert.NoError(t, err)
errs = append(errs, stepErrs...)
for _, item := range items {
stepErr, err := step.Run(ctx, &advisor.CheckSpec{}, item)
assert.NoError(t, err)
if stepErr != nil {
errs = append(errs, *stepErr)
}
}
}

assert.NoError(t, err)
Expand Down Expand Up @@ -65,9 +69,13 @@ func TestCheck_Run(t *testing.T) {
assert.NoError(t, err)
errs := []advisor.CheckReportError{}
for _, step := range check.Steps() {
stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items)
assert.NoError(t, err)
errs = append(errs, stepErrs...)
for _, item := range items {
stepErr, err := step.Run(ctx, &advisor.CheckSpec{}, item)
assert.NoError(t, err)
if stepErr != nil {
errs = append(errs, *stepErr)
}
}
}

assert.NoError(t, err)
Expand Down Expand Up @@ -96,9 +104,13 @@ func TestCheck_Run(t *testing.T) {
assert.NoError(t, err)
errs := []advisor.CheckReportError{}
for _, step := range check.Steps() {
stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items)
assert.NoError(t, err)
errs = append(errs, stepErrs...)
for _, item := range items {
stepErr, err := step.Run(ctx, &advisor.CheckSpec{}, item)
assert.NoError(t, err)
if stepErr != nil {
errs = append(errs, *stepErr)
}
}
}

assert.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions apps/advisor/pkg/app/checks/ifaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ type Step interface {
Title() string
// Description returns the description of the step
Description() string
// Run executes the step and returns a list of errors
Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items []any) ([]advisorv0alpha1.CheckReportError, error)
// Run executes the step for an item and returns a report
Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, item any) (*advisorv0alpha1.CheckReportError, error)
}
114 changes: 55 additions & 59 deletions apps/advisor/pkg/app/checks/plugincheck/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,35 +78,33 @@ func (s *deprecationStep) ID() string {
return "deprecation"
}

func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) {
errs := []advisor.CheckReportError{}
for _, i := range items {
p, ok := i.(pluginstore.Plugin)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}
func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, it any) (*advisor.CheckReportError, error) {
p, ok := it.(pluginstore.Plugin)
if !ok {
return nil, fmt.Errorf("invalid item type %T", it)
}

// Skip if it's a core plugin
if p.IsCorePlugin() {
continue
}
// Skip if it's a core plugin
if p.IsCorePlugin() {
return nil, nil
}

// Check if plugin is deprecated
i, err := s.PluginRepo.PluginInfo(ctx, p.ID)
if err != nil {
continue
}
if i.Status == "deprecated" {
errs = append(errs, checks.NewCheckReportError(
advisor.CheckReportErrorSeverityHigh,
fmt.Sprintf("Plugin deprecated: %s", p.ID),
"Check the <a href='https://grafana.com/legal/plugin-deprecation/#a-plugin-i-use-is-deprecated-what-should-i-do' target=_blank>documentation</a> for recommended steps.",
s.ID(),
p.ID,
))
}
// Check if plugin is deprecated
i, err := s.PluginRepo.PluginInfo(ctx, p.ID)
if err != nil {
// Unable to check deprecation status
return nil, nil
}
return errs, nil
if i.Status == "deprecated" {
return checks.NewCheckReportError(
advisor.CheckReportErrorSeverityHigh,
fmt.Sprintf("Plugin deprecated: %s", p.ID),
"Check the <a href='https://grafana.com/legal/plugin-deprecation/#a-plugin-i-use-is-deprecated-what-should-i-do' target=_blank>documentation</a> for recommended steps.",
s.ID(),
p.ID,
), nil
}
return nil, nil
}

type updateStep struct {
Expand All @@ -127,44 +125,42 @@ func (s *updateStep) ID() string {
return "update"
}

func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) {
errs := []advisor.CheckReportError{}
for _, i := range items {
p, ok := i.(pluginstore.Plugin)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}
func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, i any) (*advisor.CheckReportError, error) {
p, ok := i.(pluginstore.Plugin)
if !ok {
return nil, fmt.Errorf("invalid item type %T", i)
}

// Skip if it's a core plugin
if p.IsCorePlugin() {
continue
}
// Skip if it's a core plugin
if p.IsCorePlugin() {
return nil, nil
}

// Skip if it's managed or pinned
if s.isManaged(ctx, p.ID) || s.PluginPreinstall.IsPinned(p.ID) {
continue
}
// Skip if it's managed or pinned
if s.isManaged(ctx, p.ID) || s.PluginPreinstall.IsPinned(p.ID) {
return nil, nil
}

// Check if plugin has a newer version available
compatOpts := repo.NewCompatOpts(services.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH)
info, err := s.PluginRepo.GetPluginArchiveInfo(ctx, p.ID, "", compatOpts)
if err != nil {
continue
}
if hasUpdate(p, info) {
errs = append(errs, checks.NewCheckReportError(
advisor.CheckReportErrorSeverityLow,
fmt.Sprintf("New version available for %s", p.ID),
fmt.Sprintf(
"Go to the <a href='/plugins/%s?page=version-history'>plugin admin page</a>"+
" and upgrade to the latest version.", p.ID),
s.ID(),
p.ID,
))
}
// Check if plugin has a newer version available
compatOpts := repo.NewCompatOpts(services.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH)
info, err := s.PluginRepo.GetPluginArchiveInfo(ctx, p.ID, "", compatOpts)
if err != nil {
// Unable to check updates
return nil, nil
}
if hasUpdate(p, info) {
return checks.NewCheckReportError(
advisor.CheckReportErrorSeverityLow,
fmt.Sprintf("New version available for %s", p.ID),
fmt.Sprintf(
"Go to the <a href='/plugins/%s?page=version-history'>plugin admin page</a>"+
" and upgrade to the latest version.", p.ID),
s.ID(),
p.ID,
), nil
}

return errs, nil
return nil, nil
}

func hasUpdate(current pluginstore.Plugin, latest *repo.PluginArchiveInfo) bool {
Expand Down
10 changes: 7 additions & 3 deletions apps/advisor/pkg/app/checks/plugincheck/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,13 @@ func TestRun(t *testing.T) {
assert.NoError(t, err)
errs := []advisor.CheckReportError{}
for _, step := range check.Steps() {
stepErrs, err := step.Run(context.Background(), &advisor.CheckSpec{}, items)
assert.NoError(t, err)
errs = append(errs, stepErrs...)
for _, item := range items {
stepErr, err := step.Run(context.Background(), &advisor.CheckSpec{}, item)
assert.NoError(t, err)
if stepErr != nil {
errs = append(errs, *stepErr)
}
}
}
assert.NoError(t, err)
assert.Equal(t, len(tt.plugins), len(items))
Expand Down
4 changes: 2 additions & 2 deletions apps/advisor/pkg/app/checks/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ func NewCheckReportError(
action string,
stepID string,
itemID string,
) advisor.CheckReportError {
return advisor.CheckReportError{
) *advisor.CheckReportError {
return &advisor.CheckReportError{
Severity: severity,
Reason: reason,
Action: action,
Expand Down
Loading

0 comments on commit e291140

Please sign in to comment.