Advisor: Cloud fixes (#101136)
This commit is contained in:
committed by
GitHub
parent
03e94e7a3e
commit
120b277664
@@ -22,7 +22,7 @@ func GetAuthorizer() authorizer.Authorizer {
|
||||
}
|
||||
|
||||
// check if is admin
|
||||
if u.GetIsGrafanaAdmin() {
|
||||
if u.HasRole(identity.RoleAdmin) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
|
||||
@@ -75,4 +75,8 @@ func (m *mockUser) GetIsGrafanaAdmin() bool {
|
||||
return m.isGrafanaAdmin
|
||||
}
|
||||
|
||||
func (m *mockUser) HasRole(role identity.RoleType) bool {
|
||||
return role == identity.RoleAdmin && m.isGrafanaAdmin
|
||||
}
|
||||
|
||||
// Implement other methods of identity.Requester as needed
|
||||
|
||||
@@ -62,4 +62,5 @@ func (s *Service) Checks() []checks.Check {
|
||||
type AdvisorAppConfig struct {
|
||||
CheckRegistry CheckService
|
||||
PluginConfig map[string]string
|
||||
StackID string
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ package datasourcecheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"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 {
|
||||
@@ -20,6 +21,7 @@ type check struct {
|
||||
PluginStore pluginstore.Store
|
||||
PluginContextProvider pluginContextProvider
|
||||
PluginClient plugins.Client
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -33,6 +35,7 @@ func New(
|
||||
PluginStore: pluginStore,
|
||||
PluginContextProvider: pluginContextProvider,
|
||||
PluginClient: pluginClient,
|
||||
log: log.New("advisor.datasourcecheck"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +61,7 @@ func (c *check) Steps() []checks.Step {
|
||||
&healthCheckStep{
|
||||
PluginContextProvider: c.PluginContextProvider,
|
||||
PluginClient: c.PluginClient,
|
||||
log: c.log,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -102,6 +106,7 @@ func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, i a
|
||||
type healthCheckStep struct {
|
||||
PluginContextProvider pluginContextProvider
|
||||
PluginClient plugins.Client
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (s *healthCheckStep) Title() string {
|
||||
@@ -134,7 +139,7 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any
|
||||
pCtx, err := s.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds)
|
||||
if err != nil {
|
||||
// Unable to check health check
|
||||
klog.Error("Failed to get plugin context", "datasource_uid", ds.UID, "error", err)
|
||||
s.log.Error("Failed to get plugin context", "datasource_uid", ds.UID, "error", err)
|
||||
return nil, nil
|
||||
}
|
||||
req := &backend.CheckHealthRequest{
|
||||
@@ -143,6 +148,15 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any
|
||||
}
|
||||
resp, err := s.PluginClient.CheckHealth(ctx, req)
|
||||
if err != nil || resp.Status != backend.HealthStatusOk {
|
||||
if err != nil {
|
||||
s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "error", err)
|
||||
if errors.Is(err, plugins.ErrMethodNotImplemented) || errors.Is(err, plugins.ErrPluginUnavailable) {
|
||||
// The plugin does not support backend health checks
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "status", resp.Status, "message", resp.Message)
|
||||
}
|
||||
return checks.NewCheckReportFailure(
|
||||
advisor.CheckReportFailureSeverityHigh,
|
||||
s.ID(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -28,6 +29,7 @@ func TestCheck_Run(t *testing.T) {
|
||||
DatasourceSvc: mockDatasourceSvc,
|
||||
PluginContextProvider: mockPluginContextProvider,
|
||||
PluginClient: mockPluginClient,
|
||||
log: log.New("advisor.datasourcecheck"),
|
||||
}
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{})
|
||||
@@ -62,6 +64,7 @@ func TestCheck_Run(t *testing.T) {
|
||||
DatasourceSvc: mockDatasourceSvc,
|
||||
PluginContextProvider: mockPluginContextProvider,
|
||||
PluginClient: mockPluginClient,
|
||||
log: log.New("advisor.datasourcecheck"),
|
||||
}
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{})
|
||||
@@ -97,6 +100,7 @@ func TestCheck_Run(t *testing.T) {
|
||||
DatasourceSvc: mockDatasourceSvc,
|
||||
PluginContextProvider: mockPluginContextProvider,
|
||||
PluginClient: mockPluginClient,
|
||||
log: log.New("advisor.datasourcecheck"),
|
||||
}
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{})
|
||||
@@ -118,6 +122,40 @@ func TestCheck_Run(t *testing.T) {
|
||||
assert.Len(t, failures, 1)
|
||||
assert.Equal(t, "health-check", failures[0].StepID)
|
||||
})
|
||||
|
||||
t.Run("should skip health check when plugin does not support backend health checks", 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{err: plugins.ErrMethodNotImplemented}
|
||||
|
||||
check := &check{
|
||||
DatasourceSvc: mockDatasourceSvc,
|
||||
PluginContextProvider: mockPluginContextProvider,
|
||||
PluginClient: mockPluginClient,
|
||||
log: log.New("advisor.datasourcecheck"),
|
||||
}
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{})
|
||||
items, err := check.Items(ctx)
|
||||
assert.NoError(t, err)
|
||||
failures := []advisor.CheckReportFailure{}
|
||||
for _, step := range check.Steps() {
|
||||
for _, item := range items {
|
||||
stepFailures, err := step.Run(ctx, &advisor.CheckSpec{}, item)
|
||||
assert.NoError(t, err)
|
||||
if stepFailures != nil {
|
||||
failures = append(failures, *stepFailures)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(items))
|
||||
assert.Len(t, failures, 0)
|
||||
})
|
||||
}
|
||||
|
||||
type MockDatasourceSvc struct {
|
||||
@@ -142,8 +180,9 @@ type MockPluginClient struct {
|
||||
plugins.Client
|
||||
|
||||
res *backend.CheckHealthResult
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *MockPluginClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
return m.res, nil
|
||||
return m.res, m.err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package checks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,3 +27,14 @@ func NewCheckReportFailure(
|
||||
Links: links,
|
||||
}
|
||||
}
|
||||
|
||||
func GetNamespace(stackID string) (string, error) {
|
||||
if stackID == "" {
|
||||
return metav1.NamespaceDefault, nil
|
||||
}
|
||||
stackId, err := strconv.ParseInt(stackID, 10, 64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid stack id: %s", stackID)
|
||||
}
|
||||
return types.CloudNamespaceFormatter(stackId), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package checks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestGetNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "empty stack ID",
|
||||
input: "",
|
||||
expected: metav1.NamespaceDefault,
|
||||
},
|
||||
{
|
||||
name: "valid stack ID",
|
||||
input: "1234567890",
|
||||
expected: "stacks-1234567890",
|
||||
},
|
||||
{
|
||||
name: "invalid stack ID",
|
||||
input: "invalid",
|
||||
expected: "",
|
||||
expectedErr: "invalid stack id: invalid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := GetNamespace(tt.input)
|
||||
if tt.expectedErr != "" {
|
||||
assert.EqualError(t, err, tt.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ type Runner struct {
|
||||
client resource.Client
|
||||
evaluationInterval time.Duration
|
||||
maxHistory int
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewRunner creates a new Runner.
|
||||
@@ -47,6 +48,10 @@ func New(cfg app.Config) (app.Runnable, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespace, err := checks.GetNamespace(specificConfig.StackID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare storage client
|
||||
clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{})
|
||||
@@ -60,6 +65,7 @@ func New(cfg app.Config) (app.Runnable, error) {
|
||||
client: client,
|
||||
evaluationInterval: evalInterval,
|
||||
maxHistory: maxHistory,
|
||||
namespace: namespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +120,7 @@ func (r *Runner) Run(ctx context.Context) error {
|
||||
// regardless of its ID. This assumes that the checks are created in batches
|
||||
// so a batch will have a similar creation time.
|
||||
func (r *Runner) checkLastCreated(ctx context.Context) (time.Time, error) {
|
||||
list, err := r.client.List(ctx, metav1.NamespaceDefault, resource.ListOptions{})
|
||||
list, err := r.client.List(ctx, r.namespace, resource.ListOptions{})
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
@@ -134,7 +140,7 @@ func (r *Runner) createChecks(ctx context.Context) error {
|
||||
obj := &advisorv0alpha1.Check{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "check-",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
Namespace: r.namespace,
|
||||
Labels: map[string]string{
|
||||
checks.TypeLabel: check.ID(),
|
||||
},
|
||||
@@ -152,7 +158,7 @@ func (r *Runner) createChecks(ctx context.Context) error {
|
||||
|
||||
// cleanupChecks deletes the olders checks if the number of checks exceeds the limit.
|
||||
func (r *Runner) cleanupChecks(ctx context.Context) error {
|
||||
list, err := r.client.List(ctx, metav1.NamespaceDefault, resource.ListOptions{Limit: -1})
|
||||
list, err := r.client.List(ctx, r.namespace, resource.ListOptions{Limit: -1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
|
||||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
type Runner struct {
|
||||
checkRegistry checkregistry.CheckService
|
||||
client resource.Client
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewRunner creates a new Runner.
|
||||
@@ -29,6 +31,10 @@ func New(cfg app.Config) (app.Runnable, error) {
|
||||
return nil, fmt.Errorf("invalid config type")
|
||||
}
|
||||
checkRegistry := specificConfig.CheckRegistry
|
||||
namespace, err := checks.GetNamespace(specificConfig.StackID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare storage client
|
||||
clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{})
|
||||
@@ -40,6 +46,7 @@ func New(cfg app.Config) (app.Runnable, error) {
|
||||
return &Runner{
|
||||
checkRegistry: checkRegistry,
|
||||
client: client,
|
||||
namespace: namespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -58,7 +65,7 @@ func (r *Runner) Run(ctx context.Context) error {
|
||||
obj := &advisorv0alpha1.CheckType{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: t.ID(),
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
Namespace: r.namespace,
|
||||
},
|
||||
Spec: advisorv0alpha1.CheckTypeSpec{
|
||||
Name: t.ID(),
|
||||
|
||||
@@ -3,6 +3,7 @@ package checktyperegisterer
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
@@ -88,6 +89,24 @@ func TestCheckTypesRegisterer_Run(t *testing.T) {
|
||||
},
|
||||
expectedErr: errors.New("update error"),
|
||||
},
|
||||
{
|
||||
name: "custom namespace",
|
||||
checks: []checks.Check{
|
||||
&mockCheck{
|
||||
id: "check1",
|
||||
steps: []checks.Step{
|
||||
&mockStep{id: "step1", title: "Step 1", description: "Description 1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
|
||||
if obj.GetNamespace() != "custom-namespace" {
|
||||
return nil, fmt.Errorf("expected namespace %s, got %s", "custom-namespace", obj.GetNamespace())
|
||||
}
|
||||
return obj, nil
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -98,6 +117,7 @@ func TestCheckTypesRegisterer_Run(t *testing.T) {
|
||||
createFunc: tt.createFunc,
|
||||
updateFunc: tt.updateFunc,
|
||||
},
|
||||
namespace: "custom-namespace",
|
||||
}
|
||||
err := r.Run(context.Background())
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user